dm-core 1.0.2 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. data/Gemfile +28 -94
  2. data/LICENSE +1 -1
  3. data/README.rdoc +44 -11
  4. data/Rakefile +1 -7
  5. data/VERSION +1 -1
  6. data/dm-core.gemspec +398 -299
  7. data/lib/dm-core.rb +23 -13
  8. data/lib/dm-core/adapters/abstract_adapter.rb +1 -1
  9. data/lib/dm-core/associations/many_to_many.rb +1 -3
  10. data/lib/dm-core/associations/many_to_one.rb +54 -36
  11. data/lib/dm-core/associations/one_to_many.rb +1 -2
  12. data/lib/dm-core/associations/relationship.rb +11 -2
  13. data/lib/dm-core/collection.rb +3 -7
  14. data/lib/dm-core/core_ext/symbol.rb +1 -1
  15. data/lib/dm-core/identity_map.rb +0 -5
  16. data/lib/dm-core/model.rb +11 -21
  17. data/lib/dm-core/model/property.rb +43 -58
  18. data/lib/dm-core/model/relationship.rb +49 -44
  19. data/lib/dm-core/property.rb +106 -130
  20. data/lib/dm-core/property/date_time.rb +1 -3
  21. data/lib/dm-core/property/decimal.rb +11 -7
  22. data/lib/dm-core/property/integer.rb +2 -2
  23. data/lib/dm-core/property/lookup.rb +3 -16
  24. data/lib/dm-core/property/numeric.rb +3 -3
  25. data/lib/dm-core/property/object.rb +2 -11
  26. data/lib/dm-core/property/string.rb +1 -1
  27. data/lib/dm-core/property_set.rb +34 -54
  28. data/lib/dm-core/query.rb +85 -56
  29. data/lib/dm-core/query/conditions/comparison.rb +3 -6
  30. data/lib/dm-core/query/direction.rb +0 -4
  31. data/lib/dm-core/query/path.rb +22 -6
  32. data/lib/dm-core/relationship_set.rb +74 -0
  33. data/lib/dm-core/resource.rb +21 -32
  34. data/lib/dm-core/resource/state.rb +3 -4
  35. data/lib/dm-core/spec/lib/spec_helper.rb +1 -4
  36. data/lib/dm-core/spec/setup.rb +12 -5
  37. data/lib/dm-core/spec/shared/public/property_spec.rb +35 -21
  38. data/lib/dm-core/spec/shared/resource_spec.rb +1 -1
  39. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +9 -9
  40. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  41. data/lib/dm-core/support/deprecate.rb +1 -1
  42. data/lib/dm-core/support/descendant_set.rb +12 -5
  43. data/lib/dm-core/support/ordered_set.rb +382 -0
  44. data/lib/dm-core/support/subject_set.rb +252 -0
  45. data/lib/dm-core/version.rb +1 -1
  46. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +1 -2
  47. data/spec/public/associations/many_to_many_spec.rb +11 -9
  48. data/spec/public/associations/many_to_one_spec.rb +1 -1
  49. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +1 -1
  50. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +2 -2
  51. data/spec/public/associations/one_to_many_spec.rb +6 -5
  52. data/spec/public/associations/one_to_one_spec.rb +1 -1
  53. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +1 -1
  54. data/spec/public/collection_spec.rb +7 -6
  55. data/spec/public/finalize_spec.rb +1 -1
  56. data/spec/public/model/hook_spec.rb +4 -3
  57. data/spec/public/model/property_spec.rb +9 -3
  58. data/spec/public/model/relationship_spec.rb +2 -4
  59. data/spec/public/model_spec.rb +1 -1
  60. data/spec/public/property/binary_spec.rb +1 -1
  61. data/spec/public/property/boolean_spec.rb +1 -1
  62. data/spec/public/property/class_spec.rb +1 -1
  63. data/spec/public/property/date_spec.rb +1 -1
  64. data/spec/public/property/date_time_spec.rb +1 -1
  65. data/spec/public/property/decimal_spec.rb +7 -6
  66. data/spec/public/property/discriminator_spec.rb +1 -1
  67. data/spec/public/property/float_spec.rb +1 -1
  68. data/spec/public/property/integer_spec.rb +1 -1
  69. data/spec/public/property/object_spec.rb +2 -2
  70. data/spec/public/property/serial_spec.rb +1 -1
  71. data/spec/public/property/string_spec.rb +1 -1
  72. data/spec/public/property/text_spec.rb +6 -3
  73. data/spec/public/property/time_spec.rb +1 -1
  74. data/spec/public/property_spec.rb +13 -11
  75. data/spec/public/resource_spec.rb +43 -1
  76. data/spec/public/sel_spec.rb +1 -1
  77. data/spec/public/setup_spec.rb +1 -1
  78. data/spec/public/shared/collection_shared_spec.rb +6 -1
  79. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  80. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +1 -1
  81. data/spec/semipublic/associations/many_to_many_spec.rb +1 -1
  82. data/spec/semipublic/associations/many_to_one_spec.rb +1 -1
  83. data/spec/semipublic/associations/one_to_many_spec.rb +1 -1
  84. data/spec/semipublic/associations/one_to_one_spec.rb +1 -1
  85. data/spec/semipublic/associations/relationship_spec.rb +1 -1
  86. data/spec/semipublic/associations_spec.rb +1 -1
  87. data/spec/semipublic/collection_spec.rb +1 -1
  88. data/spec/semipublic/model_spec.rb +1 -1
  89. data/spec/semipublic/property/binary_spec.rb +1 -1
  90. data/spec/semipublic/property/boolean_spec.rb +1 -1
  91. data/spec/semipublic/property/class_spec.rb +1 -1
  92. data/spec/semipublic/property/date_spec.rb +1 -1
  93. data/spec/semipublic/property/date_time_spec.rb +1 -1
  94. data/spec/semipublic/property/decimal_spec.rb +6 -5
  95. data/spec/semipublic/property/discriminator_spec.rb +1 -1
  96. data/spec/semipublic/property/float_spec.rb +1 -1
  97. data/spec/semipublic/property/integer_spec.rb +1 -1
  98. data/spec/semipublic/property/lookup_spec.rb +8 -5
  99. data/spec/semipublic/property/serial_spec.rb +1 -1
  100. data/spec/semipublic/property/string_spec.rb +1 -1
  101. data/spec/semipublic/property/text_spec.rb +1 -1
  102. data/spec/semipublic/property/time_spec.rb +1 -1
  103. data/spec/semipublic/property_spec.rb +32 -7
  104. data/spec/semipublic/query/conditions/comparison_spec.rb +1 -264
  105. data/spec/semipublic/query/conditions/operation_spec.rb +1 -2
  106. data/spec/semipublic/query/path_spec.rb +27 -1
  107. data/spec/semipublic/query_spec.rb +87 -36
  108. data/spec/semipublic/resource/state/clean_spec.rb +1 -2
  109. data/spec/semipublic/resource/state/deleted_spec.rb +1 -2
  110. data/spec/semipublic/resource/state/dirty_spec.rb +1 -2
  111. data/spec/semipublic/resource/state/immutable_spec.rb +1 -2
  112. data/spec/semipublic/resource/state/transient_spec.rb +7 -2
  113. data/spec/semipublic/resource/state_spec.rb +1 -1
  114. data/spec/semipublic/resource_spec.rb +1 -1
  115. data/spec/unit/array_spec.rb +1 -0
  116. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  117. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  118. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  119. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  120. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  121. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  122. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  123. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  124. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  125. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  126. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  127. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  128. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  129. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  130. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  131. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  132. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  133. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  134. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  135. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  136. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  137. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  138. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  139. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  140. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  141. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  142. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  143. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  144. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  145. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  146. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  147. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  148. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  149. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  150. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  151. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  152. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  153. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  154. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  155. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  156. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  157. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  158. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  159. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  160. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  161. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  162. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  163. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  164. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  165. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  166. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  167. data/spec/unit/hash_spec.rb +2 -1
  168. data/spec/unit/hook_spec.rb +1 -0
  169. data/spec/unit/lazy_array_spec.rb +2 -1
  170. data/spec/unit/module_spec.rb +2 -1
  171. data/spec/unit/object_spec.rb +1 -0
  172. data/spec/unit/try_dup_spec.rb +1 -0
  173. data/tasks/spec.rake +0 -3
  174. metadata +149 -52
  175. data/.gitignore +0 -37
  176. data/lib/dm-core/type.rb +0 -216
  177. data/lib/dm-core/types/boolean.rb +0 -9
  178. data/lib/dm-core/types/decimal.rb +0 -9
  179. data/lib/dm-core/types/discriminator.rb +0 -50
  180. data/lib/dm-core/types/object.rb +0 -25
  181. data/lib/dm-core/types/serial.rb +0 -11
  182. data/lib/dm-core/types/text.rb +0 -11
  183. data/tasks/local_gemfile.rake +0 -16
  184. data/tasks/metrics.rake +0 -37
