dm-core 0.10.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +27 -0
  4. data/LICENSE +20 -0
  5. data/{README.txt → README.rdoc} +14 -3
  6. data/Rakefile +23 -22
  7. data/VERSION +1 -0
  8. data/dm-core.gemspec +201 -10
  9. data/lib/dm-core.rb +32 -23
  10. data/lib/dm-core/adapters.rb +0 -1
  11. data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
  12. data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
  13. data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
  14. data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
  15. data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
  16. data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
  17. data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
  18. data/lib/dm-core/associations/many_to_many.rb +118 -56
  19. data/lib/dm-core/associations/many_to_one.rb +48 -21
  20. data/lib/dm-core/associations/one_to_many.rb +8 -30
  21. data/lib/dm-core/associations/one_to_one.rb +1 -5
  22. data/lib/dm-core/associations/relationship.rb +89 -97
  23. data/lib/dm-core/collection.rb +299 -184
  24. data/lib/dm-core/core_ext/enumerable.rb +28 -0
  25. data/lib/dm-core/core_ext/kernel.rb +0 -2
  26. data/lib/dm-core/migrations.rb +314 -170
  27. data/lib/dm-core/model.rb +97 -66
  28. data/lib/dm-core/model/descendant_set.rb +1 -1
  29. data/lib/dm-core/model/hook.rb +0 -3
  30. data/lib/dm-core/model/property.rb +7 -10
  31. data/lib/dm-core/model/relationship.rb +79 -26
  32. data/lib/dm-core/model/scope.rb +3 -4
  33. data/lib/dm-core/property.rb +152 -90
  34. data/lib/dm-core/property_set.rb +18 -37
  35. data/lib/dm-core/query.rb +452 -153
  36. data/lib/dm-core/query/conditions/comparison.rb +266 -173
  37. data/lib/dm-core/query/conditions/operation.rb +499 -57
  38. data/lib/dm-core/query/direction.rb +0 -3
  39. data/lib/dm-core/query/operator.rb +0 -4
  40. data/lib/dm-core/query/path.rb +10 -12
  41. data/lib/dm-core/query/sort.rb +4 -10
  42. data/lib/dm-core/repository.rb +10 -6
  43. data/lib/dm-core/resource.rb +343 -148
  44. data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
  45. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
  46. data/lib/dm-core/support/chainable.rb +0 -2
  47. data/lib/dm-core/support/equalizer.rb +27 -3
  48. data/lib/dm-core/transaction.rb +75 -75
  49. data/lib/dm-core/type.rb +19 -5
  50. data/lib/dm-core/types/discriminator.rb +4 -4
  51. data/lib/dm-core/types/object.rb +2 -7
  52. data/lib/dm-core/types/paranoid_boolean.rb +8 -2
  53. data/lib/dm-core/types/paranoid_datetime.rb +8 -2
  54. data/lib/dm-core/version.rb +1 -1
  55. data/script/performance.rb +7 -7
  56. data/script/profile.rb +6 -6
  57. data/spec/lib/collection_helpers.rb +2 -2
  58. data/spec/lib/pending_helpers.rb +22 -3
  59. data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
  60. data/spec/public/associations/many_to_many_spec.rb +6 -4
  61. data/spec/public/associations/many_to_one_spec.rb +10 -1
  62. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
  63. data/spec/public/associations/one_to_many_spec.rb +4 -3
  64. data/spec/public/associations/one_to_one_spec.rb +19 -1
  65. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
  66. data/spec/public/collection_spec.rb +4 -3
  67. data/spec/public/migrations_spec.rb +144 -0
  68. data/spec/public/model/relationship_spec.rb +115 -55
  69. data/spec/public/model_spec.rb +13 -13
  70. data/spec/public/property/object_spec.rb +106 -0
  71. data/spec/public/property_spec.rb +18 -14
  72. data/spec/public/resource_spec.rb +10 -1
  73. data/spec/public/sel_spec.rb +16 -49
  74. data/spec/public/setup_spec.rb +1 -1
  75. data/spec/public/shared/association_collection_shared_spec.rb +6 -14
  76. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  77. data/spec/public/shared/collection_shared_spec.rb +214 -217
  78. data/spec/public/shared/finder_shared_spec.rb +259 -365
  79. data/spec/public/shared/resource_shared_spec.rb +524 -248
  80. data/spec/public/transaction_spec.rb +27 -3
  81. data/spec/public/types/discriminator_spec.rb +1 -1
  82. data/spec/rcov.opts +6 -0
  83. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
  84. data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
  85. data/spec/semipublic/associations_spec.rb +2 -2
  86. data/spec/semipublic/collection_spec.rb +0 -32
  87. data/spec/semipublic/model_spec.rb +96 -0
  88. data/spec/semipublic/property_spec.rb +3 -3
  89. data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
  90. data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
  91. data/spec/semipublic/query_spec.rb +1285 -144
  92. data/spec/semipublic/resource_spec.rb +0 -24
  93. data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
  94. data/spec/spec.opts +1 -1
  95. data/spec/spec_helper.rb +15 -6
  96. data/tasks/ci.rake +1 -0
  97. data/tasks/metrics.rake +37 -0
  98. data/tasks/spec.rake +41 -0
  99. data/tasks/yard.rake +9 -0
  100. data/tasks/yardstick.rake +19 -0
  101. metadata +99 -29
  102. data/CONTRIBUTING +0 -51
  103. data/FAQ +0 -93
  104. data/History.txt +0 -27
  105. data/MIT-LICENSE +0 -22
  106. data/Manifest.txt +0 -121
  107. data/QUICKLINKS +0 -11
  108. data/SPECS +0 -35
  109. data/TODO +0 -1
  110. data/spec/semipublic/query/conditions_spec.rb +0 -528
  111. data/tasks/ci.rb +0 -24
  112. data/tasks/dm.rb +0 -58
  113. data/tasks/doc.rb +0 -17
  114. data/tasks/gemspec.rb +0 -23
  115. data/tasks/hoe.rb +0 -45
  116. data/tasks/install.rb +0 -18
