Base class for relationships. Each type of relationship (1 to 1, 1 to n, n to m) implements a subclass of this class with methods like get and set overridden.
ivar used to store collection of child options in source
@example for :commits association in
class VersionControl::Branch # ... has n, :commits end
instance variable name for source will be @commits
@api semipublic
Maximum number of child objects for relationship
@example for :fouls association in
class Basketball::Player # ... has 0..5, :fouls end
maximum is 5
@api semipublic
Minimum number of child objects for relationship
@example for :cores association in
class CPU::Multicore # ... has 2..n, :cores end
minimum is 2
@api semipublic
Relationship name
@example for :parent association in
class VersionControl::Commit # ... belongs_to :parent end
name is :parent
@api semipublic
Options used to set up association of this relationship
@example for :author association in
class VersionControl::Commit # ... belongs_to :author, :model => 'Person' end
options is a hash with a single key, :model
@api semipublic
Returns query options for relationship.
For this base class, always returns query options has been initialized with. Overriden in subclasses.
@api private
Initializes new Relationship: sets attributes of relationship from options as well as conventions: for instance, @ivar name for association is constructed by prefixing @ to association name.
Once attributes are set, reader and writer are created for the resource association belongs to
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 446 def initialize(name, child_model, parent_model, options = {}) initialize_object_ivar('child_model', child_model) initialize_object_ivar('parent_model', parent_model) @name = name @instance_variable_name = "@#{@name}".freeze @options = options.dup.freeze @child_repository_name = @options[:child_repository_name] @parent_repository_name = @options[:parent_repository_name] unless @options[:child_key].nil? @child_properties = DataMapper::Ext.try_dup(@options[:child_key]).freeze end unless @options[:parent_key].nil? @parent_properties = DataMapper::Ext.try_dup(@options[:parent_key]).freeze end @min = @options[:min] @max = @options[:max] @reader_visibility = @options.fetch(:reader_visibility, :public) @writer_visibility = @options.fetch(:writer_visibility, :public) @default = @options.fetch(:default, nil) # TODO: normalize the @query to become :conditions => AndOperation # - Property/Relationship/Path should be left alone # - Symbol/String keys should become a Property, scoped to the target_repository and target_model # - Extract subject (target) from Operator # - subject should be processed same as above # - each subject should be transformed into AbstractComparison # object with the subject, operator and value # - transform into an AndOperation object, and return the # query as :condition => and_object from self.query # - this should provide the best performance @query = DataMapper::Ext::Hash.except(@options, *self.class::OPTIONS).freeze end
Compares another Relationship for equivalency
@param [Relationship] other
the other Relationship to compare with
@return [Boolean]
true if they are equal, false if not
@api public
# File lib/dm-core/associations/relationship.rb, line 368 def ==(other) return true if equal?(other) other.respond_to?(:cmp_repository?, true) && other.respond_to?(:cmp_model?, true) && other.respond_to?(:cmp_key?, true) && other.respond_to?(:min) && other.respond_to?(:max) && other.respond_to?(:query) && cmp?(other, :==) end
Returns a set of keys that identify the target model
@return [PropertySet]
a set of properties that identify the target model
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 195 def child_key return @child_key if defined?(@child_key) repository_name = child_repository_name || parent_repository_name properties = child_model.properties(repository_name) @child_key = if @child_properties child_key = properties.values_at(*@child_properties) properties.class.new(child_key).freeze else properties.key end end
Returns model class used by child side of the relationship
@return [Resource]
Model for association child
@api private
# File lib/dm-core/associations/relationship.rb, line 168 def child_model return @child_model if defined?(@child_model) child_model_name = self.child_model_name @child_model = DataMapper::Ext::Module.find_const(@parent_model || Object, child_model_name) rescue NameError raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}" end
@api private
# File lib/dm-core/associations/relationship.rb, line 177 def child_model? child_model true rescue NameError false end
@api private
# File lib/dm-core/associations/relationship.rb, line 185 def child_model_name @child_model ? child_model.name : @child_model_name end
Eager load the collection using the source as a base
@param [Collection] source
the source collection to query with
@param [Query, Hash] query
optional query to restrict the collection
@return [Collection]
the loaded collection for the source
@api private
# File lib/dm-core/associations/relationship.rb, line 307 def eager_load(source, query = nil) targets = source.model.all(query_for(source, query)) # FIXME: cannot associate targets to m:m collection yet if source.loaded? && !source.kind_of?(ManyToMany::Collection) associate_targets(source, targets) end targets end
Compares another Relationship for equality
@param [Relationship] other
the other Relationship to compare with
@return [Boolean]
true if they are equal, false if not
@api public
# File lib/dm-core/associations/relationship.rb, line 354 def eql?(other) return true if equal?(other) instance_of?(other.class) && cmp?(other, :eql?) end
Returns the String the Relationship would use in a Hash
@return [String]
String name for the Relationship
@api private
# File lib/dm-core/associations/relationship.rb, line 131 def field name.to_s end
Loads and returns “other end” of the association. Must be implemented in subclasses.
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 266 def get(resource, other_query = nil) raise NotImplementedError, "#{self.class}#get not implemented" end
Gets “other end” of the association directly as @ivar on given resource. Subclasses usually use implementation of this class.
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 275 def get!(resource) resource.instance_variable_get(instance_variable_name) end
@api private
# File lib/dm-core/associations/relationship.rb, line 416 def hash self.class.hash ^ name.hash ^ child_repository_name.hash ^ parent_repository_name.hash ^ child_model.hash ^ parent_model.hash ^ child_properties.hash ^ parent_properties.hash ^ min.hash ^ max.hash ^ query.hash end
Get the inverse relationship from the target model
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 382 def inverse return @inverse if defined?(@inverse) @inverse = options[:inverse] if kind_of_inverse?(@inverse) return @inverse end relationships = target_model.relationships(relative_target_repository_name) @inverse = relationships.detect { |relationship| inverse?(relationship) } || invert @inverse.child_key @inverse end
Checks if “other end” of association is loaded on given resource.
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 322 def loaded?(resource) resource.instance_variable_defined?(instance_variable_name) end
Returns a set of keys that identify parent model
@return [PropertySet]
a set of properties that identify parent model
@api private
# File lib/dm-core/associations/relationship.rb, line 248 def parent_key return @parent_key if defined?(@parent_key) repository_name = parent_repository_name || child_repository_name properties = parent_model.properties(repository_name) @parent_key = if @parent_properties parent_key = properties.values_at(*@parent_properties) properties.class.new(parent_key).freeze else properties.key end end
Returns model class used by parent side of the relationship
@return [Resource]
Class of association parent
@api private
# File lib/dm-core/associations/relationship.rb, line 221 def parent_model return @parent_model if defined?(@parent_model) parent_model_name = self.parent_model_name @parent_model = DataMapper::Ext::Module.find_const(@child_model || Object, parent_model_name) rescue NameError raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}" end
@api private
# File lib/dm-core/associations/relationship.rb, line 230 def parent_model? parent_model true rescue NameError false end
@api private
# File lib/dm-core/associations/relationship.rb, line 238 def parent_model_name @parent_model ? parent_model.name : @parent_model_name end
Creates and returns Query instance that fetches target resource(s) (ex.: articles) for given target resource (ex.: author)
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 150 def query_for(source, other_query = nil) repository_name = relative_target_repository_name_for(source) DataMapper.repository(repository_name).scope do query = target_model.query.dup query.update(self.query) query.update(:conditions => source_scope(source)) query.update(other_query) if other_query query.update(:fields => query.fields | target_key) end end
@api private
# File lib/dm-core/associations/relationship.rb, line 402 def relative_target_repository_name target_repository_name || source_repository_name end
@api private
# File lib/dm-core/associations/relationship.rb, line 407 def relative_target_repository_name_for(source) target_repository_name || if source.respond_to?(:repository) source.repository.name else source_repository_name end end
Sets value of the “other end” of association on given resource. Must be implemented in subclasses.
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 283 def set(resource, association) raise NotImplementedError, "#{self.class}#set not implemented" end
Sets “other end” of the association directly as @ivar on given resource. Subclasses usually use implementation of this class.
@api semipublic
# File lib/dm-core/associations/relationship.rb, line 292 def set!(resource, association) resource.instance_variable_set(instance_variable_name, association) end
Returns a hash of conditions that scopes query that fetches target object
@return [Hash]
Hash of conditions that scopes query
@api private
# File lib/dm-core/associations/relationship.rb, line 142 def source_scope(source) { inverse => source } end
Test the resource to see if it is a valid target
@param [Object] source
the resource or collection to be tested
@return [Boolean]
true if the resource is valid
@api semipulic
# File lib/dm-core/associations/relationship.rb, line 335 def valid?(value, negated = false) case value when Enumerable then valid_target_collection?(value, negated) when Resource then valid_target?(value) when nil then true else raise ArgumentError, "+value+ should be an Enumerable, Resource or nil, but was a #{value.class.name}" end end
Generated with the Darkfish Rdoc Generator 2.