Mark Bolusmjak
Mark Bolusmjak

Reputation: 24399

In Rails, what is the inverse of update_attributes?

In Rails, what is the inverse of update_attributes! ?

In other words, what maps a record to an attributes hash that would recreate that record and all of it children records?

The answer is not ActiveRecord.attributes as that will not recurse into child object.

To clarify if you have the following:

class Foo < ActiveRecord::Base
  has_many :bars
  accepts_nested_attributes_for :bars
end

Then you can pass an hash like

{"name" => "a foo", "bars_attributes" => [{"name" => "a bar} ...]}

to update_attributes. But it's not clear how to easily generate such a hash programatically for this purpose.

EDIT:
As I have mentioned in a comment, I can do something like:
foo.as_json(:include => :bars)

but I wanted a solution that uses the accepts_nested_attributes_for :bars declaration to avoid having to explicitly include associations.

Upvotes: 6

Views: 432

Answers (2)

omarvelous
omarvelous

Reputation: 2784

Not sure how that would be the "inverse", but while Rails might not "have the answer" per-see, there is nothing stopping you from traversing through an object and creating this VERY efficiently.

Something to get you started:

http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for

You'll notice in the accepts_nested_attributes_for method, rails sets a hash for all models nested, in nested_attributes_options. So we can use that to get these nested associations, to populate this new hash.

def to_nested_hash
  nested_hash = self.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym } # And any other attributes you don't want

  associations = self.nested_attributes_options.keys
  associations.each do |association|
    key = "#{association}_attributes"
    nested_hash[key] = []
    self.send(association).find_each do |child|
      nested_hash[key] << child.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym }
    end
  end

  return nested_hash
end

OR just thought of this:

Using your example above:

foo.as_json(:include => foo.nested_attributes_options.keys)

One thing to note, this won't give you the bars_attributes where my first suggestions will. (neither will serializable_hash)

Upvotes: 1

Kedar Vaidya
Kedar Vaidya

Reputation: 678

You can use the following method to include nested options in hash

class Foo < ActiveRecord::Base
  has_many :bars
  accepts_nested_attributes_for :bars

  def to_nested_hash(options = nil)
    options ||= {}
    if options[:except]
      incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
    else
      incl = self.nested_attributes_options.keys
    end
    options = { :include => incl }.merge(options)
    self.serializable_hash(options)
  end
end

If for some situations you don't want bars, you can pass options

foo.to_nested_hash(:except => :bars)


Edit: Another option if you want same behaviour in as_json, to_json and to_xml

class Foo < ActiveRecord::Base
  has_many :bars
  accepts_nested_attributes_for :bars

  def serializable_hash(options = nil)
    options ||= {}
    if options[:except]
      incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
    else
      incl = self.nested_attributes_options.keys
    end
    options = { :include => incl }.merge(options)
    super(options)
  end

  def to_nested_hash(options = nil)
    self.serializable_hash(options)
  end
end

Upvotes: 0

Related Questions