dm-core 0.10.2 → 1.0.0.rc1

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