我正在處理我的第一個Rails項目,並且正在嘗試創建適當的用戶/地址(或者更確切地說是用戶/位置)關聯。我認爲我的問題很常見,其他人已經完成了我成功的嘗試,並且很容易找到答案,但事實並非如此。有很多不同的問題和極其不同的答案,我無法弄清楚如何在我的情況下做事。這裏是我的場景:在Rails中的用戶/地址關聯
該位置可以是一個國家,州/地區或城市,不是比這更具體(我正在使用谷歌地方圖書館的自動完成功能)。因此,爲了避免每當一個或多個用戶居住在同一個城市(國家,州,城市,緯度,經度等)時users
表中的大量重複信息,我想我應該有一個locations
表。更糟糕的是,用戶應該擁有「home_location」和「current_location」。現在協會:
HAS_ONE
起初我以爲:「嗯,一個用戶都有一個地址/位置,所以我也許應該使用has_one
協會但是,事實證明,如果我用user has_one location
,位置表將是一個指向用戶的表,這不是我想要的,因爲許多用戶應該能夠引用同一位置。
belongs_to的/的has_many
「好的,那麼我會使用belongs_to/has_many關聯」我想。起初看起來很整齊。似乎很有意義。我使用的是嵌套表單gem,對於我迄今爲止所做的其他所有工作都非常有效,並且已加載位置的嵌套表單以及正確發送的信息。該位置已創建併成功添加到數據庫,但存在一個問題:user.location未保存。試圖弄清楚爲什麼,我意識到這件事情有點奇怪。 Rails沒有按照我需要的方式處理這種設計結構(我覺得更直觀)。的確,通常這種關聯被用於「用戶/微博」(如Hartl's tutorial)和這種事情。但在我看來,這個想法是不同的。我們人類明白,但Rails似乎並不如此。我希望用戶能夠說出他在「編輯個人資料」頁面中的位置。但從我一直在閱讀它似乎像軌道預計位置的編輯頁面有一個用戶的嵌套形式。喜歡它的期待我創建這樣一個用戶:
@location.user.create(:email=>"[email protected]")
雖然我需要的是創造這樣一個位置:
@user.location.create(:city=>"Rio de Janeiro", etc)
確定。也許我想要的是太具體,鐵路的標準協會將無法正常工作。所有這些代碼上面會創建重複的城市,所以我需要這樣的:
@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
而且與form_for @location
的edit_profile視圖替換了f.fields_for
。
然後在控制器中我添加了一些代碼來處理地點:
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 [email protected]_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的小費,我在我的用戶模型增加了定製的setter:
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: "[email protected]", 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。但是,我得到零。
我試着寫一個自定義的getter反正:
def home_location
if @home_location.blank? then return nil end
Location.find(@home_location)
end
但是,當然,這是行不通的。還有什麼想法?
你要更改用戶的位置列名是'home_location_id'和'current_location_id'與「belongs_to的」關係中,你已經設置了工作。 – rossta 2013-05-08 23:32:57
我試過了,但它沒有真正的工作= /這些確實是數據庫中列的名稱。可能沒有添加默認的「_id」,因爲我給了它們自定義名稱,因爲有2個外鍵指向同一個表。 – Ariel 2013-05-09 21:21:08
我不知道你的意思是它沒有工作。 Rails約定在你的數據庫中使用':custom_name_id'外鍵,並在模型中使用'belongs_to:custom_name,class_name:'Location'關聯。通過爲列和belongs_to關聯使用相同的名稱,您將違反Rails約定,除非使用解決方法,否則這通常會導致問題。 – rossta 2013-05-09 22:51:10