@@ -1,6 +1,8 @@
1
1
  # TODO: update Model#respond_to? to return true if method_method missing
2
2
  # would handle the message
3
3
 
4
+ require 'dm-core/relationship_set'
5
+
4
6
  module DataMapper
5
7
  module Model
6
8
  module Relationship
@@ -31,7 +33,7 @@ module DataMapper
31
33
 
32
34
  @relationships.each do |repository_name, relationships|
33
35
  model_relationships = model.relationships(repository_name)
34
- relationships.each { |name, relationship| model_relationships[name] ||= relationship }
36
+ relationships.each { |relationship| model_relationships << relationship }
35
37
  end
36
38
 
37
39
  super
@@ -42,7 +44,7 @@ module DataMapper
42
44
  #
43
45
  # @param [Symbol] repository_name
44
46
  # Name of the repository for which relationships set is returned
45
- # @return [Mash] relationships set for given repository
47
+ # @return [RelationshipSet] relationships set for given repository
46
48
  #
47
49
  # @api semipublic
48
50
  def relationships(repository_name = default_repository_name)
@@ -53,7 +55,7 @@ module DataMapper
53
55
  default_repository_name = self.default_repository_name
54
56
 
55
57
  @relationships[repository_name] ||= if repository_name == default_repository_name
56
- Mash.new
58
+ RelationshipSet.new
57
59
  else
