Rails 4: child record ID is saved but not its attributes, using nested form with has_many through relationship

笑着哭i 提交于 2019-12-13 06:53:48

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!