@@ -50,6 +50,8 @@ module DataMapper
50
50
  # new Relationship objects to a supplied repository and model. dup does not really
51
51
  # do what is needed
52
52
 
53
+ default_repository_name = self.default_repository_name
54
+
53
55
  @relationships[repository_name] ||= if repository_name == default_repository_name
54
56
  Mash.new
55
57
  else
@@ -62,7 +64,7 @@ module DataMapper
62
64
  #
63
65
  # @api public
64
66
  def n
65
- 1.0/0
67
+ Infinity
66
68
  end
67
69
 
68
70
  # A shorthand, clear syntax for defining one-to-one, one-to-many and
@@ -99,7 +101,7 @@ module DataMapper
99
101
  #
100
102
  # @api public
101
103
  def has(cardinality, name, *args)
102
- assert_kind_of 'cardinality', cardinality, Integer, Range, n.class
104
+ assert_kind_of 'cardinality', cardinality, Integer, Range, Infinity.class
103
105
  assert_kind_of 'name', name, Symbol
104
106
 
105
107
  model = extract_model(args)
@@ -122,7 +124,7 @@ module DataMapper
122
124
  options[:child_repository_name] = options.delete(:repository)
123
125
  options[:parent_repository_name] = repository_name
124
126
 
125
- klass = if options[:max] > 1
127
+ klass = if max > 1
126
128
  options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
127
129
  else
128
130
  Associations::OneToOne::Relationship
@@ -134,6 +136,9 @@ module DataMapper
134
136
  descendant.relationships(repository_name)[name] ||= relationship
135
137
  end
136
138
 
139
+ create_relationship_reader(relationship)
140
+ create_relationship_writer(relationship)
141
+
137
142
  relationship
138
143
  end
139
144
 
@@ -162,11 +167,12 @@ module DataMapper
162
167
  def belongs_to(name, *args)
163
168
  assert_kind_of 'name', name, Symbol
164
169
 
165
- model = extract_model(args)
166
- options = extract_options(args)
170
+ model_name = self.name
171
+ model = extract_model(args)
172
+ options = extract_options(args)
167
173
 
168
174
  if options.key?(:through)
169
- warn "#{self.name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{self.name} instead (#{caller[0]})"
175
+ warn "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{model_name} instead (#{caller[0]})"
170
176
  return has(1, name, model, options)
171
177
  end
172
178
 
@@ -190,6 +196,9 @@ module DataMapper
190
196
  descendant.relationships(repository_name)[name] ||= relationship
191
197
  end
192
198
 
199
+ create_relationship_reader(relationship)
200
+ create_relationship_writer(relationship)
201
+
193
202
  relationship
194
203
  end
195
204
 
@@ -243,9 +252,9 @@ module DataMapper
243
252
  # @api private
244
253
  def extract_min_max(cardinality)
245
254
  case cardinality
246
- when Integer then [ cardinality, cardinality ]
247
- when Range then [ cardinality.first, cardinality.last ]
248
- when n then [ 0, n ]
255
+ when Integer then [ cardinality, cardinality ]
256
+ when Range then [ cardinality.first, cardinality.last ]
257
+ when Infinity then [ 0, Infinity ]
249
258
  end
250
259
  end
251
260
 
@@ -258,38 +267,45 @@ module DataMapper
258
267
  # TODO: update to match Query#assert_valid_options
259
268
  # - perform options normalization elsewhere
260
269
 
270
+ caller_method = caller[1]
271
+
261
272
  if options.key?(:min) && options.key?(:max)
