module ActsAsJoinable module Core def self.included(base) base.send :include, ActsAsJoinable::Core::InstanceMethods base.extend ActsAsJoinable::Core::ClassMethods base.initialize_acts_as_joinable_on_core end module ClassMethods def initialize_acts_as_joinable_on_core args = acts_as_joinable_config.dup options = args.extract_options! if args.empty? args = ActsAsJoinable.models as = :child else as = options[:as] || :parent end contexts = options[:contexts] || [] contexts = contexts.map(&:to_s).inject({}) {|hash, i| hash[i] = i.empty? ? i : "#{i}_"; hash} sql = options[:conditions] joins = args.inject({}) { |hash, i| hash[i.to_s.pluralize] = as; hash } fields = options[:fields] || [] fields = [fields] unless fields.respond_to?(:flatten) has_many :parent_relationships, :class_name => 'ActsAsJoinable::Relationship', :as => :child has_many :child_relationships, :class_name => 'ActsAsJoinable::Relationship', :as => :parent joins.each do |type, as| type = type.to_s singular_type = type.singularize relationship_type = "#{singular_type}_relationships".to_sym select = "#{type}.*, relationships.id AS relationship_id#{fields.empty? ? '' : ', '}" + fields.collect { |f| "relationships.#{f}" }.join(', ') role = opposite_for(as).to_sym joined_options = { :select => select, :conditions => sql, :through => relationship_type, :source => role, :class_name => type.classify, :source_type => type.classify } relationship_options = { :class_name => 'ActsAsJoinable::Relationship', :as => as } type = type.to_sym # so we don't override unless self.reflect_on_all_associations.detect {|association| association.name.to_s == type.to_s} has_many type, joined_options has_many relationship_type, relationship_options end contexts.each do |context, context_prefix| context_type = "#{context_prefix}#{type}".to_sym relationship_type = "#{context_prefix}#{singular_type}_relationships".to_sym has_many relationship_type, relationship_options.merge( :conditions => ["#{ActsAsJoinable::Relationship.table_name}.context = ?", context.to_s] ) has_many context_type, joined_options.merge( :through => relationship_type, :before_add => lambda do |parent, child| parent.set_joined(role, type, context, child) end ) # define_method context_type do # joins_for(role, type, context) # end # define_method "add_#{context_type.to_s.singularize}" do |value| # value = [value] unless value.is_a?(Array) # value.each do |item| # set_joined(role, type, context, item) # end # end end end end def acts_as_joinable_on(*args) super(*args) initialize_acts_as_joinable_on_core end private def opposite_for(role) role.to_s == "parent" ? "child" : "parent" end end module InstanceMethods def relationships_for(role, type, context = nil, options = {}) conditions = { "#{role}_type" => type.to_s.classify, } conditions[:context] = context.to_s unless context.blank? options.merge!(:conditions => conditions) self.send("#{type.to_s.singularize}_relationships").all(options) end def relationship_for(role, type, context, value, options = {}) relationships_for(role, type, context).detect do |relationship| relationship.send(role).id == value.id end end def joins_for(role, type, context) relationships_for(role, type, context, :include => opposite_for(role)).map(&role) end def set_joined(role, type, context, value) relationship = relationship_for(role, type, context, value) || ActsAsJoinable::Relationship.new clazz = get_join_class(type) relationship.send("#{role}=", value.is_a?(clazz) ? value : clazz.find(value)) relationship.send("#{opposite_for(role)}=", self) relationship.context = context.to_s relationship.save self.send("#{context.to_s}_#{type.to_s.pluralize}") end private def get_join_class(type) if type.is_a?(String) || type.is_a?(Symbol) type.to_s.singularize.camelize.constantize elsif type.is_a?(Class) type else type.class end end def opposite_for(role) role.to_s == "parent" ? "child" : "parent" end end end end