Sasha
Sasha

Reputation: 6466

Updating nested models in rails

I have an app where there's a model Animal which has many Packages. I'm trying to create a form where the :true_weight attribute of a subset of the packages associated with a particular animal can be updated. This form is in a custom url/action (not show/new/edit or whatever). I feel like I've done most of it right, but it's just not updating the packages. I'll put my (abbreviated) code below and then explain what has and hasn't been working. Pretty stumped by this:

package.rb

attr_accessible :true_weight # And irrelevant others
belongs_to :animal

animal.rb

attr_accessible :finalized, :name # And irrelevant others
has_many :packages
accepts_nested_attributes_for :packages

routes.rb

match '/animals/:animal_id/log' => "animals#log"

animals/log.html.erb

<%= form_for(@animal) do |f| %> 

<div style="width: 200px">
        <%= f.label :name %>
        <%= f.text_field :name %>
</div>

    # This produces the subset of packages I want to be able to edit. It's an array
    # of arrays, so that each render produces a separate table made from one of the 
    # inner arrays.
<% @animal.packages_for_log.each do |bundle| %> 
    <%= render partial: 'package_bundle', locals: {:bundle => bundle} %><br>
<% end %>

<h4>And then you'd allow addition of extra packages here.</h4>

<%= f.submit "Submit Animal Log", :class => "btn image-button right" %>

<% end %>

_package_bundle.html.erb

<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Notes</th>
            <th>Label</th>
            <th>True Weight></th>
        </tr>
    </thead>
    <tbody>
        <% bundle.each do |package| %> <!-- Produces for each "bundle" the list of identical packages -->
            <%= fields_for package do |p| %>
                <% if package.order.user %>
                    <tr>
                        <td><%= package.name %></td>
                        <td><%= package.line.processed_notes %></td>
                        <td><%= "#{package.order.user.name.first(3).upcase}-#{package.id}" %></td>
                        <% if !package.true_weight %>
                            <td colspan=1><%= p.text_field :true_weight %></td
                        <% else %>
                            <td><%= package.true_weight.round(0) %>lb</td>

                        <% end %>  
                    </tr>
                <% end %>
            <% end %>   
        <% end %>
    </tbody>
</table>

animals_controller.rb (log is an animal action/view)

def log
    @animal = Animal.find(params[:animal_id])

    if @animal.update_attributes(params[:animal])
      puts "Inside If Block"
      @animal.toggle!(:finalized)
    else
      # I don't know about this. I wanted to redirect to the updated log page, with a flash
      # but when I last tried that, it had some recursive error, or would update without my 
      # submitting.
      render action: "log"
    end

  end

So. What is working:

  1. The if block in my animals controller is being accessed. But it looks like it's possibly being accessed BEFORE I hit submit on my log page. (I searched for and found that puts line in the server logs). May just be misreading it.
  2. The form elements look right. I get a whole bunch of text boxes in the right places, and the information about each package that is being printed to the page in the table next to the text fields is correct.
  3. If I fill in Animal Name attribute at the top of the form, that attribute (of the animal) IS updated.

What isn't working:

  1. When I submit, it redirects me to the animal show page (!?!?), with the flash that's shown after the regular update action.
  2. The @animal.toggle!(:finalized) action doesn't seem to do anything. Maybe, given 1 above, that boolean is being toggled twice? Back and forth?
  3. The big one -- it doesn't update my packages, at all.

Any ideas what's going on here, or what I need to add? I'm really confused about what I'm not doing right.

Thanks!

EDIT -- Per request, adding the code for my packages_for_log method (in the animal.rb file)

def packages_for_log 
    master = []
    self.packages.each do |p|
      if p.sold

        # The master has sublists
        if master.any?
          counter = 0

          # Check for matching list. Add if found.
          master.each do |list|
            if p.notes == list.first.notes && p.type == list.first.type
              list << p
              counter += 1 # Don't create list is appropriate one found.
            end
          end
          if counter == 0
            master << [p] if counter == 0 # Add as new list if list doesn't yet exist.
          end
        else # If master is empty, add the package to a new sub-list.
            master << [p]
        end

      end

    end
    master.sort
  end

Basically, packages have types and notes. I want the log to create a separate table for each package type/note. So I create an array, and then populate it with arrays where all the packages share the same type/notes. (First, I make sure they're all sold. I don't want the log page to show unsold packages.)

Upvotes: 0

Views: 460

Answers (1)

Thanh
Thanh

Reputation: 8604

I think the problem here:

<%= form_for(@animal) do |f| %>

Default with this definition, Rails will create or update a record with information on form, so when you submit form, and rails check this is not a new record, it performed update action, not log action. Try change to this :

<%= form_for(@animal), :url => { :action => "log" }  do |f| %>

and submit form again and check if it works.

Edit: You're not used fields_for in:

<% @animal.packages_for_log.each do |bundle| %> 
  <%= render partial: 'package_bundle', locals: {:bundle => bundle} %><br>
<% end %>

Add fields_for and try again:

<%= fields_for @animal.packages_for_log.each do |bundle| %> 
  <%= render partial: 'package_bundle', locals: {:bundle => bundle} %><br>
<% end %>

Upvotes: 1

Related Questions