Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

activemodel - updating children of object when saving parent

this is a followup to my question here: Updating child association in ActiveModel

i'm looking for the standard/correct way to update a number of children records associated with a parent.

lets say i have a parent (connected to a child table with has_many, and :autosave=>true).

obj = Parent.first

now i iterate over its children, and update them.

obj.each.do |child|
    child.remark = "something"
end

i would like the children to be saved with the parent, when calling obj.save, but as explained to me in the previous question, the only way to do it is to update it directly, like this:

obj.children.first.remark = "something"

(or save each child, but this will require an explicit transaction which i believe shouldn't be used here).

what is the correct way to implement this?

thanks!

*edit : following the advice given here, i've added this to the model:

class Parent < ActiveRecord::Base

     has_many :children, :inverse_of => :parent,:autosave=>true

     accepts_nested_attributes_for :children

But still,

x = Parent.first
c = x.children.first
c.remark = "something"
x.save    # => doesn't update c
like image 325
shaish Avatar asked Oct 04 '22 07:10

shaish


2 Answers

You want ActiveRecord nested_attributes

class Parent
  include ActiveModel::Model

  accepts_nested_attributes_for :children
end

Update your children and save the parent, you should be done

EDIT: you have to call parent.children first:

irb(main):001:0> x = Parent.first
  Parent Load (0.3ms)  SELECT "parents".* FROM "parents" ORDER BY "parents"."id" ASC LIMIT 1
=> #<Parent id: 1, created_at: "2013-08-07 21:21:10", updated_at: "2013-08-07 21:21:10">
irb(main):002:0> x.children
  Child Load (3.0ms)  SELECT "children".* FROM "children" WHERE "children"."parent_id" = ?  [["parent_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [ ]>
irb(main):003:0> x.children.first.remark = "foo"
=> "foo"
irb(main):004:0> x.save
   (0.3ms)  begin transaction
  SQL (2.3ms)  UPDATE "children" SET "remark" = ?, "updated_at" = ? WHERE "children"."id" = 1  [["remark", "foo"], ["updated_at", Wed, 07 Aug 2013 21:33:04 UTC +00:00]]
   (0.3ms)  commit transaction
=> true
like image 86
mdemolin Avatar answered Oct 07 '22 18:10

mdemolin


You could overwrite the save method but I would not do that. Why not do something like this?

def save_with_children
  Child.transaction do
    children.each do |child|
      if child.changed?
        child.save
      end
    end
  end
  save
end

This should run 1 query. Probably wouldn't work if you had 10'000'000 records but you didn't mention that.

like image 43
Stephan1990 Avatar answered Oct 07 '22 19:10

Stephan1990