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.



178
179
180
# File 'lib/dm-core/associations/relationship.rb', line 178

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.



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

def child_model
  return @child_model if model_defined?(@child_model)
  @child_model = @parent_model.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.



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
125
126
127
128
129
# File 'lib/dm-core/associations/relationship.rb', line 78

def get_children(parent, options = {}, finder = :all, *args)
  parent_value = parent_key.get(parent)
  bind_values  = [ parent_value ]

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

    query_values = parent_identity_map.keys

    bind_values = query_values unless query_values.empty?
    query = child_key.zip(bind_values.transpose).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 = {}
    collection.each do |resource|
      child_value = child_key.get(resource)
      parent_obj = parent_identity_map[child_value]
      grouped_collection[parent_obj] ||= []
      grouped_collection[parent_obj] << 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|
        property = property.property if property.class == DataMapper::Query::Path
        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_value
        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_value ]).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.



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
158
159
160
161
162
163
# File 'lib/dm-core/associations/relationship.rb', line 132

def get_parent(child, parent = nil)
  child_value = child_key.get(child)
  return nil if child_value.any? { |v| v.nil? }

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

    if parent = parent_identity_map[child_value]
      return parent
    end

    children = child_identity_map.values
    children << child unless child_identity_map[child.key]

    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
67
# File 'lib/dm-core/associations/relationship.rb', line 62

def parent_model
  return @parent_model if model_defined?(@parent_model)
  @parent_model = @child_model.find_const(@parent_model)
rescue NameError
  raise NameError, "Cannot find the parent_model #{@parent_model} for #{@child_model}"
end

#with_repository(object = 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.



166
167
168
169
170
171
172
173
174
175
# File 'lib/dm-core/associations/relationship.rb', line 166

def with_repository(object = nil)
  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_args| yield(*block_args) }
  else
    repository(@repository_name) { |block_args| yield(*block_args) }
  end
end