Ruby on rails Rails中的用户/地址关联

Ruby on rails Rails中的用户/地址关联,ruby-on-rails,location,associations,has-many,belongs-to,Ruby On Rails,Location,Associations,Has Many,Belongs To,我正在做我的第一个Rails项目,我正在尝试创建一个合适的用户/地址(或者更确切地说是用户/位置)关联。我认为我的问题很常见,其他人已经成功地做了我正在尝试的事情,很容易找到答案,但事实并非如此。有很多不同的问题,答案也非常不同,我不知道该怎么做。以下是我的设想: 位置可以是一个国家、州/地区或城市,但不能比这更具体(我使用的是Google Places Library的自动完成功能)。因此,为了避免每次一个或多个用户居住在同一个城市(国家、州、城市、纬度、经度等)时,users表中出现大量重复

我正在做我的第一个Rails项目,我正在尝试创建一个合适的用户/地址(或者更确切地说是用户/位置)关联。我认为我的问题很常见,其他人已经成功地做了我正在尝试的事情,很容易找到答案,但事实并非如此。有很多不同的问题,答案也非常不同,我不知道该怎么做。以下是我的设想:

位置可以是一个国家、州/地区或城市,但不能比这更具体(我使用的是Google Places Library的自动完成功能)。因此,为了避免每次一个或多个用户居住在同一个城市(国家、州、城市、纬度、经度等)时,
users
表中出现大量重复信息,我想我应该有一个
locations
表。更糟糕的是,用户应该有“主页位置”和“当前位置”。现在各协会:

有一个

起初我想:“嗯,用户有地址/位置,所以我可能应该使用
has_one
关联。但事实证明,如果我使用
user has_one location
,位置表将指向用户的表,这不是我想要的,因为许多用户应该能够引用相同的位置

属于/拥有许多

“好吧,那我就用“属于/有很多协会”这个词吧。”我想。一开始看起来很整洁。似乎很有道理。我使用的是嵌套表单gem,它对我到目前为止所做的所有其他工作都非常有效,并且用于加载位置和正确发送信息的嵌套表单。已成功创建位置并将其添加到数据库,但存在一个问题:未保存user.location。想弄清楚原因,我意识到整件事有点奇怪。Rails并没有按照我需要的方式处理这种设计结构(我觉得这种方式更直观)。事实上,这种关联通常用于“用户/微操作”(如中)和类似的事情。但在我的情况下,想法是不同的。我们人类理解这一点,但Rails似乎并不理解。我希望用户能够在他的“编辑个人资料”页面中说出他住在哪里。但据我所知,rails似乎希望该位置的编辑页面为用户提供嵌套表单。就像它期望我创建一个这样的用户:

@location.user.create(:email=>"john@example.com")
而我需要的是创建这样一个位置:

@user.location.create(:city=>"Rio de Janeiro", etc)
嗯。也许我想要的太具体了,Rail的标准协会不起作用。以上代码将创建重复的城市,因此我需要类似以下内容:

class User < ActiveRecord::Base

  def location= city_name
    write_attribute :location, Location.find_by_city_name(city_name)
  end

end
@user.location.find_或_create(:city=>“Rio de Janeiro”)
(关联对象是否存在此功能?)

以下是此尝试成功保存位置但未创建关联的代码:

型号

class User < ActiveRecord::Base
  attr_accessible ... :home_location, :home_location_attributes,
                  :current_location, :current_location_attributes, etc


  belongs_to :home_location, :class_name => 'Location'
  accepts_nested_attributes_for :home_location

  belongs_to :current_location, :class_name => 'Location'
  accepts_nested_attributes_for :current_location

  ...
end

class Location < ActiveRecord::Base
  attr_accessible :google_id, :latitude, :longitude,
                  :administrative_area_level_1, :administrative_area_level_2,
                  :city, :neighborhood, :country, :country_id

  belongs_to :country

  has_many :users
end
attr_accessible :first_name, :last_name, ...,
                :home_location, :home_location_id, :home_location_attributes,
                :current_location, :current_location_id, :current_location_attributes, ...

...

belongs_to :home_location, :class_name => 'Location'
accepts_nested_attributes_for :home_location

belongs_to :current_location, :class_name => 'Location'
accepts_nested_attributes_for :current_location
但后来似乎我想要的东西太特别了,所以我放弃了一切,只想为@location添加一个
表单。然后我可以在用户控制器中检查记录是否已经存在,并将其保存在用户的home_location属性中。因此,我从用户模型中删除了以下行:

accepts_nested_attributes_for :home_location
accepts_nested_attributes_for :current_location
  def home_location= location #params[:user][:home_location]
   locations = Location.where(location)
   @home_location = locations.first.id unless locations.empty?
  end
并将编辑纵断面图中的
f.fields\u替换为@location
form\u

然后在控制器中,我添加了一些代码来处理位置:

