dm-core 0.10.2 → 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. data/.gitignore +10 -1
  2. data/Gemfile +143 -0
  3. data/Rakefile +9 -5
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +160 -57
  6. data/lib/dm-core.rb +131 -56
  7. data/lib/dm-core/adapters.rb +98 -14
  8. data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
  9. data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
  10. data/lib/dm-core/associations/many_to_many.rb +19 -30
  11. data/lib/dm-core/associations/many_to_one.rb +58 -42
  12. data/lib/dm-core/associations/one_to_many.rb +33 -23
  13. data/lib/dm-core/associations/one_to_one.rb +27 -11
  14. data/lib/dm-core/associations/relationship.rb +4 -4
  15. data/lib/dm-core/collection.rb +23 -16
  16. data/lib/dm-core/core_ext/array.rb +36 -0
  17. data/lib/dm-core/core_ext/hash.rb +30 -0
  18. data/lib/dm-core/core_ext/module.rb +46 -0
  19. data/lib/dm-core/core_ext/object.rb +31 -0
  20. data/lib/dm-core/core_ext/pathname.rb +20 -0
  21. data/lib/dm-core/core_ext/string.rb +22 -0
  22. data/lib/dm-core/core_ext/try_dup.rb +44 -0
  23. data/lib/dm-core/model.rb +88 -27
  24. data/lib/dm-core/model/hook.rb +75 -18
  25. data/lib/dm-core/model/property.rb +50 -9
  26. data/lib/dm-core/model/relationship.rb +31 -31
  27. data/lib/dm-core/model/scope.rb +3 -3
  28. data/lib/dm-core/property.rb +196 -516
  29. data/lib/dm-core/property/binary.rb +7 -0
  30. data/lib/dm-core/property/boolean.rb +35 -0
  31. data/lib/dm-core/property/class.rb +24 -0
  32. data/lib/dm-core/property/date.rb +47 -0
  33. data/lib/dm-core/property/date_time.rb +48 -0
  34. data/lib/dm-core/property/decimal.rb +43 -0
  35. data/lib/dm-core/property/discriminator.rb +48 -0
  36. data/lib/dm-core/property/float.rb +24 -0
  37. data/lib/dm-core/property/integer.rb +32 -0
  38. data/lib/dm-core/property/numeric.rb +43 -0
  39. data/lib/dm-core/property/object.rb +32 -0
  40. data/lib/dm-core/property/serial.rb +8 -0
  41. data/lib/dm-core/property/string.rb +49 -0
  42. data/lib/dm-core/property/text.rb +12 -0
  43. data/lib/dm-core/property/time.rb +48 -0
  44. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  45. data/lib/dm-core/property/typecast/time.rb +28 -0
  46. data/lib/dm-core/property_set.rb +10 -4
  47. data/lib/dm-core/query.rb +14 -37
  48. data/lib/dm-core/query/conditions/comparison.rb +8 -6
  49. data/lib/dm-core/query/conditions/operation.rb +33 -2
  50. data/lib/dm-core/query/operator.rb +2 -5
  51. data/lib/dm-core/query/path.rb +4 -6
  52. data/lib/dm-core/repository.rb +21 -6
  53. data/lib/dm-core/resource.rb +316 -133
  54. data/lib/dm-core/resource/state.rb +79 -0
  55. data/lib/dm-core/resource/state/clean.rb +40 -0
  56. data/lib/dm-core/resource/state/deleted.rb +30 -0
  57. data/lib/dm-core/resource/state/dirty.rb +86 -0
  58. data/lib/dm-core/resource/state/immutable.rb +34 -0
  59. data/lib/dm-core/resource/state/persisted.rb +29 -0
  60. data/lib/dm-core/resource/state/transient.rb +70 -0
  61. data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
  62. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  63. data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
  64. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  65. data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
  66. data/lib/dm-core/spec/setup.rb +165 -0
  67. data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
  68. data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
  69. data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
  70. data/lib/dm-core/support/assertions.rb +8 -0
  71. data/lib/dm-core/support/equalizer.rb +1 -0
  72. data/lib/dm-core/support/hook.rb +420 -0
  73. data/lib/dm-core/support/lazy_array.rb +453 -0
  74. data/lib/dm-core/support/local_object_space.rb +12 -0
  75. data/lib/dm-core/support/logger.rb +193 -6
  76. data/lib/dm-core/support/naming_conventions.rb +8 -8
  77. data/lib/dm-core/support/subject.rb +33 -0
  78. data/lib/dm-core/type.rb +4 -0
  79. data/lib/dm-core/types/boolean.rb +2 -0
  80. data/lib/dm-core/types/decimal.rb +9 -0
  81. data/lib/dm-core/types/discriminator.rb +2 -0
  82. data/lib/dm-core/types/object.rb +3 -0
  83. data/lib/dm-core/types/serial.rb +2 -0
  84. data/lib/dm-core/types/text.rb +2 -0
  85. data/lib/dm-core/version.rb +1 -1
  86. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
  87. data/spec/public/model/hook_spec.rb +209 -0
  88. data/spec/public/model/property_spec.rb +35 -0
  89. data/spec/public/model/relationship_spec.rb +33 -20
  90. data/spec/public/model_spec.rb +142 -10
  91. data/spec/public/property/binary_spec.rb +14 -0
  92. data/spec/public/property/boolean_spec.rb +14 -0
  93. data/spec/public/property/class_spec.rb +20 -0
  94. data/spec/public/property/date_spec.rb +14 -0
  95. data/spec/public/property/date_time_spec.rb +14 -0
  96. data/spec/public/property/decimal_spec.rb +14 -0
  97. data/spec/public/{types → property}/discriminator_spec.rb +2 -12
  98. data/spec/public/property/float_spec.rb +14 -0
  99. data/spec/public/property/integer_spec.rb +14 -0
  100. data/spec/public/property/object_spec.rb +9 -17
  101. data/spec/public/property/serial_spec.rb +14 -0
  102. data/spec/public/property/string_spec.rb +14 -0
  103. data/spec/public/property/text_spec.rb +52 -0
  104. data/spec/public/property/time_spec.rb +14 -0
  105. data/spec/public/property_spec.rb +28 -87
  106. data/spec/public/resource_spec.rb +101 -0
  107. data/spec/public/sel_spec.rb +5 -15
  108. data/spec/public/shared/collection_shared_spec.rb +16 -30
  109. data/spec/public/shared/finder_shared_spec.rb +2 -4
  110. data/spec/public/shared/property_shared_spec.rb +176 -0
  111. data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
  112. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
  113. data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
  114. data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
  115. data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
  116. data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
  117. data/spec/semipublic/associations/relationship_spec.rb +3 -3
  118. data/spec/semipublic/associations_spec.rb +1 -1
  119. data/spec/semipublic/property/binary_spec.rb +13 -0
  120. data/spec/semipublic/property/boolean_spec.rb +65 -0
  121. data/spec/semipublic/property/class_spec.rb +33 -0
  122. data/spec/semipublic/property/date_spec.rb +43 -0
  123. data/spec/semipublic/property/date_time_spec.rb +46 -0
  124. data/spec/semipublic/property/decimal_spec.rb +82 -0
  125. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  126. data/spec/semipublic/property/float_spec.rb +82 -0
  127. data/spec/semipublic/property/integer_spec.rb +82 -0
  128. data/spec/semipublic/property/serial_spec.rb +13 -0
  129. data/spec/semipublic/property/string_spec.rb +13 -0
  130. data/spec/semipublic/property/text_spec.rb +31 -0
  131. data/spec/semipublic/property/time_spec.rb +50 -0
  132. data/spec/semipublic/property_spec.rb +2 -532
  133. data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
  134. data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
  135. data/spec/semipublic/query/path_spec.rb +17 -17
  136. data/spec/semipublic/query_spec.rb +47 -78
  137. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  138. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  139. data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
  140. data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
  141. data/spec/semipublic/resource/state/transient_spec.rb +128 -0
  142. data/spec/semipublic/resource/state_spec.rb +226 -0
  143. data/spec/semipublic/shared/property_shared_spec.rb +143 -0
  144. data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
  145. data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
  146. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  147. data/spec/spec_helper.rb +21 -97
  148. data/spec/support/types/huge_integer.rb +17 -0
  149. data/spec/unit/array_spec.rb +48 -0
  150. data/spec/unit/hash_spec.rb +35 -0
  151. data/spec/unit/hook_spec.rb +1234 -0
  152. data/spec/unit/lazy_array_spec.rb +1959 -0
  153. data/spec/unit/module_spec.rb +70 -0
  154. data/spec/unit/object_spec.rb +37 -0
  155. data/spec/unit/try_dup_spec.rb +45 -0
  156. data/tasks/local_gemfile.rake +18 -0
  157. data/tasks/spec.rake +0 -3
  158. metadata +197 -71
  159. data/deps.rip +0 -2
  160. data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
  161. data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
  162. data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
  163. data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
  164. data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
  165. data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
  166. data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
  167. data/lib/dm-core/core_ext/enumerable.rb +0 -28
  168. data/lib/dm-core/migrations.rb +0 -1427
  169. data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
  170. data/lib/dm-core/transaction.rb +0 -508
  171. data/lib/dm-core/types/paranoid_boolean.rb +0 -42
  172. data/lib/dm-core/types/paranoid_datetime.rb +0 -41
  173. data/spec/lib/adapter_helpers.rb +0 -105
  174. data/spec/lib/collection_helpers.rb +0 -18
  175. data/spec/lib/pending_helpers.rb +0 -46
  176. data/spec/public/migrations_spec.rb +0 -503
  177. data/spec/public/transaction_spec.rb +0 -153
  178. data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
  179. data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
  180. data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
  181. data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
  182. data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
  183. data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
