module ActsAsJoinable def self.models @models ||= Dir[Rails.root + "/app/models/*.rb"].collect { |f| File.basename f, '.rb' } end def self.models=(value) @models = value end def self.included(base) base.extend ClassMethods end module ClassMethods # the parent in the relationship, so to speak def acts_as_joinable_on(*args, &block) if args.empty? # Relationship model belongs_to :parent, :polymorphic => true belongs_to :child, :polymorphic => true ActsAsJoinable.models.each do |m| belongs_to "parent_#{m}".intern, :foreign_key => 'parent_id', :class_name => m.camelize belongs_to "child_#{m}".intern, :foreign_key => 'child_id', :class_name => m.camelize end else options = args.extract_options! sql = options[:conditions] table = options[:table] 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 args.each do |type| type = type.to_s table = table || type select = "#{table}.*, relationships.id AS relationship_id#{fields.empty? ? '' : ', '}" + fields.collect { |f| "relationships.#{f}" }.join(', ') opts = { :select => select, :conditions => sql, :through => :parent_relationships, :source => :parent, :class_name => type.classify, :source_type => table.classify } has_many "parent_#{type}", opts do fields.each do |field| define_method field.to_s.pluralize do |*args| value = args[0] || 1 scoped :conditions => [ "relationships.#{field} = ?", value ] end end end opts = { :select => select, :conditions => sql, :through => :child_relationships, :source => :child, :class_name => type.classify, :source_type => table.classify } has_many "child_#{type}", opts do fields.each do |field| define_method field.to_s.pluralize do |*args| value = args[0] || 1 scoped :conditions => [ "relationships.#{field} = ?", value ] end end end self.class_eval do # Records reader define_method type do |*args| if (read_attribute(:type) || self.class.to_s) < (args.empty? ? type.classify : args[0].to_s) eval "self.child_#{type}" else eval "self.parent_#{type}" end end end fields.each do |field| # Relationship field writer self.class_eval do define_method field.to_s + '=' do |value| modified = read_attribute(:modified_relationship_fields) || [] modified << field write_attribute :modified_relationship_fields, modified.uniq write_attribute field, value end end end end unless included_modules.include?(InstanceMethods) extend ClassMethods include InstanceMethods before_save :save_relationship_fields end end end # the child in the relationship, so to speak def acts_as_joinable(*args, &block) acts_as_joinable_on(*args, &block) end end module InstanceMethods # Before save def save_relationship_fields return unless read_attribute(:relationship_id) && read_attribute(:modified_relationship_fields) r = Relationship.find self.relationship_id read_attribute(:modified_relationship_fields).each do |field| r[field] = self[field] end r.save write_attribute :modified_relationship_fields, nil end def get_joining(type, context) get_joinings(type, context).first end def get_joinings(type, context) self.joinings.select do |joining| joining.context == context.to_s end end def get_joined(type, context) get_joineds(type, context).first end def get_joineds(type, context) return [] unless self.joineds and !self.joineds.empty? get_joinings(context).collect do |joining| joining.joined end end def set_joined(type, context, value) joining = get_joining(context) || Joining.new clazz = get_join_class(type) joining.joined = value.is_a?(clazz) ? value : clazz.find(value) joining.joining = self joining.context = context.to_s joining.save self.send("#{context.to_s}_#{type.to_s}") end private def get_join_class(type) if type.is_a?(String) || type.is_a?(Symbol) type.to_s.camelize.constantize elsif type.is_a?(Class) type else type.class end end end end ActiveRecord::Base.send(:include, ActsAsJoinable) if defined?(ActiveRecord::Base) Dir["#{File.dirname(__FILE__)}/../app/models/*"].each { |c| require c if File.extname(c) == ".rb" }