Included Modules

Class Index [+]

Quicksearch

DataMapper::Associations::Relationship

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.

Constants

OPTIONS

Attributes

name[R]

Relationship name

@example for :parent association in

  class VersionControl::Commit
    # ...

    belongs_to :parent
  end

name is :parent

@api semipublic

options[R]

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

instance_variable_name[R]

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

child_repository_name[R]

Repository from where child objects are loaded

@api semipublic

parent_repository_name[R]

Repository from where parent objects are loaded

@api semipublic

min[R]

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

max[R]

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

reader_visibility[R]

Returns the visibility for the source accessor

@return [Symbol]

  the visibility for the accessor added to the source

@api semipublic

writer_visibility[R]

Returns the visibility for the source mutator

@return [Symbol]

  the visibility for the mutator added to the source

@api semipublic

query[R]

Returns query options for relationship.

For this base class, always returns query options has been initialized with. Overriden in subclasses.

@api private

child_properties[R]

@api private

parent_properties[R]

@api private

Public Class Methods

new(name, child_model, parent_model, options = {}) click to toggle source

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
446:       def initialize(name, child_model, parent_model, options = {})
447:         initialize_object_ivar('child_model',  child_model)
448:         initialize_object_ivar('parent_model', parent_model)
449: 
450:         @name                   = name
451:         @instance_variable_name = "@#{@name}".freeze
452:         @options                = options.dup.freeze
453:         @child_repository_name  = @options[:child_repository_name]
454:         @parent_repository_name = @options[:parent_repository_name]
455: 
456:         unless @options[:child_key].nil?
457:           @child_properties     = DataMapper::Ext.try_dup(@options[:child_key]).freeze
458:         end
459:         unless @options[:parent_key].nil?
460:           @parent_properties    = DataMapper::Ext.try_dup(@options[:parent_key]).freeze
461:         end
462: 
463:         @min                    = @options[:min]
464:         @max                    = @options[:max]
465:         @reader_visibility      = @options.fetch(:reader_visibility, :public)
466:         @writer_visibility      = @options.fetch(:writer_visibility, :public)
467:         @default                = @options.fetch(:default, nil)
468: 
469:         # TODO: normalize the @query to become :conditions => AndOperation
470:         #  - Property/Relationship/Path should be left alone
471:         #  - Symbol/String keys should become a Property, scoped to the target_repository and target_model
472:         #  - Extract subject (target) from Operator
473:         #    - subject should be processed same as above
474:         #  - each subject should be transformed into AbstractComparison
475:         #    object with the subject, operator and value
476:         #  - transform into an AndOperation object, and return the
477:         #    query as :condition => and_object from self.query
478:         #  - this should provide the best performance
479: 
480:         @query = DataMapper::Ext::Hash.except(@options, *self.class::OPTIONS).freeze
481:       end

Public Instance Methods

==(other) click to toggle source

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
368:       def ==(other)
369:         return true  if equal?(other)
370:         other.respond_to?(:cmp_repository?, true) &&
371:         other.respond_to?(:cmp_model?, true)      &&
372:         other.respond_to?(:cmp_key?, true)        &&
373:         other.respond_to?(:min)                   &&
374:         other.respond_to?(:max)                   &&
375:         other.respond_to?(:query)                 &&
376:         cmp?(other, :==)
377:       end
child_key() click to toggle source

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
195:       def child_key
196:         return @child_key if defined?(@child_key)
197: 
198:         repository_name = child_repository_name || parent_repository_name
199:         properties      = child_model.properties(repository_name)
200: 
201:         @child_key = if @child_properties
202:           child_key = properties.values_at(*@child_properties)
203:           properties.class.new(child_key).freeze
204:         else
205:           properties.key
206:         end
207:       end
Also aliased as: relationship_child_key
child_model() click to toggle source

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
168:       def child_model
169:         return @child_model if defined?(@child_model)
170:         child_model_name = self.child_model_name
171:         @child_model = DataMapper::Ext::Module.find_const(@parent_model || Object, child_model_name)
172:       rescue NameError
173:         raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}"
174:       end
child_model?() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 177
177:       def child_model?
178:         child_model
179:         true
180:       rescue NameError
181:         false
182:       end
child_model_name() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 185
185:       def child_model_name
186:         @child_model ? child_model.name : @child_model_name
187:       end
eager_load(source, query = nil) click to toggle source

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
307:       def eager_load(source, query = nil)
308:         targets = source.model.all(query_for(source, query))
309: 
310:         # FIXME: cannot associate targets to m:m collection yet
311:         if source.loaded? && !source.kind_of?(ManyToMany::Collection)
312:           associate_targets(source, targets)
313:         end
314: 
315:         targets
316:       end
eql?(other) click to toggle source

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
354:       def eql?(other)
355:         return true if equal?(other)
356:         instance_of?(other.class) && cmp?(other, :eql?)
357:       end
field() click to toggle source

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
131:       def field
132:         name.to_s
133:       end
get(resource, other_query = nil) click to toggle source

