views:

195

answers:

1

I have the following one to many associations. Document has many Sections and Section has many Items.

class Document < ActiveRecord::Base
  has_many :document_sections, :dependent => :destroy, :autosave => true
  has_many :document_items, :through => :document_sections
end

class DocumentSection < ActiveRecord::Base
  belongs_to :document
  has_many :document_items, :dependent => :destroy, :autosave => true
end

class DocumentItem < ActiveRecord::Base
  belongs_to :document_section
end

Here is the params hash:

-
Parameters: {"commit"=>"Submit Document", "authenticity_token"=>"4nx2B0pJkvavDmkEQ305ABHy+h5R4bZTrmHUv1setnc=", "id"=>"10184", "document"=>{"section"=>{"10254"=>{"seqnum"=>"3", "item"=>{"10259"=>{"comments"=>"tada"}}}}, "comment"=>"blah"}}

I have the following update method...

# PUT /documents/1                                                                                                                                                 
# PUT /documents/1.xml




def update
    @document = Document.find(params[:id])

    # This is header comment
    @document.comment = params[:document][:comment]

    params[:document][:section].each do |k,v|
       document_section =  @document.document_sections.find_by_id(k)
       if document_section

          v[:item].each do |key, value|
             document_item = document_section.feedback_items.find_by_id(key)
             if document_item

                # This is item comments
                document_item.comments = value[:comments]
             end
          end

       end
     end

    @document.save

  end

When I save the document it only updates the document header comments. It does not save the document_item comments. Shouldn't the autosave option also update the associations.

In the log only the following DML is registered:

UPDATE documents SET updated_at = TO_DATE('2010-03-09 08:35:59','YYYY-MM-DD HH24:MI:SS'), comment = 'blah' WHERE id = 10184

How do I save the associations by saving the document.

+1  A: 

I think I see what the problem is. I'm pretty sure that you cannot do the following:

# Triggers a database call
document_section =  @document.document_sections.find_by_id(k)

And expect ActiveRecord to keep the association for autosaves. Instead, you should save the loaded records individually. Which of course would not be atomic.

I believe for autosave to work like you are thinking, you want to do something like this:

# untested...
@document.document_sections.collect { |s| s.id == k }.foo = "bar"

Notice that here I'm actually modifying a fake param foo in the array, instead of calling find_by_id, which will re-query the database and return a new object.

A third option you have is that you could of course, do what you had originally planned, but handle all the transactions yourself, or use nested transactions, etc, to get the atmoic saves. This would be necessary if your data was too large for array manipulation to work since autosave by it's natures triggers a load of all associated data into memory.

It all depends on your application.


Some clarifications on the underlying problem:

If you run the find_by_id method, you are asking ActiveRecord to return to you a new set of objects that match that query. The fact that you executed that method from an instance (document_sections) is really just another way of saying:

DocumentSection.find_by_id(k)

Calling it from an object instance I think is just some syntactic niceness that rails is adding on the top of things, but in my mind it doesn't make a lot of sense; I think it could be handy in some application, I'm not sure.

On the other side, collect is a Ruby Array method that offers a way to "slice" an array using a block. Basically a fancy foreach loop. :) By interacting with the document_sections array directly, you are changing the same objects already loaded into the containing object (@document), which will then be committed when you save with the special autosave flag set.

HTH! Glad you are back and running. :)

dpb
The model is posted from the second line onwards in the main topic. I am aware of 'accepts_nested_attributes_for'. Since I am not using mass assignment I would expect this to save without the 'accepts_nested_attributes_for' option.
ash34
I re-read your question, and adjusted my answer. Let me know if that helps.
dpb
sorry about the formatting. Thanks much for the reply. Your untested solution with the 'collect' method works!. I kind of get what you are saying. In your comments you mention "will re-query the database and return a new object". I was under the impression that the object loaded by autosave and a database query both return a reference to the same object (same underlying row from the table) and it would not matter which object reference is used to modify the object. Can you clarify the underlying concept. Thanks again.
ash34
@ash34: no problem about formatting, just some helpful tips to make sure you are clear, it's also nice if you mark my answer as the "accepted" answer, which says that this answer solves your problem. I'll put some clarifications in my answer, as this space is really just for comments.
dpb
@dpb: appreciate the help, marked as accepted solution.
ash34