58
60
  relationships(default_repository_name).dup
59
61
  end
@@ -124,10 +126,12 @@ module DataMapper
124
126
  Associations::OneToOne::Relationship
125
127
  end
126
128
 
127
- relationship = relationships(repository_name)[name] = klass.new(name, model, self, options)
129
+ relationship = klass.new(name, model, self, options)
130
+
131
+ relationships(repository_name) << relationship
128
132
 
129
133
  descendants.each do |descendant|
130
- descendant.relationships(repository_name)[name] ||= relationship
134
+ descendant.relationships(repository_name) << relationship
131
135
  end
132
136
 
133
137
  create_relationship_reader(relationship)
@@ -161,16 +165,13 @@ module DataMapper
161
165
  options = extract_options(args)
162
166
 
163
167
  if options.key?(:through)
164
- warn "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{model_name} instead (#{caller[0]})"
165
- return has(1, name, model, options)
168
+ raise "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{model_name} instead (#{caller.first})"
169
+ elsif options.key?(:model) && model
170
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
166
171
  end
167
172
 
168
173
  assert_valid_options(options)
169
174
 
170
- if options.key?(:model) && model
171
- raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
172
- end
173
-
174
175
  model ||= options.delete(:model)
175
176
 
176
177
  repository_name = repository.name
@@ -179,10 +180,12 @@ module DataMapper
179
180
  options[:child_repository_name] = repository_name
180
181
  options[:parent_repository_name] = options.delete(:repository)
181
182
 
182
- relationship = relationships(repository_name)[name] = Associations::ManyToOne::Relationship.new(name, self, model, options)
183
+ relationship = Associations::ManyToOne::Relationship.new(name, self, model, options)
184
+
185
+ relationships(repository_name) << relationship
183
186
 
184
187
  descendants.each do |descendant|
185
- descendant.relationships(repository_name)[name] ||= relationship
188
+ descendant.relationships(repository_name) << relationship
186
189
  end
187
190
 
188
191
  create_relationship_reader(relationship)
@@ -253,8 +256,6 @@ module DataMapper
253
256
  # TODO: update to match Query#assert_valid_options
254
257
  # - perform options normalization elsewhere
255
258
 
256
- caller_method = caller[1]
257
-
258
259
  if options.key?(:min) && options.key?(:max)
259
260
  min = options[:min]
