Skip to content

Instantly share code, notes, and snippets.

@krisr
Created July 23, 2008 08:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krisr/1612 to your computer and use it in GitHub Desktop.
Save krisr/1612 to your computer and use it in GitHub Desktop.
module HasAttributesFor
module ActiveRecord
def self.included(ar)
ar.extend ClassMethods
end
module ClassMethods
def has_attributes_for(association)
primary_key_name = reflect_on_association(association).primary_key_name
class_eval %{
def #{association}_attributes=(params)
@original_#{association} ||= #{association}.clone
existing = new_record? ? {} : #{association}.index_by(&:id)
keep = []
(params||[]).each do |attributes|
id = attributes[:id].to_i
if id && r = existing[id]
r.attributes = attributes.except(:id)
else
# we use build so that it doesn't save the record yet instead of <<
r = #{association}.build(attributes.except(:id))
end
keep << r
end
# remove all the original records that were no longer referenced
# this will not actually delete these records, only unreference them
deletions = self.#{association} - keep
deletions.each do |r|
# important to use target here to remove the record from memory but not the database
self.#{association}.target.delete(r)
end
end
}
class_eval %{
def original_#{association}
@original_#{association}
end
def original_#{association}=(value)
@original_#{association} = value
end
}, __FILE__, __LINE__
class_eval %{
before_update do |p|
if p.original_#{association}
new_records = p.#{association}
p.original_#{association}.each do |r|
p.#{association}.delete(r) unless new_records.member?(r)
end
end
p.#{association}.each do |r|
if r.changed?
r.#{primary_key_name} = p.id
r.save
end
end
end
after_save do |p|
p.original_#{association} = nil
end
}, __FILE__, __LINE__
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment