## # Creates methods on object which delegate to an association proxy. # see delegate_belongs_to for two uses # # Todo - integrate with ActiveRecord::Dirty to make sure changes to delegate object are noticed # Should do # class User < Spree::Base; delegate_belongs_to :contact, :firstname; end # class Contact < Spree::Base; end # u = User.first # u.changed? # => false # u.firstname = 'Bobby' # u.changed? # => true # # Right now the second call to changed? would return false # # Todo - add has_one support. fairly straightforward addition ## module DelegateBelongsTo extend ActiveSupport::Concern module ClassMethods @@default_rejected_delegate_columns = ['created_at','created_on','updated_at','updated_on','lock_version','type','id','position','parent_id','lft','rgt'] mattr_accessor :default_rejected_delegate_columns ## # Creates methods for accessing and setting attributes on an association. Uses same # default list of attributes as delegates_to_association. # @todo Integrate this with ActiveRecord::Dirty, so if you set a property through one of these setters and then call save on this object, it will save the associated object automatically. # delegate_belongs_to :contact # delegate_belongs_to :contact, [:defaults] ## same as above, and useless # delegate_belongs_to :contact, [:defaults, :address, :fullname], :class_name => 'VCard' ## def delegate_belongs_to(association, *attrs) opts = attrs.extract_options! initialize_association :belongs_to, association, opts attrs = get_association_column_names(association) if attrs.empty? attrs.concat get_association_column_names(association) if attrs.delete :defaults attrs.each do |attr| class_def attr do |*args| send(:delegator_for, association, attr, *args) end class_def "#{attr}=" do |val| send(:delegator_for_setter, association, attr, val) end end end protected def get_association_column_names(association, without_default_rejected_delegate_columns=true) begin association_klass = reflect_on_association(association).klass methods = association_klass.column_names methods.reject!{|x|default_rejected_delegate_columns.include?(x.to_s)} if without_default_rejected_delegate_columns return methods rescue return [] end end ## # initialize_association :belongs_to, :contact def initialize_association(type, association, opts={}) raise 'Illegal or unimplemented association type.' unless [:belongs_to].include?(type.to_s.to_sym) send type, association, opts if reflect_on_association(association).nil? end private def class_def(name, method=nil, &blk) class_eval { method.nil? ? define_method(name, &blk) : define_method(name, method) } end end def delegator_for(association, attr, *args) return if self.class.column_names.include?(attr.to_s) send "build_#{association}" if send(association).nil? if args.empty? send(association).send(attr) else send(association).send(attr, *args) end end def delegator_for_setter(association, attr, val) return if self.class.column_names.include?(attr.to_s) send "build_#{association}" if send(association).nil? send(association).send("#{attr}=", val) end protected :delegator_for protected :delegator_for_setter end ActiveRecord::Base.send :include, DelegateBelongsTo