Ruby on rails Rails中的用户/地址关联
我正在做我的第一个Rails项目,我正在尝试创建一个合适的用户/地址(或者更确切地说是用户/位置)关联。我认为我的问题很常见,其他人已经成功地做了我正在尝试的事情,很容易找到答案,但事实并非如此。有很多不同的问题,答案也非常不同,我不知道该怎么做。以下是我的设想: 位置可以是一个国家、州/地区或城市,但不能比这更具体(我使用的是Google Places Library的自动完成功能)。因此,为了避免每次一个或多个用户居住在同一个城市(国家、州、城市、纬度、经度等)时,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表中出现大量重复
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城市社区来确定一个地点是否已经存在。我能寄一份满是钱的杂烩吗