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 @options[:target_class] = args.pop 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 args.empty? @options[target_name] = args.first else @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_target if target_class.is_a?(Symbol) c = owner_class.name.dup c = "::" + c unless c =~ /::/ c.gsub!(/::.*$/, '::') c << target_class.to_s begin klass = constant(c) rescue c = target_class retry end @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 klass.__meta[:relations] for relation in klass.__meta[:relations] relation.send(action) end end end def enchant(klass) if klass.__meta[:relations] for relation in klass.__meta[: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