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 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 target_name = if collection :target_plural_name else :target_singular_name end 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 def resolve_options @options[:owner_pk], @options[:owner_pkclass] = owner_class.primary_key @options[:target_pk], @options[:target_pkclass] = target_class.primary_key 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 def enchant end # Access the hash values as methods. def method_missing(sym, *args) return @options[sym] end class << self def enchant(klass) if klass.__meta[:relations] for relation in klass.__meta[:relations] relation.resolve_target relation.resolve_options relation.enchant 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