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.
- data/.gitignore +10 -1
- data/Gemfile +143 -0
- data/Rakefile +9 -5
- data/VERSION +1 -1
- data/dm-core.gemspec +160 -57
- data/lib/dm-core.rb +131 -56
- data/lib/dm-core/adapters.rb +98 -14
- data/lib/dm-core/adapters/abstract_adapter.rb +24 -4
- data/lib/dm-core/adapters/in_memory_adapter.rb +7 -2
- data/lib/dm-core/associations/many_to_many.rb +19 -30
- data/lib/dm-core/associations/many_to_one.rb +58 -42
- data/lib/dm-core/associations/one_to_many.rb +33 -23
- data/lib/dm-core/associations/one_to_one.rb +27 -11
- data/lib/dm-core/associations/relationship.rb +4 -4
- data/lib/dm-core/collection.rb +23 -16
- data/lib/dm-core/core_ext/array.rb +36 -0
- data/lib/dm-core/core_ext/hash.rb +30 -0
- data/lib/dm-core/core_ext/module.rb +46 -0
- data/lib/dm-core/core_ext/object.rb +31 -0
- data/lib/dm-core/core_ext/pathname.rb +20 -0
- data/lib/dm-core/core_ext/string.rb +22 -0
- data/lib/dm-core/core_ext/try_dup.rb +44 -0
- data/lib/dm-core/model.rb +88 -27
- data/lib/dm-core/model/hook.rb +75 -18
- data/lib/dm-core/model/property.rb +50 -9
- data/lib/dm-core/model/relationship.rb +31 -31
- data/lib/dm-core/model/scope.rb +3 -3
- data/lib/dm-core/property.rb +196 -516
- data/lib/dm-core/property/binary.rb +7 -0
- data/lib/dm-core/property/boolean.rb +35 -0
- data/lib/dm-core/property/class.rb +24 -0
- data/lib/dm-core/property/date.rb +47 -0
- data/lib/dm-core/property/date_time.rb +48 -0
- data/lib/dm-core/property/decimal.rb +43 -0
- data/lib/dm-core/property/discriminator.rb +48 -0
- data/lib/dm-core/property/float.rb +24 -0
- data/lib/dm-core/property/integer.rb +32 -0
- data/lib/dm-core/property/numeric.rb +43 -0
- data/lib/dm-core/property/object.rb +32 -0
- data/lib/dm-core/property/serial.rb +8 -0
- data/lib/dm-core/property/string.rb +49 -0
- data/lib/dm-core/property/text.rb +12 -0
- data/lib/dm-core/property/time.rb +48 -0
- data/lib/dm-core/property/typecast/numeric.rb +32 -0
- data/lib/dm-core/property/typecast/time.rb +28 -0
- data/lib/dm-core/property_set.rb +10 -4
- data/lib/dm-core/query.rb +14 -37
- data/lib/dm-core/query/conditions/comparison.rb +8 -6
- data/lib/dm-core/query/conditions/operation.rb +33 -2
- data/lib/dm-core/query/operator.rb +2 -5
- data/lib/dm-core/query/path.rb +4 -6
- data/lib/dm-core/repository.rb +21 -6
- data/lib/dm-core/resource.rb +316 -133
- data/lib/dm-core/resource/state.rb +79 -0
- data/lib/dm-core/resource/state/clean.rb +40 -0
- data/lib/dm-core/resource/state/deleted.rb +30 -0
- data/lib/dm-core/resource/state/dirty.rb +86 -0
- data/lib/dm-core/resource/state/immutable.rb +34 -0
- data/lib/dm-core/resource/state/persisted.rb +29 -0
- data/lib/dm-core/resource/state/transient.rb +70 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +52 -0
- data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
- data/{spec → lib/dm-core/spec}/lib/counter_adapter.rb +5 -1
- data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
- data/lib/dm-core/spec/lib/spec_helper.rb +68 -0
- data/lib/dm-core/spec/setup.rb +165 -0
- data/lib/dm-core/spec/{adapter_shared_spec.rb → shared/adapter_spec.rb} +21 -7
- data/{spec/public/shared/resource_shared_spec.rb → lib/dm-core/spec/shared/resource_spec.rb} +120 -83
- data/{spec/public/shared/sel_shared_spec.rb → lib/dm-core/spec/shared/sel_spec.rb} +5 -6
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/equalizer.rb +1 -0
- data/lib/dm-core/support/hook.rb +420 -0
- data/lib/dm-core/support/lazy_array.rb +453 -0
- data/lib/dm-core/support/local_object_space.rb +12 -0
- data/lib/dm-core/support/logger.rb +193 -6
- data/lib/dm-core/support/naming_conventions.rb +8 -8
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/type.rb +4 -0
- data/lib/dm-core/types/boolean.rb +2 -0
- data/lib/dm-core/types/decimal.rb +9 -0
- data/lib/dm-core/types/discriminator.rb +2 -0
- data/lib/dm-core/types/object.rb +3 -0
- data/lib/dm-core/types/serial.rb +2 -0
- data/lib/dm-core/types/text.rb +2 -0
- data/lib/dm-core/version.rb +1 -1
- data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +67 -0
- data/spec/public/model/hook_spec.rb +209 -0
- data/spec/public/model/property_spec.rb +35 -0
- data/spec/public/model/relationship_spec.rb +33 -20
- data/spec/public/model_spec.rb +142 -10
- data/spec/public/property/binary_spec.rb +14 -0
- data/spec/public/property/boolean_spec.rb +14 -0
- data/spec/public/property/class_spec.rb +20 -0
- data/spec/public/property/date_spec.rb +14 -0
- data/spec/public/property/date_time_spec.rb +14 -0
- data/spec/public/property/decimal_spec.rb +14 -0
- data/spec/public/{types → property}/discriminator_spec.rb +2 -12
- data/spec/public/property/float_spec.rb +14 -0
- data/spec/public/property/integer_spec.rb +14 -0
- data/spec/public/property/object_spec.rb +9 -17
- data/spec/public/property/serial_spec.rb +14 -0
- data/spec/public/property/string_spec.rb +14 -0
- data/spec/public/property/text_spec.rb +52 -0
- data/spec/public/property/time_spec.rb +14 -0
- data/spec/public/property_spec.rb +28 -87
- data/spec/public/resource_spec.rb +101 -0
- data/spec/public/sel_spec.rb +5 -15
- data/spec/public/shared/collection_shared_spec.rb +16 -30
- data/spec/public/shared/finder_shared_spec.rb +2 -4
- data/spec/public/shared/property_shared_spec.rb +176 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +1 -1
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +2 -2
- data/spec/semipublic/associations/many_to_many_spec.rb +89 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +24 -1
- data/spec/semipublic/associations/one_to_many_spec.rb +51 -0
- data/spec/semipublic/associations/one_to_one_spec.rb +49 -0
- data/spec/semipublic/associations/relationship_spec.rb +3 -3
- data/spec/semipublic/associations_spec.rb +1 -1
- data/spec/semipublic/property/binary_spec.rb +13 -0
- data/spec/semipublic/property/boolean_spec.rb +65 -0
- data/spec/semipublic/property/class_spec.rb +33 -0
- data/spec/semipublic/property/date_spec.rb +43 -0
- data/spec/semipublic/property/date_time_spec.rb +46 -0
- data/spec/semipublic/property/decimal_spec.rb +82 -0
- data/spec/semipublic/property/discriminator_spec.rb +19 -0
- data/spec/semipublic/property/float_spec.rb +82 -0
- data/spec/semipublic/property/integer_spec.rb +82 -0
- data/spec/semipublic/property/serial_spec.rb +13 -0
- data/spec/semipublic/property/string_spec.rb +13 -0
- data/spec/semipublic/property/text_spec.rb +31 -0
- data/spec/semipublic/property/time_spec.rb +50 -0
- data/spec/semipublic/property_spec.rb +2 -532
- data/spec/semipublic/query/conditions/comparison_spec.rb +171 -169
- data/spec/semipublic/query/conditions/operation_spec.rb +53 -51
- data/spec/semipublic/query/path_spec.rb +17 -17
- data/spec/semipublic/query_spec.rb +47 -78
- data/spec/semipublic/resource/state/clean_spec.rb +88 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +133 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +99 -0
- data/spec/semipublic/resource/state/transient_spec.rb +128 -0
- data/spec/semipublic/resource/state_spec.rb +226 -0
- data/spec/semipublic/shared/property_shared_spec.rb +143 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +16 -15
- data/spec/semipublic/shared/resource_state_shared_spec.rb +78 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec_helper.rb +21 -97
- data/spec/support/types/huge_integer.rb +17 -0
- data/spec/unit/array_spec.rb +48 -0
- data/spec/unit/hash_spec.rb +35 -0
- data/spec/unit/hook_spec.rb +1234 -0
- data/spec/unit/lazy_array_spec.rb +1959 -0
- data/spec/unit/module_spec.rb +70 -0
- data/spec/unit/object_spec.rb +37 -0
- data/spec/unit/try_dup_spec.rb +45 -0
- data/tasks/local_gemfile.rake +18 -0
- data/tasks/spec.rake +0 -3
- metadata +197 -71
- data/deps.rip +0 -2
- data/lib/dm-core/adapters/data_objects_adapter.rb +0 -712
- data/lib/dm-core/adapters/mysql_adapter.rb +0 -42
- data/lib/dm-core/adapters/oracle_adapter.rb +0 -229
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -22
- data/lib/dm-core/adapters/sqlite3_adapter.rb +0 -17
- data/lib/dm-core/adapters/sqlserver_adapter.rb +0 -114
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -111
- data/lib/dm-core/core_ext/enumerable.rb +0 -28
- data/lib/dm-core/migrations.rb +0 -1427
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +0 -366
- data/lib/dm-core/transaction.rb +0 -508
- data/lib/dm-core/types/paranoid_boolean.rb +0 -42
- data/lib/dm-core/types/paranoid_datetime.rb +0 -41
- data/spec/lib/adapter_helpers.rb +0 -105
- data/spec/lib/collection_helpers.rb +0 -18
- data/spec/lib/pending_helpers.rb +0 -46
- data/spec/public/migrations_spec.rb +0 -503
- data/spec/public/transaction_spec.rb +0 -153
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +0 -194
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +0 -17
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +0 -12
data/lib/dm-core/model/hook.rb
CHANGED
@@ -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,
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
22
|
-
|
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
|
-
|
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
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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 <<
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
226
|
-
|
227
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
277
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
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
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
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
|
|
data/lib/dm-core/model/scope.rb
CHANGED
@@ -27,7 +27,7 @@ module DataMapper
|
|
27
27
|
#
|
28
28
|
# @api private
|
29
29
|
def query
|
30
|
-
|
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
|
-
|
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[
|
83
|
+
scope_stack_for[object_id] ||= []
|
84
84
|
end
|
85
85
|
end # module Scope
|
86
86
|
|
data/lib/dm-core/property.rb
CHANGED
@@ -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::
|
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
|
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
|
-
|
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
|
-
|
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, :
|
339
|
-
|
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
|
-
|
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
|
-
|
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
|
665
|
-
|
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
|
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?(
|
744
|
-
dumped_value =
|
745
|
-
|
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
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
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
|
-
|
774
|
+
protected
|
779
775
|
|
780
776
|
# @api semipublic
|
781
|
-
def initialize(model, name,
|
782
|
-
|
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
|
-
|
812
|
-
|
813
|
-
|
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
|
-
|
821
|
-
|
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
|
-
@
|
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 =
|
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,
|
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
|
-
|
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 -
|
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
|
888
|
+
end
|