260
261
  max = options[:max]
@@ -278,15 +279,9 @@ module DataMapper
278
279
  end
279
280
 
280
281
  if options.key?(:class_name)
281
- options[:class_name] = options[:class_name].to_str
282
- warn "+options[:class_name]+ is deprecated, use :model instead (#{caller_method})"
283
- options[:model] = options.delete(:class_name)
284
- end
285
-
286
- if options.key?(:remote_name)
287
- options[:remote_name] = options[:remote_name].to_sym
288
- warn "+options[:remote_name]+ is deprecated, use :via instead (#{caller_method})"
289
- options[:via] = options.delete(:remote_name)
282
+ raise "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
283
+ elsif options.key?(:remote_name)
284
+ raise "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
290
285
  end
291
286
 
292
287
  if options.key?(:through)
@@ -313,6 +308,20 @@ module DataMapper
313
308
  end
314
309
  end
315
310
 
311
+ # Defines the anonymous module that is used to add relationships.
312
+ # Using a single module here prevents having a very large number
313
+ # of anonymous modules, where each property has their own module.
314
+ # @api private
315
+ def relationship_module
316
+ @relationship_module ||= begin
317
+ mod = Module.new
318
+ class_eval do
319
+ include mod
320
+ end
321
+ mod
322
+ end
323
+ end
324
+
316
325
  # Dynamically defines reader method
317
326
  #
318
327
  # @api private
@@ -324,18 +333,16 @@ module DataMapper
324
333
 
325
334
  reader_visibility = relationship.reader_visibility
326
335
 