262
- assert_kind_of 'options[:min]', options[:min], Integer
263
- assert_kind_of 'options[:max]', options[:max], Integer, n.class
273
+ min = options[:min]
274
+ max = options[:max]
264
275
 
265
- if options[:min] == n && options[:max] == n
276
+ assert_kind_of 'options[:min]', min, Integer
277
+ assert_kind_of 'options[:max]', max, Integer, Infinity.class
278
+
279
+ if min == Infinity && max == Infinity
266
280
  raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
267
- elsif options[:min] > options[:max]
268
- raise ArgumentError, "Cardinality min (#{options[:min]}) cannot be larger than the max (#{options[:max]})"
269
- elsif options[:min] < 0
270
- raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{options[:min]}"
271
- elsif options[:max] < 1
272
- raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{options[:max]}"
281
+ elsif min > max
282
+ raise ArgumentError, "Cardinality min (#{min}) cannot be larger than the max (#{max})"
283
+ elsif min < 0
284
+ raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{min}"
285
+ elsif max < 1
286
+ raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{max}"
273
287
  end
274
288
  end
275
289
 
276
290
  if options.key?(:repository)
277
- assert_kind_of 'options[:repository]', options[:repository], Repository, Symbol
291
+ repository = options[:repository]
292
+
293
+ assert_kind_of 'options[:repository]', repository, Repository, Symbol
278
294
 
279
- if options[:repository].kind_of?(Repository)
280
- options[:repository] = options[:repository].name
295
+ if repository.kind_of?(Repository)
296
+ options[:repository] = repository.name
281
297
  end
282
298
  end
283
299
 
284
300
  if options.key?(:class_name)
285
301
  assert_kind_of 'options[:class_name]', options[:class_name], String
286
- warn "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
302
+ warn "+options[:class_name]+ is deprecated, use :model instead (#{caller_method})"
287
303
  options[:model] = options.delete(:class_name)
288
304
  end
289
305
 
290
306
  if options.key?(:remote_name)
291
307
  assert_kind_of 'options[:remote_name]', options[:remote_name], Symbol
292
- warn "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
308
+ warn "+options[:remote_name]+ is deprecated, use :via instead (#{caller_method})"
293
309
  options[:via] = options.delete(:remote_name)
294
310
  end
295
311
 
@@ -307,8 +323,8 @@ module DataMapper
307
323
  # :target_key (will mean something different for each relationship)
308
324
 
309
325
  [ :child_key, :parent_key ].each do |key|
310
- if options.key?(key)
311
- assert_kind_of "options[#{key.inspect}]", options[key], Enumerable
326
+ if options.key?(key) && !options[key].is_a?(Enumerable)
327
+ options[key] = Array(options[key])
312
328
  end
313
329
  end
314
330
 
@@ -317,8 +333,45 @@ module DataMapper
317
333
  end
318
334
  end
319
335
 
