class DataMapper::Associations::ManyToMany::Relationship
Constants
- OPTIONS
Public Instance Methods
Returns a set of keys that identify the target model
@return [DataMapper::PropertySet]
a set of properties that identify the target model
@api semipublic
# File lib/dm-core/associations/many_to_many.rb, line 15 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
Eager load the collection using the source as a base
@param [Resource, Collection] source
the source to query with
@param [Query, Hash] other_query
optional query to restrict the collection
@return [ManyToMany::Collection]
the loaded collection for the source
@api private
# File lib/dm-core/associations/many_to_many.rb, line 158 def eager_load(source, other_query = nil) # FIXME: enable SEL for m:m relationships source.model.all(query_for(source, other_query)) end
Initialize the chain for “many to many” relationships
@api public
# File lib/dm-core/associations/many_to_many.rb, line 130 def finalize through via end
@api semipublic
# File lib/dm-core/associations/many_to_many.rb, line 110 def links return @links if defined?(@links) @links = [] links = [ through, via ] while relationship = links.shift if relationship.respond_to?(:links) links.unshift(*relationship.links) else @links << relationship end end @links.freeze end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 141 def query # TODO: consider making this a query_for method, so that ManyToMany::Relationship#query only # returns the query supplied in the definition @many_to_many_query ||= super.merge(:links => links).freeze end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 136 def source_scope(source) { through.inverse => source } end
Intermediate association for through model relationships
Example: for :bugs association in
class Software::Engineer
include DataMapper::Resource has n, :missing_tests has n, :bugs, :through => :missing_tests
end
through is :missing_tests
TODO: document a case when through option is a model and not an association name
@api semipublic
# File lib/dm-core/associations/many_to_many.rb, line 51 def through return @through if defined?(@through) @through = options[:through] if @through.kind_of?(Associations::Relationship) return @through end model = source_model repository_name = source_repository_name relationships = model.relationships(repository_name) name = through_relationship_name @through = relationships[name] || DataMapper.repository(repository_name) do model.has(min..max, name, through_model, one_to_many_options) end @through.child_key @through end
@api semipublic
# File lib/dm-core/associations/many_to_many.rb, line 76 def via return @via if defined?(@via) @via = options[:via] if @via.kind_of?(Associations::Relationship) return @via end name = self.name through = self.through repository_name = through.relative_target_repository_name through_model = through.target_model relationships = through_model.relationships(repository_name) singular_name = DataMapper::Inflector.singularize(name.to_s).to_sym @via = relationships[@via] || relationships[name] || relationships[singular_name] @via ||= if anonymous_through_model? DataMapper.repository(repository_name) do through_model.belongs_to(singular_name, target_model, many_to_one_options) end else raise UnknownRelationshipError, "No relationships named #{name} or #{singular_name} in #{through_model}" end @via.child_key @via end
Private Instance Methods
Check if the :through association uses an anonymous model
An anonymous model means that DataMapper creates the model in-memory, and sets the relationships to join the source and the target model.
@return [Boolean]
true if the through model is anonymous
@api private
# File lib/dm-core/associations/many_to_many.rb, line 222 def anonymous_through_model? options[:through] == Resource end
Returns collection class used by this type of relationship
@api private
# File lib/dm-core/associations/many_to_many.rb, line 313 def collection_class ManyToMany::Collection end
Returns the inverse relationship class
@api private
# File lib/dm-core/associations/many_to_many.rb, line 274 def inverse_class self.class end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 279 def invert inverse_class.new(inverse_name, parent_model, child_model, inverted_options) end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 284 def inverted_options links = self.links.dup through = links.pop.inverse links.reverse_each do |relationship| inverse = relationship.inverse through = self.class.new( inverse.name, inverse.child_model, inverse.parent_model, inverse.options.merge(:through => through) ) end options = self.options DataMapper::Ext::Hash.only(options, *OPTIONS - [ :min, :max ]).update( :through => through, :child_key => options[:parent_key], :parent_key => options[:child_key], :inverse => self ) end
@api semipublic
# File lib/dm-core/associations/many_to_many.rb, line 261 def many_to_one_options { :parent_key => target_key.map { |property| property.name } } end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 227 def nearest_relationship return @nearest_relationship if defined?(@nearest_relationship) nearest_relationship = self while nearest_relationship.respond_to?(:through) nearest_relationship = nearest_relationship.through end @nearest_relationship = nearest_relationship end
@api semipublic
# File lib/dm-core/associations/many_to_many.rb, line 266 def one_to_many_options { :parent_key => source_key.map { |property| property.name } } end
all properties added to the anonymous through model are keys
# File lib/dm-core/associations/many_to_many.rb, line 174 def property(name, type, options = {}) options[:key] = true options.delete(:index) super end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 166 def through_model namespace, name = through_model_namespace_name if namespace.const_defined?(name) namespace.const_get(name) else Model.new(name, namespace) do # all properties added to the anonymous through model are keys def property(name, type, options = {}) options[:key] = true options.delete(:index) super end end end end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 184 def through_model_namespace_name target_parts = target_model.base_model.name.split('::') source_parts = source_model.base_model.name.split('::') name = [ target_parts.pop, source_parts.pop ].sort.join namespace = Object # find the common namespace between the target_model and source_model target_parts.zip(source_parts) do |target_part, source_part| break if target_part != source_part namespace = namespace.const_get(target_part) end return namespace, name end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 202 def through_relationship_name if anonymous_through_model? namespace = through_model_namespace_name.first relationship_name = DataMapper::Inflector.underscore(through_model.name.sub(/\A#{namespace.name}::/, '')).tr('/', '_') DataMapper::Inflector.pluralize(relationship_name).to_sym else options[:through] end end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 250 def valid_source?(source) relationship = nearest_relationship source_key = relationship.source_key target_key = relationship.target_key source.kind_of?(source_model) && target_key.valid?(source_key.get(source)) end
@api private
# File lib/dm-core/associations/many_to_many.rb, line 240 def valid_target?(target) relationship = via source_key = relationship.source_key target_key = relationship.target_key target.kind_of?(target_model) && source_key.valid?(target_key.get(target)) end