@@ -3,38 +3,95 @@ module DataMapper
3
3
  module Hook
4
4
  Model.append_inclusions self
5
5
 
6
+ extend Chainable
7
+
6
8
  def self.included(model)
7
- model.send(:include, Extlib::Hook)
9
+ model.send(:include, DataMapper::Hook)
8
10
  model.extend Methods
9
- model.register_instance_hooks :create_hook, :update_hook, :destroy
10
11
  end
11
12
 
12
13
  module Methods
13
- # @api public
14
- def before(target_method, *args, &block)
15
- remap_target_method(target_method).each do |target_method|
16
- super(target_method, *args, &block)
17
- end
14
+ def inherited(model)
15
+ copy_hooks(model)
16
+ super
18
17
  end
19
18
 
20
19
  # @api public
21
- def after(target_method, *args, &block)
22
- remap_target_method(target_method).each do |target_method|
23
- super(target_method, *args, &block)
24
- end
20
+ def before(target_method, method_sym = nil, &block)
21
+ setup_hook(:before, target_method, method_sym, block) { super }
25
22
  end
26
23
 
27
- private
24
+ # @api public
25
+ def after(target_method, method_sym = nil, &block)
26
+ setup_hook(:after, target_method, method_sym, block) { super }
27
+ end
28
28
 
29
29
  # @api private
