2013-05-08 24 views
1

我正在處理我的第一個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 

但是,當然,這是行不通的。還有什麼想法?

+0

你要更改用戶的位置列名是'home_location_id'和'current_location_id'與「belongs_to的」關係中,你已經設置了工作。 – rossta 2013-05-08 23:32:57

+0

我試過了,但它沒有真正的工作= /這些確實是數據庫中列的名稱。可能沒有添加默認的「_id」,因爲我給了它們自定義名稱,因爲有2個外鍵指向同一個表。 – Ariel 2013-05-09 21:21:08

+0

我不知道你的意思是它沒有工作。 Rails約定在你的數據庫中使用':custom_name_id'外鍵,並在模型中使用'belongs_to:custom_name,class_name:'Location'關聯。通過爲列和belongs_to關聯使用相同的名稱,您將違反Rails約定,除非使用解決方法,否則這通常會導致問題。 – rossta 2013-05-09 22:51:10

回答

1

解決!

感謝RobHeaton爲我提供了關於良好實踐的啓發,並感謝rossta爲解釋Rails的慣例。

這裏是我的配置現在有:

模式

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.string "first_name" 
    t.string "last_name" 
    ... 
    t.integer "home_location_id" 
    t.integer "current_location_id" 
    ... 
end 

我控制器不變。實際上比以前更乾淨,沒有更多的與params混淆。

至於定製二傳手,它們甚至不是我在問題中描述的目的所必需的。但是它幫助我組織代碼並更容易發現錯誤。我現在正在使用它們來完成其他一些我必須在此過程中修復的事情,並保持我的控制器清潔。

看來Rails確實理解belongs_to/has_many關聯的「類型」畢竟!有趣的是,儘管沒有發現明顯的區別。最後假設Rails無法做到這一點,事實上,我所要做的只是糾正一些尊重公約的名稱。

1

喜歡的東西:

class User < ActiveRecord::Base 

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

end 

應該這樣做。您可以定義state=city=等,在每種情況下只需執行相應列的查找。作爲一般原則,您希望儘可能多地從您的控制器中獲取這些邏輯。只要你寫了10多行代碼來處理一些傳入的參數,你想考慮你是否可以在模型中做到這一點。

+0

現在,我得到'未定義的方法「to_i」爲#<地點:0x007fc2d9f2ff28>'在這條線: '@user.home_location = Location.new' – Ariel 2013-05-08 21:48:07

+0

除此之外,我無法保存新的位置只有一個城市名。有與同一城市名稱的不同位置(斯普林菲爾德AR X斯普林菲爾德CA,甚至在同一個城市不同的街區),所以我用5列: 國家 administrative_area_level_1 administrative_area_level_2 城市 附近 以確定是否一個位置已經是否存在。我可以發送一個完整的參數散列到這個分配方法而不是隻有city_name嗎? – Ariel 2013-05-08 21:59:28

+0

原則上是的,雖然最好的辦法取決於幾件事情。你從你的表格發送給你什麼數據? – RobHeaton 2013-05-09 08:38:06