datamapper-dm-core 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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