class UsersController < ApplicationController
  before_filter :signed_in_user,  only: [:index, :edit, :update, :destroy]
  before_filter :signed_out_user, only: [:new, :create]
  before_filter :correct_user,    only: [:edit, :update]

  ...
  def edit
    if @user.speaks.length == 0
      @user.speaks.new
    end
    if @user.wants_to_learn.length == 0
      @user.wants_to_learn.new
    end
    if @user.home_location.blank?
      @user.home_location = Location.new
    end
  end

  def update
    logger.debug params

    ...

    @home_location = nil

    params[:home_location][:country_id] = params[:home_location].delete(:country)

    unless params[:home_location][:country_id].blank?
      params[:home_location][:country_id] = Country.find_by_iso_3166_code(params[:home_location][:country_id]).id

      #@home_location = Location.where(params[:home_location])
      #The line above describe the behavior I actually want,
      #but for testing purposes I simplified as below:
      @home_location = Location.find(2) #There is a Location with id=2 in the DB

      if @home_location.nil?
        @home_location = Location.new(params[:home_location])
        if !@home_location.save then @home_location = nil end
      end
    end

    if @user.update_attributes(params[:user])
      @user.home_location = @home_location
      @user.save
      flash[:success] = "Profile updated"
      sign_in @user
      #render :action => :edit
      redirect_to :action => :edit
    else
      render :action => :edit
    end
  end
  ...
end
在解决了一些突然出现的问题后,它开始工作了!数据最终保存到数据库中。我仍然不知道为什么它不能在控制器中工作。有人猜测吗?我想知道这个错误是否真的存在于其他地方,我最终在没有注意到的情况下修复了它,只是因为代码在模型中组织得更好

然而,这个问题并没有完全解决。虽然该值保存到数据库中,但我无法以标准Rails方式访问关联的模型

1.9.3p362 :001 > ariel = User.first
  User Load (0.5ms)  SELECT "users".* FROM "users" LIMIT 1
 => #<User id: 1, first_name: "Ariel", last_name: "Pontes", username: nil, email: "pontes@ariel.com", password_digest: "$2a$10$Ue8dh/nRh9kL9lUd8JjQU.okD6TtE
i4H1tphmRoNIQ15...", remember_token: "kadybXrh1g0X-BE_HxhA4w", date_of_birth: nil, gender: nil, interested_in: 0, relationship_status: nil, home_location: 3, curr
ent_location: nil, about_me: "", created_at: "2013-05-07 14:28:05", updated_at: "2013-05-09 20:10:41", home_details: nil, current_details: nil> 
1.9.3p362 :002 > ariel.home_location
 => nil
但这当然行不通。还有什么想法吗?

比如:

class User < ActiveRecord::Base

  def location= city_name
    write_attribute :location, Location.find_by_city_name(city_name)
  end

end
class用户

我应该这样做。您可以定义
state=
city=
等等,在每种情况下,只需通过适当的列进行查找。作为一般原则,您希望从控制器中获得尽可能多的逻辑。当您编写10行代码来处理一些传入的参数时,您需要考虑是否可以在模型中执行此操作。

< P> <强>已解决!<强>

感谢RobHeaton对我的良好实践的启发,感谢rossta对Rails惯例的解释

以下是我现在的配置:

型号

class User < ActiveRecord::Base
  attr_accessible ... :home_location, :home_location_attributes,
                  :current_location, :current_location_attributes, etc


  belongs_to :home_location, :class_name => 'Location'
  accepts_nested_attributes_for :home_location

  belongs_to :current_location, :class_name => 'Location'
  accepts_nested_attributes_for :current_location

  ...
end

class Location < ActiveRecord::Base
  attr_accessible :google_id, :latitude, :longitude,
                  :administrative_area_level_1, :administrative_area_level_2,
                  :city, :neighborhood, :country, :country_id

  belongs_to :country

  has_many :users
end
attr_accessible :first_name, :last_name, ...,
                :home_location, :home_location_id, :home_location_attributes,
                :current_location, :current_location_id, :current_location_attributes, ...

...

belongs_to :home_location, :class_name => 'Location'
accepts_nested_attributes_for :home_location

belongs_to :current_location, :class_name => 'Location'
accepts_nested_attributes_for :current_location
模式

  create_table "users", :force => true do |t|
    ...
    t.integer  "home_location"
    t.integer  "current_location"
    t.text     "about_me"
    t.datetime "created_at",                         :null => false
    t.datetime "updated_at",                         :null => false
    ...
  end

  create_table "locations", :force => true do |t|
    t.string   "google_id",                   :null => false
    t.string   "latitude",                    :null => false
    t.string   "longitude",                   :null => false
    t.integer  "country_id",                  :null => false
    t.string   "administrative_area_level_1"
    t.string   "administrative_area_level_2"
    t.string   "city"
    t.string   "neighborhood"
    t.datetime "created_at",                  :null => false
    t.datetime "updated_at",                  :null => false
  end
create_table "users", :force => true do |t|
  t.string   "first_name"
  t.string   "last_name"
  ...
  t.integer  "home_location_id"
  t.integer  "current_location_id"
  ...
end
我的控制器没动。实际上比以前更干净了,不再和params乱搞了

至于定制设置器,它们对于我在问题中描述的目的来说根本不是必需的。但它帮助我组织代码,使我更容易发现错误。我现在用它们来做其他事情,我必须在这过程中修复它们,保持我的控制器干净


Rails似乎确实理解这两种“类型”的关联,毕竟它们都属于/有很多关联!有趣的是,在任何地方都没有明确区分。最后假设Rails无法做到这一点,而事实上我所要做的就是修改一些名称以遵守约定。

现在我在这行中为#
找到了
未定义的方法:
@user.home\u location=location.new
此外,我不能仅用城市名称保存新位置。有不同的地点具有相同的城市名称(Springfield AR x Springfield CA,甚至是同一城市中的不同社区),因此我使用5列:国家行政区级别1行政区级别2城市社区来确定一个地点是否已经存在。我能寄一份满是钱的杂烩吗