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/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
|