Class: DataMapper::Associations::Relationship

Inherits:
Object
  • Object
show all
Includes:
DataMapper::Assertions
Defined in:
lib/dm-core/associations/relationship.rb

Direct Known Subclasses

RelationshipChain

Constant Summary collapse

OPTIONS =
[ :class_name, :child_key, :parent_key, :min, :max, :through ]

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DataMapper::Assertions

#assert_kind_of

Instance Attribute Details

#nameObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



9
10
11
# File 'lib/dm-core/associations/relationship.rb', line 9

def name
  @name
end

#optionsObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



9
10
11
# File 'lib/dm-core/associations/relationship.rb', line 9

def options
  @options
end

#queryObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



9
10
11
# File 'lib/dm-core/associations/relationship.rb', line 9

def query
  @query
end

Instance Method Details

#attach_parent(child, parent) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



172
173
174
# File 'lib/dm-core/associations/relationship.rb', line 172

def attach_parent(child, parent)
  child_key.set(child, parent && parent_key.get(parent))
end

#child_keyObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/dm-core/associations/relationship.rb', line 12

def child_key
  @child_key ||= begin
    child_key = nil
    child_model.repository.scope do |r|
      model_properties = child_model.properties(r.name)

      child_key = parent_key.zip(@child_properties || []).map do |parent_property,property_name|
        # TODO: use something similar to DM::NamingConventions to determine the property name
        parent_name = Extlib::Inflection.underscore(Extlib::Inflection.demodulize(parent_model.base_model.name))
        property_name ||= "#{parent_name}_#{parent_property.name}".to_sym

        if model_properties.has_property?(property_name)
          model_properties[property_name]
        else
          options = {}

          [ :length, :precision, :scale ].each do |option|
            options[option] = parent_property.send(option)
          end

          # NOTE: hack to make each many to many child_key a true key,
          # until I can figure out a better place for this check
          if child_model.respond_to?(:many_to_many)
            options[:key] = true
          end

          child_model.property(property_name, parent_property.primitive, options)
        end
      end
    end
    PropertySet.new(child_key)
  end
end

#child_modelObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



69
70
71
72
73
# File 'lib/dm-core/associations/relationship.rb', line 69

def child_model
  Class === @child_model ? @child_model : (Class === @parent_model ? @parent_model.find_const(@child_model) : Object.find_const(@child_model))
rescue NameError
  raise NameError, "Cannot find the child_model #{@child_model} for #{@parent_model}"
end

#get_children(parent, options = {}, finder = :all, *args) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/dm-core/associations/relationship.rb', line 76

def get_children(parent, options = {}, finder = :all, *args)
  bind_values   = parent_key.get(parent)
  parent_values = bind_values.dup

  with_repository(child_model) do |r|
    parent_identity_map = parent.repository.identity_map(parent_model)
    child_identity_map  = r.identity_map(child_model)

    query_values = parent_identity_map.keys.flatten
    query_values.reject! { |k| child_identity_map[[k]] }

    bind_values = query_values unless query_values.empty?
    query = child_key.map { |k| [ k, bind_values ] }.to_hash

    collection = child_model.send(finder, *(args.dup << @query.merge(options).merge(query)))
    return collection unless collection.kind_of?(Collection) && collection.any?

    grouped_collection = Hash.new { |h,k| h[k] = [] }
    collection.each do |resource|
      grouped_collection[get_parent(resource, parent)] << resource
    end

    association_accessor = "#{self.name}_association"

    ret = nil
    grouped_collection.each do |parent, children|
      association = parent.send(association_accessor)

      query = collection.query.dup
      query.conditions.map! do |operator, property, bind_value|
        if operator != :raw && child_key.has_property?(property.name)
          bind_value = *children.map { |child| property.get(child) }.uniq
        end
        [ operator, property, bind_value ]
      end

      parents_children = Collection.new(query)
      children.each { |child| parents_children.send(:add, child) }

      if parent_key.get(parent) == parent_values
        ret = parents_children
      else
        association.instance_variable_set(:@children, parents_children)
      end
    end

    ret || child_model.send(finder, *(args.dup << @query.merge(options).merge(child_key.zip(parent_values).to_hash)))
  end
end

#get_parent(child, parent = nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/dm-core/associations/relationship.rb', line 127

def get_parent(child, parent = nil)
  child_value = child_key.get(child)
  return nil unless child_value.nitems == child_value.size

  with_repository(parent || parent_model) do
    parent_identity_map = (parent || parent_model).repository.identity_map(parent_model)
    child_identity_map  = child.repository.identity_map(child_model)

    if parent = parent_identity_map[child_value]
      return parent
    end

    children = child_identity_map.values | [child]

    bind_values = children.map { |c| child_key.get(c) }.uniq
    query_values = bind_values.reject { |k| parent_identity_map[k] }

    bind_values = query_values unless query_values.empty?
    query = parent_key.zip(bind_values.transpose).to_hash
    association_accessor = "#{self.name}_association"

    collection = parent_model.send(:all, query)
    unless collection.empty?
      collection.send(:lazy_load)
      children.each do |c|
        c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
      end
      child.send(association_accessor).instance_variable_get(:@parent)
    end
  end
end

#parent_keyObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/dm-core/associations/relationship.rb', line 47

def parent_key
  @parent_key ||= begin
    parent_key = nil
    parent_model.repository.scope do |r|
      parent_key = if @parent_properties
        parent_model.properties(r.name).slice(*@parent_properties)
      else
        parent_model.key
      end
    end
    PropertySet.new(parent_key)
  end
end

#parent_modelObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



62
63
64
65
66
# File 'lib/dm-core/associations/relationship.rb', line 62

def parent_model
  Class === @parent_model ? @parent_model : (Class === @child_model ? @child_model.find_const(@parent_model) : Object.find_const(@parent_model))
rescue NameError
  raise NameError, "Cannot find the parent_model #{@parent_model} for #{@child_model}"
end

#with_repository(object = nil, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



160
161
162
163
164
165
166
167
168
169
# File 'lib/dm-core/associations/relationship.rb', line 160

def with_repository(object = nil, &block)
  other_model = object.model == child_model ? parent_model : child_model if object.respond_to?(:model)
  other_model = object       == child_model ? parent_model : child_model if object.kind_of?(DataMapper::Resource)

  if other_model && other_model.repository == object.repository && object.repository.name != @repository_name
    object.repository.scope(&block)
  else
    repository(@repository_name, &block)
  end
end