336
+ # Dynamically defines reader method
337
+ #
338
+ # @api private
339
+ def create_relationship_reader(relationship)
340
+ name = relationship.name
341
+ reader_name = name.to_s
342
+
343
+ return if resource_method_defined?(reader_name)
344
+
345
+ reader_visibility = relationship.reader_visibility
346
+
347
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
348
+ #{reader_visibility} # public
349
+ def #{reader_name}(query = nil) # def author(query = nil)
350
+ relationships[#{name.inspect}].get(self, query) # relationships[:author].get(self, query)
351
+ end # end
352
+ RUBY
353
+ end
354
+
355
+ # Dynamically defines writer method
356
+ #
357
+ # @api private
358
+ def create_relationship_writer(relationship)
359
+ name = relationship.name
360
+ writer_name = "#{name}="
361
+
362
+ return if resource_method_defined?(writer_name)
363
+
364
+ writer_visibility = relationship.writer_visibility
365
+
366
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
367
+ #{writer_visibility} # public
368
+ def #{writer_name}(target) # def author=(target)
369
+ relationships[#{name.inspect}].set(self, target) # relationships[:author].set(self, target)
370
+ end # end
371
+ RUBY
372
+ end
373
+
320
374
  chainable do
321
- # TODO: document
322
375
  # @api public
323
376
  def method_missing(method, *args, &block)
324
377
  if relationship = relationships(repository_name)[method]
@@ -10,11 +10,12 @@ module DataMapper
10
10
  # It is also possible to get exclusive scope access
11
11
  # using +with_exclusive_scope+
12
12
  module Scope
13
- # TODO: document
14
13
  # @api private
15
14
  def default_scope(repository_name = default_repository_name)
16
15
  @default_scope ||= {}
17
16
 
17
+ default_repository_name = self.default_repository_name
18
+
18
19
  @default_scope[repository_name] ||= if repository_name == default_repository_name
19
20
  {}
20
21
  else
@@ -29,7 +30,6 @@ module DataMapper
29
30
  Query.new(repository, self, current_scope).freeze
30
31
  end
31
32
 
32
- # TODO: document
33
33
  # @api private
34
34
  def current_scope
35
35
  scope_stack.last || default_scope(repository.name)
@@ -66,6 +66,7 @@ module DataMapper
66
66
  query.dup
67
67
  end
68
68
 
69
+ scope_stack = self.scope_stack
69
70
  scope_stack << query.options
70
71
 
71
72
  begin
@@ -75,8 +76,6 @@ module DataMapper
75
76
  end
76
77
  end
77
78
 
78
- private
79
-
80
79
  # Initializes (if necessary) and returns current scope stack
81
80
  # @api private
82
81
  def scope_stack
@@ -1,7 +1,5 @@
1
1
  module DataMapper
2
2
 
3
- # :include:QUICKLINKS
4
- #
5
3
  # = Properties
6
4
  # Properties for a model are not derived from a database structure, but
7
5
  # instead explicitly declared inside your model class definitions. These
@@ -32,7 +30,7 @@ module DataMapper
32
30
  # class Post
33
31
  # include DataMapper::Resource
34
32
  #
35
- # property :title, String, :nullable => false # Cannot be null
33
+ # property :title, String, :required => true # Cannot be null
36
34
  # property :publish, Boolean, :default => false # Default value for new records is false
37
35
  # end
38
36
  #
@@ -195,13 +193,13 @@ module DataMapper
195
193
  # # => infers 'validates_length :title,
196
194
  # :minimum => 0, :maximum => 250'
197
195
  #
198
- # property :title, String, :nullable => false
196
+ # property :title, String, :required => true
199
197
  # # => infers 'validates_present :title
200
198
  #
201
199
  # property :email, String, :format => :email_address
202
200
  # # => infers 'validates_format :email, :with => :email_address
203
201
  #
204
- # property :title, String, :length => 255, :nullable => false
202
+ # property :title, String, :length => 255, :required => true
205
203
  # # => infers both 'validates_length' as well as
206
204
  # # 'validates_present'
207
205
  # # better: property :title, String, :length => 1..255
@@ -247,7 +245,7 @@ module DataMapper
247
245
  #
248
246
  # :default default value of this property
249
247
  #
250
- # :nullable if true, property may have a nil value on save
248
+ # :allow_nil if true, property may have a nil value on save
251
249
  #
252
250
  # :key name of the key associated with this property.
253
251
  #
@@ -295,8 +293,9 @@ module DataMapper
295
293
  extend Deprecate
296
294
  extend Equalizer
297
295
 
298
- deprecate :unique, :unique?
299
- deprecate :size, :length
296
+ deprecate :unique, :unique?
297
+ deprecate :size, :length
298
+ deprecate :nullable?, :allow_nil?
300
299
 
301
300
  equalize :model, :name
302
301
 
@@ -304,9 +303,10 @@ module DataMapper
304
303
  # them here
305
304
  OPTIONS = [
306
305
  :accessor, :reader, :writer,
307
- :lazy, :default, :nullable, :key, :serial, :field, :size, :length,
306
+ :lazy, :default, :key, :serial, :field, :size, :length,
308
307
  :format, :index, :unique_index, :auto_validation,
309
- :validates, :unique, :precision, :scale, :min, :max
308
+ :validates, :unique, :precision, :scale, :min, :max,
309
+ :allow_nil, :allow_blank, :required
310
310
  ]
311
311
 
312
312
  PRIMITIVES = [
@@ -320,6 +320,7 @@ module DataMapper
320
320
  Time,
321
321
  Object,
322
322
  Class,
323
+ DataMapper::Types::Text,
323
324
  ].to_set.freeze
324
325
 
325
326
  # Possible :visibility option values
@@ -334,7 +335,8 @@ module DataMapper
334
335
 
335
336
  attr_reader :primitive, :model, :name, :instance_variable_name,
336
337
  :type, :reader_visibility, :writer_visibility, :options,
337
- :default, :precision, :scale, :min, :max, :repository_name
338
+ :default, :precision, :scale, :min, :max, :repository_name,
339
+ :allow_nil, :allow_blank, :required
338
340
 
339
341
  # Supplies the field in the data-store which the property corresponds to
340
342
  #
@@ -342,17 +344,20 @@ module DataMapper
342
344
  #
343
345
  # @api semipublic
344
346
  def field(repository_name = nil)
347
+ self_repository_name = self.repository_name
348
+ klass = self.class
349
+
345
350
  if repository_name
346
- warn "Passing in +repository_name+ to #{self.class}#field is deprecated (#{caller[0]})"
351
+ warn "Passing in +repository_name+ to #{klass}#field is deprecated (#{caller[0]})"
347
352
 
348
- if repository_name != self.repository_name
349
- raise ArgumentError, "Mismatching +repository_name+ with #{self.class}#repository_name (#{repository_name.inspect} != #{self.repository_name.inspect})"
353
+ if repository_name != self_repository_name
354
+ raise ArgumentError, "Mismatching +repository_name+ with #{klass}#repository_name (#{repository_name.inspect} != #{self_repository_name.inspect})"
350
355
  end
351
356
  end
352
357
 
353
358
  # defer setting the field with the adapter specific naming
354
359
  # conventions until after the adapter has been setup
355
- @field ||= model.field_naming_convention(self.repository_name).call(self).freeze
360
+ @field ||= model.field_naming_convention(self_repository_name).call(self).freeze
356
361
  end
357
362
 
358
363
  # Returns true if property is unique. Serial properties and keys
@@ -383,7 +388,7 @@ module DataMapper
383
388
  # This usually only makes sense when property is of
384
389
  # type Range or custom type.
385
390
  #
386
- # @return [Integer, NilClass]
391
+ # @return [Integer, nil]
387
392
  # the maximum length of this property
388
393
  #
389
394
  # @api semipublic
@@ -452,14 +457,34 @@ module DataMapper
452
457
  @serial
453
458
  end
454
459
 
460
+ # Returns whether or not the property must be non-nil and non-blank
461
+ #
462
+ # @return [Boolean]
463
+ # whether or not the property is required
464
+ #
465
+ # @api public
466
+ def required?
467
+ @required
468
+ end
469
+
455
470
  # Returns whether or not the property can accept 'nil' as it's value
456
471
  #
457
472
  # @return [Boolean]
458
473
  # whether or not the property can accept 'nil'
459
474
  #
460
475
  # @api public
461
- def nullable?
462
- @nullable
476
+ def allow_nil?
477
+ @allow_nil
478
+ end
479
+
480
+ # Returns whether or not the property can be a blank value
481
+ #
482
+ # @return [Boolean]
483
+ # whether or not the property can be blank
484
+ #
485
+ # @api public
486
+ def allow_blank?
487
+ @allow_blank
463
488
  end
464
489
 
465
490
  # Returns whether or not the property is custom (not provided by dm-core)
@@ -515,19 +540,25 @@ module DataMapper
515
540
  #
516
541
  # @param [Resource] resource
517
542
  # model instance for which to set the original value
518
- # @param [Object] original
519
- # value to set as original value for this property in +resource+
543
+ # @param [Object] new_value
544
+ # the new value that will be set for the property
520
545
  #
521
546
  # @api private
522
- def set_original_value(resource, original)
547
+ def set_original_value(resource, new_value)
523
548
  original_attributes = resource.original_attributes
524
- original = self.value(original)
525
-
526
- if original_attributes.key?(self)
527
- # stop tracking the value if it has not changed
528
- original_attributes.delete(self) if original == original_attributes[self] && resource.saved?
529
- else
530
- original_attributes[self] = original
549
+ old_value = get!(resource)
550
+
551
+ if resource.new?
552
+ # always track changes to a new resource
553
+ original_attributes[self] = nil
554
+ elsif original_attributes.key?(self)
555
+ # stop tracking if the new value is the same as the original
556
+ if new_value == original_attributes[self]
557
+ original_attributes.delete(self)
558
+ end
559
+ elsif new_value != old_value
560
+ # track the changed value
561
+ original_attributes[self] = old_value
531
562
  end
532
563
  end
533
564
 
@@ -545,17 +576,9 @@ module DataMapper
545
576
  #
546
577
  # @api private
547
578
  def set(resource, value)
548
- loaded = loaded?(resource)
549
- original = get!(resource) if loaded
550
- value = typecast(value)
551
-
552
- if loaded && value == original
553
- return original
554
- end
555
-
556
- set_original_value(resource, original)
557
-
558
- set!(resource, value)
579
+ new_value = typecast(value)
580
+ set_original_value(resource, new_value)
581
+ set!(resource, new_value)
559
582
  end
560
583
 
561
584
  # Set the ivar value in the resource
@@ -593,16 +616,18 @@ module DataMapper
593
616
  #
594
617
  # @api private
595
618
  def lazy_load(resource)
596
- resource.send(:lazy_load, lazy_load_properties)
619
+ resource.__send__(:lazy_load, lazy_load_properties)
597
620
  end
598
621
 
599
- # TODO: document
600
622
  # @api private
601
623
  def lazy_load_properties
602
- @lazy_load_properties ||= properties.in_context(lazy? ? [ self ] : properties.defaults)
624
+ @lazy_load_properties ||=
625
+ begin
626
+ properties = self.properties
627
+ properties.in_context(lazy? ? [ self ] : properties.defaults)
628
+ end
603
629
  end
604
630
 
605
- # TODO: document
606
631
  # @api private
607
632
  def properties
608
633
  @properties ||= model.properties(repository_name)
@@ -636,6 +661,9 @@ module DataMapper
636
661
  #
637
662
  # @api semipublic
638
663
  def typecast(value)
664
+ type = self.type
665
+ primitive = self.primitive
666
+
639
667
  return type.typecast(value, self) if type.respond_to?(:typecast)
640
668
  return value if primitive?(value) || value.nil?
641
669
 
@@ -688,33 +716,33 @@ module DataMapper
688
716
  # Returns given value unchanged for core types and
689
717
  # uses +dump+ method of the property type for custom types.
690
718
  #
691
- # @param [Object] value
719
+ # @param [Object] loaded_value
692
720
  # the value to be converted into a storeable (ie., primitive) value
693
721
  #
694
722
  # @return [Object]
695
723
  # the primitive value to be stored in the repository for +val+
696
724
  #
697
725
  # @api semipublic
698
- def value(value)
726
+ def value(loaded_value)
699
727
  if custom?
700
- type.dump(value, self)
728
+ type.dump(loaded_value, self)
701
729
  else
702
- value
730
+ loaded_value
703
731
  end
704
732
  end
705
733
 
706
734
  # Test the value to see if it is a valid value for this Property
707
735
  #
708
- # @param [Object] value
736
+ # @param [Object] loaded_value
709
737
  # the value to be tested
710
738
  #
711
739
  # @return [Boolean]
712
740
  # true if the value is valid
713
741
  #
714
742
  # @api semipulic
715
- def valid?(value)
716
- value = self.value(value)
717
- primitive?(value) || (value.nil? && nullable?)
743
+ def valid?(loaded_value, negated = false)
744
+ dumped_value = self.value(loaded_value)
745
+ primitive?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
718
746
  end
719
747
 
720
748
  # Returns a concise string representation of the property instance.
@@ -737,8 +765,11 @@ module DataMapper
737
765
  #
738
766
  # @api semipublic
739
767
  def primitive?(value)
768
+ primitive = self.primitive
740
769
  if primitive == TrueClass
741
770
  value == true || value == false
771
+ elsif primitive == Types::Text
772
+ value.kind_of?(String)
742
773
  else
743
774
  value.kind_of?(primitive)
744
775
  end
@@ -746,7 +777,6 @@ module DataMapper
746
777
 
747
778
  private
748
779
 
749
- # TODO: document
750
780
  # @api semipublic
751
781
  def initialize(model, name, type, options = {})
752
782
  assert_kind_of 'model', model, Model
@@ -754,30 +784,37 @@ module DataMapper
754
784
  assert_kind_of 'type', type, Class, Module
755
785
  assert_kind_of 'options', options, Hash
756
786
 
757
- options = options.dup
787
+ options = options.dup
788
+ caller_method = caller[2]
758
789
 
759
790
  if TrueClass == type
760
- warn "#{type} is deprecated, use Boolean instead at #{caller[2]}"
791
+ warn "#{type} is deprecated, use Boolean instead at #{caller_method}"
761
792
  type = Types::Boolean
762
793
  elsif Integer == type && options.delete(:serial)
763
- warn "#{type} with explicit :serial option is deprecated, use Serial instead (#{caller[2]})"
794
+ warn "#{type} with explicit :serial option is deprecated, use Serial instead (#{caller_method})"
764
795
  type = Types::Serial
765
796
  elsif options.key?(:size)
766
797
  if String == type
767
- warn ":size option is deprecated, use #{type} with :length instead (#{caller[2]})"
798
+ warn ":size option is deprecated, use #{type} with :length instead (#{caller_method})"
768
799
  length = options.delete(:size)
769
800
  options[:length] = length unless options.key?(:length)
770
801
  elsif Numeric > type
771
- warn ":size option is deprecated, specify :min and :max instead (#{caller[2]})"
802
+ warn ":size option is deprecated, specify :min and :max instead (#{caller_method})"
772
803
  end
804
+ elsif options.key?(:nullable)
805
+ nullable_options = options.only(:nullable)
806
+ required_options = { :required => !options.delete(:nullable) }
807
+ warn "#{nullable_options.inspect} is deprecated, use #{required_options.inspect} instead (#{caller_method})"
808
+ options.update(required_options)
773
809
  end
774
810
 
775
811
  assert_valid_options(options)
776
812
 
777
813
  # if the type can be found within Types then
778
814
  # use that class rather than the primitive
779
- unless type.name.blank?
780
- type = Types.find_const(type.name)
815
+ type_name = type.name
816
+ unless type_name.blank?
817
+ type = Types.find_const(type_name)
781
818
  end
782
819
 
783
820
  unless PRIMITIVES.include?(type) || (Type > type && PRIMITIVES.include?(type.primitive))
@@ -789,7 +826,7 @@ module DataMapper
789
826
  @name = name.to_s.sub(/\?$/, '').to_sym
790
827
  @type = type
791
828
  @custom = Type > @type
792
- @options = (@custom ? @type.options.merge(options) : options.dup).freeze
829
+ @options = (@custom ? @type.options.merge(options) : options).freeze
793
830
  @instance_variable_name = "@#{@name}".freeze
794
831
 
795
832
  @primitive = @type.respond_to?(:primitive) ? @type.primitive : @type
@@ -798,30 +835,39 @@ module DataMapper
798
835
 
799
836
  @serial = @options.fetch(:serial, false)
800
837
  @key = @options.fetch(:key, @serial || false)
801
- @nullable = @options.fetch(:nullable, @key == false)
838
+ @required = @options.fetch(:required, @key)
839
+ @allow_nil = @options.fetch(:allow_nil, !@required)
840
+ @allow_blank = @options.fetch(:allow_blank, !@required)
802
841
  @index = @options.fetch(:index, nil)
803
842
  @unique_index = @options.fetch(:unique_index, nil)
804
843
  @unique = @options.fetch(:unique, @serial || @key || false)
805
844
  @lazy = @options.fetch(:lazy, @type.respond_to?(:lazy) ? @type.lazy : false) && !@key
806
845
 
846
+ float_primitive = Float == @primitive
847
+
807
848
  # assign attributes per-type
808
- if String == @primitive || Class == @primitive
849
+ if [ String, Class ].include?(@primitive)
809
850
  @length = @options.fetch(:length, DEFAULT_LENGTH)
810
- elsif BigDecimal == @primitive || Float == @primitive
851
+ elsif DataMapper::Types::Text == @primitive
852
+ @length = @options.fetch(:length)
853
+ elsif [ BigDecimal, Float ].include?(@primitive)
811
854
  @precision = @options.fetch(:precision, DEFAULT_PRECISION)
812
- @scale = @options.fetch(:scale, Float == @primitive ? DEFAULT_SCALE_FLOAT : DEFAULT_SCALE_BIGDECIMAL)
855
+ @scale = @options.fetch(:scale, float_primitive ? DEFAULT_SCALE_FLOAT : DEFAULT_SCALE_BIGDECIMAL)
856
+
857
+ precision_inspect = @precision.inspect
858
+ scale_inspect = @scale.inspect
813
859
 
814
860
  unless @precision > 0
815
- raise ArgumentError, "precision must be greater than 0, but was #{@precision.inspect}"
861
+ raise ArgumentError, "precision must be greater than 0, but was #{precision_inspect}"
816
862
  end
817
863
 
818
- unless Float == @primitive && @scale.nil?
864
+ unless float_primitive && @scale.nil?
819
865
  unless @scale >= 0
820
- raise ArgumentError, "scale must be equal to or greater than 0, but was #{@scale.inspect}"
866
+ raise ArgumentError, "scale must be equal to or greater than 0, but was #{scale_inspect}"
821
867
  end
822
868
 
823
869
  unless @precision >= @scale
824
- raise ArgumentError, "precision must be equal to or greater than scale, but was #{@precision.inspect} and scale was #{@scale.inspect}"
870
+ raise ArgumentError, "precision must be equal to or greater than scale, but was #{precision_inspect} and scale was #{scale_inspect}"
825
871
  end
826
872
  end
827
873
  end
@@ -847,44 +893,51 @@ module DataMapper
847
893
  @model.auto_generate_validations(self) if @model.respond_to?(:auto_generate_validations)
848
894
  end
849
895
 
850
- # TODO: document
851
896
  # @api private
852
897
  def assert_valid_options(options)
853
- if (unknown_keys = options.keys - OPTIONS).any?
898
+ keys = options.keys
899
+
900
+ if (unknown_keys = keys - OPTIONS).any?
854
901
  raise ArgumentError, "options #{unknown_keys.map { |key| key.inspect }.join(' and ')} are unknown"
855
902
  end
856
903
 
857
904
  options.each do |key, value|
905
+ boolean_value = value == true || value == false
906
+
858
907
  case key
859
908
  when :field
860
- assert_kind_of "options[#{key.inspect}]", value, String
909
+ assert_kind_of "options[:#{key}]", value, String
861
910
 
862
911
  when :default
863
912
  if value.nil?
864
- raise ArgumentError, "options[#{key.inspect}] must not be nil"
913
+ raise ArgumentError, "options[:#{key}] must not be nil"
914
+ end
915
+
916
+ when :serial, :key, :allow_nil, :allow_blank, :required, :auto_validation
917
+ unless boolean_value
918
+ raise ArgumentError, "options[:#{key}] must be either true or false"
865
919
  end
866
920
 
867
- when :serial, :key, :nullable, :auto_validation
868
- unless value == true || value == false
869
- raise ArgumentError, "options[#{key.inspect}] must be either true or false"
921
+ if key == :required && (keys & [ :allow_nil, :allow_blank ]).size > 0
922
+ raise ArgumentError, 'options[:required] cannot be mixed with :allow_nil or :allow_blank'
870
923
  end
871
924
 
872
925
  when :index, :unique_index, :unique, :lazy
873
- unless value == true || value == false || value.kind_of?(Symbol) || (value.kind_of?(Array) && value.any? && value.all? { |val| val.kind_of?(Symbol) })
874
- raise ArgumentError, "options[#{key.inspect}] must be either true, false, a Symbol or an Array of Symbols"
926
+ unless boolean_value || value.kind_of?(Symbol) || (value.kind_of?(Array) && value.any? && value.all? { |val| val.kind_of?(Symbol) })
927
+ raise ArgumentError, "options[:#{key}] must be either true, false, a Symbol or an Array of Symbols"
875
928
  end
876
929
 
877
930
  when :length
878
- assert_kind_of "options[#{key.inspect}]", value, Range, Integer
931
+ assert_kind_of "options[:#{key}]", value, Range, Integer
879
932
 
880
933
  when :size, :precision, :scale
881
- assert_kind_of "options[#{key.inspect}]", value, Integer
934
+ assert_kind_of "options[:#{key}]", value, Integer
882
935
 
883
936
  when :reader, :writer, :accessor
884
- assert_kind_of "options[#{key.inspect}]", value, Symbol
937
+ assert_kind_of "options[:#{key}]", value, Symbol
885
938
 
886
939
  unless VISIBILITY_OPTIONS.include?(value)
887
- raise ArgumentError, "options[#{key.inspect}] must be #{VISIBILITY_OPTIONS.join(' or ')}"
940
+ raise ArgumentError, "options[:#{key}] must be #{VISIBILITY_OPTIONS.join(' or ')}"
888
941
  end
889
942
  end
890
943
  end
@@ -894,16 +947,18 @@ module DataMapper
894
947
  #
895
948
  # Will raise ArgumentError if this Property's reader and writer
896
949
  # visibilities are not included in VISIBILITY_OPTIONS.
897
- # @return [NilClass]
950
+ #
951
+ # @return [undefined]
898
952
  #
899
953
  # @raise [ArgumentError] "property visibility must be :public, :protected, or :private"
900
954
  #
901
955
  # @api private
902
956
  def determine_visibility
903
- @reader_visibility = @options[:reader] || @options[:accessor] || :public
904
- @writer_visibility = @options[:writer] || @options[:accessor] || :public
905
- end
957
+ default_accessor = @options[:accessor] || :public
906
958
 
959
+ @reader_visibility = @options[:reader] || default_accessor
960
+ @writer_visibility = @options[:writer] || default_accessor
961
+ end
907
962
 
908
963
  # Typecast a value to an Integer
909
964
  #
@@ -945,8 +1000,9 @@ module DataMapper
945
1000
  return true if value == 1
946
1001
  return false if value == 0
947
1002
  elsif value.respond_to?(:to_str)
948
- return true if %w[ true 1 t ].include?(value.to_str.downcase)
949
- return false if %w[ false 0 f ].include?(value.to_str.downcase)
1003
+ string_value = value.to_str.downcase
1004
+ return true if %w[ true 1 t ].include?(string_value)
1005
+ return false if %w[ false 0 f ].include?(string_value)
950
1006
  end
951
1007
 
952
1008
  value
@@ -1019,7 +1075,9 @@ module DataMapper
1019
1075
  #
1020
1076
  # @api private
1021
1077
  def typecast_to_datetime(value)
1022
- if value.respond_to?(:to_mash)
1078
+ if value.respond_to?(:to_datetime)
1079
+ value.to_datetime
1080
+ elsif value.respond_to?(:to_mash)
1023
1081
  typecast_hash_to_datetime(value)
1024
1082
  else
1025
1083
  DateTime.parse(value.to_s)
@@ -1039,7 +1097,9 @@ module DataMapper
1039
1097
  #
1040
1098
  # @api private
1041
1099
  def typecast_to_date(value)
1042
- if value.respond_to?(:to_mash)
1100
+ if value.respond_to?(:to_date)
1101
+ value.to_date
1102
+ elsif value.respond_to?(:to_mash)
1043
1103
  typecast_hash_to_date(value)
1044
1104
  else
1045
1105
  Date.parse(value.to_s)
@@ -1059,7 +1119,9 @@ module DataMapper
1059
1119
  #
1060
1120
  # @api private
1061
1121
  def typecast_to_time(value)
1062
- if value.respond_to?(:to_mash)
1122
+ if value.respond_to?(:to_time)
1123
+ value.to_time
1124
+ elsif value.respond_to?(:to_mash)
1063
1125
  typecast_hash_to_time(value)
1064
1126
  else
1065
1127
  Time.parse(value.to_s)