私は最初のRailsプロジェクトに取り組んでおり、適切なユーザー/アドレス(またはユーザー/場所)の関連付けを作成しようとしています. 私の問題はよくあることで、他の人が私が試みていることをうまくやっていて、答えを見つけるのは簡単だろうと思っていましたが、そうではありませんでした. 非常に異なる答えを持つ多くの異なる質問があり、私の場合はどうすればよいかわかりませんでした。これが私のシナリオです:
場所は、国、州/地域、または市区町村にすることができますが、それ以上具体的ではありません (私は Google Places Library のオートコンプリート機能を使用しています)。users
そのため、 1 人以上のユーザーが同じ都市 (国、州、都市、緯度、経度など) に住むたびに、テーブル内の多くの情報が重複するのを避けるために、テーブルが必要であると考えましたlocations
。さらに悪いことに、ユーザーには「home_location」と「current_location」が必要です。関連付けは次のとおりです。
has_one
最初は、「まあ、ユーザーには住所/場所があるので、おそらくhas_one
関連付けを使用する必要があります。しかし、を使用するuser has_one location
と、場所テーブルはユーザーのテーブルを指すものになることがわかりました。これは何ですか?多くのユーザーが同じ場所を参照できるはずなので、欲しいです。
属している/has_many
「よし、じゃあbelongs_to/has_manyアソシエーションを使おう」と思いました。最初はかなりきれいに見えました。とても理にかなっているように見えました。ネストされたフォームの宝石を使用しています。これは、これまでに行った他のすべてのものでうまく機能し、場所のネストされたフォームが読み込まれ、情報が正しく送信されました。場所が作成され、データベースに正常に追加されましたが、1 つの問題がありました: user.location が保存されませんでした。その理由を突き止めようとしていると、この全体に非常に奇妙な何かがあることに気付きました。Rails は、この設計構造を私が必要とする方法で扱っていません (私が感じる方法はより直感的です)。実際、通常、この関連付けは「ユーザー/マイクロポスト」に使用されます ( Hartl のチュートリアルのように))と、こういうこと。しかし、私の場合は考え方が異なります。私たち人間はそれを理解していますが、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 のヒントに従って、User モデルにカスタム セッターを追加しました。
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
しかし、もちろんうまくいきません。もっとアイデアはありますか??