问题
I have 3 models with a has_many through relationship: Food (eg: Chocolate), Sub (Chocolate food substitute), Joint (joint table).
Say @food = Food.find(1); The has_many through relationship allows me to do @subs = @food.subs which return all substitutes associated with @food. This work fine, however only the Sub id is saved and not its attributes which are :name and :description as you can see it returned nil when trying to save @food.subs in my create action in my controller:
=> #<ActiveRecord::Associations::CollectionProxy [#<Sub id: 28,name:nil,description:nil,created_at:
"2015-01-07 00:40:35", updated_at: "2015-01-07 00:40:35">]>
I guess the issue lies with my create action in my food controller and perhaps something to do with my nested form as well. I spent countless hours trying to figure this out I am so desperate to find an answer. I really do not know where to look anymore. I am new to rails so thanks a lot for your help and your time, I really appreciate it. Please if possible adapt your answer to my beginner level :-) .
Down below are samples of my controller, form and relevant information.
Here are my models:
class Food < ActiveRecord::Base
has_many :joints
has_many :subs, :through => :joints
accepts_nested_attributes_for :subs
end
class Sub < ActiveRecord::Base
has_many :joints
has_many :foods, :through => :joints
accepts_nested_attributes_for :foods
end
class Joint < ActiveRecord::Base
belongs_to :food
belongs_to :sub
end
Here is my db-schema FYI:
create_table "foods", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "joints", force: true do |t|
t.integer "food_id"
t.integer "sub_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subs", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Here is my foods_controller:
def new
@food = Food.new
@sub = Sub.new
end
def create
@food = Food.new(food_params)
@food.subs.build(params[:subs])
@food.save
respond_to do |format|
if @food.save
format.html { redirect_to @food, notice: 'Food was successfully created.' }
format.json { render :show, status: :created, location: @food }
else
format.html { render :new }
format.json { render json: @food.errors, status: :unprocessable_entity }
end
end
end
private
def food_params
params.require(:food).permit(:name, :description, subs_attributes: [:name, :description])
end
end
Here is my views/foods/_form:
<%= form_for(@food) do |f| %>
<% if @food.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@food.errors.count, "error") %> prohibited this food from being saved:</h2>
<ul>
<% @food.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div>
<%= f.fields_for(@sub) do |sub| %>
<div class="field">
<%= sub.label :name %>
<%= sub.text_field :name %>
</div>
<div class="field">
<%= sub.label :description %>
<%= sub.text_area :description %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My routes in case it helps: resources :foods
resources :subs
resources :joints
root "foods#index"
Thank you very much !
Antoine.
回答1:
In your new action:
def new
@food = Food.new
@food.subs.build
end
and in your view:
<%= f.fields_for :subs do |sub| %>
When you're passing directly an object, this object becomes the new form_builder's object - rails have no idea it is in any way connected with original object so it will result in different field names.
When you pass a symbol, rails will first try to find if your current object defines subs_attributes
method. If so it will loop over subs
association and build the fields for each associated model.
Reference here.
UPDATE - answer to comment:
Firstly - @subs
is not a symbol, it is an instance variable. Symbols start with a colon like :subs
. When fields_for receives an argument, it checks whether it is a symbol or object. In former case it search an object associated with form builder (f.object
) to find out if it defines <passed_symbol>_attributes=
. That way it knows that the model accepts nested attributes for this association so it can behave accordingly (the new form builder is created for each associated object with a correct name - <symbol>_attributes
).
When object is passed, rails has no way of detecting if this is in ay way connected to the current object - you could have two associations for the same type of objects, or even it might have absolutely nothing to do with the original object. In that case fields_for
acts like it was a nested form_for
- resulting form builder will carry the model name of the object (f.object.class.model_name.singular
)
来源:https://stackoverflow.com/questions/27810158/rails-4-child-record-id-is-saved-but-not-its-attributes-using-nested-form-with