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.
- 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/query/path.rb
CHANGED
@@ -10,11 +10,11 @@ module DataMapper
|
|
10
10
|
# TODO: replace this with BasicObject
|
11
11
|
instance_methods.each do |method|
|
12
12
|
next if method =~ /\A__/ ||
|
13
|
-
%w[ send class dup object_id kind_of? instance_of? respond_to? equal? should should_not instance_variable_set instance_variable_get instance_variable_defined? extend hash inspect copy_object ].include?(method.to_s)
|
13
|
+
%w[ send class dup object_id kind_of? instance_of? respond_to? respond_to_missing? equal? freeze frozen? should should_not instance_variables instance_variable_set instance_variable_get instance_variable_defined? remove_instance_variable extend hash inspect copy_object initialize_dup ].include?(method.to_s)
|
14
14
|
undef_method method
|
15
15
|
end
|
16
16
|
|
17
|
-
include
|
17
|
+
include DataMapper::Assertions
|
18
18
|
extend Equalizer
|
19
19
|
|
20
20
|
equalize :relationships, :property
|
@@ -62,16 +62,14 @@ module DataMapper
|
|
62
62
|
|
63
63
|
# @api semipublic
|
64
64
|
def initialize(relationships, property_name = nil)
|
65
|
-
|
66
|
-
assert_kind_of 'property_name', property_name, Symbol, NilClass
|
67
|
-
|
68
|
-
@relationships = relationships.dup
|
65
|
+
@relationships = relationships.to_ary.dup
|
69
66
|
|
70
67
|
last_relationship = @relationships.last
|
71
68
|
@repository_name = last_relationship.relative_target_repository_name
|
72
69
|
@model = last_relationship.target_model
|
73
70
|
|
74
71
|
if property_name
|
72
|
+
property_name = property_name.to_sym
|
75
73
|
@property = @model.properties(@repository_name)[property_name] ||
|
76
74
|
raise(ArgumentError, "Unknown property '#{property_name}' in #{@model}")
|
77
75
|
end
|
data/lib/dm-core/repository.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module DataMapper
|
2
2
|
class Repository
|
3
|
-
include
|
3
|
+
include DataMapper::Assertions
|
4
4
|
extend Equalizer
|
5
5
|
|
6
|
-
equalize :name
|
6
|
+
equalize :name
|
7
7
|
|
8
8
|
# Get the list of adapters registered for all Repositories,
|
9
9
|
# keyed by repository name.
|
@@ -45,6 +45,9 @@ module DataMapper
|
|
45
45
|
# @api semipublic
|
46
46
|
attr_reader :name
|
47
47
|
|
48
|
+
# @api semipublic
|
49
|
+
alias to_sym name
|
50
|
+
|
48
51
|
# Get the adapter for this repository
|
49
52
|
#
|
50
53
|
# Lazy loads adapter setup from registered adapters
|
@@ -114,6 +117,20 @@ module DataMapper
|
|
114
117
|
end
|
115
118
|
end
|
116
119
|
|
120
|
+
# Create a Query or subclass instance for this repository.
|
121
|
+
#
|
122
|
+
# @param [Model] model
|
123
|
+
# the Model to retrieve results from
|
124
|
+
# @param [Hash] options
|
125
|
+
# the conditions and scope
|
126
|
+
#
|
127
|
+
# @return [Query]
|
128
|
+
#
|
129
|
+
# @api semipublic
|
130
|
+
def new_query(model, options = {})
|
131
|
+
adapter.new_query(self, model, options)
|
132
|
+
end
|
133
|
+
|
117
134
|
# Create one or more resource instances in this repository.
|
118
135
|
#
|
119
136
|
# TODO: create example
|
@@ -159,7 +176,7 @@ module DataMapper
|
|
159
176
|
#
|
160
177
|
# @api semipublic
|
161
178
|
def update(attributes, collection)
|
162
|
-
return 0 unless collection.query.valid?
|
179
|
+
return 0 unless collection.query.valid? && attributes.any?
|
163
180
|
adapter.update(attributes, collection)
|
164
181
|
end
|
165
182
|
|
@@ -202,9 +219,7 @@ module DataMapper
|
|
202
219
|
#
|
203
220
|
# @api semipublic
|
204
221
|
def initialize(name)
|
205
|
-
|
206
|
-
|
207
|
-
@name = name
|
222
|
+
@name = name.to_sym
|
208
223
|
@identity_maps = {}
|
209
224
|
end
|
210
225
|
end # class Repository
|
data/lib/dm-core/resource.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# TODO: DRY up raise_on_save_failure with attr_accessor_with_default
|
2
|
+
# once AS branch is merged in
|
3
|
+
|
1
4
|
module DataMapper
|
2
5
|
module Resource
|
3
|
-
include
|
6
|
+
include DataMapper::Assertions
|
4
7
|
extend Chainable
|
5
8
|
extend Deprecate
|
6
9
|
|
@@ -24,20 +27,51 @@ module DataMapper
|
|
24
27
|
Model.descendants
|
25
28
|
end
|
26
29
|
|
30
|
+
# Return if Resource#save should raise an exception on save failures (per-resource)
|
31
|
+
#
|
32
|
+
# This delegates to model.raise_on_save_failure by default.
|
33
|
+
#
|
34
|
+
# user.raise_on_save_failure # => false
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
# true if a failure in Resource#save should raise an exception
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def raise_on_save_failure
|
41
|
+
if defined?(@raise_on_save_failure)
|
42
|
+
@raise_on_save_failure
|
43
|
+
else
|
44
|
+
model.raise_on_save_failure
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Specify if Resource#save should raise an exception on save failures (per-resource)
|
49
|
+
#
|
50
|
+
# @param [Boolean]
|
51
|
+
# a boolean that if true will cause Resource#save to raise an exception
|
52
|
+
#
|
53
|
+
# @return [Boolean]
|
54
|
+
# true if a failure in Resource#save should raise an exception
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def raise_on_save_failure=(raise_on_save_failure)
|
58
|
+
@raise_on_save_failure = raise_on_save_failure
|
59
|
+
end
|
60
|
+
|
27
61
|
# Deprecated API for updating attributes and saving Resource
|
28
62
|
#
|
29
63
|
# @see #update
|
30
64
|
#
|
31
65
|
# @deprecated
|
32
66
|
def update_attributes(attributes = {}, *allowed)
|
33
|
-
model
|
34
|
-
|
67
|
+
model = self.model
|
68
|
+
call_stack = caller[0]
|
35
69
|
|
36
|
-
warn "#{model}#update_attributes is deprecated, use #{model}#update instead (#{
|
70
|
+
warn "#{model}#update_attributes is deprecated, use #{model}#update instead (#{call_stack})"
|
37
71
|
|
38
72
|
if allowed.any?
|
39
73
|
warn "specifying allowed in #{model}#update_attributes is deprecated, " \
|
40
|
-
"use Hash#only to filter the attributes in the caller (#{
|
74
|
+
"use Hash#only to filter the attributes in the caller (#{call_stack})"
|
41
75
|
attributes = attributes.only(*allowed)
|
42
76
|
end
|
43
77
|
|
@@ -55,6 +89,38 @@ module DataMapper
|
|
55
89
|
# @api public
|
56
90
|
alias_method :model, :class
|
57
91
|
|
92
|
+
# Get the persisted state for the resource
|
93
|
+
#
|
94
|
+
# @return [Resource::State]
|
95
|
+
# the current persisted state for the resource
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
def persisted_state
|
99
|
+
@_state ||= Resource::State::Transient.new(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Set the persisted state for the resource
|
103
|
+
#
|
104
|
+
# @param [Resource::State]
|
105
|
+
# the new persisted state for the resource
|
106
|
+
#
|
107
|
+
# @return [undefined]
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
def persisted_state=(state)
|
111
|
+
@_state = state
|
112
|
+
end
|
113
|
+
|
114
|
+
# Test if the persisted state is set
|
115
|
+
#
|
116
|
+
# @return [Boolean]
|
117
|
+
# true if the persisted state is set
|
118
|
+
#
|
119
|
+
# @api private
|
120
|
+
def persisted_state?
|
121
|
+
defined?(@_state) ? true : false
|
122
|
+
end
|
123
|
+
|
58
124
|
# Repository this resource belongs to in the context of this collection
|
59
125
|
# or of the resource's class.
|
60
126
|
#
|
@@ -98,7 +164,7 @@ module DataMapper
|
|
98
164
|
#
|
99
165
|
# @api public
|
100
166
|
def new?
|
101
|
-
|
167
|
+
persisted_state.kind_of?(State::Transient)
|
102
168
|
end
|
103
169
|
|
104
170
|
# Checks if this Resource instance is saved
|
@@ -108,7 +174,7 @@ module DataMapper
|
|
108
174
|
#
|
109
175
|
# @api public
|
110
176
|
def saved?
|
111
|
-
|
177
|
+
persisted_state.kind_of?(State::Persisted)
|
112
178
|
end
|
113
179
|
|
114
180
|
# Checks if this Resource instance is destroyed
|
@@ -118,7 +184,7 @@ module DataMapper
|
|
118
184
|
#
|
119
185
|
# @api public
|
120
186
|
def destroyed?
|
121
|
-
|
187
|
+
readonly? && !key.nil?
|
122
188
|
end
|
123
189
|
|
124
190
|
# Checks if the resource has no changes to save
|
@@ -128,7 +194,7 @@ module DataMapper
|
|
128
194
|
#
|
129
195
|
# @api public
|
130
196
|
def clean?
|
131
|
-
|
197
|
+
persisted_state.kind_of?(State::Clean) || persisted_state.kind_of?(State::Immutable)
|
132
198
|
end
|
133
199
|
|
134
200
|
# Checks if the resource has unsaved changes
|
@@ -150,7 +216,7 @@ module DataMapper
|
|
150
216
|
#
|
151
217
|
# @api public
|
152
218
|
def readonly?
|
153
|
-
|
219
|
+
persisted_state.kind_of?(State::Immutable)
|
154
220
|
end
|
155
221
|
|
156
222
|
# Returns the value of the attribute.
|
@@ -185,7 +251,7 @@ module DataMapper
|
|
185
251
|
#
|
186
252
|
# @api public
|
187
253
|
def attribute_get(name)
|
188
|
-
properties[name]
|
254
|
+
persisted_state.get(properties[name])
|
189
255
|
end
|
190
256
|
|
191
257
|
alias [] attribute_get
|
@@ -222,13 +288,11 @@ module DataMapper
|
|
222
288
|
# @param [Object] value
|
223
289
|
# value to store
|
224
290
|
#
|
225
|
-
# @return [
|
226
|
-
# the value stored at that given attribute, nil if none,
|
227
|
-
# and default if necessary
|
291
|
+
# @return [undefined]
|
228
292
|
#
|
229
293
|
# @api public
|
230
294
|
def attribute_set(name, value)
|
231
|
-
properties[name]
|
295
|
+
self.persisted_state = persisted_state.set(properties[name], value)
|
232
296
|
end
|
233
297
|
|
234
298
|
alias []= attribute_set
|
@@ -283,7 +347,7 @@ module DataMapper
|
|
283
347
|
raise ArgumentError, "The attribute '#{name}' is not accessible in #{model}"
|
284
348
|
end
|
285
349
|
when Associations::Relationship, Property
|
286
|
-
|
350
|
+
self.persisted_state = persisted_state.set(name, value)
|
287
351
|
end
|
288
352
|
end
|
289
353
|
end
|
@@ -307,6 +371,8 @@ module DataMapper
|
|
307
371
|
clear_subjects
|
308
372
|
end
|
309
373
|
|
374
|
+
self.persisted_state = persisted_state.rollback
|
375
|
+
|
310
376
|
self
|
311
377
|
end
|
312
378
|
|
@@ -319,7 +385,7 @@ module DataMapper
|
|
319
385
|
# true if resource and storage state match
|
320
386
|
#
|
321
387
|
# @api public
|
322
|
-
def update(attributes
|
388
|
+
def update(attributes)
|
323
389
|
assert_update_clean_only(:update)
|
324
390
|
self.attributes = attributes
|
325
391
|
save
|
@@ -334,7 +400,7 @@ module DataMapper
|
|
334
400
|
# true if resource and storage state match
|
335
401
|
#
|
336
402
|
# @api public
|
337
|
-
def update!(attributes
|
403
|
+
def update!(attributes)
|
338
404
|
assert_update_clean_only(:update!)
|
339
405
|
self.attributes = attributes
|
340
406
|
save!
|
@@ -348,7 +414,9 @@ module DataMapper
|
|
348
414
|
# @api public
|
349
415
|
def save
|
350
416
|
assert_not_destroyed(:save)
|
351
|
-
_save
|
417
|
+
retval = _save
|
418
|
+
assert_save_successful(:save, retval)
|
419
|
+
retval
|
352
420
|
end
|
353
421
|
|
354
422
|
# Save the instance and loaded, dirty associations to the data-store, bypassing hooks
|
@@ -359,7 +427,9 @@ module DataMapper
|
|
359
427
|
# @api public
|
360
428
|
def save!
|
361
429
|
assert_not_destroyed(:save!)
|
362
|
-
_save(false)
|
430
|
+
retval = _save(false)
|
431
|
+
assert_save_successful(:save!, retval)
|
432
|
+
retval
|
363
433
|
end
|
364
434
|
|
365
435
|
# Destroy the instance, remove it from the repository
|
@@ -369,7 +439,13 @@ module DataMapper
|
|
369
439
|
#
|
370
440
|
# @api public
|
371
441
|
def destroy
|
372
|
-
|
442
|
+
return true if destroyed?
|
443
|
+
catch :halt do
|
444
|
+
before_destroy_hook
|
445
|
+
retval = _destroy
|
446
|
+
after_destroy_hook
|
447
|
+
retval
|
448
|
+
end
|
373
449
|
end
|
374
450
|
|
375
451
|
# Destroy the instance, remove it from the repository, bypassing hooks
|
@@ -380,15 +456,7 @@ module DataMapper
|
|
380
456
|
# @api public
|
381
457
|
def destroy!
|
382
458
|
return true if destroyed?
|
383
|
-
|
384
|
-
if saved?
|
385
|
-
repository.delete(collection_for_self)
|
386
|
-
reset
|
387
|
-
@_readonly = true
|
388
|
-
@_destroyed = true
|
389
|
-
else
|
390
|
-
false
|
391
|
-
end
|
459
|
+
_destroy(false)
|
392
460
|
end
|
393
461
|
|
394
462
|
# Compares another Resource for equality
|
@@ -423,9 +491,7 @@ module DataMapper
|
|
423
491
|
# @api public
|
424
492
|
def ==(other)
|
425
493
|
return true if equal?(other)
|
426
|
-
other.
|
427
|
-
other.respond_to?(:key) &&
|
428
|
-
other.respond_to?(:clean?) &&
|
494
|
+
return false unless other.kind_of?(Resource) && model.base_model.equal?(other.model.base_model)
|
429
495
|
cmp?(other, :==)
|
430
496
|
end
|
431
497
|
|
@@ -443,14 +509,13 @@ module DataMapper
|
|
443
509
|
def <=>(other)
|
444
510
|
model = self.model
|
445
511
|
unless other.kind_of?(model.base_model)
|
446
|
-
raise ArgumentError, "Cannot compare a #{other.
|
512
|
+
raise ArgumentError, "Cannot compare a #{other.class} instance with a #{model} instance"
|
447
513
|
end
|
448
|
-
cmp = 0
|
449
514
|
model.default_order(repository_name).each do |direction|
|
450
515
|
cmp = direction.get(self) <=> direction.get(other)
|
451
|
-
|
516
|
+
return cmp if cmp.nonzero?
|
452
517
|
end
|
453
|
-
|
518
|
+
0
|
454
519
|
end
|
455
520
|
|
456
521
|
# Returns hash value of the object.
|
@@ -497,7 +562,11 @@ module DataMapper
|
|
497
562
|
#
|
498
563
|
# @api semipublic
|
499
564
|
def original_attributes
|
500
|
-
|
565
|
+
if persisted_state.respond_to?(:original_attributes)
|
566
|
+
persisted_state.original_attributes.dup.freeze
|
567
|
+
else
|
568
|
+
{}.freeze
|
569
|
+
end
|
501
570
|
end
|
502
571
|
|
503
572
|
# Checks if an attribute has been loaded from the repository
|
@@ -546,24 +615,13 @@ module DataMapper
|
|
546
615
|
dirty_attributes = {}
|
547
616
|
|
548
617
|
original_attributes.each_key do |property|
|
549
|
-
|
618
|
+
next unless property.respond_to?(:value)
|
619
|
+
dirty_attributes[property] = property.dump(property.get!(self))
|
550
620
|
end
|
551
621
|
|
552
622
|
dirty_attributes
|
553
623
|
end
|
554
624
|
|
555
|
-
# Reset the Resource to a similar state as a new record:
|
556
|
-
# removes it from identity map and clears original property
|
557
|
-
# values (thus making all properties non dirty)
|
558
|
-
#
|
559
|
-
# @api private
|
560
|
-
def reset
|
561
|
-
@_saved = false
|
562
|
-
remove_from_identity_map
|
563
|
-
original_attributes.clear
|
564
|
-
self
|
565
|
-
end
|
566
|
-
|
567
625
|
# Returns the Collection the Resource is associated with
|
568
626
|
#
|
569
627
|
# @return [nil]
|
@@ -609,29 +667,81 @@ module DataMapper
|
|
609
667
|
#
|
610
668
|
# @api semipublic
|
611
669
|
def query
|
612
|
-
|
670
|
+
repository.new_query(model, :fields => fields, :conditions => conditions)
|
613
671
|
end
|
614
672
|
|
615
673
|
protected
|
616
674
|
|
617
|
-
# Method for hooking callbacks
|
675
|
+
# Method for hooking callbacks before resource saving
|
618
676
|
#
|
619
|
-
# @return [
|
620
|
-
# true if the create was successful, false if not
|
677
|
+
# @return [undefined]
|
621
678
|
#
|
622
679
|
# @api private
|
623
|
-
def
|
624
|
-
|
680
|
+
def before_save_hook
|
681
|
+
execute_hooks_for(:before, :save)
|
625
682
|
end
|
626
683
|
|
627
|
-
# Method for hooking callbacks
|
684
|
+
# Method for hooking callbacks after resource saving
|
628
685
|
#
|
629
|
-
# @return [
|
630
|
-
#
|
686
|
+
# @return [undefined]
|
687
|
+
#
|
688
|
+
# @api private
|
689
|
+
def after_save_hook
|
690
|
+
execute_hooks_for(:after, :save)
|
691
|
+
end
|
692
|
+
|
693
|
+
# Method for hooking callbacks before resource creation
|
694
|
+
#
|
695
|
+
# @return [undefined]
|
696
|
+
#
|
697
|
+
# @api private
|
698
|
+
def before_create_hook
|
699
|
+
execute_hooks_for(:before, :create)
|
700
|
+
end
|
701
|
+
|
702
|
+
# Method for hooking callbacks after resource creation
|
703
|
+
#
|
704
|
+
# @return [undefined]
|
705
|
+
#
|
706
|
+
# @api private
|
707
|
+
def after_create_hook
|
708
|
+
execute_hooks_for(:after, :create)
|
709
|
+
end
|
710
|
+
|
711
|
+
# Method for hooking callbacks before resource updating
|
712
|
+
#
|
713
|
+
# @return [undefined]
|
631
714
|
#
|
632
715
|
# @api private
|
633
|
-
def
|
634
|
-
|
716
|
+
def before_update_hook
|
717
|
+
execute_hooks_for(:before, :update)
|
718
|
+
end
|
719
|
+
|
720
|
+
# Method for hooking callbacks after resource updating
|
721
|
+
#
|
722
|
+
# @return [undefined]
|
723
|
+
#
|
724
|
+
# @api private
|
725
|
+
def after_update_hook
|
726
|
+
execute_hooks_for(:after, :update)
|
727
|
+
end
|
728
|
+
|
729
|
+
# Method for hooking callbacks before resource destruction
|
730
|
+
#
|
731
|
+
# @return [undefined]
|
732
|
+
#
|
733
|
+
# @api private
|
734
|
+
def before_destroy_hook
|
735
|
+
execute_hooks_for(:before, :destroy)
|
736
|
+
end
|
737
|
+
|
738
|
+
# Method for hooking callbacks after resource destruction
|
739
|
+
#
|
740
|
+
# @return [undefined]
|
741
|
+
#
|
742
|
+
# @api private
|
743
|
+
def after_destroy_hook
|
744
|
+
execute_hooks_for(:after, :destroy)
|
635
745
|
end
|
636
746
|
|
637
747
|
private
|
@@ -649,6 +759,15 @@ module DataMapper
|
|
649
759
|
self.attributes = attributes
|
650
760
|
end
|
651
761
|
|
762
|
+
# @api private
|
763
|
+
def initialize_copy(original)
|
764
|
+
instance_variables.each do |ivar|
|
765
|
+
instance_variable_set(ivar, instance_variable_get(ivar).try_dup)
|
766
|
+
end
|
767
|
+
|
768
|
+
self.persisted_state = persisted_state.class.new(self)
|
769
|
+
end
|
770
|
+
|
652
771
|
# Returns name of the repository this object
|
653
772
|
# was loaded from
|
654
773
|
#
|
@@ -721,7 +840,6 @@ module DataMapper
|
|
721
840
|
def reset_key
|
722
841
|
properties.key.zip(key) do |property, value|
|
723
842
|
property.set!(self, value)
|
724
|
-
original_attributes.delete(property)
|
725
843
|
end
|
726
844
|
end
|
727
845
|
|
@@ -736,7 +854,6 @@ module DataMapper
|
|
736
854
|
(model_properties - model_properties.key | relationships.values).each do |subject|
|
737
855
|
next unless subject.loaded?(self)
|
738
856
|
remove_instance_variable(subject.instance_variable_name)
|
739
|
-
original_attributes.delete(subject)
|
740
857
|
end
|
741
858
|
end
|
742
859
|
|
@@ -762,7 +879,10 @@ module DataMapper
|
|
762
879
|
#
|
763
880
|
# @api private
|
764
881
|
def eager_load(properties)
|
765
|
-
unless properties.empty? || key.nil?
|
882
|
+
unless properties.empty? || key.nil? || collection.nil?
|
883
|
+
# set an initial value to prevent recursive lazy loads
|
884
|
+
properties.each { |property| property.set!(self, nil) }
|
885
|
+
|
766
886
|
collection.reload(:fields => properties)
|
767
887
|
end
|
768
888
|
|
@@ -794,7 +914,10 @@ module DataMapper
|
|
794
914
|
parent_relationships = []
|
795
915
|
|
796
916
|
relationships.each_value do |relationship|
|
797
|
-
next unless relationship.respond_to?(:resource_for)
|
917
|
+
next unless relationship.respond_to?(:resource_for)
|
918
|
+
set_default_value(relationship)
|
919
|
+
next unless relationship.loaded?(self) && relationship.get!(self)
|
920
|
+
|
798
921
|
parent_relationships << relationship
|
799
922
|
end
|
800
923
|
|
@@ -811,7 +934,10 @@ module DataMapper
|
|
811
934
|
child_relationships = []
|
812
935
|
|
813
936
|
relationships.each_value do |relationship|
|
814
|
-
next unless relationship.respond_to?(:collection_for)
|
937
|
+
next unless relationship.respond_to?(:collection_for)
|
938
|
+
set_default_value(relationship)
|
939
|
+
next unless relationship.loaded?(self)
|
940
|
+
|
815
941
|
child_relationships << relationship
|
816
942
|
end
|
817
943
|
|
@@ -823,13 +949,13 @@ module DataMapper
|
|
823
949
|
end
|
824
950
|
|
825
951
|
# @api private
|
826
|
-
def
|
952
|
+
def parent_associations
|
827
953
|
parent_relationships.map { |relationship| relationship.get!(self) }
|
828
954
|
end
|
829
955
|
|
830
956
|
# @api private
|
831
|
-
def
|
832
|
-
child_relationships.map { |relationship| relationship.
|
957
|
+
def child_associations
|
958
|
+
child_relationships.map { |relationship| relationship.get_collection(self) }
|
833
959
|
end
|
834
960
|
|
835
961
|
# Creates the resource with default values
|
@@ -850,26 +976,26 @@ module DataMapper
|
|
850
976
|
#
|
851
977
|
# @api private
|
852
978
|
def _create
|
853
|
-
|
854
|
-
|
979
|
+
self.persisted_state = persisted_state.commit
|
980
|
+
true
|
981
|
+
end
|
855
982
|
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
983
|
+
# This method executes the hooks before and after resource creation
|
984
|
+
#
|
985
|
+
# @return [Boolean]
|
986
|
+
#
|
987
|
+
# @see Resource#_create
|
988
|
+
#
|
989
|
+
# @api private
|
990
|
+
def create_with_hooks
|
991
|
+
catch :halt do
|
992
|
+
before_save_hook
|
993
|
+
before_create_hook
|
994
|
+
retval = _create
|
995
|
+
after_create_hook
|
996
|
+
after_save_hook
|
997
|
+
retval
|
861
998
|
end
|
862
|
-
|
863
|
-
@_repository = repository
|
864
|
-
@_repository.create([ self ])
|
865
|
-
|
866
|
-
@_saved = true
|
867
|
-
|
868
|
-
original_attributes.clear
|
869
|
-
|
870
|
-
add_to_identity_map
|
871
|
-
|
872
|
-
true
|
873
999
|
end
|
874
1000
|
|
875
1001
|
# Updates resource state
|
@@ -883,33 +1009,39 @@ module DataMapper
|
|
883
1009
|
#
|
884
1010
|
# @api private
|
885
1011
|
def _update
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
true
|
890
|
-
elsif original_attributes.any? { |property, _value| !property.valid?(property.get!(self)) }
|
891
|
-
false
|
892
|
-
else
|
893
|
-
# remove from the identity map
|
894
|
-
remove_from_identity_map
|
895
|
-
|
896
|
-
repository.update(dirty_attributes, collection_for_self)
|
897
|
-
|
898
|
-
original_attributes.clear
|
899
|
-
|
900
|
-
# remove the cached key in case it is updated
|
901
|
-
remove_instance_variable(:@_key)
|
902
|
-
|
903
|
-
add_to_identity_map
|
1012
|
+
self.persisted_state = persisted_state.commit if valid_attributes?
|
1013
|
+
clean?
|
1014
|
+
end
|
904
1015
|
|
905
|
-
|
1016
|
+
# This method executes the hooks before and after resource updating
|
1017
|
+
#
|
1018
|
+
# @return [Boolean]
|
1019
|
+
#
|
1020
|
+
# @see Resource#_update
|
1021
|
+
#
|
1022
|
+
# @api private
|
1023
|
+
def update_with_hooks
|
1024
|
+
catch :halt do
|
1025
|
+
before_save_hook
|
1026
|
+
before_update_hook
|
1027
|
+
retval = _update
|
1028
|
+
after_update_hook
|
1029
|
+
after_save_hook
|
1030
|
+
retval
|
906
1031
|
end
|
907
1032
|
end
|
908
1033
|
|
909
1034
|
# @api private
|
910
|
-
def
|
1035
|
+
def _destroy(execute_hooks = true)
|
1036
|
+
deleted = persisted_state.delete
|
1037
|
+
self.persisted_state = deleted.commit
|
1038
|
+
true
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# @api private
|
1042
|
+
def _save(execute_hooks = true)
|
911
1043
|
run_once(true) do
|
912
|
-
save_parents(
|
1044
|
+
save_parents(execute_hooks) && save_self(execute_hooks) && save_children(execute_hooks)
|
913
1045
|
end
|
914
1046
|
end
|
915
1047
|
|
@@ -919,10 +1051,13 @@ module DataMapper
|
|
919
1051
|
# true if the resource was successfully saved
|
920
1052
|
#
|
921
1053
|
# @api semipublic
|
922
|
-
def save_self(
|
1054
|
+
def save_self(execute_hooks = true)
|
1055
|
+
# short-circuit if the resource is not dirty
|
1056
|
+
return saved? unless dirty_self?
|
1057
|
+
|
923
1058
|
new_resource = new?
|
924
|
-
if
|
925
|
-
new_resource ?
|
1059
|
+
if execute_hooks
|
1060
|
+
new_resource ? create_with_hooks : update_with_hooks
|
926
1061
|
else
|
927
1062
|
new_resource ? _create : _update
|
928
1063
|
end
|
@@ -934,15 +1069,15 @@ module DataMapper
|
|
934
1069
|
# true if the parents were successfully saved
|
935
1070
|
#
|
936
1071
|
# @api private
|
937
|
-
def save_parents(
|
1072
|
+
def save_parents(execute_hooks)
|
938
1073
|
run_once(true) do
|
939
|
-
parent_relationships.
|
1074
|
+
parent_relationships.map do |relationship|
|
940
1075
|
parent = relationship.get!(self)
|
941
1076
|
|
942
|
-
if parent.__send__(:save_parents,
|
1077
|
+
if parent.__send__(:save_parents, execute_hooks) && parent.__send__(:save_self, execute_hooks)
|
943
1078
|
relationship.set(self, parent) # set the FK values
|
944
1079
|
end
|
945
|
-
end
|
1080
|
+
end.all?
|
946
1081
|
end
|
947
1082
|
end
|
948
1083
|
|
@@ -952,10 +1087,10 @@ module DataMapper
|
|
952
1087
|
# true if the children were successfully saved
|
953
1088
|
#
|
954
1089
|
# @api private
|
955
|
-
def save_children(
|
956
|
-
|
957
|
-
|
958
|
-
end
|
1090
|
+
def save_children(execute_hooks)
|
1091
|
+
child_associations.map do |association|
|
1092
|
+
association.__send__(execute_hooks ? :save : :save!)
|
1093
|
+
end.all?
|
959
1094
|
end
|
960
1095
|
|
961
1096
|
# Checks if the resource has unsaved changes
|
@@ -963,7 +1098,7 @@ module DataMapper
|
|
963
1098
|
# @return [Boolean]
|
964
1099
|
# true if the resource has unsaged changes
|
965
1100
|
#
|
966
|
-
# @api
|
1101
|
+
# @api semipublic
|
967
1102
|
def dirty_self?
|
968
1103
|
if original_attributes.any?
|
969
1104
|
true
|
@@ -982,8 +1117,8 @@ module DataMapper
|
|
982
1117
|
# @api private
|
983
1118
|
def dirty_parents?
|
984
1119
|
run_once(false) do
|
985
|
-
|
986
|
-
|
1120
|
+
parent_associations.any? do |association|
|
1121
|
+
association.__send__(:dirty_self?) || association.__send__(:dirty_parents?)
|
987
1122
|
end
|
988
1123
|
end
|
989
1124
|
end
|
@@ -998,7 +1133,7 @@ module DataMapper
|
|
998
1133
|
#
|
999
1134
|
# @api private
|
1000
1135
|
def dirty_children?
|
1001
|
-
|
1136
|
+
child_associations.any? { |association| association.dirty? }
|
1002
1137
|
end
|
1003
1138
|
|
1004
1139
|
# Return true if +other+'s is equivalent or equal to +self+'s
|
@@ -1013,17 +1148,46 @@ module DataMapper
|
|
1013
1148
|
#
|
1014
1149
|
# @api private
|
1015
1150
|
def cmp?(other, operator)
|
1016
|
-
return false unless
|
1017
|
-
|
1151
|
+
return false unless repository.send(operator, other.repository) &&
|
1152
|
+
key.send(operator, other.key)
|
1018
1153
|
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1154
|
+
if saved? && other.saved?
|
1155
|
+
# if dirty attributes match then they are the same resource
|
1156
|
+
dirty_attributes == other.dirty_attributes
|
1157
|
+
else
|
1158
|
+
# compare properties for unsaved resources
|
1159
|
+
properties.all? do |property|
|
1160
|
+
__send__(property.name).send(operator, other.__send__(property.name))
|
1161
|
+
end
|
1023
1162
|
end
|
1163
|
+
end
|
1024
1164
|
|
1025
|
-
|
1026
|
-
|
1165
|
+
# @api private
|
1166
|
+
def set_default_value(subject)
|
1167
|
+
return unless persisted_state.respond_to?(:set_default_value, true)
|
1168
|
+
persisted_state.__send__(:set_default_value, subject)
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
# @api private
|
1172
|
+
def valid_attributes?
|
1173
|
+
original_attributes.each_key do |property|
|
1174
|
+
return false if property.kind_of?(Property) && !property.valid?(property.get!(self))
|
1175
|
+
end
|
1176
|
+
true
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
# Execute all the queued up hooks for a given type and name
|
1180
|
+
#
|
1181
|
+
# @param [Symbol] type
|
1182
|
+
# the type of hook to execute (before or after)
|
1183
|
+
# @param [Symbol] name
|
1184
|
+
# the name of the hook to execute
|
1185
|
+
#
|
1186
|
+
# @return [undefined]
|
1187
|
+
#
|
1188
|
+
# @api private
|
1189
|
+
def execute_hooks_for(type, name)
|
1190
|
+
model.hooks[name][type].each { |hook| hook.call(self) }
|
1027
1191
|
end
|
1028
1192
|
|
1029
1193
|
# Raises an exception if #update is performed on a dirty resource
|
@@ -1039,7 +1203,7 @@ module DataMapper
|
|
1039
1203
|
# @api private
|
1040
1204
|
def assert_update_clean_only(method)
|
1041
1205
|
if dirty?
|
1042
|
-
raise UpdateConflictError, "#{model}##{method} cannot be called on a dirty resource"
|
1206
|
+
raise UpdateConflictError, "#{model}##{method} cannot be called on a #{new? ? 'new' : 'dirty'} resource"
|
1043
1207
|
end
|
1044
1208
|
end
|
1045
1209
|
|
@@ -1060,6 +1224,25 @@ module DataMapper
|
|
1060
1224
|
end
|
1061
1225
|
end
|
1062
1226
|
|
1227
|
+
# Raises an exception if #save returns false
|
1228
|
+
#
|
1229
|
+
# @param [Symbol] method
|
1230
|
+
# the name of the method to use in the exception
|
1231
|
+
# @param [Boolean] save_result
|
1232
|
+
# the result of the #save call
|
1233
|
+
#
|
1234
|
+
# @return [undefined]
|
1235
|
+
#
|
1236
|
+
# @raise [SaveFailureError]
|
1237
|
+
# raise if the resource was not saved
|
1238
|
+
#
|
1239
|
+
# @api private
|
1240
|
+
def assert_save_successful(method, save_retval)
|
1241
|
+
if save_retval != true && raise_on_save_failure
|
1242
|
+
raise SaveFailureError, "#{model}##{method} returned #{save_retval.inspect}, #{model} was not saved"
|
1243
|
+
end
|
1244
|
+
end
|
1245
|
+
|
1063
1246
|
# Prevent a method from being in the stack more than once
|
1064
1247
|
#
|
1065
1248
|
# The purpose of this method is to prevent SystemStackError from
|