datamapper-dm-core 0.9.11 → 0.10.0

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.
Files changed (192) hide show
  1. data/.autotest +17 -14
  2. data/.gitignore +3 -1
  3. data/FAQ +6 -5
  4. data/History.txt +5 -39
  5. data/Manifest.txt +67 -76
  6. data/QUICKLINKS +1 -1
  7. data/README.txt +21 -15
  8. data/Rakefile +16 -15
  9. data/SPECS +2 -29
  10. data/TODO +1 -1
  11. data/dm-core.gemspec +11 -15
  12. data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
  13. data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
  14. data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
  15. data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
  16. data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
  19. data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
  20. data/lib/dm-core/adapters.rb +135 -16
  21. data/lib/dm-core/associations/many_to_many.rb +372 -90
  22. data/lib/dm-core/associations/many_to_one.rb +220 -73
  23. data/lib/dm-core/associations/one_to_many.rb +319 -255
  24. data/lib/dm-core/associations/one_to_one.rb +66 -53
  25. data/lib/dm-core/associations/relationship.rb +560 -158
  26. data/lib/dm-core/collection.rb +1104 -381
  27. data/lib/dm-core/core_ext/kernel.rb +12 -0
  28. data/lib/dm-core/core_ext/symbol.rb +10 -0
  29. data/lib/dm-core/identity_map.rb +4 -34
  30. data/lib/dm-core/migrations.rb +1283 -0
  31. data/lib/dm-core/model/descendant_set.rb +81 -0
  32. data/lib/dm-core/model/hook.rb +45 -0
  33. data/lib/dm-core/model/is.rb +32 -0
  34. data/lib/dm-core/model/property.rb +248 -0
  35. data/lib/dm-core/model/relationship.rb +335 -0
  36. data/lib/dm-core/model/scope.rb +90 -0
  37. data/lib/dm-core/model.rb +570 -369
  38. data/lib/dm-core/property.rb +753 -280
  39. data/lib/dm-core/property_set.rb +141 -98
  40. data/lib/dm-core/query/conditions/comparison.rb +814 -0
  41. data/lib/dm-core/query/conditions/operation.rb +247 -0
  42. data/lib/dm-core/query/direction.rb +43 -0
  43. data/lib/dm-core/query/operator.rb +42 -0
  44. data/lib/dm-core/query/path.rb +102 -0
  45. data/lib/dm-core/query/sort.rb +45 -0
  46. data/lib/dm-core/query.rb +974 -492
  47. data/lib/dm-core/repository.rb +147 -107
  48. data/lib/dm-core/resource.rb +644 -429
  49. data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
  50. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
  51. data/lib/dm-core/support/chainable.rb +20 -0
  52. data/lib/dm-core/support/deprecate.rb +12 -0
  53. data/lib/dm-core/support/equalizer.rb +23 -0
  54. data/lib/dm-core/support/logger.rb +13 -0
  55. data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
  56. data/lib/dm-core/transaction.rb +333 -92
  57. data/lib/dm-core/type.rb +98 -60
  58. data/lib/dm-core/types/boolean.rb +1 -1
  59. data/lib/dm-core/types/discriminator.rb +34 -20
  60. data/lib/dm-core/types/object.rb +7 -4
  61. data/lib/dm-core/types/paranoid_boolean.rb +11 -9
  62. data/lib/dm-core/types/paranoid_datetime.rb +11 -9
  63. data/lib/dm-core/types/serial.rb +3 -3
  64. data/lib/dm-core/types/text.rb +3 -4
  65. data/lib/dm-core/version.rb +1 -1
  66. data/lib/dm-core.rb +106 -110
  67. data/script/performance.rb +102 -109
  68. data/script/profile.rb +169 -38
  69. data/spec/lib/adapter_helpers.rb +105 -0
  70. data/spec/lib/collection_helpers.rb +18 -0
  71. data/spec/lib/counter_adapter.rb +34 -0
  72. data/spec/lib/pending_helpers.rb +27 -0
  73. data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
  74. data/spec/public/associations/many_to_many_spec.rb +193 -0
  75. data/spec/public/associations/many_to_one_spec.rb +73 -0
  76. data/spec/public/associations/one_to_many_spec.rb +77 -0
  77. data/spec/public/associations/one_to_one_spec.rb +156 -0
  78. data/spec/public/collection_spec.rb +65 -0
  79. data/spec/public/model/relationship_spec.rb +924 -0
  80. data/spec/public/model_spec.rb +159 -0
  81. data/spec/public/property_spec.rb +829 -0
  82. data/spec/public/resource_spec.rb +71 -0
  83. data/spec/public/sel_spec.rb +44 -0
  84. data/spec/public/setup_spec.rb +145 -0
  85. data/spec/public/shared/association_collection_shared_spec.rb +317 -0
  86. data/spec/public/shared/collection_shared_spec.rb +1723 -0
  87. data/spec/public/shared/finder_shared_spec.rb +1619 -0
  88. data/spec/public/shared/resource_shared_spec.rb +924 -0
  89. data/spec/public/shared/sel_shared_spec.rb +112 -0
  90. data/spec/public/transaction_spec.rb +129 -0
  91. data/spec/public/types/discriminator_spec.rb +130 -0
  92. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  93. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  94. data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
  95. data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
  96. data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
  97. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
  98. data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
  99. data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
  100. data/spec/semipublic/associations/relationship_spec.rb +194 -0
  101. data/spec/semipublic/associations_spec.rb +177 -0
  102. data/spec/semipublic/collection_spec.rb +142 -0
  103. data/spec/semipublic/property_spec.rb +61 -0
  104. data/spec/semipublic/query/conditions_spec.rb +528 -0
  105. data/spec/semipublic/query/path_spec.rb +443 -0
  106. data/spec/semipublic/query_spec.rb +2626 -0
  107. data/spec/semipublic/resource_spec.rb +47 -0
  108. data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
  109. data/spec/spec.opts +3 -1
  110. data/spec/spec_helper.rb +80 -57
  111. data/tasks/ci.rb +19 -31
  112. data/tasks/dm.rb +43 -48
  113. data/tasks/doc.rb +8 -11
  114. data/tasks/gemspec.rb +5 -5
  115. data/tasks/hoe.rb +15 -16
  116. data/tasks/install.rb +8 -10
  117. metadata +72 -93
  118. data/lib/dm-core/associations/relationship_chain.rb +0 -81
  119. data/lib/dm-core/associations.rb +0 -207
  120. data/lib/dm-core/auto_migrations.rb +0 -105
  121. data/lib/dm-core/dependency_queue.rb +0 -32
  122. data/lib/dm-core/hook.rb +0 -11
  123. data/lib/dm-core/is.rb +0 -16
  124. data/lib/dm-core/logger.rb +0 -232
  125. data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
  126. data/lib/dm-core/migrator.rb +0 -29
  127. data/lib/dm-core/scope.rb +0 -58
  128. data/lib/dm-core/support/array.rb +0 -13
  129. data/lib/dm-core/support/assertions.rb +0 -8
  130. data/lib/dm-core/support/errors.rb +0 -23
  131. data/lib/dm-core/support/kernel.rb +0 -11
  132. data/lib/dm-core/support/symbol.rb +0 -41
  133. data/lib/dm-core/support.rb +0 -7
  134. data/lib/dm-core/type_map.rb +0 -80
  135. data/lib/dm-core/types.rb +0 -19
  136. data/script/all +0 -4
  137. data/spec/integration/association_spec.rb +0 -1382
  138. data/spec/integration/association_through_spec.rb +0 -203
  139. data/spec/integration/associations/many_to_many_spec.rb +0 -449
  140. data/spec/integration/associations/many_to_one_spec.rb +0 -163
  141. data/spec/integration/associations/one_to_many_spec.rb +0 -188
  142. data/spec/integration/auto_migrations_spec.rb +0 -413
  143. data/spec/integration/collection_spec.rb +0 -1073
  144. data/spec/integration/data_objects_adapter_spec.rb +0 -32
  145. data/spec/integration/dependency_queue_spec.rb +0 -46
  146. data/spec/integration/model_spec.rb +0 -197
  147. data/spec/integration/mysql_adapter_spec.rb +0 -85
  148. data/spec/integration/postgres_adapter_spec.rb +0 -731
  149. data/spec/integration/property_spec.rb +0 -253
  150. data/spec/integration/query_spec.rb +0 -514
  151. data/spec/integration/repository_spec.rb +0 -61
  152. data/spec/integration/resource_spec.rb +0 -513
  153. data/spec/integration/sqlite3_adapter_spec.rb +0 -352
  154. data/spec/integration/sti_spec.rb +0 -273
  155. data/spec/integration/strategic_eager_loading_spec.rb +0 -156
  156. data/spec/integration/transaction_spec.rb +0 -75
  157. data/spec/integration/type_spec.rb +0 -275
  158. data/spec/lib/logging_helper.rb +0 -18
  159. data/spec/lib/mock_adapter.rb +0 -27
  160. data/spec/lib/model_loader.rb +0 -100
  161. data/spec/lib/publicize_methods.rb +0 -28
  162. data/spec/models/content.rb +0 -16
  163. data/spec/models/vehicles.rb +0 -34
  164. data/spec/models/zoo.rb +0 -48
  165. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
  166. data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
  167. data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
  168. data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
  169. data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
  170. data/spec/unit/associations/many_to_many_spec.rb +0 -32
  171. data/spec/unit/associations/many_to_one_spec.rb +0 -159
  172. data/spec/unit/associations/one_to_many_spec.rb +0 -393
  173. data/spec/unit/associations/one_to_one_spec.rb +0 -7
  174. data/spec/unit/associations/relationship_spec.rb +0 -71
  175. data/spec/unit/associations_spec.rb +0 -242
  176. data/spec/unit/auto_migrations_spec.rb +0 -111
  177. data/spec/unit/collection_spec.rb +0 -182
  178. data/spec/unit/data_mapper_spec.rb +0 -35
  179. data/spec/unit/identity_map_spec.rb +0 -126
  180. data/spec/unit/is_spec.rb +0 -80
  181. data/spec/unit/migrator_spec.rb +0 -33
  182. data/spec/unit/model_spec.rb +0 -321
  183. data/spec/unit/naming_conventions_spec.rb +0 -36
  184. data/spec/unit/property_set_spec.rb +0 -90
  185. data/spec/unit/property_spec.rb +0 -753
  186. data/spec/unit/query_spec.rb +0 -571
  187. data/spec/unit/repository_spec.rb +0 -93
  188. data/spec/unit/resource_spec.rb +0 -649
  189. data/spec/unit/scope_spec.rb +0 -142
  190. data/spec/unit/transaction_spec.rb +0 -493
  191. data/spec/unit/type_map_spec.rb +0 -114
  192. data/spec/unit/type_spec.rb +0 -119
