Sasha
Sasha

Reputation: 6466

Nested Models not updating

Using Nested Model Form (revised) Railscast to try to create a page with nested models which can be updated at once. Wikis have many topics, which have many notes.

The problem is, a) The pre-existing data isn't pre-populating the fields the way it would if this were performing correctly, and b) hitting update doesn't throw any errors (and in fact, it shows the success banner), but it doesn't do anything, either.

Here's my code:

wiki, topic, note.rb (combined for reading, not in actuality):

class Wiki < ActiveRecord::Base
  attr_accessible :topics_attributes
  has_many :topics
  accepts_nested_attributes_for :topics, :allow_destroy => true
end
class Topic < ActiveRecord::Base
  attr_accessible :name, :notes_attributes, :wiki_id
  has_many :notes
  belongs_to :wiki
  accepts_nested_attributes_for :notes, :allow_destroy => true
end
class Note < ActiveRecord::Base
  attr_accessible :name, :info, :topic_id
  belongs_to :topic
end

wikis_controller.rb

I'm doing the wiki.first thing because I only want one wiki. The model exists so I can update its dependent models through a single object. I will never have more than that one wiki.

class WikisController < ApplicationController
  before_filter :authorize

  def show
    @wiki = Wiki.first
  end

  def edit
    @wiki = Wiki.first
  end

  def update
    @wiki = Wiki.first
    respond_to do |format|
      if @wiki.update_attributes(params[:wiki])
        format.html { redirect_to wikis_path, notice: 'Wiki was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @wiki.errors, status: :unprocessable_entity }
      end
    end
  end

end

routes.rb # I only want one "wiki", and I want the route for it to be /wikis get 'wikis', to: 'wikis#show', as: 'home' resources: wikis

wikis/edit.html.haml

.row.container.wiki
    %h1{ :style => 'margin-bottom: 40px'} Update the Wiki
    = form_for @wiki do |w|
        = w.fields_for :topics do |builder|
            = render "topic_fields", f: builder
        = link_to_add_fields "add topic", w, :topics
        %br
        = w.submit "Update", class: "btn"

wikis/_topic_fields.html.haml

.field_inset
    = f.text_field :name
    = f.hidden_field :_destroy
    = link_to "remove", '#', class: "remove_fields"

    = f.fields_for :notes do |builder|
        = render 'note_fields', f: builder

wikis/_notes_fields.html.haml

.field_inset
    .add_inset
      = f.fields_for :notes do |builder|
        .remove
          = builder.text_field :name
          = builder.text_field :info, class: "no_margin"
          = link_to "remove", '#', class: "remove_fields"

The classes are mostly for styling, with the exception of remove_fields, which calls some javascript. Any idea what I'm doing wrong here? I'm pretty stumped by this.

NEW ERROR

After fixing my update action in wikis_controller (which used to reference params[:post], by mistake), I now get this error when I try to submit an update. Still, the fields are NOT prepopulated:

Can't mass-assign protected attributes: notes

I would have thought that the :topics_attributes and :notes_attributes would take care of this??

ALSO, it's worth noting that while the Notes fields aren't prepopulated, the Topics field is.

EDIT -- params hash from mass-assign error below

It looks like the params are getting passed correctly

{"utf8"=>"✓",
 "_method"=>"put",
 "authenticity_token"=>"3/4k8eEa4/PfeNI9cSj7zqCm9+scWBS3gwEacmc/hd0=",
 "wiki"=>{"topics_attributes"=>{"0"=>{"name"=>"Pre-existing Topic Name",
 "_destroy"=>"false",
 "notes_attributes"=>{"0"=>{"notes"=>{"name"=>"New Notes Name",
 "info"=>"New Notes Info"},
 "id"=>"1"}},
 "id"=>"1"}}},
 "commit"=>"Update",
 "id"=>"1"}

Upvotes: 0

Views: 238

Answers (1)

Kocur4d
Kocur4d

Reputation: 6921

You have one to many fields_for call in your view. Basic rule of thumb is to have one fields_for call for each accepts_nested_attributes_for call.

change wikis/_notes_fields.html.haml from:

.field_inset
    .add_inset
      = f.fields_for :notes do |builder|
        .remove
          = builder.text_field :name
          = builder.text_field :info, class: "no_margin"
          = link_to "remove", '#', class: "remove_fields"

to:

.field_inset
    .add_inset
      .remove
        = f.text_field :name
        = f.text_field :info, class: "no_margin"
        = link_to "remove", '#', class: "remove_fields"

I am not a haml guy so I am not 100% sure is this code valid but basic idea is to remove fields_for call.

Your notes_attributes hash looks like this:

"notes_attributes"=>{"0"=>{"notes"=>{"name"=>"New Notes Name",
                                     "info"=>"New Notes Info"}, "id"=>"1"}}

And it should:

"notes_attributes"=>{"notes"=>{"name"=>"New Notes Name",
                               "info"=>"New Notes Info"}}

Upvotes: 1

Related Questions