327
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
328
- chainable do
329
- #{reader_visibility}
330
- def #{reader_name}(query = nil)
331
- # TODO: when no query is passed in, return the results from
332
- # the ivar directly. This will require that the ivar
333
- # actually hold the resource/collection, and in the case
334
- # of 1:1, the underlying collection is hidden in a
335
- # private ivar, and the resource is in a known ivar
336
-
337
- persisted_state.get(relationships[#{name.inspect}], query)
338
- end
336
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
337
+ #{reader_visibility}
338
+ def #{reader_name}(query = nil)
339
+ # TODO: when no query is passed in, return the results from
340
+ # the ivar directly. This will require that the ivar
341
+ # actually hold the resource/collection, and in the case
342
+ # of 1:1, the underlying collection is hidden in a
343
+ # private ivar, and the resource is in a known ivar
344
+
345
+ persisted_state.get(relationships[#{name.inspect}], query)
339
346
  end
340
347
  RUBY
341
348
  end
@@ -351,14 +358,12 @@ module DataMapper
351
358
 
352
359
  writer_visibility = relationship.writer_visibility
353
360
 
354
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
355
- chainable do
356
- #{writer_visibility}
357
- def #{writer_name}(target)
358
- relationship = relationships[#{name.inspect}]
359
- self.persisted_state = persisted_state.set(relationship, target)
360
- persisted_state.get(relationship)
361
- end
361
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
362
+ #{writer_visibility}
363
+ def #{writer_name}(target)
364
+ relationship = relationships[#{name.inspect}]
365
+ self.persisted_state = persisted_state.set(relationship, target)
366
+ persisted_state.get(relationship)
362
367
  end
363
368
  RUBY
364
369
  end
@@ -1,3 +1,6 @@
1
+ require 'dm-core/resource'
2
+ require 'dm-core/query'
3
+
1
4
  module DataMapper
2
5
  # = Properties
3
6
  # Properties for a model are not derived from a database structure, but
@@ -17,8 +20,7 @@ module DataMapper
17
20
  # developers on your team to take a model-centric view of development.
18
21
  # * it provides the ability to use Ruby's access control functions.
19
22
  # * and, because DataMapper only cares about properties explicitly defined
20
- # in
21
- # your models, DataMapper plays well with legacy databases, and shares
23
+ # in your models, DataMapper plays well with legacy databases, and shares
22
24
  # databases easily with other applications.
23
25
  #
24
26
  # == Declaring Properties
@@ -30,27 +32,23 @@ module DataMapper
30
32
  # include DataMapper::Resource
31
33
  #
32
34
  # property :title, String, :required => true # Cannot be null
33
- # property :publish, Boolean, :default => false # Default value for new records is false
35
+ # property :publish, Boolean, :default => false # Default value for new records is false
34
36
  # end
35
37
  #
36
38
  # By default, DataMapper supports the following primitive (Ruby) types
37
- # also called core types:
39
+ # also called core properties:
38
40
  #
39
41
  # * Boolean
40
- # * String (default length is 50)
41
- # * Text (limit of 65k characters by default)
42
+ # * Class (datastore primitive is the same as String. Used for Inheritance)
43
+ # * Date
44
+ # * DateTime
45
+ # * Decimal
42
46
  # * Float
43
47
  # * Integer
44
- # * BigDecimal
45
- # * DateTime
46
- # * Date
47
- # * Time
48
48
  # * Object (marshalled out during serialization)
49
- # * Class (datastore primitive is the same as String. Used for Inheritance)
50
- #
51
- # Other types are known as custom types.
52
- #
53
- # For more information about available Types, see DataMapper::Type
49
+ # * String (default length is 50)
50
+ # * Text (limit of 65k characters by default)
51
+ # * Time
54
52
  #
55
53
  # == Limiting Access
56
54
  # Property access control is uses the same terminology Ruby does. Properties
@@ -205,9 +203,8 @@ module DataMapper
205
203
  #
206
204
  # end
207
205
  #
208
- # This functionality is available with the dm-validations gem, part of the
209
- # dm-more bundle. For more information about validations, check the
210
- # documentation for dm-validations.
206
+ # This functionality is available with the dm-validations gem. For more information
207
+ # about validations, check the documentation for dm-validations.
211
208
  #
212
209
  # == Default Values
213
210
  # To set a default for a property, use the <tt>:default</tt> key. The
@@ -224,7 +221,7 @@ module DataMapper
224
221
  # Word of warning. Don't try to read the value of the property you're setting
225
222
  # the default for in the proc. An infinite loop will ensue.
226
223
  #
227
- # == Embedded Values
224
+ # == Embedded Values (not implemented yet)
228
225
  # As an alternative to extraneous has_one relationships, consider using an
229
226
  # EmbeddedValue.
230
227
  #
@@ -248,8 +245,6 @@ module DataMapper
248
245
  #
249
246
  # :key name of the key associated with this property.
250
247
  #
251
- # :serial if true, field value is auto incrementing
252
- #
253
248
  # :field field in the data-store which the property corresponds to
254
249
  #
255
250
  # :length string field length
@@ -278,6 +273,31 @@ module DataMapper
278
273
  # All other keys you pass to +property+ method are stored and available
279
274
  # as options[:extra_keys].
280
275
  #
276
+ # == Overriding default Property options
277
+ #
278
+ # There is the ability to reconfigure a Property and it's subclasses by explicitly
279
+ # setting a value in the Property, eg:
280
+ #
281
+ # # set all String properties to have a default length of 255
282
+ # DataMapper::Property::String.length(255)
283
+ #
284
+ # # set all Boolean properties to not allow nil (force true or false)
285
+ # DataMapper::Property::Boolean.allow_nil(false)
286
+ #
287
+ # # set all properties to be required by default
288
+ # DataMapper::Property.required(true)
289
+ #
290
+ # # turn off auto-validation for all properties by default
291
+ # DataMapper::Property.auto_validation(false)
292
+ #
293
+ # # set all mutator methods to be private by default
294
+ # DataMapper::Property.writer(false)
295
+ #
296
+ # Please note that this has no effect when a subclass has explicitly
297
+ # defined it's own option. For example, setting the String length to
298
+ # 255 will not affect the Text property even though it inherits from
299
+ # String, because it sets it's own default length to 65535.
300
+ #
281
301
  # == Misc. Notes
282
302
  # * Properties declared as strings will default to a length of 50, rather than
283
303
  # 255 (typical max varchar column size). To overload the default, pass
@@ -291,12 +311,8 @@ module DataMapper
291
311
  module PassThroughLoadDump
292
312
  # @api semipublic
293
313
  def load(value)
294
- unless value.nil?
295
- value = type.load(value, self) if type
296
- typecast(value)
297
- else
298
- value
299
- end
314
+ return if value.nil?
315
+ typecast(value)
300
316
  end
301
317
 
302
318
  # Stub instance method for dumping
@@ -307,24 +323,15 @@ module DataMapper
307
323
  #
308
324
  # @api semipublic
309
325
  def dump(value)
310
- if type
311
- type.dump(value, self)
312
- else
313
- value
314
- end
326
+ value
315
327
  end
316
328
  end
317
329
 
318
330
  include DataMapper::Assertions
319
331
  include Subject
320
332
  extend Chainable
321
- extend Deprecate
322
333
  extend Equalizer
323
334
 
324
- deprecate :unique, :unique?
325
- deprecate :nullable?, :allow_nil?
326
- deprecate :value, :dump
327
-
328
335
  equalize :model, :name
329
336
 
330
337
  PRIMITIVES = [
@@ -349,8 +356,11 @@ module DataMapper
349
356
  # Possible :visibility option values
350
357
  VISIBILITY_OPTIONS = [ :public, :protected, :private ].to_set.freeze
351
358
 
359
+ # Invalid property names
360
+ INVALID_NAMES = (Resource.instance_methods + Resource.private_instance_methods + Query::OPTIONS.to_a).map { |name| name.to_s }.freeze
361
+
352
362
  attr_reader :primitive, :model, :name, :instance_variable_name,
353
- :type, :reader_visibility, :writer_visibility, :options,
363
+ :reader_visibility, :writer_visibility, :options,
354
364
  :default, :repository_name, :allow_nil, :allow_blank, :required
355
365
 
356
366
  class << self
@@ -360,30 +370,19 @@ module DataMapper
360
370
 
361
371
  # @api semipublic
362
372
  def determine_class(type)
363
- if type < DataMapper::Property::Object
364
- return type
365
- end
366
-
367
- name = DataMapper::Inflector.demodulize(type.name)
368
- klass = find_class(name)
369
-
370
- if !klass && type < DataMapper::Type
371
- klass = find_class(type.primitive.name)
372
- end
373
+ return type if type < DataMapper::Property::Object
374
+ find_class(DataMapper::Inflector.demodulize(type.name))
375
+ end
373
376
 
374
- klass
377
+ # @api private
378
+ def demodulized_names
379
+ @demodulized_names ||= {}
375
380
  end
376
381
 
377
382
  # @api semipublic
378
383
  def find_class(name)
379
- klass = descendants.detect do |descendant|
380
- DataMapper::Inflector.demodulize(descendant.name) == name
381
- end
382
-
383
- if !klass && const_defined?(name)
384
- klass = const_get(name)
385
- end
386
-
384
+ klass = demodulized_names[name]
385
+ klass ||= const_get(name) if const_defined?(name)
387
386
  klass
388
387
  end
389
388
 
@@ -394,8 +393,28 @@ module DataMapper
394
393
 
395
394
  # @api private
396
395
  def inherited(descendant)
396
+ # Descendants is a tree rooted in DataMapper::Property that tracks
397
+ # inheritance. We pre-calculate each comparison value (demodulized
398
+ # class name) to achieve a Hash[]-time lookup, rather than walk the
399
+ # entire descendant tree and calculate names on-demand (expensive,
400
+ # redundant).
401
+ #
402
+ # Since the algorithm relegates property class name lookups to a flat
403
+ # namespace, we need to ensure properties defined outside of DM don't
404
+ # override built-ins (Serial, String, etc) by merely defining a property
405
+ # of a same name. We avoid this by only ever adding to the lookup
406
+ # table. Given that DM loads its own property classes first, we can
407
+ # assume that their names are "reserved" when added to the table.
408
+ #
409
+ # External property authors who want to provide "replacements" for
410
+ # builtins (e.g. in a non-DM-supported adapter) should follow the
411
+ # convention of wrapping those properties in a module, and include'ing
412
+ # the module on the model class directly. This bypasses the DM-hooked
413
+ # const_missing lookup that would normally check this table.
397
414
  descendants << descendant
398
415
 
416
+ Property.demodulized_names[DataMapper::Inflector.demodulize(descendant.name)] ||= descendant
417
+
399
418
  # inherit accepted options
400
419
  descendant.accepted_options.concat(accepted_options)
401
420
 
@@ -415,13 +434,15 @@ module DataMapper
415
434
  # create methods for each new option
416
435
  args.each do |property_option|
417
436
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
418
- def self.#{property_option}(value = Undefined) # def self.unique(value = Undefined)
419
- return @#{property_option} if value.equal?(Undefined) # return @unique if value.equal?(Undefined)
420
- descendants.each do |descendant| # descendants.each do |descendant|
421
- descendant.#{property_option}(value) # descendant.unique(value)
422
- end # end
423
- @#{property_option} = value # @unique = value
424
- end # end
437
+ def self.#{property_option}(value = Undefined) # def self.unique(value = Undefined)
438
+ return @#{property_option} if value.equal?(Undefined) # return @unique if value.equal?(Undefined)
439
+ descendants.each do |descendant| # descendants.each do |descendant|
440
+ unless descendant.instance_variable_defined?(:@#{property_option}) # unless descendant.instance_variable_defined?(:@unique)
441
+ descendant.#{property_option}(value) # descendant.unique(value)
442
+ end # end
443
+ end # end
444
+ @#{property_option} = value # @unique = value
445
+ end # end
425
446
  RUBY
426
447
  end
427
448
 
@@ -431,20 +452,19 @@ module DataMapper
431
452
  # @api private
432
453
  def nullable(*args)
433
454
  # :required is preferable to :allow_nil, but :nullable maps precisely to :allow_nil
434
- warn "#nullable is deprecated, use #required instead (#{caller[0]})"
435
- allow_nil(*args)
455
+ raise "#nullable is deprecated, use #required instead (#{caller.first})"
436
456
  end
437
457
 
438
- # Gives all the options set on this type
458
+ # Gives all the options set on this property
439
459
  #
440
- # @return [Hash] with all options and their values set on this type
460
+ # @return [Hash] with all options and their values set on this property
441
461
  #
442
462
  # @api public
443
463
  def options
444
464
  options = {}
445
465
  accepted_options.each do |method|
446
466
  value = send(method)
447
- options[method] = send(method) unless value.nil?
467
+ options[method] = value unless value.nil?
448
468
  end
449
469
  options
450
470
  end
@@ -452,9 +472,9 @@ module DataMapper
452
472
 
453
473
  accept_options :primitive, *Property::OPTIONS
454
474
 
455
- # A hook to allow types to extend or modify property it's bound to.
456
- # Implementations are not supposed to modify the state of the type class, and
457
- # should produce no side-effects on the type class.
475
+ # A hook to allow properties to extend or modify the model it's bound to.
476
+ # Implementations are not supposed to modify the state of the property
477
+ # class, and should produce no side-effects on the property instance.
458
478
  def bind
459
479
  # no op
460
480
  end
@@ -464,21 +484,14 @@ module DataMapper
464
484
  # @return [String] name of field in data-store
465
485
  #
466
486
  # @api semipublic
467
- def field(repository_name = Undefined)
468
- self_repository_name = self.repository_name
469
- klass = self.class
470
-
471
- unless repository_name.equal?(Undefined)
472
- warn "Passing in +repository_name+ to #{klass}#field is deprecated (#{caller[0]})"
473
-
474
- if repository_name != self_repository_name
475
- raise ArgumentError, "Mismatching +repository_name+ with #{klass}#repository_name (#{repository_name.inspect} != #{self_repository_name.inspect})"
476
- end
487
+ def field(repository_name = nil)
488
+ if repository_name
489
+ raise "Passing in +repository_name+ to #{self.class}#field is deprecated (#{caller.first})"
477
490
  end
478
491
 
479
492
  # defer setting the field with the adapter specific naming
480
493
  # conventions until after the adapter has been setup
481
- @field ||= model.field_naming_convention(self_repository_name).call(self).freeze
494
+ @field ||= model.field_naming_convention(self.repository_name).call(self).freeze
482
495
  end
483
496
 
484
497
  # Returns true if property is unique. Serial properties and keys
@@ -492,19 +505,6 @@ module DataMapper
492
505
  !!@unique
493
506
  end
494
507
 
495
- # Returns the hash of the property name
496
- #
497
- # This is necessary to allow comparisons between different properties
498
- # in different models, having the same base model
499
- #
500
- # @return [Integer]
501
- # the property name hash
502
- #
503
- # @api semipublic
504
- def hash
505
- name.hash
506
- end
507
-
508
508
  # Returns index name if property has index.
509
509
  #
510
510
  # @return [Boolean, Symbol, Array]
@@ -514,9 +514,7 @@ module DataMapper
514
514
  # returns false if the property does not belong to any indexes
515
515
  #
516
516
  # @api public
517
- def index
518
- @index
519
- end
517
+ attr_reader :index
520
518
 
521
519
  # Returns true if property has unique index. Serial properties and
522
520
  # keys are unique by default.
@@ -528,9 +526,7 @@ module DataMapper
528
526
  # returns false if the property does not belong to any indexes
529
527
  #
530
528
  # @api public
531
- def unique_index
532
- @unique_index
533
- end
529
+ attr_reader :unique_index
534
530
 
535
531
  # Returns whether or not the property is to be lazy-loaded
536
532
  #
@@ -592,16 +588,6 @@ module DataMapper
592
588
  @allow_blank
593
589
  end
594
590
 
595
- # Returns whether or not the property is custom (not provided by dm-core)
596
- #
597
- # @return [Boolean]
598
- # whether or not the property is custom
599
- #
600
- # @api public
601
- def custom?
602
- @custom
603
- end
604
-
605
591
  # Standardized reader method for the property
606
592
  #
607
593
  # @param [Resource] resource
@@ -702,9 +688,7 @@ module DataMapper
702
688
 
703
689
  # @api semipublic
704
690
  def typecast(value)
705
- if @type && @type.respond_to?(:typecast)
706
- @type.typecast(value, self)
707
- elsif value.nil? || primitive?(value)
691
+ if value.nil? || primitive?(value)
708
692
  value
709
693
  elsif respond_to?(:typecast_to_primitive)
710
694
  typecast_to_primitive(value)
@@ -754,7 +738,7 @@ module DataMapper
754
738
  end
755
739
 
756
740
  chainable do
757
- def self.new(model, name, options = {}, type = nil)
741
+ def self.new(model, name, options = {})
758
742
  super
759
743
  end
760
744
  end
@@ -762,23 +746,16 @@ module DataMapper
762
746
  protected
763
747
 
764
748
  # @api semipublic
765
- def initialize(model, name, options = {}, type = nil)
749
+ def initialize(model, name, options = {})
766
750
  options = options.to_hash.dup
767
751
 
768
- if type && !kind_of?(type)
769
- warn "#{type} < DataMapper::Type is deprecated, use the new DataMapper::Property API instead (#{caller[2]})"
770
- @type = type
771
- end
772
-
773
- reserved_method_names = DataMapper::Resource.instance_methods + DataMapper::Resource.private_instance_methods
774
- if reserved_method_names.map { |m| m.to_s }.include?(name.to_s)
775
- raise ArgumentError, "+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method"
752
+ if INVALID_NAMES.include?(name.to_s)
753
+ raise ArgumentError, "+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method or a query option"
776
754
  end
777
755
 
778
756
  assert_valid_options(options)
779
757
 
780
758
  predefined_options = self.class.options
781
- predefined_options.merge!(@type.options) if @type
782
759
 
783
760
  @repository_name = model.repository_name
784
761
  @model = model
@@ -786,8 +763,7 @@ module DataMapper
786
763
  @options = predefined_options.merge(options).freeze
787
764
  @instance_variable_name = "@#{@name}".freeze
788
765
 
789
- @primitive = self.class.primitive || @type.primitive
790
- @custom = !@type.nil?
766
+ @primitive = self.class.primitive
791
767
  @field = @options[:field].freeze
792
768
  @default = @options[:default]
793
769
 
@@ -803,7 +779,7 @@ module DataMapper
803
779
 
804
780
  determine_visibility
805
781
 
806
- @type ? @type.bind(self) : bind
782
+ bind
807
783
  end
808
784
 
809
785
  # @api private
@@ -831,7 +807,7 @@ module DataMapper
831
807
  raise ArgumentError, "options[:#{key}] must be either true or false"
832
808
  end
833
809
 
834
- if key == :required && (keys & [ :allow_nil, :allow_blank ]).size > 0
810
+ if key == :required && (keys.include?(:allow_nil) || keys.include?(:allow_blank))
835
811
  raise ArgumentError, 'options[:required] cannot be mixed with :allow_nil or :allow_blank'
836
812
  end
837
813