我正在开发我的第一个 Rails 项目,我正在尝试创建一个正确的用户/地址(或者更确切地说是用户/位置)关联。我认为我的问题很常见,其他人已经成功地完成了我正在尝试的事情,并且很容易找到答案,但事实并非如此。有许多不同的问题有非常不同的答案,我不知道如何在我的情况下做事。这是我的场景:
该位置可以是一个国家、州/地区或城市,但不会比这更具体(我正在使用 Google Places Library 的自动完成功能)。users
因此,为了避免每次一个或多个用户居住在同一个城市(国家、州、城市、纬度、经度等)时表中出现大量重复信息,我想我应该有一张locations
表。更糟糕的是,用户应该有一个“home_location”和“current_location”。现在的协会:
has_one
起初我想:“好吧,一个用户有一个地址/位置,所以我应该使用has_one
关联。但事实证明,如果我使用user has_one location
,位置表将是指向用户表的那个,这不是什么我想要,因为许多用户应该能够引用相同的位置。
属于_to/has_many
“好吧,那我就使用belongs_to/has_many 关联”我想。起初看起来很整洁。似乎说得很有道理。我正在使用嵌套表单 gem,它对我迄今为止所做的所有其他事情都非常有效,并且用于位置的嵌套表单已加载并且信息已正确发送。该位置已成功创建并添加到数据库中,但存在一个问题:未保存 user.location。试图找出原因,我意识到这整件事有些奇怪。Rails 并没有按照我需要的方式处理这种设计结构(我觉得这种方式更直观)。事实上,这种关联通常用于“用户/微博”(就像在Hartl 的教程中一样)) 和这种事情。但在我的情况下,这个想法是不同的。我们人类明白这一点,但 Rails 似乎不明白。我希望用户能够在他的“编辑个人资料”页面中说出他住在哪里。但从我一直在阅读的内容来看,rails 似乎希望LOCATION 的编辑页面为用户提供一个嵌套表单。就像它期望我创建这样的用户:
@location.user.create(:email=>"john@example.com")
虽然我需要的是创建一个这样的位置:
@user.location.create(:city=>"Rio de Janeiro", etc)
好的。也许我想要的太具体了,Rail 的标准协会不起作用。毕竟上面的所有代码都会创建重复的城市,所以我需要类似的东西:
@user.location.find_or_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
图式
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
但是后来似乎我想要的东西太特别了,所以我放弃了一切并尝试简单地添加一个form_for @location
. 然后,如果记录已经存在,我可以签入用户控制器并将其保存在用户的 home_location 属性中。所以我从用户模型中删除了以下几行:
accepts_nested_attributes_for :home_location
accepts_nested_attributes_for :current_location
f.fields_for
并将edit_profile 视图中的替换为form_for @location
.
然后在控制器中我添加了一些代码来处理位置:
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
但它仍然没有拯救协会。事实上,即使在控制台中,无论我做什么,用户的 home_location 属性都不会保存到数据库中。
有人对可能发生的事情有任何想法吗?
为什么没有保存关联?在 Rails 中执行此操作的最正确方法是什么?
我很抱歉这个问题的长度。希望有人读。我将不胜感激!
谢谢
编辑 1
按照 RobHeaton 的提示,我在我的用户模型中添加了一个自定义设置器:
def home_location= location #params[:user][:home_location]
locations = Location.where(location)
@home_location = locations.first.id unless locations.empty?
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
奇怪的是,Rails 不仅不能返回关联的对象,它甚至不会返回存储在对象中的整数 id!这有什么意义?我希望有一个 Location 对象,在最坏的情况下,我希望得到一个 3 作为回报。但相反,我得到了零。
无论如何,我尝试编写自定义吸气剂:
def home_location
if @home_location.blank? then return nil end
Location.find(@home_location)
end
但它当然行不通。还有什么想法吗??