Loads and returns “other end” of the association. Must be implemented in subclasses.

@api semipublic

     # File lib/dm-core/associations/relationship.rb, line 266
266:       def get(resource, other_query = nil)
267:         raise NotImplementedError, "#{self.class}#get not implemented"
268:       end
get!(resource) click to toggle source

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
275:       def get!(resource)
276:         resource.instance_variable_get(instance_variable_name)
277:       end
hash() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 416
416:       def hash
417:         self.class.hash             ^
418:         name.hash                   ^
419:         child_repository_name.hash  ^
420:         parent_repository_name.hash ^
421:         child_model.hash            ^
422:         parent_model.hash           ^
423:         child_properties.hash       ^
424:         parent_properties.hash      ^
425:         min.hash                    ^
426:         max.hash                    ^
427:         query.hash
428:       end
inverse() click to toggle source

Get the inverse relationship from the target model

@api semipublic

     # File lib/dm-core/associations/relationship.rb, line 382
382:       def inverse
383:         return @inverse if defined?(@inverse)
384: 
385:         @inverse = options[:inverse]
386: 
387:         if kind_of_inverse?(@inverse)
388:           return @inverse
389:         end
390: 
391:         relationships = target_model.relationships(relative_target_repository_name)
392: 
393:         @inverse = relationships.detect { |relationship| inverse?(relationship) } ||
394:           invert
395: 
396:         @inverse.child_key
397: 
398:         @inverse
399:       end
loaded?(resource) click to toggle source

Checks if “other end” of association is loaded on given resource.

@api semipublic

     # File lib/dm-core/associations/relationship.rb, line 322