@@ -0,0 +1,335 @@
1
+ # TODO: update Model#respond_to? to return true if method_method missing
2
+ # would handle the message
3
+
4
+ module DataMapper
5
+ module Model
6
+ module Relationship
7
+ Model.append_extensions self
8
+
9
+ include Extlib::Assertions
10
+ extend Chainable
11
+
12
+ # Initializes relationships hash for extended model
13
+ # class.
14
+ #
15
+ # When model calls has n, has 1 or belongs_to, relationships
16
+ # are stored in that hash: keys are repository names and
17
+ # values are relationship sets.
18
+ #
19
+ # @api private
20
+ def self.extended(model)
21
+ model.instance_variable_set(:@relationships, {})
22
+ end
23
+
24
+ chainable do
25
+ # When DataMapper model is inherited, relationships
26
+ # of parent are duplicated and copied to subclass model
27
+ #
28
+ # @api private
29
+ def inherited(model)
30
+ # TODO: Create a RelationshipSet class, and then add a method that allows copying the relationships to the supplied repository and model
31
+ model.instance_variable_set(:@relationships, duped_relationships = {})
32
+
33
+ @relationships.each do |repository_name, relationships|
34
+ dup = duped_relationships[repository_name] ||= Mash.new
35
+
36
+ relationships.each do |name, relationship|
37
+ dup[name] = relationship.inherited_by(model)
38
+ end
39
+ end
40
+
41
+ super
42
+ end
43
+ end
44
+
45
+ # Returns copy of relationships set in given repository.
46
+ #
47
+ # @param [Symbol] repository_name
48
+ # Name of the repository for which relationships set is returned
49
+ # @return [Mash] relationships set for given repository
50
+ #
51
+ # @api semipublic
52
+ def relationships(repository_name = default_repository_name)
53
+ # TODO: create RelationshipSet#copy that will copy the relationships, but assign the
54
+ # new Relationship objects to a supplied repository and model. dup does not really
55
+ # do what is needed
56
+
57
+ @relationships[repository_name] ||= if repository_name == default_repository_name
58
+ Mash.new
59
+ else
60
+ relationships(default_repository_name).dup
61
+ end
62
+ end
63
+
64
+ # Used to express unlimited cardinality of association,
65
+ # see +has+
66
+ #
67
+ # @api public
68
+ def n
69
+ 1.0/0
70
+ end
71
+
72
+ # A shorthand, clear syntax for defining one-to-one, one-to-many and
73
+ # many-to-many resource relationships.
74
+ #
75
+ # * has 1, :friend # one friend
76
+ # * has n, :friends # many friends
77
+ # * has 1..3, :friends # many friends (at least 1, at most 3)
78
+ # * has 3, :friends # many friends (exactly 3)
79
+ # * has 1, :friend, 'User' # one friend with the class User
80
+ # * has 3, :friends, :through => :friendships # many friends through the friendships relationship
81
+ #
82
+ # @param cardinality [Integer, Range, Infinity]
83
+ # cardinality that defines the association type and constraints
84
+ # @param name [Symbol]
85
+ # the name that the association will be referenced by
86
+ # @param model [Model, #to_str]
87
+ # the target model of the relationship
88
+ # @param opts [Hash]
89
+ # an options hash
90
+ #
91
+ # @option :through[Symbol] A association that this join should go through to form
92
+ # a many-to-many association
93
+ # @option :model[Model, String] The name of the class to associate with, if omitted
94
+ # then the association name is assumed to match the class name
95
+ # @option :repository[Symbol]
96
+ # name of child model repository
97
+ #
98
+ # @return [Association::Relationship] the relationship that was
99
+ # created to reflect either a one-to-one, one-to-many or many-to-many
100
+ # relationship
101
+ # @raise [ArgumentError] if the cardinality was not understood. Should be a
102
+ # Integer, Range or Infinity(n)
103
+ #
104
+ # @api public
105
+ def has(cardinality, name, *args)
106
+ assert_kind_of 'cardinality', cardinality, Integer, Range, n.class
107
+ assert_kind_of 'name', name, Symbol
108
+
109
+ model = extract_model(args)
110
+ options = extract_options(args)
111
+
112
+ min, max = extract_min_max(cardinality)
113
+ options.update(:min => min, :max => max)
114
+
115
+ assert_valid_options(options)
116
+
117
+ if options.key?(:model) && model
118
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
119
+ end
120
+
121
+ model ||= options.delete(:model)
122
+
123
+ # TODO: change to :target_respository_name and :source_repository_name
124
+ options[:child_repository_name] = options.delete(:repository)
125
+ options[:parent_repository_name] = repository.name
126
+
127
+ klass = if options[:max] > 1
128
+ options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
129
+ else
130
+ Associations::OneToOne::Relationship
131
+ end
132
+
133
+ relationship = relationships(repository.name)[name] = klass.new(name, model, self, options)
134
+
135
+ descendants.each do |descendant|
136
+ descendant.relationships(repository.name)[name] ||= relationship.inherited_by(descendant)
137
+ end
138
+
139
+ relationship
140
+ end
141
+
142
+ # A shorthand, clear syntax for defining many-to-one resource relationships.
143
+ #
144
+ # * belongs_to :user # many to one user
145
+ # * belongs_to :friend, :model => 'User' # many to one friend
146
+ # * belongs_to :reference, :repository => :pubmed # association for repository other than default
147
+ #
148
+ # @param name [Symbol]
149
+ # the name that the association will be referenced by
150
+ # @param model [Model, #to_str]
151
+ # the target model of the relationship
152
+ # @param opts [Hash]
153
+ # an options hash
154
+ #
155
+ # @option :model[Model, String] The name of the class to associate with, if omitted
156
+ # then the association name is assumed to match the class name
157
+ # @option :repository[Symbol]
158
+ # name of child model repository
159
+ #
160
+ # @return [Association::Relationship] The association created
161
+ # should not be accessed directly
162
+ #
163
+ # @api public
164
+ def belongs_to(name, *args)
165
+ assert_kind_of 'name', name, Symbol
166
+
167
+ model = extract_model(args)
168
+ options = extract_options(args)
169
+
170
+ if options.key?(:through)
171
+ warn "#{self.name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{self.name} instead (#{caller[0]})"
172
+ return has(1, name, model, options)
173
+ end
174
+
175
+ assert_valid_options(options)
176
+
177
+ if options.key?(:model) && model
178
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
179
+ end
180
+
181
+ model ||= options.delete(:model)
182
+
183
+ repository_name = repository.name
184
+
185
+ # TODO: change to source_repository_name and target_respository_name
186
+ options[:child_repository_name] = repository_name
187
+ options[:parent_repository_name] = options.delete(:repository)
188
+
189
+ relationship = relationships(repository.name)[name] = Associations::ManyToOne::Relationship.new(name, self, model, options)
190
+
191
+ descendants.each do |descendant|
192
+ descendant.relationships(repository.name)[name] ||= relationship.inherited_by(descendant)
193
+ end
194
+
195
+ relationship
196
+ end
197
+
198
+ private
199
+
200
+ # Extract the model from an Array of arguments
201
+ #
202
+ # @param [Array(Model, String, Hash)]
203
+ # The arguments passed to an relationship declaration
204
+ #
205
+ # @return [Model, #to_str]
206
+ # target model for the association
207
+ #
208
+ # @api private
209
+ def extract_model(args)
210
+ model = args.first
211
+
212
+ if model.kind_of?(Model)
213
+ model
214
+ elsif model.respond_to?(:to_str)
215
+ model.to_str
216
+ else
217
+ nil
218
+ end
219
+ end
220
+
221
+ # Extract the model from an Array of arguments
222
+ #
223
+ # @param [Array(Model, String, Hash)]
224
+ # The arguments passed to an relationship declaration
225
+ #
226
+ # @return [Hash]
227
+ # options for the association
228
+ #
229
+ # @api private
230
+ def extract_options(args)
231
+ options = args.last
232
+
233
+ if options.kind_of?(Hash)
234
+ options.dup
235
+ else
236
+ {}
237
+ end
238
+ end
239
+
240
+ # A support method for converting Integer, Range or Infinity values into two
241
+ # values representing the minimum and maximum cardinality of the association
242
+ #
243
+ # @return [Array] A pair of integers, min and max
244
+ #
245
+ # @api private
246
+ def extract_min_max(cardinality)
247
+ case cardinality
248
+ when Integer then [ cardinality, cardinality ]
249
+ when Range then [ cardinality.first, cardinality.last ]
250
+ when n then [ 0, n ]
251
+ end
252
+ end
253
+
254
+ # Validates options of association method like belongs_to or has:
255
+ # verifies types of cardinality bounds, repository, association class,
256
+ # keys and possible values of :through option.
257
+ #
258
+ # @api private
259
+ def assert_valid_options(options)
260
+ # TODO: update to match Query#assert_valid_options
261
+ # - perform options normalization elsewhere
262
+
263
+ if options.key?(:min) && options.key?(:max)
264
+ assert_kind_of 'options[:min]', options[:min], Integer
265
+ assert_kind_of 'options[:max]', options[:max], Integer, n.class
266
+
267
+ if options[:min] == n && options[:max] == n
268
+ raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
269
+ elsif options[:min] > options[:max]
270
+ raise ArgumentError, "Cardinality min (#{options[:min]}) cannot be larger than the max (#{options[:max]})"
271
+ elsif options[:min] < 0
272
+ raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{options[:min]}"
273
+ elsif options[:max] < 1
274
+ raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{options[:max]}"
275
+ end
276
+ end
277
+
278
+ if options.key?(:repository)
279
+ assert_kind_of 'options[:repository]', options[:repository], Repository, Symbol
280
+
281
+ if options[:repository].kind_of?(Repository)
282
+ options[:repository] = options[:repository].name
283
+ end
284
+ end
285
+
286
+ if options.key?(:class_name)
287
+ assert_kind_of 'options[:class_name]', options[:class_name], String
288
+ warn "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
289
+ options[:model] = options.delete(:class_name)
290
+ end
291
+
292
+ if options.key?(:remote_name)
293
+ assert_kind_of 'options[:remote_name]', options[:remote_name], Symbol
294
+ warn "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
295
+ options[:via] = options.delete(:remote_name)
296
+ end
297
+
298
+ if options.key?(:through)
299
+ assert_kind_of 'options[:through]', options[:through], Symbol, Module
300
+ end
301
+
302
+ [ :via, :inverse ].each do |key|
303
+ if options.key?(key)
304
+ assert_kind_of "options[#{key.inspect}]", options[key], Symbol, Associations::Relationship
305
+ end
306
+ end
307
+
308
+ # TODO: deprecate :child_key and :parent_key in favor of :source_key and
309
+ # :target_key (will mean something different for each relationship)
310
+
311
+ [ :child_key, :parent_key ].each do |key|
312
+ if options.key?(key)
313
+ assert_kind_of "options[#{key.inspect}]", options[key], Enumerable
314
+ end
315
+ end
316
+
317
+ if options.key?(:limit)
318
+ raise ArgumentError, '+options[:limit]+ should not be specified on a relationship'
319
+ end
320
+ end
321
+
322
+ chainable do
323
+ # TODO: document
324
+ # @api public
325
+ def method_missing(method, *args, &block)
326
+ if relationship = relationships(repository_name)[method]
327
+ return Query::Path.new([ relationship ])
328
+ end
329
+
330
+ super
331
+ end
332
+ end
333
+ end # module Relationship
334
+ end # module Model
335
+ end # module DataMapper
@@ -0,0 +1,90 @@
1
+ module DataMapper
2
+ module Model
3
+ # Module with query scoping functionality.
4
+ #
5
+ # Scopes are implemented using simple array based
6
+ # stack that is thread local. Default scope can be set
7
+ # on a per repository basis.
8
+ #
9
+ # Scopes are merged as new queries are nested.
10
+ # It is also possible to get exclusive scope access
11
+ # using +with_exclusive_scope+
12
+ module Scope
13
+ # TODO: document
14
+ # @api private
15
+ def default_scope(repository_name = default_repository_name)
16
+ @default_scope ||= {}
17
+
18
+ @default_scope[repository_name] ||= if repository_name == default_repository_name
19
+ {}
20
+ else
21
+ default_scope(default_repository_name).dup
22
+ end
23
+ end
24
+
25
+ # Returns query on top of scope stack
26
+ #
27
+ # @api private
28
+ def query
29
+ Query.new(repository, self, current_scope).freeze
30
+ end
31
+
32
+ # TODO: document
33
+ # @api private
34
+ def current_scope
35
+ scope_stack.last || default_scope(repository.name)
36
+ end
37
+
38
+ protected
39
+
40
+ # Pushes given query on top of the stack
41
+ #
42
+ # @param [Hash, Query] Query to add to current scope nesting
43
+ #
44
+ # @api private
45
+ def with_scope(query)
46
+ options = if query.kind_of?(Hash)
47
+ query
48
+ else
49
+ query.options
50
+ end
51
+
52
+ # merge the current scope with the passed in query
53
+ with_exclusive_scope(self.query.merge(options)) { |*block_args| yield(*block_args) }
54
+ end
55
+
56
+ # Pushes given query on top of scope stack and yields
57
+ # given block, then pops the stack. During block execution
58
+ # queries previously pushed onto the stack
59
+ # have no effect.
60
+ #
61
+ # @api private
62
+ def with_exclusive_scope(query)
63
+ query = if query.kind_of?(Hash)
64
+ Query.new(repository, self, query)
65
+ else
66
+ query.dup
67
+ end
68
+
69
+ scope_stack << query.options
70
+
71
+ begin
72
+ yield query.freeze
73
+ ensure
74
+ scope_stack.pop
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ # Initializes (if necessary) and returns current scope stack
81
+ # @api private
82
+ def scope_stack
83
+ scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
84
+ scope_stack_for[self] ||= []
85
+ end
86
+ end # module Scope
87
+
88
+ include Scope
89
+ end # module Model
90
+ end # module DataMapper