30
- def remap_target_method(target_method)
31
- case target_method
32
- when :create then [ :create_hook ]
33
- when :update then [ :update_hook ]
34
- when :save then [ :create_hook, :update_hook ]
35
- else [ target_method ]
30
+ def hooks
31
+ @hooks ||= {
32
+ :save => { :before => [], :after => [] },
33
+ :create => { :before => [], :after => [] },
34
+ :update => { :before => [], :after => [] },
35
+ :destroy => { :before => [], :after => [] },
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def setup_hook(type, name, method, proc)
42
+ if types = hooks[name]
43
+ types[type] << if proc
44
+ ProcCommand.new(proc)
45
+ else
46
+ MethodCommand.new(self, method)
47
+ end
48
+ else
49
+ yield
36
50
  end
37
51
  end
52
+
53
+ # deep copy hooks from the parent model
54
+ def copy_hooks(model)
55
+ hooks = Hash.new do |hooks, name|
56
+ hooks[name] = Hash.new do |types, type|
57
+ types[type] = self.hooks[name][type].map do |command|
58
+ command.copy(model)
59
+ end
60
+ end
61
+ end
62
+
63
+ model.instance_variable_set(:@hooks, hooks)
64
+ end
65
+
66
+ end
67
+
68
+ class ProcCommand
69
+ def initialize(proc)
70
+ @proc = proc.to_proc
71
+ end
72
+
73
+ def call(resource)
74
+ resource.instance_eval(&@proc)
75
+ end
76
+
77
+ def copy(model)
78
+ self
79
+ end
80
+ end
81
+
82
+ class MethodCommand
83
+ def initialize(model, method)
84
+ @model, @method = model, method.to_sym
85
+ end
86
+
87
+ def call(resource)
88
+ resource.__send__(@method)
89
+ end
90
+
91
+ def copy(model)
92
+ self.class.new(model, @method)
93
+ end
94
+
38
95
  end
39
96
 
40
97
  end # module Hook
@@ -48,7 +48,40 @@ module DataMapper
48
48
  #
49
49
  # @api public
50
50
  def property(name, type, options = {})
51
- property = DataMapper::Property.new(self, name, type, options)
51
+ caller_method = caller[2]
52
+
53
+ if TrueClass == type
54
+ warn "#{type} is deprecated, use Boolean instead at #{caller_method}"
55
+ type = DataMapper::Property::Boolean
56
+ elsif BigDecimal == type
57
+ warn "#{type} is deprecated, use Decimal instead at #{caller_method}"
58
+ type = DataMapper::Property::Decimal
59
+ end
60
+
61
+ # if the type can be found within Property then
62
+ # use that class rather than the primitive
63
+ type_name = type.name
64
+ unless type_name.blank?
65
+ type_name = ActiveSupport::Inflector.demodulize(type_name)
66
+
67
+ if DataMapper::Property.const_defined?(type_name)
68
+ type = DataMapper::Property.find_const(type_name)
69
+ elsif DataMapper::Types.const_defined?(type_name)
70
+ type = DataMapper::Types.find_const(type_name)
71
+ end
72
+ end
73
+
74
+ unless type < DataMapper::Property || type < DataMapper::Type
75
+ raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
76
+ end
77
+
78
+ klass = if type < DataMapper::Property
79
+ type
80
+ else
81
+ DataMapper::Property.find_const(ActiveSupport::Inflector.demodulize(type.primitive.name))
82
+ end
83
+
84
+ property = klass.new(self, name, options, type)
52
85
 
53
86
  repository_name = self.repository_name
54
87
  properties = properties(repository_name)
@@ -63,7 +96,7 @@ module DataMapper
63
96
 
64
97
  # make sure the property is created within the correct repository scope
65
98
  DataMapper.repository(repository_name) do
66
- properties << DataMapper::Property.new(self, name, type, options)
99
+ properties << klass.new(self, name, options, type)
67
100
  end
68
101
  end
69
102
  end
@@ -109,6 +142,7 @@ module DataMapper
109
142
  # TODO: create PropertySet#copy that will copy the properties, but assign the
110
143
  # new Relationship objects to a supplied repository and model. dup does not really
111
144
  # do what is needed
145
+ repository_name = repository_name.to_sym
112
146
 
113
147
  default_repository_name = self.default_repository_name
114
148
 
@@ -192,10 +226,13 @@ module DataMapper
192
226
 
193
227
  unless resource_method_defined?(name)
194
228
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
195
- #{reader_visibility}
196
- def #{name}
197
- return #{instance_variable_name} if defined?(#{instance_variable_name})
198
- #{instance_variable_name} = properties[#{name.inspect}].get(self)
229
+ chainable do
230
+ #{reader_visibility}
231
+ def #{name}
232
+ return #{instance_variable_name} if defined?(#{instance_variable_name})
233
+ property = properties[#{name.inspect}]
234
+ #{instance_variable_name} = property ? persisted_state.get(property) : nil
235
+ end
199
236
  end
200
237
  RUBY
201
238
  end
@@ -222,9 +259,13 @@ module DataMapper
222
259
  return if resource_method_defined?(writer_name)
223
260
 
224
261
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
225
- #{writer_visibility}
226
- def #{writer_name}(value)
227
- properties[#{name.inspect}].set(self, value)
262
+ chainable do
263
+ #{writer_visibility}
264
+ def #{writer_name}(value)
265
+ property = properties[#{name.inspect}]
266
+ self.persisted_state = persisted_state.set(property, value)
267
+ persisted_state.get(property)
268
+ end
228
269
  end
229
270
  RUBY
230
271
  end
@@ -6,7 +6,7 @@ module DataMapper
6
6
  module Relationship
7
7
  Model.append_extensions self
8
8
 
9
- include Extlib::Assertions
9
+ include DataMapper::Assertions
10
10
  extend Chainable
11
11
 
12
12
  # Initializes relationships hash for extended model
@@ -101,9 +101,7 @@ module DataMapper
101
101
  #
102
102
  # @api public
103
103
  def has(cardinality, name, *args)
104
- assert_kind_of 'cardinality', cardinality, Integer, Range, Infinity.class
105
- assert_kind_of 'name', name, Symbol
106
-
104
+ name = name.to_sym
107
105
  model = extract_model(args)
108
106
  options = extract_options(args)
109
107
 
@@ -165,8 +163,7 @@ module DataMapper
165
163
  #
166
164
  # @api public
167
165
  def belongs_to(name, *args)
168
- assert_kind_of 'name', name, Symbol
169
-
166
+ name = name.to_sym
170
167
  model_name = self.name
171
168
  model = extract_model(args)
172
169
  options = extract_options(args)
@@ -236,12 +233,7 @@ module DataMapper
236
233
  # @api private
237
234
  def extract_options(args)
238
235
  options = args.last
239
-
240
- if options.kind_of?(Hash)
241
- options.dup
242
- else
243
- {}
244
- end
236
+ options.respond_to?(:to_hash) ? options.to_hash.dup : {}
245
237
  end
246
238
 
247
239
  # A support method for converting Integer, Range or Infinity values into two
@@ -255,6 +247,8 @@ module DataMapper
255
247
  when Integer then [ cardinality, cardinality ]
256
248
  when Range then [ cardinality.first, cardinality.last ]
257
249
  when Infinity then [ 0, Infinity ]
250
+ else
251
+ assert_kind_of 'options', options, Integer, Range, Infinity.class
258
252
  end
259
253
  end
260
254
 
@@ -273,8 +267,8 @@ module DataMapper
273
267
  min = options[:min]
274
268
  max = options[:max]
275
269
 
276
- assert_kind_of 'options[:min]', min, Integer
277
- assert_kind_of 'options[:max]', max, Integer, Infinity.class
270
+ min = min.to_int unless min == Infinity
271
+ max = max.to_int unless max == Infinity
278
272
 
279
273
  if min == Infinity && max == Infinity
280
274
  raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
@@ -288,23 +282,17 @@ module DataMapper
288
282
  end
289
283
 
290
284
  if options.key?(:repository)
291
- repository = options[:repository]
292
-
293
- assert_kind_of 'options[:repository]', repository, Repository, Symbol
294
-
295
- if repository.kind_of?(Repository)
296
- options[:repository] = repository.name
297
- end
285
+ options[:repository] = options[:repository].to_sym
298
286
  end
299
287
 
300
288
  if options.key?(:class_name)
301
- assert_kind_of 'options[:class_name]', options[:class_name], String
289
+ options[:class_name] = options[:class_name].to_str
302
290
  warn "+options[:class_name]+ is deprecated, use :model instead (#{caller_method})"
303
291
  options[:model] = options.delete(:class_name)
304
292
  end
305
293
 
306
294
  if options.key?(:remote_name)
307
- assert_kind_of 'options[:remote_name]', options[:remote_name], Symbol
295
+ options[:remote_name] = options[:remote_name].to_sym
308
296
  warn "+options[:remote_name]+ is deprecated, use :via instead (#{caller_method})"
309
297
  options[:via] = options.delete(:remote_name)
310
298
  end
@@ -345,10 +333,18 @@ module DataMapper
345
333
  reader_visibility = relationship.reader_visibility
346
334
 
347
335
  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
336
+ chainable do
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)
346
+ end
347
+ end
352
348
  RUBY
353
349
  end
354
350
 
@@ -364,10 +360,14 @@ module DataMapper
364
360
  writer_visibility = relationship.writer_visibility
365
361
 
366
362
  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
363
+ chainable do
364
+ #{writer_visibility}
365
+ def #{writer_name}(target)
366
+ relationship = relationships[#{name.inspect}]
367
+ self.persisted_state = persisted_state.set(relationship, target)
368
+ persisted_state.get(relationship)
369
+ end
370
+ end
371
371
  RUBY
372
372
  end
373
373
 
@@ -27,7 +27,7 @@ module DataMapper
27
27
  #
28
28
  # @api private
29
29
  def query
30
- Query.new(repository, self, current_scope).freeze
30
+ repository.new_query(self, current_scope).freeze
31
31
  end
32
32
 
33
33
  # @api private
@@ -61,7 +61,7 @@ module DataMapper
61
61
  # @api private
62
62
  def with_exclusive_scope(query)
63
63
  query = if query.kind_of?(Hash)
64
- Query.new(repository, self, query)
64
+ repository.new_query(self, query)
65
65
  else
66
66
  query.dup
67
67
  end
@@ -80,7 +80,7 @@ module DataMapper
80
80
  # @api private
81
81
  def scope_stack
82
82
  scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
83
- scope_stack_for[self] ||= []
83
+ scope_stack_for[object_id] ||= []
84
84
  end
85
85
  end # module Scope
86
86
 
@@ -1,5 +1,4 @@
1
1
  module DataMapper
2
-
3
2
  # = Properties
4
3
  # Properties for a model are not derived from a database structure, but
5
4
  # instead explicitly declared inside your model class definitions. These
@@ -96,7 +95,7 @@ module DataMapper
96
95
  # DataMapper. These lazily loaded properties are fetched on demand when their
97
96
  # accessor is called for the first time (as it is often unnecessary to
98
97
  # instantiate -every- property -every- time an object is loaded). For
99
- # instance, DataMapper::Types::Text fields are lazy loading by default,
98
+ # instance, DataMapper::Property::Text fields are lazy loading by default,
100
99
  # although you can over-ride this behavior if you wish:
101
100
  #
102
101
  # Example:
@@ -274,7 +273,7 @@ module DataMapper
274
273
  #
275
274
  # :scale The number of significant digits to the right of the decimal point.
276
275
  # Only makes sense for float type properties. Must be > 0.
277
- # Default is nil for Float type and 10 for BigDecimal type.
276
+ # Default is nil for Float type and 10 for BigDecimal
278
277
  #
279
278
  # All other keys you pass to +property+ method are stored and available
280
279
  # as options[:extra_keys].
@@ -289,54 +288,160 @@ module DataMapper
289
288
  # * You may declare a Property with the data-type of <tt>Class</tt>.
290
289
  # see SingleTableInheritance for more on how to use <tt>Class</tt> columns.
291
290
  class Property
292
- include Extlib::Assertions
291
+ module PassThroughLoadDump
292
+ # @api semipublic
293
+ def load(value)
294
+ unless value.nil?
295
+ value = @type.load(value, self) if @type
296
+ typecast(value)
297
+ else
298
+ value
299
+ end
300
+ end
301
+
302
+ # Stub instance method for dumping
303
+ #
304
+ # @param value [Object, nil] value to dump
305
+ #
306
+ # @return [Object] Dumped object
307
+ #
308
+ # @api semipublic
309
+ def dump(value)
310
+ if @type
311
+ @type.dump(value, self)
312
+ else
313
+ value
314
+ end
315
+ end
316
+ end
317
+
318
+ include DataMapper::Assertions
319
+ include Subject
320
+ extend Chainable
293
321
  extend Deprecate
294
322
  extend Equalizer
295
323
 
296
324
  deprecate :unique, :unique?
297
- deprecate :size, :length
298
325
  deprecate :nullable?, :allow_nil?
326
+ deprecate :value, :dump
299
327
 
300
328
  equalize :model, :name
301
329
 
302
- # NOTE: PLEASE update OPTIONS in DataMapper::Type when updating
303
- # them here
304
- OPTIONS = [
305
- :accessor, :reader, :writer,
306
- :lazy, :default, :key, :serial, :field, :size, :length,
307
- :format, :index, :unique_index, :auto_validation,
308
- :validates, :unique, :precision, :scale, :min, :max,
309
- :allow_nil, :allow_blank, :required
310
- ]
311
-
312
330
  PRIMITIVES = [
313
331
  TrueClass,
314
- String,
315
- Float,
316
- Integer,
317
- BigDecimal,
318
- DateTime,
319
- Date,
320
- Time,
321
- Object,
322
- Class,
323
- DataMapper::Types::Text,
332
+ ::String,
333
+ ::Float,
334
+ ::Integer,
335
+ ::BigDecimal,
336
+ ::DateTime,
337
+ ::Date,
338
+ ::Time,
339
+ ::Class
324
340
  ].to_set.freeze
325
341
 
342
+ OPTIONS = [
343
+ :accessor, :reader, :writer,
344
+ :lazy, :default, :key, :field,
345
+ :index, :unique_index,
346
+ :unique, :allow_nil, :allow_blank, :required
347
+ ]
348
+
326
349
  # Possible :visibility option values
327
350
  VISIBILITY_OPTIONS = [ :public, :protected, :private ].to_set.freeze
328
351
 
329
- DEFAULT_LENGTH = 50
330
- DEFAULT_PRECISION = 10
331
- DEFAULT_SCALE_BIGDECIMAL = 0 # Default scale for BigDecimal type
332
- DEFAULT_SCALE_FLOAT = nil # Default scale for Float type
333
- DEFAULT_NUMERIC_MIN = 0
334
- DEFAULT_NUMERIC_MAX = 2**31-1
335
-
336
352
  attr_reader :primitive, :model, :name, :instance_variable_name,
337
353
  :type, :reader_visibility, :writer_visibility, :options,
338
- :default, :precision, :scale, :min, :max, :repository_name,
339
- :allow_nil, :allow_blank, :required
354
+ :default, :repository_name, :allow_nil, :allow_blank, :required
355
+
356
+ class << self
357
+ # @api public
358
+ def descendants
359
+ @descendants ||= []
360
+ end
361
+
362
+ # @api public
363
+ def accepted_options
364
+ @accepted_options ||= []
365
+ end
366
+
367
+ # @api public
368
+ def accept_options(*args)
369
+ accepted_options.concat(args)
370
+
371
+ # Load Property options
372
+ accepted_options.each do |property_option|
373
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
374
+ def self.#{property_option}(*args) # def unique(*args)
375
+ if args.any? # if args.any?
376
+ @#{property_option} = args.first # @unique = args.first
377
+ else # else
378
+ defined?(@#{property_option}) ? @#{property_option} : nil # defined?(@unique) ? @unique : nil
379
+ end # end
380
+ end # end
381
+ RUBY
382
+ end
383
+
384
+ descendants.each {|descendant| descendant.accept_options(*args)}
385
+ end
386
+
387
+ # Gives all the options set on this type
388
+ #
389
+ # @return [Hash] with all options and their values set on this type
390
+ #
391
+ # @api public
392
+ def options
393
+ options = {}
394
+ accepted_options.each do |method|
395
+ next if !respond_to?(method) || (value = send(method)).nil?
396
+ options[method] = value
397
+ end
398
+ options
399
+ end
400
+
401
+ # Ruby primitive type to use as basis for this type. See
402
+ # Property::PRIMITIVES for list of types.
403
+ #
404
+ # @param primitive [Class, nil]
405
+ # The class for the primitive. If nil is passed in, it returns the
406
+ # current primitive
407
+ #
408
+ # @return [Class] if the <primitive> param is nil, return the current primitive.
409
+ #
410
+ # @api public
411
+ def primitive(primitive = nil)
412
+ return @primitive if primitive.nil?
413
+ @primitive = primitive
414
+ end
415
+
416
+ def nullable(value)
417
+ # :required is preferable to :allow_nil, but :nullable maps precisely to :allow_nil
418
+ warn "#nullable is deprecated, use #required instead (#{caller[0]})"
419
+ allow_nil(value)
420
+ end
421
+
422
+ def inherited(base)
423
+ descendants << base
424
+
425
+ base.primitive primitive
426
+ base.accept_options(*accepted_options)
427
+
428
+ # inherit the options from the parent class
429
+ base_options = base.options
430
+
431
+ options.each do |key, value|
432
+ base.send(key, value) unless base_options.key?(key)
433
+ end
434
+ end
435
+ end
436
+
437
+ accept_options *Property::OPTIONS
438
+
439
+ # A hook to allow types to extend or modify property it's bound to.
440
+ # Implementations are not supposed to modify the state of the type class, and
441
+ # should produce no side-effects on the type class.
442
+ def bind
443
+ # no op
444
+ end
340
445
 
341
446
  # Supplies the field in the data-store which the property corresponds to
342
447
  #
@@ -384,22 +489,6 @@ module DataMapper
384
489
  name.hash
385
490
  end
386
491
 
387
- # Returns maximum property length (if applicable).
388
- # This usually only makes sense when property is of
389
- # type Range or custom type.
390
- #
391
- # @return [Integer, nil]
392
- # the maximum length of this property
393
- #
394
- # @api semipublic
395
- def length
396
- if @length.kind_of?(Range)
397
- @length.max
398
- else
399
- @length
400
- end
401
- end
402
-
403
492
  # Returns index name if property has index.
404
493
  #
405
494
  # @return [true, Symbol, Array, nil]
@@ -427,6 +516,16 @@ module DataMapper
427
516
  @unique_index
428
517
  end
429
518
 
519
+ # @api public
520
+ def kind_of?(klass)
521
+ super || klass == Property
522
+ end
523
+
524
+ # @api public
525
+ def instance_of?(klass)
526
+ super || klass == Property
527
+ end
528
+
430
529
  # Returns whether or not the property is to be lazy-loaded
431
530
  #
432
531
  # @return [Boolean]
@@ -509,13 +608,7 @@ module DataMapper
509
608
  #
510
609
  # @api private
511
610
  def get(resource)
512
- lazy_load(resource) unless loaded?(resource) || resource.new?
513
-
514
- if loaded?(resource)
515
- get!(resource)
516
- else
517
- set(resource, default? ? default_for(resource) : nil)
518
- end
611
+ get!(resource)
519
612
  end
520
613
 
521
614
  # Fetch the ivar value in the resource
@@ -531,37 +624,6 @@ module DataMapper
531
624
  resource.instance_variable_get(instance_variable_name)
532
625
  end
533
626
 
534
- # Sets original value of the property on given resource.
535
- # When property is set on DataMapper resource instance,
536
- # original value is preserved. This makes possible to
537
- # track dirty attributes and save only those really changed,
538
- # and avoid extra queries to the data source in certain
539
- # situations.
540
- #
541
- # @param [Resource] resource
542
- # model instance for which to set the original value
543
- # @param [Object] new_value
544
- # the new value that will be set for the property
545
- #
546
- # @api private
547
- def set_original_value(resource, new_value)
548
- original_attributes = resource.original_attributes
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
562
- end
563
- end
564
-
565
627
  # Provides a standardized setter method for the property
566
628
  #
567
629
  # @param [Resource] resource
@@ -576,9 +638,7 @@ module DataMapper
576
638
  #
577
639
  # @api private
578
640
  def set(resource, value)
579
- new_value = typecast(value)
580
- set_original_value(resource, new_value)
581
- set!(resource, new_value)
641
+ set!(resource, typecast(value))
582
642
  end
583
643
 
584
644
  # Set the ivar value in the resource
@@ -616,6 +676,7 @@ module DataMapper
616
676
  #
617
677
  # @api private
618
678
  def lazy_load(resource)
679
+ return if loaded?(resource)
619
680
  resource.__send__(:lazy_load, lazy_load_properties)
620
681
  end
621
682
 
@@ -633,86 +694,17 @@ module DataMapper
633
694
  @properties ||= model.properties(repository_name)
634
695
  end
635
696
 
636
- # typecasts values into a primitive (Ruby class that backs DataMapper
637
- # property type). If property type can handle typecasting, it is delegated.
638
- # How typecasting is perfomed, depends on the primitive of the type.
639
- #
640
- # If type's primitive is a TrueClass, values of 1, t and true are casted to true.
641
- #
642
- # For String primitive, +to_s+ is called on value.
643
- #
644
- # For Float primitive, +to_f+ is called on value but only if value is a number
645
- # otherwise value is returned.
646
- #
647
- # For Integer primitive, +to_i+ is called on value but only if value is a
648
- # number, otherwise value is returned.
649
- #
650
- # For BigDecimal primitive, +to_d+ is called on value but only if value is a
651
- # number, otherwise value is returned.
652
- #
653
- # Casting to DateTime, Time and Date can handle both hashes with keys like :day or
654
- # :hour and strings in format methods like Time.parse can handle.
655
- #
656
- # @param [#to_s, #to_f, #to_i, #to_d, Hash] value
657
- # the value to typecast
658
- #
659
- # @return [rue, String, Float, Integer, BigDecimal, DateTime, Date, Time, Class]
660
- # The typecasted +value+
661
- #
662
697
  # @api semipublic
663
698
  def typecast(value)
664
- type = self.type
665
- primitive = self.primitive
666
-
667
- return type.typecast(value, self) if type.respond_to?(:typecast)
668
- return value if primitive?(value) || value.nil?
669
-
670
- if primitive == Integer then typecast_to_integer(value)
671
- elsif primitive == String then typecast_to_string(value)
672
- elsif primitive == TrueClass then typecast_to_boolean(value)
673
- elsif primitive == BigDecimal then typecast_to_bigdecimal(value)
674
- elsif primitive == Float then typecast_to_float(value)
675
- elsif primitive == DateTime then typecast_to_datetime(value)
676
- elsif primitive == Time then typecast_to_time(value)
677
- elsif primitive == Date then typecast_to_date(value)
678
- elsif primitive == Class then typecast_to_class(value)
679
- else
699
+ if @type && @type.respond_to?(:typecast)
700
+ @type.typecast(value, self)
701
+ elsif value.nil? || primitive?(value)
680
702
  value
703
+ elsif respond_to?(:typecast_to_primitive)
704
+ typecast_to_primitive(value)
681
705
  end
682
706
  end
683
707
 
684
- # Returns a default value of the
685
- # property for given resource.
686
- #
687
- # When default value is a callable object,
688
- # it is called with resource and property passed
689
- # as arguments.
690
- #
691
- # @param [Resource] resource
692
- # the model instance for which the default is to be set
693
- #
694
- # @return [Object]
695
- # the default value of this property for +resource+
696
- #
697
- # @api semipublic
698
- def default_for(resource)
699
- if @default.respond_to?(:call)
700
- @default.call(resource, self)
701
- else
702
- @default.try_dup
703
- end
704
- end
705
-
706
- # Returns true if the property has a default value
707
- #
708
- # @return [Boolean]
709
- # true if the property has a default value
710
- #
711
- # @api semipublic
712
- def default?
713
- @options.key?(:default)
714
- end
715
-
716
708
  # Returns given value unchanged for core types and
717
709
  # uses +dump+ method of the property type for custom types.
718
710
  #
@@ -723,7 +715,7 @@ module DataMapper
723
715
  # the primitive value to be stored in the repository for +val+
724
716
  #
725
717
  # @api semipublic
726
- def value(loaded_value)
718
+ def dump(value)
727
719
  if custom?
728
720
  type.dump(loaded_value, self)
729
721
  else
@@ -740,9 +732,14 @@ module DataMapper
740
732
  # true if the value is valid
741
733
  #
742
734
  # @api semipulic
743
- def valid?(loaded_value, negated = false)
744
- dumped_value = self.value(loaded_value)
745
- primitive?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
735
+ def valid?(value, negated = false)
736
+ dumped_value = dump(value)
737
+
738
+ if required? && dumped_value.nil?
739
+ negated || false
740
+ else
741
+ primitive?(dumped_value) || (dumped_value.nil? && (allow_nil? || negated))
742
+ end
746
743
  end
747
744
 
748
745
  # Returns a concise string representation of the property instance.
@@ -765,71 +762,44 @@ module DataMapper
765
762
  #
766
763
  # @api semipublic
767
764
  def primitive?(value)
768
- primitive = self.primitive
769
- if primitive == TrueClass
770
- value == true || value == false
771
- elsif primitive == Types::Text
772
- value.kind_of?(String)
773
- else
774
- value.kind_of?(primitive)
765
+ value.kind_of?(primitive)
766
+ end
767
+
768
+ chainable do
769
+ def self.new(model, name, options = {}, type = nil)
770
+ super
775
771
  end
776
772
  end
777
773
 
778
- private
774
+ protected
779
775
 
780
776
  # @api semipublic
781
- def initialize(model, name, type, options = {})
782
- assert_kind_of 'model', model, Model
783
- assert_kind_of 'name', name, Symbol
784
- assert_kind_of 'type', type, Class, Module
785
- assert_kind_of 'options', options, Hash
786
-
787
- options = options.dup
788
- caller_method = caller[2]
789
-
790
- if TrueClass == type
791
- warn "#{type} is deprecated, use Boolean instead at #{caller_method}"
792
- type = Types::Boolean
793
- elsif Integer == type && options.delete(:serial)
794
- warn "#{type} with explicit :serial option is deprecated, use Serial instead (#{caller_method})"
795
- type = Types::Serial
796
- elsif options.key?(:size)
797
- if String == type
798
- warn ":size option is deprecated, use #{type} with :length instead (#{caller_method})"
799
- length = options.delete(:size)
800
- options[:length] = length unless options.key?(:length)
801
- elsif Numeric > type
802
- warn ":size option is deprecated, specify :min and :max instead (#{caller_method})"
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)
809
- end
777
+ def initialize(model, name, options = {}, type = nil)
778
+ options = options.to_hash.dup
810
779
 
811
- assert_valid_options(options)
812
-
813
- # if the type can be found within Types then
814
- # use that class rather than the primitive
815
- type_name = type.name
816
- unless type_name.blank?
817
- type = Types.find_const(type_name)
780
+ if type && !self.kind_of?(type)
781
+ warn "#{type} < DataMapper::Type is deprecated, use the new DataMapper::Property API instead (#{caller[2]})"
782
+ @type = type
818
783
  end
819
784
 
820
- unless PRIMITIVES.include?(type) || (Type > type && PRIMITIVES.include?(type.primitive))
821
- raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
785
+ reserved_method_names = DataMapper::Resource.instance_methods + DataMapper::Resource.private_instance_methods
786
+ if reserved_method_names.map { |m| m.to_s }.include?(name.to_s)
787
+ raise ArgumentError, "+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method"
822
788
  end
823
789
 
790
+ assert_valid_options(options)
791
+
792
+ predefined_options = self.class.options
793
+ predefined_options.merge!(@type.options) if @type
794
+
824
795
  @repository_name = model.repository_name
825
796
  @model = model
826
797
  @name = name.to_s.sub(/\?$/, '').to_sym
827
- @type = type
828
- @custom = Type > @type
829
- @options = (@custom ? @type.options.merge(options) : options).freeze
798
+ @options = predefined_options.merge(options).freeze
830
799
  @instance_variable_name = "@#{@name}".freeze
831
800
 
832
- @primitive = @type.respond_to?(:primitive) ? @type.primitive : @type
801
+ @primitive = self.class.primitive || @type.primitive
802
+ @custom = !@type.nil?
833
803
  @field = @options[:field].freeze
834
804
  @default = @options[:default]
835
805
 
@@ -841,63 +811,18 @@ module DataMapper
841
811
  @index = @options.fetch(:index, nil)
842
812
  @unique_index = @options.fetch(:unique_index, nil)
843
813
  @unique = @options.fetch(:unique, @serial || @key || false)
844
- @lazy = @options.fetch(:lazy, @type.respond_to?(:lazy) ? @type.lazy : false) && !@key
845
-
846
- float_primitive = Float == @primitive
847
-
848
- # assign attributes per-type
849
- if [ String, Class ].include?(@primitive)
850
- @length = @options.fetch(:length, DEFAULT_LENGTH)
851
- elsif DataMapper::Types::Text == @primitive
852
- @length = @options.fetch(:length)
853
- elsif [ BigDecimal, Float ].include?(@primitive)
854
- @precision = @options.fetch(:precision, DEFAULT_PRECISION)
855
- @scale = @options.fetch(:scale, float_primitive ? DEFAULT_SCALE_FLOAT : DEFAULT_SCALE_BIGDECIMAL)
856
-
857
- precision_inspect = @precision.inspect
858
- scale_inspect = @scale.inspect
859
-
860
- unless @precision > 0
861
- raise ArgumentError, "precision must be greater than 0, but was #{precision_inspect}"
862
- end
863
-
864
- unless float_primitive && @scale.nil?
865
- unless @scale >= 0
866
- raise ArgumentError, "scale must be equal to or greater than 0, but was #{scale_inspect}"
867
- end
868
-
869
- unless @precision >= @scale
870
- raise ArgumentError, "precision must be equal to or greater than scale, but was #{precision_inspect} and scale was #{scale_inspect}"
871
- end
872
- end
873
- end
874
-
875
- if Numeric > @primitive && (@options.keys & [ :min, :max ]).any?
876
- @min = @options.fetch(:min, DEFAULT_NUMERIC_MIN)
877
- @max = @options.fetch(:max, DEFAULT_NUMERIC_MAX)
878
-
879
- if @max < DEFAULT_NUMERIC_MIN && !@options.key?(:min)
880
- raise ArgumentError, "min should be specified when the max is less than #{DEFAULT_NUMERIC_MIN}"
881
- elsif @max < @min
882
- raise ArgumentError, "max must be less than the min, but was #{@max} while the min was #{@min}"
883
- end
884
- end
814
+ @lazy = @options.fetch(:lazy, false) && !@key
885
815
 
886
816
  determine_visibility
887
817
 
888
- if custom?
889
- type.bind(self)
890
- end
891
-
892
- # comes from dm-validations
893
- @model.auto_generate_validations(self) if @model.respond_to?(:auto_generate_validations)
818
+ @type ? @type.bind(self) : bind
894
819
  end
895
820
 
896
821
  # @api private
897
822
  def assert_valid_options(options)
898
823
  keys = options.keys
899
824
 
900
- if (unknown_keys = keys - OPTIONS).any?
825
+ if (unknown_keys = keys - self.class.accepted_options).any?
901
826
  raise ArgumentError, "options #{unknown_keys.map { |key| key.inspect }.join(' and ')} are unknown"
902
827
  end
903
828
 
@@ -906,7 +831,7 @@ module DataMapper
906
831
 
907
832
  case key
908
833
  when :field
909
- assert_kind_of "options[:#{key}]", value, String
834
+ assert_kind_of "options[:#{key}]", value, ::String
910
835
 
911
836
  when :default
912
837
  if value.nil?
@@ -928,10 +853,10 @@ module DataMapper
928
853
  end
929
854
 
930
855
  when :length
931
- assert_kind_of "options[:#{key}]", value, Range, Integer
856
+ assert_kind_of "options[:#{key}]", value, Range, ::Integer
932
857
 
933
858
  when :size, :precision, :scale
934
- assert_kind_of "options[:#{key}]", value, Integer
859
+ assert_kind_of "options[:#{key}]", value, ::Integer
935
860
 
936
861
  when :reader, :writer, :accessor
937
862
  assert_kind_of "options[:#{key}]", value, Symbol
@@ -959,250 +884,5 @@ module DataMapper
959
884
  @reader_visibility = @options[:reader] || default_accessor
960
885
  @writer_visibility = @options[:writer] || default_accessor
961
886
  end
962
-
963
- # Typecast a value to an Integer
964
- #
965
- # @param [#to_str, #to_i] value
966
- # value to typecast
967
- #
968
- # @return [Integer]
969
- # Integer constructed from value
970
- #
971
- # @api private
972
- def typecast_to_integer(value)
973
- typecast_to_numeric(value, :to_i)
974
- end
975
-
976
- # Typecast a value to a String
977
- #
978
- # @param [#to_s] value
979
- # value to typecast
980
- #
981
- # @return [String]
982
- # String constructed from value
983
- #
984
- # @api private
985
- def typecast_to_string(value)
986
- value.to_s
987
- end
988
-
989
- # Typecast a value to a true or false
990
- #
991
- # @param [Integer, #to_str] value
992
- # value to typecast
993
- #
994
- # @return [Boolean]
995
- # true or false constructed from value
996
- #
997
- # @api private
998
- def typecast_to_boolean(value)
999
- if value.kind_of?(Integer)
1000
- return true if value == 1
1001
- return false if value == 0
1002
- elsif value.respond_to?(:to_str)
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)
1006
- end
1007
-
1008
- value
1009
- end
1010
-
1011
- # Typecast a value to a BigDecimal
1012
- #
1013
- # @param [#to_str, #to_d, Integer] value
1014
- # value to typecast
1015
- #
1016
- # @return [BigDecimal]
1017
- # BigDecimal constructed from value
1018
- #
1019
- # @api private
1020
- def typecast_to_bigdecimal(value)
1021
- if value.kind_of?(Integer)
1022
- # TODO: remove this case when Integer#to_d added by extlib
1023
- value.to_s.to_d
1024
- else
1025
- typecast_to_numeric(value, :to_d)
1026
- end
1027
- end
1028
-
1029
- # Typecast a value to a Float
1030
- #
1031
- # @param [#to_str, #to_f] value
1032
- # value to typecast
1033
- #
1034
- # @return [Float]
1035
- # Float constructed from value
1036
- #
1037
- # @api private
1038
- def typecast_to_float(value)
1039
- typecast_to_numeric(value, :to_f)
1040
- end
1041
-
1042
- # Match numeric string
1043
- #
1044
- # @param [#to_str, Numeric] value
1045
- # value to typecast
1046
- # @param [Symbol] method
1047
- # method to typecast with
1048
- #
1049
- # @return [Numeric]
1050
- # number if matched, value if no match
1051
- #
1052
- # @api private
1053
- def typecast_to_numeric(value, method)
1054
- if value.respond_to?(:to_str)
1055
- if value.to_str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
1056
- $1.send(method)
1057
- else
1058
- value
1059
- end
1060
- elsif value.respond_to?(method)
1061
- value.send(method)
1062
- else
1063
- value
1064
- end
1065
- end
1066
-
1067
- # Typecasts an arbitrary value to a DateTime.
1068
- # Handles both Hashes and DateTime instances.
1069
- #
1070
- # @param [#to_mash, #to_s] value
1071
- # value to be typecast
1072
- #
1073
- # @return [DateTime]
1074
- # DateTime constructed from value
1075
- #
1076
- # @api private
1077
- def typecast_to_datetime(value)
1078
- if value.respond_to?(:to_datetime)
1079
- value.to_datetime
1080
- elsif value.respond_to?(:to_mash)
1081
- typecast_hash_to_datetime(value)
1082
- else
1083
- DateTime.parse(value.to_s)
1084
- end
1085
- rescue ArgumentError
1086
- value
1087
- end
1088
-
1089
- # Typecasts an arbitrary value to a Date
1090
- # Handles both Hashes and Date instances.
1091
- #
1092
- # @param [#to_mash, #to_s] value
1093
- # value to be typecast
1094
- #
1095
- # @return [Date]
1096
- # Date constructed from value
1097
- #
1098
- # @api private
1099
- def typecast_to_date(value)
1100
- if value.respond_to?(:to_date)
1101
- value.to_date
1102
- elsif value.respond_to?(:to_mash)
1103
- typecast_hash_to_date(value)
1104
- else
1105
- Date.parse(value.to_s)
1106
- end
1107
- rescue ArgumentError
1108
- value
1109
- end
1110
-
1111
- # Typecasts an arbitrary value to a Time
1112
- # Handles both Hashes and Time instances.
1113
- #
1114
- # @param [#to_mash, #to_s] value
1115
- # value to be typecast
1116
- #
1117
- # @return [Time]
1118
- # Time constructed from value
1119
- #
1120
- # @api private
1121
- def typecast_to_time(value)
1122
- if value.respond_to?(:to_time)
1123
- value.to_time
1124
- elsif value.respond_to?(:to_mash)
1125
- typecast_hash_to_time(value)
1126
- else
1127
- Time.parse(value.to_s)
1128
- end
1129
- rescue ArgumentError
1130
- value
1131
- end
1132
-
1133
- # Creates a DateTime instance from a Hash with keys :year, :month, :day,
1134
- # :hour, :min, :sec
1135
- #
1136
- # @param [#to_mash] value
1137
- # value to be typecast
1138
- #
1139
- # @return [DateTime]
1140
- # DateTime constructed from hash
1141
- #
1142
- # @api private
1143
- def typecast_hash_to_datetime(value)
1144
- DateTime.new(*extract_time(value))
1145
- end
1146
-
1147
- # Creates a Date instance from a Hash with keys :year, :month, :day
1148
- #
1149
- # @param [#to_mash] value
1150
- # value to be typecast
1151
- #
1152
- # @return [Date]
1153
- # Date constructed from hash
1154
- #
1155
- # @api private
1156
- def typecast_hash_to_date(value)
1157
- Date.new(*extract_time(value)[0, 3])
1158
- end
1159
-
1160
- # Creates a Time instance from a Hash with keys :year, :month, :day,
1161
- # :hour, :min, :sec
1162
- #
1163
- # @param [#to_mash] value
1164
- # value to be typecast
1165
- #
1166
- # @return [Time]
1167
- # Time constructed from hash
1168
- #
1169
- # @api private
1170
- def typecast_hash_to_time(value)
1171
- Time.local(*extract_time(value))
1172
- end
1173
-
1174
- # Extracts the given args from the hash. If a value does not exist, it
1175
- # uses the value of Time.now.
1176
- #
1177
- # @param [#to_mash] value
1178
- # value to extract time args from
1179
- #
1180
- # @return [Array]
1181
- # Extracted values
1182
- #
1183
- # @api private
1184
- def extract_time(value)
1185
- mash = value.to_mash
1186
- now = Time.now
1187
-
1188
- [ :year, :month, :day, :hour, :min, :sec ].map do |segment|
1189
- typecast_to_numeric(mash.fetch(segment, now.send(segment)), :to_i)
1190
- end
1191
- end
1192
-
1193
- # Typecast a value to a Class
1194
- #
1195
- # @param [#to_s] value
1196
- # value to typecast
1197
- #
1198
- # @return [Class]
1199
- # Class constructed from value
1200
- #
1201
- # @api private
1202
- def typecast_to_class(value)
1203
- model.find_const(value.to_s)
1204
- rescue NameError
1205
- value
1206
- end
1207
887
  end # class Property
1208
- end # module DataMapper
888
+ end