322:       def loaded?(resource)
323:         resource.instance_variable_defined?(instance_variable_name)
324:       end
parent_key() click to toggle source

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
248:       def parent_key
249:         return @parent_key if defined?(@parent_key)
250: 
251:         repository_name = parent_repository_name || child_repository_name
252:         properties      = parent_model.properties(repository_name)
253: 
254:         @parent_key = if @parent_properties
255:           parent_key = properties.values_at(*@parent_properties)
256:           properties.class.new(parent_key).freeze
257:         else
258:           properties.key
259:         end
260:       end
parent_model() click to toggle source

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
221:       def parent_model
222:         return @parent_model if defined?(@parent_model)
223:         parent_model_name = self.parent_model_name
224:         @parent_model = DataMapper::Ext::Module.find_const(@child_model || Object, parent_model_name)
225:       rescue NameError
226:         raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}"
227:       end
parent_model?() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 230
230:       def parent_model?
231:         parent_model
232:         true
233:       rescue NameError
234:         false
235:       end
parent_model_name() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 238
238:       def parent_model_name
239:         @parent_model ? parent_model.name : @parent_model_name
240:       end
query_for(source, other_query = nil) click to toggle source

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
150:       def query_for(source, other_query = nil)
151:         repository_name = relative_target_repository_name_for(source)
152: 
153:         DataMapper.repository(repository_name).scope do
154:           query = target_model.query.dup
155:           query.update(self.query)
156:           query.update(:conditions => source_scope(source))
157:           query.update(other_query) if other_query
158:           query.update(:fields => query.fields | target_key)
159:         end
160:       end
relative_target_repository_name() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 402
402:       def relative_target_repository_name
403:         target_repository_name || source_repository_name
404:       end
relative_target_repository_name_for(source) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 407
407:       def relative_target_repository_name_for(source)
408:         target_repository_name || if source.respond_to?(:repository)
409:           source.repository.name
410:         else
411:           source_repository_name
412:         end
413:       end
set(resource, association) click to toggle source

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
283:       def set(resource, association)
284:         raise NotImplementedError, "#{self.class}#set not implemented"
285:       end
set!(resource, association) click to toggle source

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
292:       def set!(resource, association)
293:         resource.instance_variable_set(instance_variable_name, association)
294:       end
source_scope(source) click to toggle source

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
142:       def source_scope(source)
143:         { inverse => source }
144:       end
valid?(value, negated = false) click to toggle source

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
335:       def valid?(value, negated = false)
336:         case value
337:           when Enumerable then valid_target_collection?(value, negated)
338:           when Resource   then valid_target?(value)
339:           when nil        then true
340:           else
341:             raise ArgumentError, "+value+ should be an Enumerable, Resource or nil, but was a #{value.class.name}"
342:         end
343:       end

Private Instance Methods

associate_targets(source, targets) click to toggle source
     # File lib/dm-core/associations/relationship.rb, line 645
645:       def associate_targets(source, targets)
646:         # TODO: create an object that wraps this logic, and when the first
647:         # kicker is fired, then it'll load up the collection, and then
648:         # populate all the other methods
649: 
650:         target_maps = Hash.new { |hash, key| hash[key] = [] }
651: 
652:         targets.each do |target|
653:           target_maps[target_key.get(target)] << target
654:         end
655: 
656:         Array(source).each do |source|
657:           key = source_key.get(source)
658:           eager_load_targets(source, target_maps[key], query)
659:         end
660:       end
cmp?(other, operator) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 605
605:       def cmp?(other, operator)
606:         name.send(operator, other.name)           &&
607:         cmp_repository?(other, operator, :child)  &&
608:         cmp_repository?(other, operator, :parent) &&
609:         cmp_model?(other,      operator, :child)  &&
610:         cmp_model?(other,      operator, :parent) &&
611:         cmp_key?(other,        operator, :child)  &&
612:         cmp_key?(other,        operator, :parent) &&
613:         min.send(operator, other.min)             &&
614:         max.send(operator, other.max)             &&
615:         query.send(operator, other.query)
616:       end
cmp_key?(other, operator, type) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 636
636:       def cmp_key?(other, operator, type)
637:         property_method = "#{type}_properties"
638: 
639:         self_key  = send(property_method)
640:         other_key = other.send(property_method)
641: 
642:         self_key.send(operator, other_key)
643:       end
cmp_model?(other, operator, type) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 629
629:       def cmp_model?(other, operator, type)
630:         send("#{type}_model?")       &&
631:         other.send("#{type}_model?") &&
632:         send("#{type}_model").base_model.send(operator, other.send("#{type}_model").base_model)
633:       end
cmp_repository?(other, operator, type) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 619
619:       def cmp_repository?(other, operator, type)
620:         # if either repository is nil, then the relationship is relative,
621:         # and the repositories are considered equivalent
622:         return true unless repository_name = send("#{type}_repository_name")
623:         return true unless other_repository_name = other.send("#{type}_repository_name")
624: 
625:         repository_name.send(operator, other_repository_name)
626:       end
eager_load_targets(source, targets, query) click to toggle source

Sets the association targets in the resource

