dm-core 0.9.11 → 0.10.0
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/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -50
- data/Manifest.txt +66 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +6 -7
- data/SPECS +2 -29
- data/TODO +1 -1
- data/deps.rip +2 -0
- data/dm-core.gemspec +11 -15
- data/lib/dm-core.rb +105 -110
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/adapters/abstract_adapter.rb +251 -181
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +561 -156
- data/lib/dm-core/collection.rb +1101 -379
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +247 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/property.rb +808 -273
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query.rb +1037 -483
- data/lib/dm-core/query/conditions/comparison.rb +872 -0
- data/lib/dm-core/query/conditions/operation.rb +221 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +84 -0
- data/lib/dm-core/query/path.rb +138 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/repository.rb +210 -94
- data/lib/dm-core/resource.rb +641 -421
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +22 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/migrations_spec.rb +359 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1670 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +74 -111
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- data/spec/unit/type_spec.rb +0 -119
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# TODO: add #reverse and #reverse! methods
|
|
2
|
+
|
|
3
|
+
module DataMapper
|
|
4
|
+
class Query
|
|
5
|
+
class Sort
|
|
6
|
+
# TODO: document
|
|
7
|
+
# @api semipublic
|
|
8
|
+
attr_reader :value
|
|
9
|
+
|
|
10
|
+
# TODO: document
|
|
11
|
+
# @api semipublic
|
|
12
|
+
def direction
|
|
13
|
+
@ascending ? :ascending : :descending
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# TODO: document
|
|
17
|
+
# @api private
|
|
18
|
+
def <=>(other)
|
|
19
|
+
other_value = other.value
|
|
20
|
+
|
|
21
|
+
cmp = case
|
|
22
|
+
when @value.nil? && other_value.nil?
|
|
23
|
+
0
|
|
24
|
+
when @value.nil?
|
|
25
|
+
1
|
|
26
|
+
when other_value.nil?
|
|
27
|
+
-1
|
|
28
|
+
else
|
|
29
|
+
@value <=> other_value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@ascending ? cmp : cmp * -1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# TODO: document
|
|
38
|
+
# @api private
|
|
39
|
+
def initialize(value, ascending = true)
|
|
40
|
+
@value = value
|
|
41
|
+
@ascending = ascending
|
|
42
|
+
end
|
|
43
|
+
end # class Sort
|
|
44
|
+
end # class Query
|
|
45
|
+
end # module DataMapper
|
data/lib/dm-core/repository.rb
CHANGED
|
@@ -1,106 +1,264 @@
|
|
|
1
1
|
module DataMapper
|
|
2
2
|
class Repository
|
|
3
|
-
include Assertions
|
|
3
|
+
include Extlib::Assertions
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
# Get the list of adapters registered for all Repositories,
|
|
6
|
+
# keyed by repository name.
|
|
7
|
+
#
|
|
8
|
+
# TODO: create example
|
|
9
|
+
#
|
|
10
|
+
# @return [Hash(Symbol => Adapters::AbstractAdapter)]
|
|
11
|
+
# the adapters registered for all Repositories
|
|
8
12
|
#
|
|
9
|
-
# @
|
|
13
|
+
# @api private
|
|
10
14
|
def self.adapters
|
|
11
|
-
@adapters
|
|
15
|
+
@adapters ||= {}
|
|
12
16
|
end
|
|
13
17
|
|
|
18
|
+
# Get the stack of current repository contexts
|
|
19
|
+
#
|
|
20
|
+
# TODO: create example
|
|
21
|
+
#
|
|
22
|
+
# @return [Array]
|
|
23
|
+
# List of Repository contexts for the current Thread
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
14
26
|
def self.context
|
|
15
27
|
Thread.current[:dm_repository_contexts] ||= []
|
|
16
28
|
end
|
|
17
29
|
|
|
30
|
+
# Get the default name of this Repository
|
|
31
|
+
#
|
|
32
|
+
# TODO: create example
|
|
33
|
+
#
|
|
34
|
+
# @return [Symbol]
|
|
35
|
+
# the default name of this repository
|
|
36
|
+
#
|
|
37
|
+
# @api private
|
|
18
38
|
def self.default_name
|
|
19
39
|
:default
|
|
20
40
|
end
|
|
21
41
|
|
|
42
|
+
# TODO: document
|
|
43
|
+
# @api semipublic
|
|
22
44
|
attr_reader :name
|
|
23
45
|
|
|
46
|
+
# Get the adapter for this repository
|
|
47
|
+
#
|
|
48
|
+
# Lazy loads adapter setup from registered adapters
|
|
49
|
+
#
|
|
50
|
+
# TODO: create example
|
|
51
|
+
#
|
|
52
|
+
# @return [Adapters::AbstractAdapter]
|
|
53
|
+
# the adapter for this repository
|
|
54
|
+
#
|
|
55
|
+
# @raise [RepositoryNotSetupError]
|
|
56
|
+
# if there is no adapter registered for a repository named @name
|
|
57
|
+
#
|
|
58
|
+
# @api semipublic
|
|
24
59
|
def adapter
|
|
25
60
|
# Make adapter instantiation lazy so we can defer repository setup until it's actually
|
|
26
61
|
# needed. Do not remove this code.
|
|
27
|
-
@adapter ||=
|
|
28
|
-
|
|
29
|
-
|
|
62
|
+
@adapter ||=
|
|
63
|
+
begin
|
|
64
|
+
raise RepositoryNotSetupError, "Adapter not set: #{@name}. Did you forget to setup?" \
|
|
65
|
+
unless self.class.adapters.key?(@name)
|
|
30
66
|
|
|
31
|
-
|
|
32
|
-
|
|
67
|
+
self.class.adapters[@name]
|
|
68
|
+
end
|
|
33
69
|
end
|
|
34
70
|
|
|
71
|
+
# Get the identity for a particular model within this repository.
|
|
72
|
+
#
|
|
73
|
+
# If one doesn't yet exist, create a new default in-memory IdentityMap
|
|
74
|
+
# for the requested model.
|
|
75
|
+
#
|
|
76
|
+
# TODO: create example
|
|
77
|
+
#
|
|
78
|
+
# @param [Model] model
|
|
79
|
+
# Model whose identity map should be returned
|
|
80
|
+
#
|
|
81
|
+
# @return [IdentityMap]
|
|
82
|
+
# The IdentityMap for model in this Repository
|
|
83
|
+
#
|
|
84
|
+
# @api private
|
|
35
85
|
def identity_map(model)
|
|
36
|
-
@identity_maps[model] ||= IdentityMap.new
|
|
86
|
+
@identity_maps[model.base_model] ||= IdentityMap.new
|
|
37
87
|
end
|
|
38
88
|
|
|
39
|
-
#
|
|
89
|
+
# Executes a block in the scope of this Repository
|
|
90
|
+
#
|
|
91
|
+
# TODO: create example
|
|
92
|
+
#
|
|
93
|
+
# @yieldparam [Repository] repository
|
|
94
|
+
# yields self within the block
|
|
95
|
+
#
|
|
96
|
+
# @yield
|
|
97
|
+
# execute block in the scope of this Repository
|
|
98
|
+
#
|
|
99
|
+
# @api private
|
|
40
100
|
def scope
|
|
41
101
|
Repository.context << self
|
|
42
102
|
|
|
43
103
|
begin
|
|
44
|
-
|
|
104
|
+
yield self
|
|
45
105
|
ensure
|
|
46
106
|
Repository.context.pop
|
|
47
107
|
end
|
|
48
108
|
end
|
|
49
109
|
|
|
110
|
+
# Create one or more resource instances in this repository.
|
|
111
|
+
#
|
|
112
|
+
# TODO: create example
|
|
113
|
+
#
|
|
114
|
+
# @param [Enumerable(Resource)] resources
|
|
115
|
+
# The list of resources (model instances) to create
|
|
116
|
+
#
|
|
117
|
+
# @return [Integer]
|
|
118
|
+
# The number of records that were actually saved into the data-store
|
|
119
|
+
#
|
|
120
|
+
# @api semipublic
|
|
50
121
|
def create(resources)
|
|
51
122
|
adapter.create(resources)
|
|
52
123
|
end
|
|
53
124
|
|
|
54
|
-
|
|
55
|
-
# retrieve a collection of results of a query
|
|
125
|
+
# Retrieve a collection of results of a query
|
|
56
126
|
#
|
|
57
|
-
#
|
|
58
|
-
# @return <DataMapper::Collection> result set of the query
|
|
59
|
-
# @see DataMapper::Query
|
|
60
|
-
def read_many(query)
|
|
61
|
-
adapter.read_many(query)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
##
|
|
65
|
-
# retrieve a resource instance by a query
|
|
127
|
+
# TODO: create example
|
|
66
128
|
#
|
|
67
|
-
# @param
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# @
|
|
71
|
-
|
|
72
|
-
|
|
129
|
+
# @param [Query] query
|
|
130
|
+
# composition of the query to perform
|
|
131
|
+
#
|
|
132
|
+
# @return [Array]
|
|
133
|
+
# result set of the query
|
|
134
|
+
#
|
|
135
|
+
# @api semipublic
|
|
136
|
+
def read(query)
|
|
137
|
+
return [] unless query.valid?
|
|
138
|
+
query.model.load(adapter.read(query), query)
|
|
73
139
|
end
|
|
74
140
|
|
|
75
|
-
|
|
76
|
-
|
|
141
|
+
# Update the attributes of one or more resource instances
|
|
142
|
+
#
|
|
143
|
+
# TODO: create example
|
|
144
|
+
#
|
|
145
|
+
# @param [Hash(Property => Object)] attributes
|
|
146
|
+
# hash of attribute values to set, keyed by Property
|
|
147
|
+
# @param [Collection] collection
|
|
148
|
+
# collection of records to be updated
|
|
149
|
+
#
|
|
150
|
+
# @return [Integer]
|
|
151
|
+
# the number of records updated
|
|
152
|
+
#
|
|
153
|
+
# @api semipublic
|
|
154
|
+
def update(attributes, collection)
|
|
155
|
+
return 0 unless collection.query.valid?
|
|
156
|
+
adapter.update(attributes, collection)
|
|
77
157
|
end
|
|
78
158
|
|
|
79
|
-
|
|
80
|
-
|
|
159
|
+
# Delete one or more resource instances
|
|
160
|
+
#
|
|
161
|
+
# TODO: create example
|
|
162
|
+
#
|
|
163
|
+
# @param [Collection] collection
|
|
164
|
+
# collection of records to be deleted
|
|
165
|
+
#
|
|
166
|
+
# @return [Integer]
|
|
167
|
+
# the number of records deleted
|
|
168
|
+
#
|
|
169
|
+
# @api semipublic
|
|
170
|
+
def delete(collection)
|
|
171
|
+
return 0 unless collection.query.valid?
|
|
172
|
+
adapter.delete(collection)
|
|
81
173
|
end
|
|
82
174
|
|
|
175
|
+
# Compares another Repository for equality
|
|
176
|
+
#
|
|
177
|
+
# Repository is equal to +other+ if they are the same object (identity)
|
|
178
|
+
# or if they are of the same class and have the same name
|
|
179
|
+
#
|
|
180
|
+
# @param [Repository] other
|
|
181
|
+
# the other Repository to compare with
|
|
182
|
+
#
|
|
183
|
+
# @return [Boolean]
|
|
184
|
+
# true if they are equal, false if not
|
|
185
|
+
#
|
|
186
|
+
# @api public
|
|
83
187
|
def eql?(other)
|
|
84
|
-
|
|
85
|
-
|
|
188
|
+
if equal?(other)
|
|
189
|
+
return true
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
unless instance_of?(other.class)
|
|
193
|
+
return false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
cmp?(other, :eql?)
|
|
86
197
|
end
|
|
87
198
|
|
|
88
|
-
|
|
199
|
+
# Compares another Repository for equivalency
|
|
200
|
+
#
|
|
201
|
+
# Repository is equal to +other+ if they are the same object (identity)
|
|
202
|
+
# or if they both have the same name
|
|
203
|
+
#
|
|
204
|
+
# @param [Repository] other
|
|
205
|
+
# the other Repository to compare with
|
|
206
|
+
#
|
|
207
|
+
# @return [Boolean]
|
|
208
|
+
# true if they are equal, false if not
|
|
209
|
+
#
|
|
210
|
+
# @api public
|
|
211
|
+
def ==(other)
|
|
212
|
+
if equal?(other)
|
|
213
|
+
return true
|
|
214
|
+
end
|
|
89
215
|
|
|
90
|
-
|
|
91
|
-
|
|
216
|
+
unless other.respond_to?(:name)
|
|
217
|
+
return false
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
unless other.respond_to?(:adapter)
|
|
221
|
+
return false
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
cmp?(other, :==)
|
|
92
225
|
end
|
|
93
226
|
|
|
94
|
-
|
|
95
|
-
|
|
227
|
+
# Return the hash of the Repository
|
|
228
|
+
#
|
|
229
|
+
# This is necessary for properly determining the unique Repository
|
|
230
|
+
# in a Set or Hash
|
|
231
|
+
#
|
|
232
|
+
# @return [Integer]
|
|
233
|
+
# the Hash of the Repository name
|
|
234
|
+
#
|
|
235
|
+
# @api private
|
|
236
|
+
def hash
|
|
237
|
+
name.hash
|
|
96
238
|
end
|
|
97
239
|
|
|
98
|
-
|
|
99
|
-
|
|
240
|
+
# Return a human readable representation of the repository
|
|
241
|
+
#
|
|
242
|
+
# TODO: create example
|
|
243
|
+
#
|
|
244
|
+
# @return [String]
|
|
245
|
+
# human readable representation of the repository
|
|
246
|
+
#
|
|
247
|
+
# @api private
|
|
248
|
+
def inspect
|
|
249
|
+
"#<#{self.class.name} @name=#{@name}>"
|
|
100
250
|
end
|
|
101
251
|
|
|
102
252
|
private
|
|
103
253
|
|
|
254
|
+
# Initializes a new Repository
|
|
255
|
+
#
|
|
256
|
+
# TODO: create example
|
|
257
|
+
#
|
|
258
|
+
# @param [Symbol] name
|
|
259
|
+
# The name of the Repository
|
|
260
|
+
#
|
|
261
|
+
# @api semipublic
|
|
104
262
|
def initialize(name)
|
|
105
263
|
assert_kind_of 'name', name, Symbol
|
|
106
264
|
|
|
@@ -108,60 +266,18 @@ module DataMapper
|
|
|
108
266
|
@identity_maps = {}
|
|
109
267
|
end
|
|
110
268
|
|
|
111
|
-
# TODO:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# TODO: move to dm-more/dm-migrations
|
|
119
|
-
def type_map
|
|
120
|
-
@type_map ||= TypeMap.new(adapter.class.type_map)
|
|
269
|
+
# TODO: document
|
|
270
|
+
# @api private
|
|
271
|
+
def cmp?(other, operator)
|
|
272
|
+
unless name.send(operator, other.name)
|
|
273
|
+
return false
|
|
121
274
|
end
|
|
122
275
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# @return <True, False> whether or not the data-store exists for this repo
|
|
126
|
-
#
|
|
127
|
-
# TODO: move to dm-more/dm-migrations
|
|
128
|
-
def storage_exists?(storage_name)
|
|
129
|
-
adapter.storage_exists?(storage_name)
|
|
276
|
+
unless adapter.send(operator, other.adapter)
|
|
277
|
+
return false
|
|
130
278
|
end
|
|
131
279
|
|
|
132
|
-
|
|
133
|
-
def migrate!
|
|
134
|
-
Migrator.migrate(name)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# TODO: move to dm-more/dm-migrations
|
|
138
|
-
def auto_migrate!
|
|
139
|
-
AutoMigrator.auto_migrate(name)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# TODO: move to dm-more/dm-migrations
|
|
143
|
-
def auto_upgrade!
|
|
144
|
-
AutoMigrator.auto_upgrade(name)
|
|
145
|
-
end
|
|
280
|
+
true
|
|
146
281
|
end
|
|
147
|
-
|
|
148
|
-
include Migration
|
|
149
|
-
|
|
150
|
-
# TODO: move to dm-more/dm-transactions
|
|
151
|
-
module Transaction
|
|
152
|
-
##
|
|
153
|
-
# Produce a new Transaction for this Repository
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
# @return <DataMapper::Adapters::Transaction> a new Transaction (in state
|
|
157
|
-
# :none) that can be used to execute code #with_transaction
|
|
158
|
-
#
|
|
159
|
-
# TODO: move to dm-more/dm-transactions
|
|
160
|
-
def transaction
|
|
161
|
-
DataMapper::Transaction.new(self)
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
include Transaction
|
|
166
282
|
end # class Repository
|
|
167
283
|
end # module DataMapper
|
data/lib/dm-core/resource.rb
CHANGED
|
@@ -1,94 +1,161 @@
|
|
|
1
|
-
require 'set'
|
|
2
|
-
|
|
3
1
|
module DataMapper
|
|
4
2
|
module Resource
|
|
5
|
-
include Assertions
|
|
3
|
+
include Extlib::Assertions
|
|
4
|
+
extend Chainable
|
|
5
|
+
extend Deprecate
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
# DataMapper::Resource.
|
|
11
|
-
#
|
|
12
|
-
# This is a useful way to extend DataMapper::Resource while still retaining
|
|
13
|
-
# a self.included method.
|
|
14
|
-
#
|
|
15
|
-
# @param [Module] inclusion the module that is to be appended to the module
|
|
16
|
-
# after DataMapper::Resource
|
|
17
|
-
#
|
|
18
|
-
# @return [TrueClass, FalseClass] whether or not the inclusions have been
|
|
19
|
-
# successfully appended to the list
|
|
20
|
-
# @return <TrueClass, FalseClass>
|
|
21
|
-
#-
|
|
22
|
-
# @api public
|
|
7
|
+
deprecate :new_record?, :new?
|
|
8
|
+
|
|
9
|
+
# @deprecated
|
|
23
10
|
def self.append_inclusions(*inclusions)
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
warn "DataMapper::Resource.append_inclusions is deprecated, use DataMapper::Model.append_inclusions instead (#{caller[0]})"
|
|
12
|
+
Model.append_inclusions(*inclusions)
|
|
26
13
|
end
|
|
27
14
|
|
|
15
|
+
# @deprecated
|
|
28
16
|
def self.extra_inclusions
|
|
29
|
-
|
|
17
|
+
warn "DataMapper::Resource.extra_inclusions is deprecated, use DataMapper::Model.extra_inclusions instead (#{caller[0]})"
|
|
18
|
+
Model.extra_inclusions
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @deprecated
|
|
22
|
+
def self.descendants
|
|
23
|
+
warn "DataMapper::Resource.descendants is deprecated, use DataMapper::Model.descendants instead (#{caller[0]})"
|
|
24
|
+
DataMapper::Model.descendants
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Deprecated API for updating attributes and saving Resource
|
|
28
|
+
#
|
|
29
|
+
# @see #update
|
|
30
|
+
#
|
|
31
|
+
# @deprecated
|
|
32
|
+
def update_attributes(attributes = {}, *allowed)
|
|
33
|
+
assert_update_clean_only(:update_attributes)
|
|
34
|
+
|
|
35
|
+
warn "#{model}#update_attributes is deprecated, use #{model}#update instead (#{caller[0]})"
|
|
36
|
+
|
|
37
|
+
if allowed.any?
|
|
38
|
+
warn "specifying allowed in #{model}#update_attributes is deprecated, " \
|
|
39
|
+
"use Hash#only to filter the attributes in the caller (#{caller[0]})"
|
|
40
|
+
attributes = attributes.only(*allowed)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
update(attributes)
|
|
30
44
|
end
|
|
31
45
|
|
|
32
|
-
#
|
|
33
|
-
# it gets all the methods
|
|
46
|
+
# Makes sure a class gets all the methods when it includes Resource
|
|
34
47
|
#
|
|
35
|
-
# -
|
|
36
48
|
# @api private
|
|
37
49
|
def self.included(model)
|
|
38
50
|
model.extend Model
|
|
39
|
-
model.extend ClassMethods if defined?(ClassMethods)
|
|
40
|
-
model.const_set('Resource', self) unless model.const_defined?('Resource')
|
|
41
|
-
extra_inclusions.each { |inclusion| model.send(:include, inclusion) }
|
|
42
|
-
descendants << model
|
|
43
|
-
class << model
|
|
44
|
-
@_valid_model = false
|
|
45
|
-
attr_reader :_valid_model
|
|
46
|
-
end
|
|
47
51
|
end
|
|
48
52
|
|
|
49
|
-
#
|
|
53
|
+
# Collection this resource associated with.
|
|
54
|
+
# Used by SEL.
|
|
50
55
|
#
|
|
51
|
-
#
|
|
52
|
-
|
|
56
|
+
# @api private
|
|
57
|
+
attr_writer :collection
|
|
58
|
+
|
|
59
|
+
# TODO: document
|
|
60
|
+
# @api public
|
|
61
|
+
alias_method :model, :class
|
|
62
|
+
|
|
63
|
+
# Repository this resource belongs to in the context of this collection
|
|
64
|
+
# or of the resource's class.
|
|
53
65
|
#
|
|
54
|
-
#
|
|
66
|
+
# @return [Repository]
|
|
67
|
+
# the respository this resource belongs to, in the context of
|
|
68
|
+
# a collection OR in the instance's Model's context
|
|
55
69
|
#
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
# @api semipublic
|
|
71
|
+
def repository
|
|
72
|
+
# only set @repository explicitly when persisted
|
|
73
|
+
defined?(@repository) ? @repository : model.repository
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Retrieve the key(s) for this resource.
|
|
59
77
|
#
|
|
60
|
-
#
|
|
78
|
+
# This always returns the persisted key value,
|
|
79
|
+
# even if the key is changed and not yet persisted.
|
|
80
|
+
# This is done so all relations still work.
|
|
61
81
|
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
# @return [Array(Key)]
|
|
83
|
+
# the key(s) identifying this resource
|
|
84
|
+
#
|
|
85
|
+
# @api public
|
|
86
|
+
def key
|
|
87
|
+
return @key if defined?(@key)
|
|
88
|
+
|
|
89
|
+
key = model.key(repository_name).map do |property|
|
|
90
|
+
original_attributes[property] || (property.loaded?(self) ? property.get!(self) : nil)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
return unless key.all?
|
|
94
|
+
|
|
95
|
+
# memoize the key if the Resource is not frozen
|
|
96
|
+
@key = key unless frozen?
|
|
97
|
+
|
|
98
|
+
key
|
|
66
99
|
end
|
|
67
100
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
101
|
+
# Checks if this Resource instance is new
|
|
102
|
+
#
|
|
103
|
+
# @return [Boolean]
|
|
104
|
+
# true if the resource is new and not saved
|
|
105
|
+
#
|
|
106
|
+
# @api public
|
|
107
|
+
def new?
|
|
108
|
+
!saved?
|
|
109
|
+
end
|
|
70
110
|
|
|
71
|
-
|
|
111
|
+
# Checks if this Resource instance is saved
|
|
112
|
+
#
|
|
113
|
+
# @return [Boolean]
|
|
114
|
+
# true if the resource has been saved
|
|
115
|
+
#
|
|
116
|
+
# @api public
|
|
117
|
+
def saved?
|
|
118
|
+
@saved == true
|
|
119
|
+
end
|
|
72
120
|
|
|
73
|
-
|
|
121
|
+
# Checks if the resource has no changes to save
|
|
122
|
+
#
|
|
123
|
+
# @return [Boolean]
|
|
124
|
+
# true if the resource may not be persisted
|
|
125
|
+
#
|
|
126
|
+
# @api public
|
|
127
|
+
def clean?
|
|
128
|
+
!dirty?
|
|
129
|
+
end
|
|
74
130
|
|
|
75
|
-
#
|
|
76
|
-
# but use this method. This method handels the lazy loading the attribute and returning
|
|
77
|
-
# of defaults if nessesary.
|
|
131
|
+
# Checks if the resource has unsaved changes
|
|
78
132
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
133
|
+
# @return [Boolean]
|
|
134
|
+
# true if resource may be persisted
|
|
81
135
|
#
|
|
82
|
-
#
|
|
83
|
-
|
|
136
|
+
# @api public
|
|
137
|
+
def dirty?
|
|
138
|
+
if original_attributes.any?
|
|
139
|
+
true
|
|
140
|
+
elsif new?
|
|
141
|
+
model.serial || properties.any? { |property| property.default? }
|
|
142
|
+
else
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Returns the value of the attribute.
|
|
84
148
|
#
|
|
85
|
-
#
|
|
149
|
+
# Do not read from instance variables directly, but use this method.
|
|
150
|
+
# This method handles lazy loading the attribute and returning of
|
|
151
|
+
# defaults if nessesary.
|
|
86
152
|
#
|
|
87
|
-
#
|
|
153
|
+
# @example
|
|
154
|
+
# class Foo
|
|
88
155
|
# include DataMapper::Resource
|
|
89
156
|
#
|
|
90
157
|
# property :first_name, String
|
|
91
|
-
# property :last_name,
|
|
158
|
+
# property :last_name, String
|
|
92
159
|
#
|
|
93
160
|
# def full_name
|
|
94
161
|
# "#{attribute_get(:first_name)} #{attribute_get(:last_name)}"
|
|
@@ -100,32 +167,32 @@ module DataMapper
|
|
|
100
167
|
# end
|
|
101
168
|
# end
|
|
102
169
|
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
170
|
+
# @param [Symbol] name
|
|
171
|
+
# name of attribute to retrieve
|
|
172
|
+
#
|
|
173
|
+
# @return [Object]
|
|
174
|
+
# the value stored at that given attribute
|
|
175
|
+
# (nil if none, and default if necessary)
|
|
176
|
+
#
|
|
177
|
+
# @api public
|
|
105
178
|
def attribute_get(name)
|
|
106
179
|
properties[name].get(self)
|
|
107
180
|
end
|
|
108
181
|
|
|
109
|
-
|
|
182
|
+
alias [] attribute_get
|
|
183
|
+
|
|
184
|
+
# Sets the value of the attribute and marks the attribute as dirty
|
|
110
185
|
# if it has been changed so that it may be saved. Do not set from
|
|
111
186
|
# instance variables directly, but use this method. This method
|
|
112
|
-
#
|
|
187
|
+
# handles the lazy loading the property and returning of defaults
|
|
113
188
|
# if nessesary.
|
|
114
189
|
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
# value<Type>:: value to store at that location
|
|
118
|
-
#
|
|
119
|
-
# ==== Returns
|
|
120
|
-
# <Types>:: the value stored at that given attribute, nil if none, and default if necessary
|
|
121
|
-
#
|
|
122
|
-
# ==== Example
|
|
123
|
-
#
|
|
124
|
-
# Class Foo
|
|
190
|
+
# @example
|
|
191
|
+
# class Foo
|
|
125
192
|
# include DataMapper::Resource
|
|
126
193
|
#
|
|
127
194
|
# property :first_name, String
|
|
128
|
-
# property :last_name,
|
|
195
|
+
# property :last_name, String
|
|
129
196
|
#
|
|
130
197
|
# def full_name(name)
|
|
131
198
|
# name = name.split(' ')
|
|
@@ -141,531 +208,684 @@ module DataMapper
|
|
|
141
208
|
# end
|
|
142
209
|
# end
|
|
143
210
|
#
|
|
144
|
-
#
|
|
145
|
-
#
|
|
211
|
+
# @param [Symbol] name
|
|
212
|
+
# name of attribute to set
|
|
213
|
+
# @param [Object] value
|
|
214
|
+
# value to store
|
|
215
|
+
#
|
|
216
|
+
# @return [Object]
|
|
217
|
+
# the value stored at that given attribute, nil if none,
|
|
218
|
+
# and default if necessary
|
|
219
|
+
#
|
|
220
|
+
# @api public
|
|
146
221
|
def attribute_set(name, value)
|
|
147
222
|
properties[name].set(self, value)
|
|
148
223
|
end
|
|
149
224
|
|
|
150
|
-
|
|
225
|
+
alias []= attribute_set
|
|
226
|
+
|
|
227
|
+
# Gets all the attributes of the Resource instance
|
|
151
228
|
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
229
|
+
# @param [Symbol] key_on
|
|
230
|
+
# Use this attribute of the Property as keys.
|
|
231
|
+
# defaults to :name. :field is useful for adapters
|
|
232
|
+
# :property or nil use the actual Property object.
|
|
155
233
|
#
|
|
234
|
+
# @return [Hash]
|
|
235
|
+
# All the attributes
|
|
236
|
+
#
|
|
237
|
+
# @api public
|
|
238
|
+
def attributes(key_on = :name)
|
|
239
|
+
attributes = {}
|
|
240
|
+
properties.each do |property|
|
|
241
|
+
if model.public_method_defined?(name = property.name)
|
|
242
|
+
key = case key_on
|
|
243
|
+
when :name then name
|
|
244
|
+
when :field then property.field
|
|
245
|
+
else property
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
attributes[key] = send(name)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
attributes
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Assign values to multiple attributes in one call (mass assignment)
|
|
156
255
|
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
256
|
+
# @param [Hash] attributes
|
|
257
|
+
# names and values of attributes to assign
|
|
159
258
|
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
259
|
+
# @return [Hash]
|
|
260
|
+
# names and values of attributes assigned
|
|
162
261
|
#
|
|
163
|
-
# -
|
|
164
262
|
# @api public
|
|
165
|
-
def
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# get all the loaded and non-loaded properties that are not keys,
|
|
178
|
-
# since the key comparison was performed earlier
|
|
179
|
-
loaded, not_loaded = properties.select { |p| !p.key? }.partition do |property|
|
|
180
|
-
attribute_loaded?(property.name) && other.attribute_loaded?(property.name)
|
|
263
|
+
def attributes=(attributes)
|
|
264
|
+
attributes.each do |name, value|
|
|
265
|
+
case name
|
|
266
|
+
when String, Symbol
|
|
267
|
+
if model.public_method_defined?(setter = "#{name}=")
|
|
268
|
+
send(setter, value)
|
|
269
|
+
else
|
|
270
|
+
raise ArgumentError, "The attribute '#{name}' is not accessible in #{model}"
|
|
271
|
+
end
|
|
272
|
+
when Associations::Relationship, Property
|
|
273
|
+
name.set(self, value)
|
|
274
|
+
end
|
|
181
275
|
end
|
|
182
|
-
|
|
183
|
-
# check all loaded properties, and then all unloaded properties
|
|
184
|
-
(loaded + not_loaded).all? { |p| p.get(self) == p.get(other) }
|
|
185
276
|
end
|
|
186
277
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Computes a hash for the resource
|
|
278
|
+
# Reloads association and all child association
|
|
190
279
|
#
|
|
191
|
-
#
|
|
192
|
-
#
|
|
280
|
+
# @return [Resource]
|
|
281
|
+
# the receiver, the current Resource instance
|
|
193
282
|
#
|
|
194
|
-
# -
|
|
195
283
|
# @api public
|
|
196
|
-
def
|
|
197
|
-
|
|
284
|
+
def reload
|
|
285
|
+
if saved?
|
|
286
|
+
reload_attributes(loaded_properties)
|
|
287
|
+
child_relationships.each { |relationship| relationship.get!(self).reload }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
self
|
|
198
291
|
end
|
|
199
292
|
|
|
200
|
-
#
|
|
293
|
+
# Updates attributes and saves this Resource instance
|
|
201
294
|
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
295
|
+
# @param [Hash] attributes
|
|
296
|
+
# attributes to be updated
|
|
204
297
|
#
|
|
205
|
-
#
|
|
298
|
+
# @return [Boolean]
|
|
299
|
+
# true if resource and storage state match
|
|
206
300
|
#
|
|
207
|
-
# >> Foo.new
|
|
208
|
-
# => #<Foo name=nil updated_at=nil created_at=nil id=nil>
|
|
209
|
-
#
|
|
210
|
-
# -
|
|
211
301
|
# @api public
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
'<not loaded>'
|
|
218
|
-
else
|
|
219
|
-
send(property.getter).inspect
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
attrs << "#{property.name}=#{value}"
|
|
302
|
+
chainable do
|
|
303
|
+
def update(attributes = {})
|
|
304
|
+
assert_update_clean_only(:update)
|
|
305
|
+
self.attributes = attributes
|
|
306
|
+
save
|
|
223
307
|
end
|
|
308
|
+
end
|
|
224
309
|
|
|
225
|
-
|
|
310
|
+
# Updates attributes and saves this Resource instance, bypassing hooks
|
|
311
|
+
#
|
|
312
|
+
# @param [Hash] attributes
|
|
313
|
+
# attributes to be updated
|
|
314
|
+
#
|
|
315
|
+
# @return [Boolean]
|
|
316
|
+
# true if resource and storage state match
|
|
317
|
+
#
|
|
318
|
+
# @api public
|
|
319
|
+
def update!(attributes = {})
|
|
320
|
+
assert_update_clean_only(:update!)
|
|
321
|
+
self.attributes = attributes
|
|
322
|
+
save!
|
|
226
323
|
end
|
|
227
324
|
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
325
|
+
# Save the instance and loaded, dirty associations to the data-store
|
|
326
|
+
#
|
|
327
|
+
# @return [Boolean]
|
|
328
|
+
# true if Resource instance and all associations were saved
|
|
329
|
+
#
|
|
330
|
+
# @api public
|
|
331
|
+
chainable do
|
|
332
|
+
def save
|
|
333
|
+
save_parents && save_self && save_children
|
|
237
334
|
end
|
|
238
335
|
end
|
|
239
336
|
|
|
240
|
-
|
|
337
|
+
# Save the instance and loaded, dirty associations to the data-store, bypassing hooks
|
|
241
338
|
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
339
|
+
# @return [Boolean]
|
|
340
|
+
# true if Resource instance and all associations were saved
|
|
244
341
|
#
|
|
245
342
|
# @api public
|
|
246
|
-
def
|
|
247
|
-
|
|
343
|
+
def save!
|
|
344
|
+
save_parents(false) && save_self(false) && save_children(false)
|
|
248
345
|
end
|
|
249
346
|
|
|
250
|
-
#
|
|
251
|
-
# single key, and the model was defined with a primary key named
|
|
252
|
-
# something other than id
|
|
347
|
+
# Destroy the instance, remove it from the repository
|
|
253
348
|
#
|
|
254
|
-
#
|
|
255
|
-
#
|
|
349
|
+
# @return [Boolean]
|
|
350
|
+
# true if resource was destroyed
|
|
256
351
|
#
|
|
257
|
-
# --
|
|
258
352
|
# @api public
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
353
|
+
chainable do
|
|
354
|
+
def destroy
|
|
355
|
+
destroy!
|
|
356
|
+
end
|
|
262
357
|
end
|
|
263
358
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
359
|
+
# Destroy the instance, remove it from the repository, bypassing hooks
|
|
360
|
+
#
|
|
361
|
+
# @return [Boolean]
|
|
362
|
+
# true if resource was destroyed
|
|
363
|
+
#
|
|
364
|
+
# @api public
|
|
365
|
+
def destroy!
|
|
366
|
+
if saved? && repository.delete(Collection.new(query, [ self ])) == 1
|
|
367
|
+
@collection.delete(self) if @collection
|
|
368
|
+
reset
|
|
369
|
+
freeze
|
|
370
|
+
true
|
|
371
|
+
else
|
|
372
|
+
false
|
|
267
373
|
end
|
|
268
374
|
end
|
|
269
375
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
376
|
+
# Compares another Resource for equality
|
|
377
|
+
#
|
|
378
|
+
# Resource is equal to +other+ if they are the same object (identity)
|
|
379
|
+
# or if they are both of the *same model* and all of their attributes
|
|
380
|
+
# are equivalent
|
|
381
|
+
#
|
|
382
|
+
# @param [Resource] other
|
|
383
|
+
# the other Resource to compare with
|
|
384
|
+
#
|
|
385
|
+
# @return [Boolean]
|
|
386
|
+
# true if they are equal, false if not
|
|
387
|
+
#
|
|
388
|
+
# @api public
|
|
389
|
+
def eql?(other)
|
|
390
|
+
return true if equal?(other)
|
|
391
|
+
return false unless instance_of?(other.class)
|
|
273
392
|
|
|
274
|
-
|
|
275
|
-
@readonly == true
|
|
393
|
+
cmp?(other, :eql?)
|
|
276
394
|
end
|
|
277
395
|
|
|
278
|
-
#
|
|
396
|
+
# Compares another Resource for equivalency
|
|
279
397
|
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
398
|
+
# Resource is equal to +other+ if they are the same object (identity)
|
|
399
|
+
# or if they are both of the *same base model* and all of their attributes
|
|
400
|
+
# are equivalent
|
|
282
401
|
#
|
|
283
|
-
# @
|
|
402
|
+
# @param [Resource] other
|
|
403
|
+
# the other Resource to compare with
|
|
284
404
|
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
associations_saved = false
|
|
293
|
-
child_associations.each { |a| associations_saved |= a.save }
|
|
405
|
+
# @return [Boolean]
|
|
406
|
+
# true if they are equivalent, false if not
|
|
407
|
+
#
|
|
408
|
+
# @api public
|
|
409
|
+
def ==(other)
|
|
410
|
+
return true if equal?(other)
|
|
411
|
+
return false unless other.respond_to?(:model) && model.base_model.equal?(other.model.base_model)
|
|
294
412
|
|
|
295
|
-
|
|
413
|
+
cmp?(other, :==)
|
|
414
|
+
end
|
|
296
415
|
|
|
297
|
-
|
|
298
|
-
|
|
416
|
+
# Compares two Resources to allow them to be sorted
|
|
417
|
+
#
|
|
418
|
+
# @param [Resource] other
|
|
419
|
+
# The other Resource to compare with
|
|
420
|
+
#
|
|
421
|
+
# @return [Integer]
|
|
422
|
+
# Return 0 if Resources should be sorted as the same, -1 if the
|
|
423
|
+
# other Resource should be after self, and 1 if the other Resource
|
|
424
|
+
# should be before self
|
|
425
|
+
#
|
|
426
|
+
# @api public
|
|
427
|
+
def <=>(other)
|
|
428
|
+
unless other.kind_of?(model.base_model)
|
|
429
|
+
raise ArgumentError, "Cannot compare a #{other.model} instance with a #{model} instance"
|
|
299
430
|
end
|
|
431
|
+
cmp = 0
|
|
432
|
+
model.default_order(repository_name).map do |direction|
|
|
433
|
+
cmp = direction.get(self) <=> direction.get(other)
|
|
434
|
+
break if cmp != 0
|
|
435
|
+
end
|
|
436
|
+
cmp
|
|
437
|
+
end
|
|
300
438
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
439
|
+
# Returns hash value of the object.
|
|
440
|
+
# Two objects with the same hash value assumed equal (using eql? method)
|
|
441
|
+
#
|
|
442
|
+
# DataMapper resources are equal when their models have the same hash
|
|
443
|
+
# and they have the same set of properties
|
|
444
|
+
#
|
|
445
|
+
# When used as key in a Hash or Hash subclass, objects are compared
|
|
446
|
+
# by eql? and thus hash value has direct effect on lookup
|
|
447
|
+
#
|
|
448
|
+
# @api private
|
|
449
|
+
def hash
|
|
450
|
+
key.hash
|
|
306
451
|
end
|
|
307
452
|
|
|
308
|
-
#
|
|
453
|
+
# Get a Human-readable representation of this Resource instance
|
|
454
|
+
#
|
|
455
|
+
# Foo.new #=> #<Foo name=nil updated_at=nil created_at=nil id=nil>
|
|
309
456
|
#
|
|
310
|
-
#
|
|
311
|
-
#
|
|
457
|
+
# @return [String]
|
|
458
|
+
# Human-readable representation of this Resource instance
|
|
312
459
|
#
|
|
313
|
-
# --
|
|
314
460
|
# @api public
|
|
315
|
-
def
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
461
|
+
def inspect
|
|
462
|
+
# TODO: display relationship values
|
|
463
|
+
attrs = properties.map do |property|
|
|
464
|
+
value = if new? || property.loaded?(self)
|
|
465
|
+
property.get!(self).inspect
|
|
466
|
+
else
|
|
467
|
+
'<not loaded>'
|
|
468
|
+
end
|
|
322
469
|
|
|
323
|
-
|
|
324
|
-
# We'll set the original value to nil as if we had a new record
|
|
325
|
-
original_values[property.name] = nil if attribute_loaded?(property.name)
|
|
470
|
+
"#{property.instance_variable_name}=#{value}"
|
|
326
471
|
end
|
|
327
472
|
|
|
328
|
-
|
|
473
|
+
"#<#{model.name} #{attrs.join(' ')}>"
|
|
329
474
|
end
|
|
330
475
|
|
|
331
|
-
#
|
|
476
|
+
# Hash of original values of attributes that have unsaved changes
|
|
332
477
|
#
|
|
333
|
-
#
|
|
478
|
+
# @return [Hash]
|
|
479
|
+
# original values of attributes that have unsaved changes
|
|
480
|
+
#
|
|
481
|
+
# @api semipublic
|
|
482
|
+
def original_attributes
|
|
483
|
+
@original_attributes ||= {}
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Checks if an attribute has been loaded from the repository
|
|
334
487
|
#
|
|
488
|
+
# @example
|
|
335
489
|
# class Foo
|
|
336
490
|
# include DataMapper::Resource
|
|
337
|
-
#
|
|
338
|
-
# property :
|
|
491
|
+
#
|
|
492
|
+
# property :name, String
|
|
493
|
+
# property :description, Text, :lazy => false
|
|
339
494
|
# end
|
|
340
495
|
#
|
|
341
|
-
# Foo.new.attribute_loaded?(:description)
|
|
496
|
+
# Foo.new.attribute_loaded?(:description) #=> false
|
|
342
497
|
#
|
|
343
|
-
#
|
|
344
|
-
#
|
|
498
|
+
# @return [Boolean]
|
|
499
|
+
# true if ivar +name+ has been loaded
|
|
500
|
+
#
|
|
501
|
+
# @return [Boolean]
|
|
502
|
+
# true if ivar +name+ has been loaded
|
|
503
|
+
#
|
|
504
|
+
# @api private
|
|
345
505
|
def attribute_loaded?(name)
|
|
346
|
-
|
|
506
|
+
properties[name].loaded?(self)
|
|
347
507
|
end
|
|
348
508
|
|
|
349
|
-
#
|
|
509
|
+
# Fetches all the names of the attributes that have been loaded,
|
|
350
510
|
# even if they are lazy but have been called
|
|
351
511
|
#
|
|
352
|
-
#
|
|
353
|
-
# Array[<Symbol>]:: names of attributes that have been loaded
|
|
354
|
-
#
|
|
355
|
-
# ==== Example
|
|
356
|
-
#
|
|
512
|
+
# @example
|
|
357
513
|
# class Foo
|
|
358
514
|
# include DataMapper::Resource
|
|
359
|
-
#
|
|
360
|
-
# property :
|
|
515
|
+
#
|
|
516
|
+
# property :name, String
|
|
517
|
+
# property :description, Text, :lazy => false
|
|
361
518
|
# end
|
|
362
519
|
#
|
|
363
|
-
# Foo.new.
|
|
520
|
+
# Foo.new.loaded_properties #=> [ #<Property @model=Foo @name=:name> ]
|
|
364
521
|
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
|
|
368
|
-
|
|
522
|
+
# @return [Array(Property)]
|
|
523
|
+
# names of attributes that have been loaded
|
|
524
|
+
#
|
|
525
|
+
# @api private
|
|
526
|
+
def loaded_properties
|
|
527
|
+
properties.select { |property| property.loaded?(self) }
|
|
369
528
|
end
|
|
370
529
|
|
|
371
|
-
#
|
|
530
|
+
# Checks if an attribute has unsaved changes
|
|
372
531
|
#
|
|
373
|
-
#
|
|
374
|
-
#
|
|
532
|
+
# @param [Symbol] name
|
|
533
|
+
# name of attribute to check for unsaved changes
|
|
375
534
|
#
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
|
|
379
|
-
|
|
535
|
+
# @return [Boolean]
|
|
536
|
+
# true if attribute has unsaved changes
|
|
537
|
+
#
|
|
538
|
+
# @api semipublic
|
|
539
|
+
def attribute_dirty?(name)
|
|
540
|
+
dirty_attributes.key?(properties[name])
|
|
380
541
|
end
|
|
381
542
|
|
|
382
|
-
# Hash of attributes that have
|
|
543
|
+
# Hash of attributes that have unsaved changes
|
|
383
544
|
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
545
|
+
# @return [Hash]
|
|
546
|
+
# attributes that have unsaved changes
|
|
386
547
|
#
|
|
387
|
-
#
|
|
388
|
-
# @api private
|
|
548
|
+
# @api semipublic
|
|
389
549
|
def dirty_attributes
|
|
390
550
|
dirty_attributes = {}
|
|
391
|
-
properties = self.properties
|
|
392
551
|
|
|
393
|
-
|
|
394
|
-
property
|
|
395
|
-
new_value = property.get!(self)
|
|
396
|
-
|
|
397
|
-
dirty = case property.track
|
|
398
|
-
when :hash then old_value != new_value.hash
|
|
399
|
-
else
|
|
400
|
-
property.value(old_value) != property.value(new_value)
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
if dirty
|
|
404
|
-
property.hash
|
|
405
|
-
dirty_attributes[property] = property.value(new_value)
|
|
406
|
-
end
|
|
552
|
+
original_attributes.each_key do |property|
|
|
553
|
+
dirty_attributes[property] = property.value(property.get!(self))
|
|
407
554
|
end
|
|
408
555
|
|
|
409
556
|
dirty_attributes
|
|
410
557
|
end
|
|
411
558
|
|
|
412
|
-
#
|
|
559
|
+
# Saves the resource
|
|
413
560
|
#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
561
|
+
# @return [Boolean]
|
|
562
|
+
# true if the resource was successfully saved
|
|
416
563
|
#
|
|
417
|
-
#
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
564
|
+
# @api semipublic
|
|
565
|
+
def save_self(safe = true)
|
|
566
|
+
if safe
|
|
567
|
+
new? ? create_hook : update_hook
|
|
568
|
+
else
|
|
569
|
+
new? ? _create : _update
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Saves the parent resources
|
|
574
|
+
#
|
|
575
|
+
# @return [Boolean]
|
|
576
|
+
# true if the parents were successfully saved
|
|
577
|
+
#
|
|
578
|
+
# @api private
|
|
579
|
+
def save_parents(safe = true)
|
|
580
|
+
parent_relationships.all? do |relationship|
|
|
581
|
+
parent = relationship.get!(self)
|
|
582
|
+
if parent.dirty? ? parent.save_parents(safe) && parent.save_self(safe) : parent.saved?
|
|
583
|
+
relationship.set(self, parent) # set the FK values
|
|
584
|
+
end
|
|
585
|
+
end
|
|
421
586
|
end
|
|
422
587
|
|
|
423
|
-
#
|
|
588
|
+
# Saves the children resources
|
|
424
589
|
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
590
|
+
# @return [Boolean]
|
|
591
|
+
# true if the children were successfully saved
|
|
427
592
|
#
|
|
428
|
-
#
|
|
429
|
-
|
|
593
|
+
# @api private
|
|
594
|
+
def save_children(safe = true)
|
|
595
|
+
child_relationships.all? do |relationship|
|
|
596
|
+
association = relationship.get!(self)
|
|
597
|
+
safe ? association.save : association.save!
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# Reset the Resource to a similar state as a new record:
|
|
602
|
+
# removes it from identity map and clears original property
|
|
603
|
+
# values (thus making all properties non dirty)
|
|
430
604
|
#
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
605
|
+
# @api private
|
|
606
|
+
def reset
|
|
607
|
+
@saved = false
|
|
608
|
+
identity_map.delete(key)
|
|
609
|
+
original_attributes.clear
|
|
610
|
+
self
|
|
435
611
|
end
|
|
436
612
|
|
|
613
|
+
# Gets a Collection with the current Resource instance as its only member
|
|
614
|
+
#
|
|
615
|
+
# @return [Collection, FalseClass]
|
|
616
|
+
# nil if this is a new record,
|
|
617
|
+
# otherwise a Collection with self as its only member
|
|
618
|
+
#
|
|
619
|
+
# @api private
|
|
437
620
|
def collection
|
|
438
|
-
@collection
|
|
439
|
-
|
|
440
|
-
end
|
|
621
|
+
return @collection if @collection || new? || frozen?
|
|
622
|
+
@collection = Collection.new(query, [ self ])
|
|
441
623
|
end
|
|
442
624
|
|
|
443
|
-
|
|
625
|
+
protected
|
|
626
|
+
|
|
627
|
+
# Method for hooking callbacks on resource creation
|
|
444
628
|
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
629
|
+
# @return [Boolean]
|
|
630
|
+
# true if the create was successful, false if not
|
|
447
631
|
#
|
|
448
|
-
#
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
reload_attributes(*loaded_attributes)
|
|
453
|
-
(parent_associations + child_associations).each { |association| association.reload }
|
|
454
|
-
end
|
|
632
|
+
# @api private
|
|
633
|
+
def create_hook
|
|
634
|
+
_create
|
|
635
|
+
end
|
|
455
636
|
|
|
456
|
-
|
|
637
|
+
# Method for hooking callbacks on resource updates
|
|
638
|
+
#
|
|
639
|
+
# @return [Boolean]
|
|
640
|
+
# true if the update was successful, false if not
|
|
641
|
+
#
|
|
642
|
+
# @api private
|
|
643
|
+
def update_hook
|
|
644
|
+
_update
|
|
457
645
|
end
|
|
458
646
|
|
|
459
|
-
|
|
647
|
+
private
|
|
648
|
+
|
|
649
|
+
# Initialize a new instance of this Resource using the provided values
|
|
460
650
|
#
|
|
461
|
-
#
|
|
462
|
-
#
|
|
651
|
+
# @param [Hash] attributes
|
|
652
|
+
# attribute values to use for the new instance
|
|
463
653
|
#
|
|
464
|
-
#
|
|
465
|
-
#
|
|
654
|
+
# @return [Hash]
|
|
655
|
+
# attribute values used in the new instance
|
|
466
656
|
#
|
|
467
|
-
# --
|
|
468
657
|
# @api public
|
|
469
|
-
def
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
end
|
|
658
|
+
def initialize(attributes = {}, &block) # :nodoc:
|
|
659
|
+
self.attributes = attributes
|
|
660
|
+
end
|
|
473
661
|
|
|
474
|
-
|
|
662
|
+
# Returns name of the repository this object
|
|
663
|
+
# was loaded from
|
|
664
|
+
#
|
|
665
|
+
# @return [String]
|
|
666
|
+
# name of the repository this object was loaded from
|
|
667
|
+
#
|
|
668
|
+
# @api private
|
|
669
|
+
def repository_name
|
|
670
|
+
repository.name
|
|
475
671
|
end
|
|
476
672
|
|
|
477
|
-
#
|
|
673
|
+
# Gets this instance's Model's properties
|
|
478
674
|
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
675
|
+
# @return [Array(Property)]
|
|
676
|
+
# List of this Resource's Model's properties
|
|
481
677
|
#
|
|
482
|
-
#
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
!defined?(@new_record) || @new_record
|
|
678
|
+
# @api private
|
|
679
|
+
def properties
|
|
680
|
+
model.properties(repository_name)
|
|
486
681
|
end
|
|
487
682
|
|
|
488
|
-
#
|
|
683
|
+
# Gets this instance's Model's relationships
|
|
489
684
|
#
|
|
490
|
-
#
|
|
491
|
-
#
|
|
685
|
+
# @return [Array(Associations::Relationship)]
|
|
686
|
+
# List of this instance's Model's Relationships
|
|
492
687
|
#
|
|
493
|
-
#
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
properties.map do |p|
|
|
497
|
-
[p.name, send(p.getter)] if p.reader_visibility == :public
|
|
498
|
-
end.compact.to_hash
|
|
688
|
+
# @api private
|
|
689
|
+
def relationships
|
|
690
|
+
model.relationships(repository_name)
|
|
499
691
|
end
|
|
500
692
|
|
|
501
|
-
#
|
|
693
|
+
# Returns identity map of repository this object
|
|
694
|
+
# was loaded from
|
|
502
695
|
#
|
|
503
|
-
#
|
|
504
|
-
#
|
|
696
|
+
# @return [DataMapper::IdentityMap]
|
|
697
|
+
# identity map of repository this object was loaded from
|
|
505
698
|
#
|
|
506
|
-
#
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
name = name.to_s.sub(/\?\z/, '')
|
|
699
|
+
# @api semipublic
|
|
700
|
+
def identity_map
|
|
701
|
+
repository.identity_map(model)
|
|
702
|
+
end
|
|
511
703
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
704
|
+
# Reloads attributes that belong to given lazy loading
|
|
705
|
+
# context, and not yet loaded
|
|
706
|
+
#
|
|
707
|
+
# @api private
|
|
708
|
+
def lazy_load(property_names)
|
|
709
|
+
reload_attributes(properties.in_context(property_names) - loaded_properties)
|
|
518
710
|
end
|
|
519
711
|
|
|
520
|
-
#
|
|
712
|
+
# Reloads specified attributes
|
|
521
713
|
#
|
|
522
|
-
#
|
|
523
|
-
#
|
|
524
|
-
# keys<Symbol, String, Array> keys of Hash to update (others won't be updated)
|
|
714
|
+
# @param [Enumerable(Symbol)] attributes
|
|
715
|
+
# name(s) of attribute(s) to reload
|
|
525
716
|
#
|
|
526
|
-
#
|
|
527
|
-
#
|
|
717
|
+
# @return [Resource]
|
|
718
|
+
# the receiver, the current Resource instance
|
|
528
719
|
#
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
raise ArgumentError, "Expecting the first parameter of " +
|
|
534
|
-
"update_attributes to be a hash; got #{hash.inspect}"
|
|
720
|
+
# @api private
|
|
721
|
+
def reload_attributes(attributes)
|
|
722
|
+
unless attributes.empty? || new?
|
|
723
|
+
collection.reload(:fields => attributes)
|
|
535
724
|
end
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
save
|
|
725
|
+
|
|
726
|
+
self
|
|
539
727
|
end
|
|
540
728
|
|
|
541
|
-
#
|
|
542
|
-
|
|
543
|
-
|
|
729
|
+
# Gets a Query that will return this Resource instance
|
|
730
|
+
#
|
|
731
|
+
# @return [Query]
|
|
732
|
+
# Query that will retrieve this Resource instance
|
|
733
|
+
#
|
|
734
|
+
# @api private
|
|
735
|
+
def query
|
|
736
|
+
Query.new(repository, model, model.key_conditions(repository, key))
|
|
544
737
|
end
|
|
545
738
|
|
|
546
|
-
# TODO:
|
|
739
|
+
# TODO: document
|
|
547
740
|
# @api private
|
|
548
|
-
def
|
|
549
|
-
|
|
741
|
+
def parent_relationships
|
|
742
|
+
parent_relationships = []
|
|
550
743
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
next unless
|
|
554
|
-
ivars[property.instance_variable_name] = property.get!(self)
|
|
555
|
-
end
|
|
744
|
+
relationships.each_value do |relationship|
|
|
745
|
+
next unless relationship.respond_to?(:resource_for) && relationship.loaded?(self)
|
|
746
|
+
next unless relationship.get(self)
|
|
556
747
|
|
|
557
|
-
|
|
558
|
-
%w[ @new_record @original_values @readonly @repository ].each do |name|
|
|
559
|
-
ivars[name] = instance_variable_get(name)
|
|
748
|
+
parent_relationships << relationship
|
|
560
749
|
end
|
|
561
750
|
|
|
562
|
-
|
|
751
|
+
parent_relationships
|
|
563
752
|
end
|
|
564
753
|
|
|
565
|
-
|
|
754
|
+
# Returns array of child relationships for which this resource is parent and is loaded
|
|
755
|
+
#
|
|
756
|
+
# @return [Array<DataMapper::Associations::OneToMany::Relationship>]
|
|
757
|
+
# array of child relationships for which this resource is parent and is loaded
|
|
758
|
+
#
|
|
759
|
+
# @api private
|
|
760
|
+
def child_relationships
|
|
761
|
+
child_relationships = []
|
|
566
762
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
end
|
|
763
|
+
relationships.each_value do |relationship|
|
|
764
|
+
next unless relationship.respond_to?(:collection_for) && relationship.loaded?(self)
|
|
570
765
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
end
|
|
766
|
+
association = relationship.get!(self)
|
|
767
|
+
next unless association.loaded? || association.head.any? || association.tail.any?
|
|
574
768
|
|
|
575
|
-
|
|
576
|
-
|
|
769
|
+
child_relationships << relationship
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
many_to_many, other = child_relationships.partition do |relationship|
|
|
773
|
+
relationship.kind_of?(DataMapper::Associations::ManyToMany::Relationship)
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
many_to_many + other
|
|
577
777
|
end
|
|
578
778
|
|
|
779
|
+
# Saves this Resource instance to the repository,
|
|
780
|
+
# setting default values for any unset properties
|
|
781
|
+
#
|
|
782
|
+
# If resource is not dirty or a new (not yet saved),
|
|
783
|
+
# this method returns false
|
|
784
|
+
#
|
|
785
|
+
# On successful save identity map of the repository is
|
|
786
|
+
# updated
|
|
787
|
+
#
|
|
579
788
|
# Needs to be a protected method so that it is hookable
|
|
580
|
-
|
|
789
|
+
#
|
|
790
|
+
# The primary purpose of this method is to allow before :create
|
|
791
|
+
# hooks to fire at a point just before/after resource creation
|
|
792
|
+
#
|
|
793
|
+
# @return [Boolean]
|
|
794
|
+
# true if the receiver was successfully created
|
|
795
|
+
#
|
|
796
|
+
# @api private
|
|
797
|
+
def _create
|
|
581
798
|
# Can't create a resource that is not dirty and doesn't have serial keys
|
|
582
|
-
return false if
|
|
799
|
+
return false if new? && !dirty?
|
|
800
|
+
|
|
583
801
|
# set defaults for new resource
|
|
584
802
|
properties.each do |property|
|
|
585
|
-
|
|
586
|
-
|
|
803
|
+
unless property.serial? || property.loaded?(self)
|
|
804
|
+
property.set(self, property.default_for(self))
|
|
805
|
+
end
|
|
587
806
|
end
|
|
588
807
|
|
|
589
|
-
|
|
808
|
+
repository.create([ self ])
|
|
590
809
|
|
|
591
810
|
@repository = repository
|
|
592
|
-
@
|
|
811
|
+
@saved = true
|
|
812
|
+
|
|
813
|
+
original_attributes.clear
|
|
593
814
|
|
|
594
|
-
|
|
815
|
+
identity_map[key] = self
|
|
595
816
|
|
|
596
817
|
true
|
|
597
818
|
end
|
|
598
819
|
|
|
599
|
-
#
|
|
600
|
-
|
|
820
|
+
# Updates resource state
|
|
821
|
+
#
|
|
822
|
+
# The primary purpose of this method is to allow before :update
|
|
823
|
+
# hooks to fire at a point just before/after resource update whether
|
|
824
|
+
# it is the result of Resource#save, or using Resource#update
|
|
825
|
+
#
|
|
826
|
+
# @return [Boolean]
|
|
827
|
+
# true if the receiver was successfully created
|
|
828
|
+
#
|
|
829
|
+
# @api private
|
|
830
|
+
def _update
|
|
601
831
|
dirty_attributes = self.dirty_attributes
|
|
602
|
-
return true if dirty_attributes.empty?
|
|
603
|
-
repository.update(dirty_attributes, to_query) == 1
|
|
604
|
-
end
|
|
605
832
|
|
|
606
|
-
|
|
833
|
+
if dirty_attributes.empty?
|
|
834
|
+
true
|
|
835
|
+
elsif dirty_attributes.any? { |property, value| !property.nullable? && value.nil? }
|
|
836
|
+
false
|
|
837
|
+
else
|
|
838
|
+
# remove from the identity map
|
|
839
|
+
identity_map.delete(key)
|
|
607
840
|
|
|
608
|
-
|
|
609
|
-
assert_valid_model
|
|
610
|
-
self.attributes = attributes
|
|
611
|
-
end
|
|
841
|
+
return false unless repository.update(dirty_attributes, Collection.new(query, [ self ])) == 1
|
|
612
842
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
properties = self.properties
|
|
843
|
+
# remove the cached key in case it is updated
|
|
844
|
+
remove_instance_variable(:@key)
|
|
616
845
|
|
|
617
|
-
|
|
618
|
-
raise IncompleteResourceError, "#{model.name} must have at least one property or relationship to be initialized."
|
|
619
|
-
end
|
|
620
|
-
|
|
621
|
-
if properties.key.empty?
|
|
622
|
-
raise IncompleteResourceError, "#{model.name} must have a key."
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
self.class.instance_variable_set("@_valid_model", true)
|
|
626
|
-
end
|
|
846
|
+
original_attributes.clear
|
|
627
847
|
|
|
628
|
-
|
|
629
|
-
# @api semipublic
|
|
630
|
-
def attribute_get!(name)
|
|
631
|
-
properties[name].get!(self)
|
|
632
|
-
end
|
|
848
|
+
identity_map[key] = self
|
|
633
849
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
def attribute_set!(name, value)
|
|
637
|
-
properties[name].set!(self, value)
|
|
850
|
+
true
|
|
851
|
+
end
|
|
638
852
|
end
|
|
639
853
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
854
|
+
# Return true if +other+'s is equivalent or equal to +self+'s
|
|
855
|
+
#
|
|
856
|
+
# @param [Resource] other
|
|
857
|
+
# The Resource whose attributes are to be compared with +self+'s
|
|
858
|
+
# @param [Symbol] operator
|
|
859
|
+
# The comparison operator to use to compare the attributes
|
|
860
|
+
#
|
|
861
|
+
# @return [Boolean]
|
|
862
|
+
# The result of the comparison of +other+'s attributes with +self+'s
|
|
863
|
+
#
|
|
864
|
+
# @api private
|
|
865
|
+
def cmp?(other, operator)
|
|
866
|
+
return false unless key.send(operator, other.key)
|
|
867
|
+
return true if repository.send(operator, other.repository) && !dirty? && !other.dirty?
|
|
643
868
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
869
|
+
# get all the loaded and non-loaded properties that are not keys,
|
|
870
|
+
# since the key comparison was performed earlier
|
|
871
|
+
loaded, not_loaded = properties.select { |property| !property.key? }.partition do |property|
|
|
872
|
+
property.loaded?(self) && property.loaded?(other)
|
|
873
|
+
end
|
|
647
874
|
|
|
648
|
-
|
|
649
|
-
|
|
875
|
+
# check all loaded properties, and then all unloaded properties
|
|
876
|
+
(loaded + not_loaded).all? { |property| property.get(self).send(operator, property.get(other)) }
|
|
650
877
|
end
|
|
651
878
|
|
|
652
|
-
#
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
# @api public
|
|
662
|
-
#
|
|
663
|
-
# TODO: move to dm-more/dm-transactions
|
|
664
|
-
def transaction
|
|
665
|
-
model.transaction { |*block_args| yield(*block_args) }
|
|
879
|
+
# Raises an exception if #update is performed on a dirty resource
|
|
880
|
+
#
|
|
881
|
+
# @raise [UpdateConflictError]
|
|
882
|
+
# raise if the resource is dirty
|
|
883
|
+
#
|
|
884
|
+
# @api private
|
|
885
|
+
def assert_update_clean_only(method)
|
|
886
|
+
if original_attributes.any?
|
|
887
|
+
raise UpdateConflictError, "#{model}##{method} cannot be called on a dirty resource"
|
|
666
888
|
end
|
|
667
|
-
end
|
|
668
|
-
|
|
669
|
-
include Transaction
|
|
889
|
+
end
|
|
670
890
|
end # module Resource
|
|
671
891
|
end # module DataMapper
|