rpbertp13-dm-core 0.9.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +26 -0
- data/.gitignore +18 -0
- data/CONTRIBUTING +51 -0
- data/FAQ +92 -0
- data/History.txt +52 -0
- data/MIT-LICENSE +22 -0
- data/Manifest.txt +130 -0
- data/QUICKLINKS +11 -0
- data/README.txt +143 -0
- data/Rakefile +32 -0
- data/SPECS +62 -0
- data/TODO +1 -0
- data/dm-core.gemspec +40 -0
- data/lib/dm-core.rb +217 -0
- data/lib/dm-core/adapters.rb +16 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +209 -0
- data/lib/dm-core/adapters/data_objects_adapter.rb +716 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +138 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +189 -0
- data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
- data/lib/dm-core/associations.rb +207 -0
- data/lib/dm-core/associations/many_to_many.rb +147 -0
- data/lib/dm-core/associations/many_to_one.rb +107 -0
- data/lib/dm-core/associations/one_to_many.rb +315 -0
- data/lib/dm-core/associations/one_to_one.rb +61 -0
- data/lib/dm-core/associations/relationship.rb +221 -0
- data/lib/dm-core/associations/relationship_chain.rb +81 -0
- data/lib/dm-core/auto_migrations.rb +105 -0
- data/lib/dm-core/collection.rb +670 -0
- data/lib/dm-core/dependency_queue.rb +32 -0
- data/lib/dm-core/hook.rb +11 -0
- data/lib/dm-core/identity_map.rb +42 -0
- data/lib/dm-core/is.rb +16 -0
- data/lib/dm-core/logger.rb +232 -0
- data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
- data/lib/dm-core/migrator.rb +29 -0
- data/lib/dm-core/model.rb +526 -0
- data/lib/dm-core/naming_conventions.rb +84 -0
- data/lib/dm-core/property.rb +676 -0
- data/lib/dm-core/property_set.rb +169 -0
- data/lib/dm-core/query.rb +676 -0
- data/lib/dm-core/repository.rb +167 -0
- data/lib/dm-core/resource.rb +671 -0
- data/lib/dm-core/scope.rb +58 -0
- data/lib/dm-core/support.rb +7 -0
- data/lib/dm-core/support/array.rb +13 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/errors.rb +23 -0
- data/lib/dm-core/support/kernel.rb +11 -0
- data/lib/dm-core/support/symbol.rb +41 -0
- data/lib/dm-core/transaction.rb +252 -0
- data/lib/dm-core/type.rb +160 -0
- data/lib/dm-core/type_map.rb +80 -0
- data/lib/dm-core/types.rb +19 -0
- data/lib/dm-core/types/boolean.rb +7 -0
- data/lib/dm-core/types/discriminator.rb +34 -0
- data/lib/dm-core/types/object.rb +24 -0
- data/lib/dm-core/types/paranoid_boolean.rb +34 -0
- data/lib/dm-core/types/paranoid_datetime.rb +33 -0
- data/lib/dm-core/types/serial.rb +9 -0
- data/lib/dm-core/types/text.rb +10 -0
- data/lib/dm-core/version.rb +3 -0
- data/script/all +4 -0
- data/script/performance.rb +282 -0
- data/script/profile.rb +87 -0
- data/spec/integration/association_spec.rb +1382 -0
- data/spec/integration/association_through_spec.rb +203 -0
- data/spec/integration/associations/many_to_many_spec.rb +449 -0
- data/spec/integration/associations/many_to_one_spec.rb +163 -0
- data/spec/integration/associations/one_to_many_spec.rb +188 -0
- data/spec/integration/auto_migrations_spec.rb +413 -0
- data/spec/integration/collection_spec.rb +1073 -0
- data/spec/integration/data_objects_adapter_spec.rb +32 -0
- data/spec/integration/dependency_queue_spec.rb +46 -0
- data/spec/integration/model_spec.rb +197 -0
- data/spec/integration/mysql_adapter_spec.rb +85 -0
- data/spec/integration/postgres_adapter_spec.rb +731 -0
- data/spec/integration/property_spec.rb +253 -0
- data/spec/integration/query_spec.rb +514 -0
- data/spec/integration/repository_spec.rb +61 -0
- data/spec/integration/resource_spec.rb +513 -0
- data/spec/integration/sqlite3_adapter_spec.rb +352 -0
- data/spec/integration/sti_spec.rb +273 -0
- data/spec/integration/strategic_eager_loading_spec.rb +156 -0
- data/spec/integration/transaction_spec.rb +60 -0
- data/spec/integration/type_spec.rb +275 -0
- data/spec/lib/logging_helper.rb +18 -0
- data/spec/lib/mock_adapter.rb +27 -0
- data/spec/lib/model_loader.rb +100 -0
- data/spec/lib/publicize_methods.rb +28 -0
- data/spec/models/content.rb +16 -0
- data/spec/models/vehicles.rb +34 -0
- data/spec/models/zoo.rb +48 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +91 -0
- data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
- data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
- data/spec/unit/adapters/data_objects_adapter_spec.rb +632 -0
- data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
- data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
- data/spec/unit/associations/many_to_many_spec.rb +32 -0
- data/spec/unit/associations/many_to_one_spec.rb +159 -0
- data/spec/unit/associations/one_to_many_spec.rb +393 -0
- data/spec/unit/associations/one_to_one_spec.rb +7 -0
- data/spec/unit/associations/relationship_spec.rb +71 -0
- data/spec/unit/associations_spec.rb +242 -0
- data/spec/unit/auto_migrations_spec.rb +111 -0
- data/spec/unit/collection_spec.rb +182 -0
- data/spec/unit/data_mapper_spec.rb +35 -0
- data/spec/unit/identity_map_spec.rb +126 -0
- data/spec/unit/is_spec.rb +80 -0
- data/spec/unit/migrator_spec.rb +33 -0
- data/spec/unit/model_spec.rb +321 -0
- data/spec/unit/naming_conventions_spec.rb +36 -0
- data/spec/unit/property_set_spec.rb +90 -0
- data/spec/unit/property_spec.rb +753 -0
- data/spec/unit/query_spec.rb +571 -0
- data/spec/unit/repository_spec.rb +93 -0
- data/spec/unit/resource_spec.rb +649 -0
- data/spec/unit/scope_spec.rb +142 -0
- data/spec/unit/transaction_spec.rb +469 -0
- data/spec/unit/type_map_spec.rb +114 -0
- data/spec/unit/type_spec.rb +119 -0
- data/tasks/ci.rb +36 -0
- data/tasks/dm.rb +63 -0
- data/tasks/doc.rb +20 -0
- data/tasks/gemspec.rb +23 -0
- data/tasks/hoe.rb +46 -0
- data/tasks/install.rb +20 -0
- metadata +215 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
dir = Pathname(__FILE__).dirname.expand_path / 'associations'
|
2
|
+
|
3
|
+
require dir / 'relationship'
|
4
|
+
require dir / 'relationship_chain'
|
5
|
+
require dir / 'many_to_many'
|
6
|
+
require dir / 'many_to_one'
|
7
|
+
require dir / 'one_to_many'
|
8
|
+
require dir / 'one_to_one'
|
9
|
+
|
10
|
+
module DataMapper
|
11
|
+
module Associations
|
12
|
+
include Assertions
|
13
|
+
|
14
|
+
class ImmutableAssociationError < RuntimeError
|
15
|
+
end
|
16
|
+
|
17
|
+
class UnsavedParentError < RuntimeError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns all relationships that are many-to-one for this model.
|
21
|
+
#
|
22
|
+
# Used to find the relationships that require properties in any Repository.
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
# class Plur
|
26
|
+
# include DataMapper::Resource
|
27
|
+
# def self.default_repository_name
|
28
|
+
# :plur_db
|
29
|
+
# end
|
30
|
+
# repository(:plupp_db) do
|
31
|
+
# has 1, :plupp
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# This resource has a many-to-one to the Plupp resource residing in the :plupp_db repository,
|
36
|
+
# but the Plur resource needs the plupp_id property no matter what repository itself lives in,
|
37
|
+
# ie we need to create that property when we migrate etc.
|
38
|
+
#
|
39
|
+
# Used in DataMapper::Model.properties_with_subclasses
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def many_to_one_relationships
|
43
|
+
relationships unless @relationships # needs to be initialized!
|
44
|
+
@relationships.values.collect do |rels| rels.values end.flatten.select do |relationship| relationship.child_model == self end
|
45
|
+
end
|
46
|
+
|
47
|
+
def relationships(repository_name = default_repository_name)
|
48
|
+
@relationships ||= {}
|
49
|
+
@relationships[repository_name] ||= repository_name == Repository.default_name ? {} : relationships(Repository.default_name).dup
|
50
|
+
end
|
51
|
+
|
52
|
+
def n
|
53
|
+
1.0/0
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# A shorthand, clear syntax for defining one-to-one, one-to-many and
|
58
|
+
# many-to-many resource relationships.
|
59
|
+
#
|
60
|
+
# @example [Usage]
|
61
|
+
# * has 1, :friend # one friend
|
62
|
+
# * has n, :friends # many friends
|
63
|
+
# * has 1..3, :friends
|
64
|
+
# # many friends (at least 1, at most 3)
|
65
|
+
# * has 3, :friends
|
66
|
+
# # many friends (exactly 3)
|
67
|
+
# * has 1, :friend, :class_name => 'User'
|
68
|
+
# # one friend with the class name User
|
69
|
+
# * has 3, :friends, :through => :friendships
|
70
|
+
# # many friends through the friendships relationship
|
71
|
+
# * has n, :friendships => :friends
|
72
|
+
# # identical to above example
|
73
|
+
#
|
74
|
+
# @param cardinality [Integer, Range, Infinity]
|
75
|
+
# cardinality that defines the association type and constraints
|
76
|
+
# @param name <Symbol> the name that the association will be referenced by
|
77
|
+
# @param opts <Hash> an options hash
|
78
|
+
#
|
79
|
+
# @option :through[Symbol] A association that this join should go through to form
|
80
|
+
# a many-to-many association
|
81
|
+
# @option :class_name[String] The name of the class to associate with, if omitted
|
82
|
+
# then the association name is assumed to match the class name
|
83
|
+
# @option :remote_name[Symbol] In the case of a :through option being present, the
|
84
|
+
# name of the relationship on the other end of the :through-relationship
|
85
|
+
# to be linked to this relationship.
|
86
|
+
#
|
87
|
+
# @return [DataMapper::Association::Relationship] the relationship that was
|
88
|
+
# created to reflect either a one-to-one, one-to-many or many-to-many
|
89
|
+
# relationship
|
90
|
+
# @raise [ArgumentError] if the cardinality was not understood. Should be a
|
91
|
+
# Integer, Range or Infinity(n)
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def has(cardinality, name, options = {})
|
95
|
+
|
96
|
+
# NOTE: the reason for this fix is that with the ability to pass in two
|
97
|
+
# hashes into has() there might be instances where people attempt to
|
98
|
+
# pass in the options into the name part and not know why things aren't
|
99
|
+
# working for them.
|
100
|
+
if name.kind_of?(Hash)
|
101
|
+
name_through, through = name.keys.first, name.values.first
|
102
|
+
cardinality_string = cardinality.to_s == 'Infinity' ? 'n' : cardinality.inspect
|
103
|
+
warn("In #{self.name} 'has #{cardinality_string}, #{name_through.inspect} => #{through.inspect}' is deprecated. Use 'has #{cardinality_string}, #{name_through.inspect}, :through => #{through.inspect}' instead")
|
104
|
+
end
|
105
|
+
|
106
|
+
options = options.merge(extract_min_max(cardinality))
|
107
|
+
options = options.merge(extract_throughness(name))
|
108
|
+
|
109
|
+
# do not remove this. There is alot of confusion on people's
|
110
|
+
# part about what the first argument to has() is. For the record it
|
111
|
+
# is the min cardinality and max cardinality of the association.
|
112
|
+
# simply put, it constraints the number of resources that will be
|
113
|
+
# returned by the association. It is not, as has been assumed,
|
114
|
+
# the number of results on the left and right hand side of the
|
115
|
+
# reltionship.
|
116
|
+
if options[:min] == n && options[:max] == n
|
117
|
+
raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association', caller
|
118
|
+
end
|
119
|
+
|
120
|
+
klass = options[:max] == 1 ? OneToOne : OneToMany
|
121
|
+
klass = ManyToMany if options[:through] == DataMapper::Resource
|
122
|
+
relationship = klass.setup(options.delete(:name), self, options)
|
123
|
+
|
124
|
+
# Please leave this in - I will release contextual serialization soon
|
125
|
+
# which requires this -- guyvdb
|
126
|
+
# TODO convert this to a hook in the plugin once hooks work on class
|
127
|
+
# methods
|
128
|
+
self.init_has_relationship_for_serialization(relationship) if self.respond_to?(:init_has_relationship_for_serialization)
|
129
|
+
|
130
|
+
relationship
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# A shorthand, clear syntax for defining many-to-one resource relationships.
|
135
|
+
#
|
136
|
+
# @example [Usage]
|
137
|
+
# * belongs_to :user # many_to_one, :friend
|
138
|
+
# * belongs_to :friend, :class_name => 'User' # many_to_one :friends
|
139
|
+
#
|
140
|
+
# @param name [Symbol] The name that the association will be referenced by
|
141
|
+
# @see #has
|
142
|
+
#
|
143
|
+
# @return [DataMapper::Association::ManyToOne] The association created
|
144
|
+
# should not be accessed directly
|
145
|
+
#
|
146
|
+
# @api public
|
147
|
+
def belongs_to(name, options={})
|
148
|
+
@_valid_relations = false
|
149
|
+
|
150
|
+
if options.key?(:class_name) && !options.key?(:child_key)
|
151
|
+
warn "The inferred child_key will changing to be prefixed with the relationship name #{name}. " \
|
152
|
+
"When using :class_name in belongs_to specify the :child_key explicitly to avoid problems." \
|
153
|
+
"#{caller(0)[1]}"
|
154
|
+
end
|
155
|
+
|
156
|
+
relationship = ManyToOne.setup(name, self, options)
|
157
|
+
# Please leave this in - I will release contextual serialization soon
|
158
|
+
# which requires this -- guyvdb
|
159
|
+
# TODO convert this to a hook in the plugin once hooks work on class
|
160
|
+
# methods
|
161
|
+
self.init_belongs_relationship_for_serialization(relationship) if self.respond_to?(:init_belongs_relationship_for_serialization)
|
162
|
+
|
163
|
+
relationship
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def extract_throughness(name)
|
169
|
+
assert_kind_of 'name', name, Hash, Symbol
|
170
|
+
|
171
|
+
case name
|
172
|
+
when Hash
|
173
|
+
unless name.keys.size == 1
|
174
|
+
raise ArgumentError, "name must have only one key, but had #{name.keys.size}", caller(2)
|
175
|
+
end
|
176
|
+
|
177
|
+
{ :name => name.keys.first, :through => name.values.first }
|
178
|
+
when Symbol
|
179
|
+
{ :name => name }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# A support method form converting Integer, Range or Infinity values into a
|
184
|
+
# { :min => x, :max => y } hash.
|
185
|
+
#
|
186
|
+
# @api private
|
187
|
+
def extract_min_max(constraints)
|
188
|
+
assert_kind_of 'constraints', constraints, Integer, Range unless constraints == n
|
189
|
+
|
190
|
+
case constraints
|
191
|
+
when Integer
|
192
|
+
{ :min => constraints, :max => constraints }
|
193
|
+
when Range
|
194
|
+
if constraints.first > constraints.last
|
195
|
+
raise ArgumentError, "Constraint min (#{constraints.first}) cannot be larger than the max (#{constraints.last})"
|
196
|
+
end
|
197
|
+
|
198
|
+
{ :min => constraints.first, :max => constraints.last }
|
199
|
+
when n
|
200
|
+
{ :min => 0, :max => n }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end # module Associations
|
204
|
+
|
205
|
+
Model.append_extensions DataMapper::Associations
|
206
|
+
|
207
|
+
end # module DataMapper
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "one_to_many")
|
2
|
+
module DataMapper
|
3
|
+
module Associations
|
4
|
+
module ManyToMany
|
5
|
+
extend Assertions
|
6
|
+
|
7
|
+
# Setup many to many relationship between two models
|
8
|
+
# -
|
9
|
+
# @api private
|
10
|
+
def self.setup(name, model, options = {})
|
11
|
+
assert_kind_of 'name', name, Symbol
|
12
|
+
assert_kind_of 'model', model, Model
|
13
|
+
assert_kind_of 'options', options, Hash
|
14
|
+
|
15
|
+
repository_name = model.repository.name
|
16
|
+
|
17
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
18
|
+
def #{name}(query = {})
|
19
|
+
#{name}_association.all(query)
|
20
|
+
end
|
21
|
+
|
22
|
+
def #{name}=(children)
|
23
|
+
#{name}_association.replace(children)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def #{name}_association
|
29
|
+
@#{name}_association ||= begin
|
30
|
+
unless relationship = model.relationships(#{repository_name.inspect})[#{name.inspect}]
|
31
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
32
|
+
end
|
33
|
+
association = Proxy.new(relationship, self)
|
34
|
+
parent_associations << association
|
35
|
+
association
|
36
|
+
end
|
37
|
+
end
|
38
|
+
EOS
|
39
|
+
|
40
|
+
opts = options.dup
|
41
|
+
opts.delete(:through)
|
42
|
+
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
43
|
+
opts[:parent_model] = model
|
44
|
+
opts[:repository_name] = repository_name
|
45
|
+
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || Extlib::Inflection.tableize(opts[:child_model])
|
46
|
+
opts[:parent_key] = opts[:parent_key]
|
47
|
+
opts[:child_key] = opts[:child_key]
|
48
|
+
opts[:mutable] = true
|
49
|
+
|
50
|
+
names = [ opts[:child_model], opts[:parent_model].name ].sort
|
51
|
+
model_name = names.join.gsub("::", "")
|
52
|
+
storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
|
53
|
+
|
54
|
+
opts[:near_relationship_name] = Extlib::Inflection.tableize(model_name).to_sym
|
55
|
+
|
56
|
+
model.has(model.n, opts[:near_relationship_name])
|
57
|
+
|
58
|
+
relationship = model.relationships(repository_name)[name] = RelationshipChain.new(opts)
|
59
|
+
|
60
|
+
unless Object.const_defined?(model_name)
|
61
|
+
model = DataMapper::Model.new(storage_name)
|
62
|
+
|
63
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
64
|
+
def self.name; #{model_name.inspect} end
|
65
|
+
def self.default_repository_name; #{repository_name.inspect} end
|
66
|
+
def self.many_to_many; true end
|
67
|
+
EOS
|
68
|
+
|
69
|
+
names.each do |n|
|
70
|
+
model.belongs_to(Extlib::Inflection.underscore(n).gsub('/', '_').to_sym)
|
71
|
+
end
|
72
|
+
|
73
|
+
Object.const_set(model_name, model)
|
74
|
+
end
|
75
|
+
|
76
|
+
relationship
|
77
|
+
end
|
78
|
+
|
79
|
+
class Proxy < DataMapper::Associations::OneToMany::Proxy
|
80
|
+
def delete(resource)
|
81
|
+
through = near_association.get(*(@parent.key + resource.key))
|
82
|
+
near_association.delete(through)
|
83
|
+
orphan_resource(super)
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear
|
87
|
+
near_association.clear
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
def destroy
|
92
|
+
near_association.destroy
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def save
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def new_child(attributes)
|
102
|
+
remote_relationship.parent_model.new(attributes)
|
103
|
+
end
|
104
|
+
|
105
|
+
def relate_resource(resource)
|
106
|
+
assert_mutable
|
107
|
+
add_default_association_values(resource)
|
108
|
+
@orphans.delete(resource)
|
109
|
+
|
110
|
+
# TODO: fix this so it does not automatically save on append, if possible
|
111
|
+
resource.save if resource.new_record?
|
112
|
+
through_resource = @relationship.child_model.new
|
113
|
+
@relationship.child_key.zip(@relationship.parent_key) do |child_key,parent_key|
|
114
|
+
through_resource.send("#{child_key.name}=", parent_key.get(@parent))
|
115
|
+
end
|
116
|
+
remote_relationship.child_key.zip(remote_relationship.parent_key) do |child_key,parent_key|
|
117
|
+
through_resource.send("#{child_key.name}=", parent_key.get(resource))
|
118
|
+
end
|
119
|
+
near_association << through_resource
|
120
|
+
|
121
|
+
resource
|
122
|
+
end
|
123
|
+
|
124
|
+
def orphan_resource(resource)
|
125
|
+
assert_mutable
|
126
|
+
@orphans << resource
|
127
|
+
resource
|
128
|
+
end
|
129
|
+
|
130
|
+
def assert_mutable
|
131
|
+
end
|
132
|
+
|
133
|
+
def remote_relationship
|
134
|
+
@remote_relationship ||= @relationship.send(:remote_relationship)
|
135
|
+
end
|
136
|
+
|
137
|
+
def near_association
|
138
|
+
@near_association ||= @parent.send(near_relationship_name)
|
139
|
+
end
|
140
|
+
|
141
|
+
def near_relationship_name
|
142
|
+
@near_relationship_name ||= @relationship.send(:instance_variable_get, :@near_relationship_name)
|
143
|
+
end
|
144
|
+
end # class Proxy
|
145
|
+
end # module ManyToMany
|
146
|
+
end # module Associations
|
147
|
+
end # module DataMapper
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Associations
|
3
|
+
module ManyToOne
|
4
|
+
extend Assertions
|
5
|
+
|
6
|
+
# Setup many to one relationship between two models
|
7
|
+
# -
|
8
|
+
# @api private
|
9
|
+
def self.setup(name, model, options = {})
|
10
|
+
assert_kind_of 'name', name, Symbol
|
11
|
+
assert_kind_of 'model', model, Model
|
12
|
+
assert_kind_of 'options', options, Hash
|
13
|
+
|
14
|
+
repository_name = model.repository.name
|
15
|
+
|
16
|
+
model.class_eval <<-EOS, __FILE__, __LINE__
|
17
|
+
def #{name}
|
18
|
+
#{name}_association.nil? ? nil : #{name}_association
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(parent)
|
22
|
+
#{name}_association.replace(parent)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def #{name}_association
|
28
|
+
@#{name}_association ||= begin
|
29
|
+
unless relationship = model.relationships(#{repository_name.inspect})[:#{name}]
|
30
|
+
raise ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}"
|
31
|
+
end
|
32
|
+
association = Proxy.new(relationship, self)
|
33
|
+
child_associations << association
|
34
|
+
association
|
35
|
+
end
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
|
39
|
+
model.relationships(repository_name)[name] = Relationship.new(
|
40
|
+
name,
|
41
|
+
repository_name,
|
42
|
+
model,
|
43
|
+
options.fetch(:class_name, Extlib::Inflection.classify(name)),
|
44
|
+
options
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
class Proxy
|
49
|
+
include Assertions
|
50
|
+
|
51
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ object_id kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get ].include?(m.to_s) }
|
52
|
+
|
53
|
+
def replace(parent)
|
54
|
+
@parent = parent
|
55
|
+
@relationship.attach_parent(@child, @parent)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def save
|
60
|
+
return false if @parent.nil?
|
61
|
+
return true unless parent.new_record?
|
62
|
+
|
63
|
+
@relationship.with_repository(parent) do
|
64
|
+
result = parent.save
|
65
|
+
@relationship.child_key.set(@child, @relationship.parent_key.get(parent)) if result
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def reload
|
71
|
+
@parent = nil
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def kind_of?(klass)
|
76
|
+
super || parent.kind_of?(klass)
|
77
|
+
end
|
78
|
+
|
79
|
+
def respond_to?(method, include_private = false)
|
80
|
+
super || parent.respond_to?(method, include_private)
|
81
|
+
end
|
82
|
+
|
83
|
+
def instance_variable_get(variable)
|
84
|
+
super || parent.instance_variable_get(variable)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def initialize(relationship, child)
|
90
|
+
assert_kind_of 'relationship', relationship, Relationship
|
91
|
+
assert_kind_of 'child', child, Resource
|
92
|
+
|
93
|
+
@relationship = relationship
|
94
|
+
@child = child
|
95
|
+
end
|
96
|
+
|
97
|
+
def parent
|
98
|
+
@parent ||= @relationship.get_parent(@child)
|
99
|
+
end
|
100
|
+
|
101
|
+
def method_missing(method, *args, &block)
|
102
|
+
parent.__send__(method, *args, &block)
|
103
|
+
end
|
104
|
+
end # class Proxy
|
105
|
+
end # module ManyToOne
|
106
|
+
end # module Associations
|
107
|
+
end # module DataMapper
|