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