Deeply nested Rails forms using belong_to not working?
Asked Answered
D

2

6

I'm working on a messy form which among other things has to manage a section with two-level nesting. It's almost working, but there's a snag and the only thing I can see that's different from other deeply-nested forms that work is that there's a belongs_to relationship rather than has_many. Here are the models:

Event
  has_many :company_events, :dependent => :destroy
  accepts_nested_attributes_for :company_events

CompanyEvent
  belongs_to :company
  accepts_nested_attributes_for :company, :update_only => true
  belongs_to :event
  belongs_to :event_type

Company
  has_many :company_events
  has_many :events, :through => :company_events

So it's a fairly standard many-to-many relationship via a link table, company_events. The form in question is creating/editing an event, with a dynamic "Add Company" Javascript button, all pretty much based on Ryan Bates' screencast and GitHub repo.

The main form:

<table id="companies">
  <tr><th>Company Name</th></tr>
  <% f.fields_for :company_events do |builder| %>
    <%= render 'company_event_fields', :f => builder, :f_o => nil %>
  <% end -%>
</table>
<p><br/><%= link_to_add_fields "Add Company", f, :company_events, "events" %></p>

And the included form is as follows. An important thing to note is that the company ID is set via a Javascript update, which I won't include here because it's long. Basically the user starts typing a name, an autocomplete list is displayed, and clicking on the name sets both the company name and the id in the form.

<tr class="company_event_fields">
  <td>
    <% f.fields_for(:company) do |company_form| -%>
      <%= company_form.text_field :name, :size => 80 %>
      <%= company_form.hidden_field :id %>
    <% end -%>
  </td>
  <td>
    <%= f.hidden_field :_destroy %>
    <%= link_to_function "remove", "remove_co_fields(this)" %>
  </td>
</tr>

When I update an existing record, everything works just fine. When I try to save the form with a newly-created record, though, I get:

ActiveRecord::RecordNotFound in EventsController#update

Couldn't find Company with ID=12345 for CompanyEvent with ID=

With the stacktrace:

/Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/nested_attributes.rb:401:in `raise_nested_attributes_record_not_found'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/nested_attributes.rb:289:in `assign_nested_attributes_for_one_to_one_association'
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/nested_attributes.rb:244:in `company_attributes='
/Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/base.rb:2906:in `send'

I've looked through the code in nested_attributes, and run through it with a debugger. What's happening seems to be that because there's a Company.id, ActiveRecord is assuming that there is already an entry, but then of course it doesn't find one. This seems very odd, since obviously I'd need to pass in an ID in order to create a new CompanyEvent entry. So, I'm guessing that I'm missing something.

The examples I've found that work all seem to be nested using has_many relationships all the way down, while in this case it's a belongs_to, and I'm wondering if that's the root of the problem. Any ideas would be greatly appreciated...

Derisive answered 23/9, 2010 at 23:38 Comment(0)
M
13

Here's another possible solution that I posted in a similar question: https://mcmap.net/q/595508/-use-rails-nested-model-to-create-outer-object-and-simultaneously-edit-existing-nested-object

Something like this...

  accepts_nested_attributes_for :company
  def company_attributes=(attributes)
    if attributes['id'].present?
      self.company = Company.find(attributes['id'])
    end
    super
  end
Mccullers answered 22/8, 2012 at 23:7 Comment(0)
C
4

I ran into the same problem, it just seems that rails doesn't support using nested models like this: you can't save a new object with a nested model that exists, e.g imagine this situation:

class Monkey < ActiveRecord::Base
end
class Banana < ActiveRecord::Base
    belongs_to :monkey
    accepts_nested_attributes_for :monkey
end

This wont work if you try on the console:

banana = Banana.create!
monkey = Monkey.new
monkey.attributes = {:banana_attributes => { :id => banana.id}}

But its simple to work around this, in your form you don't need to set any nested attributes if your banana is already persistent, you just need to have a hidden field on the monkey form with banana_id, which will result in something like:

monkey.attributes = {:banana_id => banana.id}

And that will save just fine.

Chimney answered 10/12, 2010 at 16:44 Comment(1)
I didn't try out the code, but if you have accepts_nested_attributes_for :monkey, shouldn't your example be banana.attributes = {:monkey_attributes ...} ? monkey.attributes = {:banana_attributes ...} doesn't look like it would work.Mccullers

© 2022 - 2024 — McMap. All rights reserved.