lib/og/relation.rb in og-0.23.0 vs lib/og/relation.rb in og-0.24.0

- old
+ new

@@ -1,24 +1,25 @@ -require 'nano/object/constant' -require 'nano/string/capitalized%3F' - +require 'nano/kernel/constant' +require 'nano/string/capitalized' require 'mega/orm_support' +require 'mega/inheritor' +require 'mega/annotation' module Og # A relation between Entities. +#-- +# Relations are resolved in multiple passes. First +# the relations are logged in :relations... +#++ class Relation # The parameters of this relation. attr_accessor :options - # Is this a polymorphic relation? - - attr_accessor :is_polymorphic - # A generalized initialize method for all relations. # Contains common setup code. def initialize(args, options = {}) @options = options @@ -51,87 +52,72 @@ # Inflect target_class if not provided. @options[:target_class] ||= @options[target_name].to_s.singular.camelize.intern - setup() + # FIXME: this is a hack! + # setup() rescue nil 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 - + # Get an option. + def [](key) @options[key] end + # Set an option. + def []=(key, val) @options[key] = val end - # Is the relation polymorphic? + # Is this a polymorphic marker? - def polymorphic? + def polymorphic_marker? 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 + # Is this a polymorphic relation ? + + def polymorphic? + target_class.ann.this[:polymorphic] 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 + +end + +# A collection of helper methods and resolvers +# for relations. + +class Relation + class << self + # 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) + def symbol_to_class(sym, owner_class) c = owner_class.name.dup c = "::" + c unless c =~ /::/ c.gsub!(/::.*$/, '::') c << sym.to_s begin @@ -141,73 +127,135 @@ c = sym retry end end end + alias_method :resolve_symbol, :symbol_to_class - def resolve_target - if target_class.is_a?(Symbol) - klass = resolve_symbol(target_class, owner_class) - @options[:target_class] = klass + def resolve_targets(klass) + for r in klass.relations + if r.target_class.is_a? Symbol + klass = symbol_to_class(r.target_class, r.owner_class) + r.options[:target_class] = klass + end end end - - # Resolve a polymorphic target class. - # Overrided in subclasses. + + # If the target class is just an Object mark this class + # (self) as a polymorphic parent class. This class acts + # as template to generate customized versions of this class. + # + # For example: + # + # class Comment + # belongs_to :parent, Object # <= polymorphic + # ... + # end + + def resolve_polymorphic_markers(klass) + for r in klass.relations + if r.polymorphic_marker? + r.owner_class.ann :this, :polymorphic => r.owner_class + end + end + end + + # Resolve polymorphic relations. + # If the target class is polymorphic, create a specialized + # version of that class (the target) enclosed in the + # owner namespace. + # + # For example: + # + # class Article + # has_many Comment + # ... + # end + + def resolve_polymorphic_relations(klass) + for r in klass.relations + if r.polymorphic? + + target_dm = r.target_class.to_s.demodulize + r.owner_class.module_eval %{ + class #{r.owner_class}::#{target_dm} < #{r.target_class} + end + } + + # Replace the target class. + + r[:target_class] = eval("#{r.owner_class}::#{target_dm}") + end - def resolve_polymorphic + r.resolve_polymorphic + end end - # This method is implemented in subclasses. + # Resolve the names of the relations. - def enchant + def resolve_names(klass) + for r in klass.relations + target_name = if r.collection + :target_plural_name + else + :target_singular_name + end + + # Inflect the relation name. + + unless r[target_name] + r[target_name] = if r.collection + r.target_class.to_s.demodulize.underscore.downcase.plural.intern + else + r.target_class.to_s.demodulize.underscore.downcase.intern + end + end + + r[:name] = r[target_name] + end end - - # Access the hash values as methods. - - def method_missing(sym, *args) - return @options[sym] + + # General resovle method. + + def resolve(klass, action = :resolve_polymorphic) + for r in klass.relations + r.send(action) + end end - class << self - - def resolve(klass, action) - if relations = klass.metadata.relations - for relation in klass.metadata.relations - relation.send(action) - end - end + # Perform relation enchanting on this class. + + def enchant(klass) + # update inherited relations. + + for r in klass.relations + r[:owner_class] = klass 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 + # enchant. + + for r in klass.relations + # p "=== #{klass} : #{r.class} : #{r.name}" + r.enchant() unless r.polymorphic_marker? end - + end - + + end end -# Relation macros. These macros are used to define Entity -# relations. +# Relations domain specific language (DSL). This +# language defines macros that are used to define Entity +# relations. Additional macros allow for relation +# inspection. -module RelationMacros +module RelationDSL def self.append_features(base) super + base.module_eval do + inheritor(:relations, [], :+) unless @relations + end base.extend(ClassMethods) end module ClassMethods @@ -218,65 +266,62 @@ # 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) + relations! << Og::BelongsTo.new(args, :owner_class => self) end # === Examples # # refers_to :topic # inflects Topic # refers_to Topic # inflects :topic def refers_to(*args) require 'og/relation/refers_to' - meta :relations, Og::RefersTo.new(args, :owner_class => self) + 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) + 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) + 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) + 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) + 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 } + relations.find { |r| r[:name] == name } end alias_method :relation, :inspect_relation end end end + +# * George Moschovitis <gm@navel.gr> \ No newline at end of file