Ruby on Rails:ネストされた属性、belongs_to 関係
-
22-09-2019 - |
質問
現在の場所フィールド (都市と国) を持つユーザー エンティティがあります。この情報を保持するために、_many Users を持つ Location というエンティティを作成しました。
User モデルに "has_one" と "belongs_to" のどちらを入力すべきかはよくわかりませんが、場所の外部キーを持たせたい場合は、"belongs_to" を入力する必要があると考えられます。ユーザーを編集するときに、ユーザーの現在の場所も編集できるようにしたいと考えています。したがって、ネストされた属性を使用しています。しかし、ユーザーを編集すると、編集されたユーザーに関連付けることなく、毎回新しい場所を追加することになります。あなたは私を助けることができます?
私のコードは次のとおりです。
#User Model
class User < ActiveRecord::Base
## Relationships
belongs_to :current_location, :class_name => 'Location'
accepts_nested_attributes_for :current_location
end
#Location Model
class Location < ActiveRecord::Base
#Relationship
has_many :users
end
# part of the _form_edit.haml
- form_edit.fields_for :current_location do |location_form|
= location_form.label :location, "Current Location"
= location_form.text_field :location
#Application Helper
#nested attributes for user and location
def setup_user(user)
returning(user) do |u|
u.build_current_location if u.current_location.nil?
end
end
#in the user controller (added after edit)
def update
@user = @current_user
if @user.update_attributes(params[:user])
flash[:notice] = "Account updated!"
redirect_to account_url
else
render :action => :edit
end
end
解決
他の人が指摘しているように、あなたが直面している正確な問題は、コントローラーが位置 ID を正常に受信していないことです。私には、ロケーションIDが間違ったパラメータを介して渡されているように見えます。残念ながら、新しいレコードには場所 ID が存在しないため、フォームではこれを行うことはできません。
あなたの問題は、belongs_to 関係で accepts_nested_attributes_for を使用することに起因します。動作は明確に定義されていません。これは文書化されたバグのようです。したがって、 accepts_nested_attributes_for は、関係の 1 つまたは複数の側にある必要があります。
考えられる解決策は次のとおりです。
accept_nested_attributes_for を Location モデルに移動し、その逆の方法でフォームを構築します。
-form_for @location do |location_form| ... =location_form.fields_for @user do |user_form| ....
残念ながら、これでは情報を論理的に提示することができません。そして、適切なユーザーの編集が困難になります。
結合モデルを使用して、 に 1 つの :through 関係を持たせます。
正直なところ、 accept_nested_attributes_for が :through 関係でどの程度うまく機能するかはわかりませんが、レコードのリンクに関する問題は間違いなく解決されます。
accepts_nested_attributes_for を無視し、コントローラーでの関連付けを昔ながらの方法で処理します。
実際には accepts_nested_attributes_for を保持します。これはいくつかの便利なメソッドを提供しますが、update_attributes/create ステートメントには到達させないでください。
def update @user = @current_user completed = false location_params = params[:user].delete(:current_location_attributes) User.transaction do @location = Location.find_or_create_by_id(location_params) @user.update_attributes(params[:user]) @user.current_location = @location @user.save! completed = true end if completed flash[:notice] = "Account updated!" redirect_to account_url else render :action => :edit end end
新しい場所を作成しない場合、フィールドには current_location_attributes ハッシュの id フィールドが自動的に設定されます。ただし、find_or_create_by_id が機能するには、ハッシュに :id エントリが必要です。ID がデータベースにない場合は、正しく自動インクリメントされた ID で作成されます。新しい場所を作成する場合は、それを追加する必要があります。フォームに追加するのが最も簡単です =location_form.hidden_field :id, 0 unless current\_location.new\_record?
.
ただし、重複したロケーションの作成を減らし、Location.find_or_create_by_id 行を Location.find_or_create_by_location に変更することもできます。これにより、一意性検証の失敗によるエラーも削減されます。
他のヒント
あなたは、ネストされた属性のIDを提供していません。だから、それは新しいものだと思っレールます。
- form_edit.fields_for :current_location do |location_form|
= location_form.label :location, "Current Location"
= location_form.text_field :location
= location_form.hidden_field :id unless location_form.new_record?
前の回答が本当に正しいかどうかわかりません。何が必要なの場所、場所ではなく自身のためにユーザーのIDを指定することです。
- form_edit.fields_for :current_location do |location_form|
= location_form.label :location, "Current Location"
= location_form.text_field :location
= location_form.hidden_field :user_id
belongs_to :current_location, :class_name => 'Location'
によって Users
テーブルがcurrent_location_id
フィールドを持って期待しています。あなたがこれを持っていたらあなたのような何かを行うことができるはずます:
@user = @current_user
@user.update_attributes(params[:user])
@location = @user.current_location or @user.build_current_location
@location.update_attributes(params[:location])
@user.current_location.save!
@user.save!