module ActsAsProxy def self.included(base) #:nodoc: base.send(:extend, ClassMethods) base.after_save {|record| record.unsaved_proxied.all? {|associated| associated.save}} end # When the proxied object is not a new_record, ActiveRecord will not automatically take care of saving it. # So, ActsAsProxy stuffs them in here for a cascading save when the main record gets saved. def unsaved_proxied @unsaved_proxied ||= [] end module ClassMethods # Creates proxies from one model to another associated model's attributes. # # Example setup: # # class User < ActiveRecord::Base # has_one :address # has_many :aliases # # proxies :address, :city, :state, :zip # proxies :aliases, :name # end # # This creates the following methods on your user objects: # # * address_city # * address_state # * address_zip # * alias_name # # Notice that it's "alias_name" and not "aliases_name". ActsAsProxy only proxies the *first* member of the associated set, so it uses singular syntax. def proxies(association_name, *methods) if [:has_one, :belongs_to].include? reflect_on_association(association_name).macro write_singular_proxies(association_name, *methods) else write_plural_proxies(association_name, *methods) end end def write_singular_proxies(association_name, *methods) #:nodoc: methods.each do |method| class_eval <<-END, __FILE__, __LINE__ def #{association_name}_#{method} #{association_name}.#{method} if #{association_name} end def #{association_name}_#{method}=(val) if #{association_name} unsaved_proxied << #{association_name} else self.#{association_name} = #{reflect_on_association(association_name).klass}.new end #{association_name}.#{method} = val end END end end def write_plural_proxies(association_name, *methods) #:nodoc: methods.each do |method| class_eval <<-END, __FILE__, __LINE__ def #{association_name.to_s.singularize}_#{method} #{association_name}.first.#{method} if #{association_name}.first end def #{association_name.to_s.singularize}_#{method}=(val) if #{association_name}.size == 0 #{association_name} << #{reflect_on_association(association_name).klass}.new else unsaved_proxied << #{association_name}.first end #{association_name}.first.#{method} = val end END end end end end