class DataMapper::Associations::ManyToMany::Relationship

Constants

OPTIONS

Public Instance Methods

child_key() click to toggle source

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
Also aliased as: target_key
eager_load(source, other_query = nil) click to toggle source

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
finalize() click to toggle source

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
query() click to toggle source

@api private

Calls superclass method
# 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
source_scope(source) click to toggle source

@api private

# File lib/dm-core/associations/many_to_many.rb, line 136
def source_scope(source)
  { through.inverse => source }
end
target_key()

@api semipublic

Alias for: child_key
through() click to toggle source

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
via() click to toggle source

@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

anonymous_through_model?() click to toggle source

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
collection_class() click to toggle source

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
inverse_class() click to toggle source

Returns the inverse relationship class

@api private

# File lib/dm-core/associations/many_to_many.rb, line 274
def inverse_class
  self.class
end
invert() click to toggle source

@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
inverted_options() click to toggle source

@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
many_to_one_options() click to toggle source

@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
nearest_relationship() click to toggle source

@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
one_to_many_options() click to toggle source

@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
property(name, type, options = {}) click to toggle source

all properties added to the anonymous through model are keys

Calls superclass method
# File lib/dm-core/associations/many_to_many.rb, line 174
def property(name, type, options = {})
  options[:key] = true
  options.delete(:index)
  super
end
through_model() click to toggle source

@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
through_model_namespace_name() click to toggle source

@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
through_relationship_name() click to toggle source

@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
valid_source?(source) click to toggle source

@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
valid_target?(target) click to toggle source

@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