require 'facet/object/constant' require 'facet/string/plural' require 'facet/string/demodulize' require 'facet/string/underscore' module Og # A relation between Entities. class Relation attr_accessor :options attr_accessor :is_polymorphic # A generalized initialize method for all relations. # Contains common setup code. def initialize(args, options = {}) @options = options @options.update(args.pop) if args.last.is_a?(Hash) if args.empty? or (not (args.last.is_a?(Class) or args.last.is_a?(Symbol))) raise 'Class of target not defined' end target_name = if collection :target_plural_name else :target_singular_name end @options[:target_class] = args.pop @options[target_name] = args.first unless args.empty? setup() end def setup if target_class == Object # If the target class is just an Object mark that class # as a polymorphic parent class. # This class acts as template # to generate customized versions of this class. owner_class.meta(:polymorphic, owner_class) elsif target_class.respond_to?(:metadata) and target_class.metadata.polymorphic # If the target class is polymorphic, create a specialized # version of the class enclosed in the owner namespace. target_dm = target_class.to_s.demodulize owner_class.module_eval %{ class #{owner_class}::#{target_dm} < #{target_class} end } eval %{ @options[:target_class] = #{owner_class}::#{target_dm} } end target_name = if collection :target_plural_name else :target_singular_name end # Inflect the relation name. unless @options[target_name] @options[target_name] = if collection target_class.to_s.demodulize.underscore.downcase.plural.intern else target_class.to_s.demodulize.underscore.downcase.intern end end @options[:name] = options[target_name] end def [](key) @options[key] end def []=(key, val) @options[key] = val end # Is the relation polymorphic? def polymorphic? target_class == Object end #-- # gmosx, TODO: remove, this is not really needed. #++ def resolve_options @options[:owner_pk], @options[:owner_pkclass] = owner_class.primary_key if target_class.respond_to?(:primary_key) @options[:target_pk], @options[:target_pkclass] = target_class.primary_key end end # To avoid forward declarations, references to undefined # (at the time of the creation of the relation) classes are # stored as symbols. These symbols are resolved by this # method. #-- # FIXME: do something more elegant here. #++ def resolve_symbol(sym, owner_class) c = owner_class.name.dup c = "::" + c unless c =~ /::/ c.gsub!(/::.*$/, '::') c << sym.to_s begin return constant(c) rescue unless c == sym c = sym retry end end end def resolve_target if target_class.is_a?(Symbol) klass = resolve_symbol(target_class, owner_class) @options[:target_class] = klass end end # Resolve a polymorphic target class. # Overrided in subclasses. def resolve_polymorphic end # This method is implemented in subclasses. def enchant end # Access the hash values as methods. def method_missing(sym, *args) return @options[sym] end class << self def resolve(klass, action) if relations = klass.metadata.relations for relation in klass.metadata.relations relation.send(action) end end end def enchant(klass) if relations = klass.metadata.relations # update inherited relations. # gmosx, FIXME: implement a better fix/rethink this! for relation in relations relation[:owner_class] = klass unless relation.target_class == Object or relation.target_class.metadata.polymorphic relation.setup end end # enchant. for relation in relations relation.enchant unless relation.target_class == Object end end end end end # Relation macros. These macros are used to define Entity # relations. module RelationMacros def self.append_features(base) super base.extend(ClassMethods) end module ClassMethods # === Examples # # belongs_to Article # belongs_to :article, Article # belongs_to :article, Article, :view => 'lala' def belongs_to(*args) require 'og/relation/belongs_to' meta :relations, Og::BelongsTo.new(args, :owner_class => self) end # === Examples # # refers_to Topic def refers_to(*args) require 'og/relation/refers_to' meta :relations, Og::RefersTo.new(args, :owner_class => self) end # === Examples # # has_one User def has_one(*args) require 'og/relation/has_one' meta :relations, Og::HasOne.new(args, :owner_class => self) end # === Examples # # has_many Comment # has_many :comments, Comment def has_many(*args) require 'og/relation/has_many' meta :relations, Og::HasMany.new(args, :owner_class => self, :collection => true) end def joins_many(*args) require 'og/relation/joins_many' meta :relations, Og::JoinsMany.new(args, :owner_class => self, :collection => true) end def many_to_many(*args) require 'og/relation/many_to_many' meta :relations, Og::ManyToMany.new(args, :owner_class => self, :collection => true) end def inspect_relations __meta[:relations] end alias_method :relations, :inspect_relations def inspect_relation(name) __meta[:relations].find { |r| r[:name] == name } end alias_method :relation, :inspect_relation end end end