@param [Resource] source

  the source to set

@param [Array] targets

  the targets for the association

@param [Query, Hash] query

  the query to scope the association with

@return [undefined]

@api private

     # File lib/dm-core/associations/relationship.rb, line 528
528:       def eager_load_targets(source, targets, query)
529:         raise NotImplementedError, "#{self.class}#eager_load_targets not implemented"
530:       end
initialize_object_ivar(name, object) click to toggle source

Set the correct ivars for the named object

This method should set the object in an ivar with the same name provided, plus it should set a String form of the object in a second ivar.

@param [String]

  the name of the ivar to set

@param [#, #, #] object

  the object to set in the ivar

@return [String]

  the String value

@raise [ArgumentError]

  raise when object does not respond to expected methods

@api private

     # File lib/dm-core/associations/relationship.rb, line 501
501:       def initialize_object_ivar(name, object)
502:         if object.respond_to?(:name)
503:           instance_variable_set("@#{name}", object)
504:           initialize_object_ivar(name, object.name)
505:         elsif object.respond_to?(:to_str)
506:           instance_variable_set("@#{name}_name", object.to_str.dup.freeze)
507:         elsif object.respond_to?(:to_sym)
508:           instance_variable_set("@#{name}_name", object.to_sym)
509:         else
510:           raise ArgumentError, "#{name} does not respond to #to_str or #name"
511:         end
512: 
513:         object
514:       end
inverse?(other) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 563
563:       def inverse?(other)
564:         return true if @inverse.equal?(other)
565: 
566:         other != self                        &&
567:         kind_of_inverse?(other)              &&
568:         cmp_repository?(other, :==, :child)  &&
569:         cmp_repository?(other, :==, :parent) &&
570:         cmp_model?(other,      :==, :child)  &&
571:         cmp_model?(other,      :==, :parent) &&
572:         cmp_key?(other,        :==, :child)  &&
573:         cmp_key?(other,        :==, :parent)
574: 
575:         # TODO: match only when the Query is empty, or is the same as the
576:         # default scope for the target model
577:       end
inverse_name() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 580
580:       def inverse_name
581:         inverse = options[:inverse]
582:         if inverse.kind_of?(Relationship)
583:           inverse.name
584:         else
585:           inverse
586:         end
587:       end
invert() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 590
590:       def invert
591:         inverse_class.new(inverse_name, child_model, parent_model, inverted_options)
592:       end
inverted_options() click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 595
595:       def inverted_options
596:         DataMapper::Ext::Hash.only(options, *OPTIONS - [ :min, :max ]).update(:inverse => self)
597:       end
kind_of_inverse?(other) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 600
600:       def kind_of_inverse?(other)
601:         other.kind_of?(inverse_class)
602:       end
relationship_child_key() click to toggle source

Access Relationship#child_key directly

@api private

Alias for: child_key
valid_source?(source) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 557
557:       def valid_source?(source)
558:         source.kind_of?(source_model) &&
559:         target_key.valid?(source_key.get(source))
560:       end
valid_target?(target) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 551
551:       def valid_target?(target)
552:         target.kind_of?(target_model) &&
553:         source_key.valid?(target_key.get(target))
554:       end
valid_target_collection?(collection, negated) click to toggle source

@api private

     # File lib/dm-core/associations/relationship.rb, line 533
533:       def valid_target_collection?(collection, negated)
534:         if collection.kind_of?(Collection)
535:           # TODO: move the check for model_key into Collection#reloadable?
536:           # since what we're really checking is a Collection's ability
537:           # to reload itself, which is (currently) only possible if the
538:           # key was loaded.
539:           model     = target_model
540:           model_key = model.key(repository.name)
541: 
542:           collection.model <= model                          &&
543:           (collection.query.fields & model_key) == model_key &&
544:           (collection.loaded? ? (collection.any? || negated) : true)
545:         else
546:           collection.all? { |resource| valid_target?(resource) }
547:         end
548:       end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.