datamapper-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 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- 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/adapters.rb +135 -16
- 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 +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- 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/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 +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- 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 +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -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/lib/dm-core.rb +106 -110
- 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/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 +1723 -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/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 +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- 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/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/support.rb +0 -7
- 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
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
|
+
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,679 @@ 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
|
|
156
236
|
#
|
|
157
|
-
#
|
|
158
|
-
|
|
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)
|
|
255
|
+
#
|
|
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
|
+
eager_load(loaded_properties)
|
|
287
|
+
child_relationships.each { |relationship| relationship.get!(self).reload }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
self
|
|
198
291
|
end
|
|
199
292
|
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
# ==== Returns
|
|
203
|
-
# <String>:: with the class name, attributes with their values
|
|
293
|
+
# Updates attributes and saves this Resource instance
|
|
204
294
|
#
|
|
205
|
-
#
|
|
295
|
+
# @param [Hash] attributes
|
|
296
|
+
# attributes to be updated
|
|
206
297
|
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
298
|
+
# @return [Boolean]
|
|
299
|
+
# true if resource and storage state match
|
|
209
300
|
#
|
|
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
|
-
|
|
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
|
+
instance_of?(other.class) && cmp?(other, :eql?)
|
|
272
392
|
end
|
|
273
393
|
|
|
274
|
-
|
|
275
|
-
|
|
394
|
+
# Compares another Resource for equivalency
|
|
395
|
+
#
|
|
396
|
+
# Resource is equal to +other+ if they are the same object (identity)
|
|
397
|
+
# or if they are both of the *same base model* and all of their attributes
|
|
398
|
+
# are equivalent
|
|
399
|
+
#
|
|
400
|
+
# @param [Resource] other
|
|
401
|
+
# the other Resource to compare with
|
|
402
|
+
#
|
|
403
|
+
# @return [Boolean]
|
|
404
|
+
# true if they are equivalent, false if not
|
|
405
|
+
#
|
|
406
|
+
# @api public
|
|
407
|
+
def ==(other)
|
|
408
|
+
return true if equal?(other)
|
|
409
|
+
other.respond_to?(:model) &&
|
|
410
|
+
model.base_model.equal?(other.model.base_model) &&
|
|
411
|
+
cmp?(other, :==)
|
|
276
412
|
end
|
|
277
413
|
|
|
278
|
-
#
|
|
414
|
+
# Compares two Resources to allow them to be sorted
|
|
279
415
|
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
416
|
+
# @param [Resource] other
|
|
417
|
+
# The other Resource to compare with
|
|
282
418
|
#
|
|
283
|
-
# @
|
|
419
|
+
# @return [Integer]
|
|
420
|
+
# Return 0 if Resources should be sorted as the same, -1 if the
|
|
421
|
+
# other Resource should be after self, and 1 if the other Resource
|
|
422
|
+
# should be before self
|
|
284
423
|
#
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
# same API through out all of dm-more. dm-validations requires a
|
|
290
|
-
# context to be passed
|
|
291
|
-
|
|
292
|
-
associations_saved = false
|
|
293
|
-
child_associations.each { |a| associations_saved |= a.save }
|
|
294
|
-
|
|
295
|
-
saved = new_record? ? create : update
|
|
296
|
-
|
|
297
|
-
if saved
|
|
298
|
-
original_values.clear
|
|
424
|
+
# @api public
|
|
425
|
+
def <=>(other)
|
|
426
|
+
unless other.kind_of?(model.base_model)
|
|
427
|
+
raise ArgumentError, "Cannot compare a #{other.model} instance with a #{model} instance"
|
|
299
428
|
end
|
|
429
|
+
cmp = 0
|
|
430
|
+
model.default_order(repository_name).each do |direction|
|
|
431
|
+
cmp = direction.get(self) <=> direction.get(other)
|
|
432
|
+
break if cmp != 0
|
|
433
|
+
end
|
|
434
|
+
cmp
|
|
435
|
+
end
|
|
300
436
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
437
|
+
# Returns hash value of the object.
|
|
438
|
+
# Two objects with the same hash value assumed equal (using eql? method)
|
|
439
|
+
#
|
|
440
|
+
# DataMapper resources are equal when their models have the same hash
|
|
441
|
+
# and they have the same set of properties
|
|
442
|
+
#
|
|
443
|
+
# When used as key in a Hash or Hash subclass, objects are compared
|
|
444
|
+
# by eql? and thus hash value has direct effect on lookup
|
|
445
|
+
#
|
|
446
|
+
# @api private
|
|
447
|
+
def hash
|
|
448
|
+
key.hash
|
|
306
449
|
end
|
|
307
450
|
|
|
308
|
-
#
|
|
451
|
+
# Get a Human-readable representation of this Resource instance
|
|
452
|
+
#
|
|
453
|
+
# Foo.new #=> #<Foo name=nil updated_at=nil created_at=nil id=nil>
|
|
309
454
|
#
|
|
310
|
-
#
|
|
311
|
-
#
|
|
455
|
+
# @return [String]
|
|
456
|
+
# Human-readable representation of this Resource instance
|
|
312
457
|
#
|
|
313
|
-
# --
|
|
314
458
|
# @api public
|
|
315
|
-
def
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
459
|
+
def inspect
|
|
460
|
+
# TODO: display relationship values
|
|
461
|
+
attrs = properties.map do |property|
|
|
462
|
+
value = if new? || property.loaded?(self)
|
|
463
|
+
property.get!(self).inspect
|
|
464
|
+
else
|
|
465
|
+
'<not loaded>'
|
|
466
|
+
end
|
|
322
467
|
|
|
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)
|
|
468
|
+
"#{property.instance_variable_name}=#{value}"
|
|
326
469
|
end
|
|
327
470
|
|
|
328
|
-
|
|
471
|
+
"#<#{model.name} #{attrs.join(' ')}>"
|
|
329
472
|
end
|
|
330
473
|
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
# ==== Example
|
|
334
|
-
#
|
|
335
|
-
# class Foo
|
|
336
|
-
# include DataMapper::Resource
|
|
337
|
-
# property :name, String
|
|
338
|
-
# property :description, Text, :lazy => false
|
|
339
|
-
# end
|
|
474
|
+
# Hash of original values of attributes that have unsaved changes
|
|
340
475
|
#
|
|
341
|
-
#
|
|
476
|
+
# @return [Hash]
|
|
477
|
+
# original values of attributes that have unsaved changes
|
|
342
478
|
#
|
|
343
|
-
#
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
instance_variable_defined?(properties[name].instance_variable_name)
|
|
479
|
+
# @api semipublic
|
|
480
|
+
def original_attributes
|
|
481
|
+
@original_attributes ||= {}
|
|
347
482
|
end
|
|
348
483
|
|
|
349
|
-
#
|
|
350
|
-
# even if they are lazy but have been called
|
|
351
|
-
#
|
|
352
|
-
# ==== Returns
|
|
353
|
-
# Array[<Symbol>]:: names of attributes that have been loaded
|
|
354
|
-
#
|
|
355
|
-
# ==== Example
|
|
484
|
+
# Checks if an attribute has been loaded from the repository
|
|
356
485
|
#
|
|
486
|
+
# @example
|
|
357
487
|
# class Foo
|
|
358
488
|
# include DataMapper::Resource
|
|
359
|
-
#
|
|
360
|
-
# property :
|
|
489
|
+
#
|
|
490
|
+
# property :name, String
|
|
491
|
+
# property :description, Text, :lazy => false
|
|
361
492
|
# end
|
|
362
493
|
#
|
|
363
|
-
# Foo.new.
|
|
494
|
+
# Foo.new.attribute_loaded?(:description) #=> false
|
|
364
495
|
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
|
|
368
|
-
|
|
496
|
+
# @return [Boolean]
|
|
497
|
+
# true if ivar +name+ has been loaded
|
|
498
|
+
#
|
|
499
|
+
# @return [Boolean]
|
|
500
|
+
# true if ivar +name+ has been loaded
|
|
501
|
+
#
|
|
502
|
+
# @api private
|
|
503
|
+
def attribute_loaded?(name)
|
|
504
|
+
properties[name].loaded?(self)
|
|
369
505
|
end
|
|
370
506
|
|
|
371
|
-
#
|
|
507
|
+
# Checks if an attribute has unsaved changes
|
|
372
508
|
#
|
|
373
|
-
#
|
|
374
|
-
#
|
|
509
|
+
# @param [Symbol] name
|
|
510
|
+
# name of attribute to check for unsaved changes
|
|
375
511
|
#
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
|
|
379
|
-
|
|
512
|
+
# @return [Boolean]
|
|
513
|
+
# true if attribute has unsaved changes
|
|
514
|
+
#
|
|
515
|
+
# @api semipublic
|
|
516
|
+
def attribute_dirty?(name)
|
|
517
|
+
dirty_attributes.key?(properties[name])
|
|
380
518
|
end
|
|
381
519
|
|
|
382
|
-
# Hash of attributes that have
|
|
520
|
+
# Hash of attributes that have unsaved changes
|
|
383
521
|
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
522
|
+
# @return [Hash]
|
|
523
|
+
# attributes that have unsaved changes
|
|
386
524
|
#
|
|
387
|
-
#
|
|
388
|
-
# @api private
|
|
525
|
+
# @api semipublic
|
|
389
526
|
def dirty_attributes
|
|
390
527
|
dirty_attributes = {}
|
|
391
|
-
properties = self.properties
|
|
392
|
-
|
|
393
|
-
original_values.each do |name, old_value|
|
|
394
|
-
property = properties[name]
|
|
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
528
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
dirty_attributes[property] = property.value(new_value)
|
|
406
|
-
end
|
|
529
|
+
original_attributes.each_key do |property|
|
|
530
|
+
dirty_attributes[property] = property.value(property.get!(self))
|
|
407
531
|
end
|
|
408
532
|
|
|
409
533
|
dirty_attributes
|
|
410
534
|
end
|
|
411
535
|
|
|
412
|
-
#
|
|
536
|
+
# Saves the resource
|
|
413
537
|
#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
538
|
+
# @return [Boolean]
|
|
539
|
+
# true if the resource was successfully saved
|
|
416
540
|
#
|
|
417
|
-
#
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
541
|
+
# @api semipublic
|
|
542
|
+
def save_self(safe = true)
|
|
543
|
+
if safe
|
|
544
|
+
new? ? create_hook : update_hook
|
|
545
|
+
else
|
|
546
|
+
new? ? _create : _update
|
|
547
|
+
end
|
|
421
548
|
end
|
|
422
549
|
|
|
423
|
-
#
|
|
550
|
+
# Saves the parent resources
|
|
424
551
|
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
552
|
+
# @return [Boolean]
|
|
553
|
+
# true if the parents were successfully saved
|
|
427
554
|
#
|
|
428
|
-
#
|
|
429
|
-
|
|
555
|
+
# @api private
|
|
556
|
+
def save_parents(safe = true)
|
|
557
|
+
parent_relationships.all? do |relationship|
|
|
558
|
+
parent = relationship.get!(self)
|
|
559
|
+
if parent.dirty? ? parent.save_parents(safe) && parent.save_self(safe) : parent.saved?
|
|
560
|
+
relationship.set(self, parent) # set the FK values
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
# Saves the children resources
|
|
430
566
|
#
|
|
431
|
-
#
|
|
432
|
-
#
|
|
433
|
-
|
|
434
|
-
|
|
567
|
+
# @return [Boolean]
|
|
568
|
+
# true if the children were successfully saved
|
|
569
|
+
#
|
|
570
|
+
# @api private
|
|
571
|
+
def save_children(safe = true)
|
|
572
|
+
child_relationships.all? do |relationship|
|
|
573
|
+
association = relationship.get!(self)
|
|
574
|
+
safe ? association.save : association.save!
|
|
575
|
+
end
|
|
435
576
|
end
|
|
436
577
|
|
|
578
|
+
# Reset the Resource to a similar state as a new record:
|
|
579
|
+
# removes it from identity map and clears original property
|
|
580
|
+
# values (thus making all properties non dirty)
|
|
581
|
+
#
|
|
582
|
+
# @api private
|
|
583
|
+
def reset
|
|
584
|
+
@saved = false
|
|
585
|
+
identity_map.delete(key)
|
|
586
|
+
original_attributes.clear
|
|
587
|
+
self
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# Gets a Collection with the current Resource instance as its only member
|
|
591
|
+
#
|
|
592
|
+
# @return [Collection, FalseClass]
|
|
593
|
+
# nil if this is a new record,
|
|
594
|
+
# otherwise a Collection with self as its only member
|
|
595
|
+
#
|
|
596
|
+
# @api private
|
|
437
597
|
def collection
|
|
438
|
-
@collection
|
|
439
|
-
|
|
440
|
-
end
|
|
598
|
+
return @collection if @collection || new? || frozen?
|
|
599
|
+
@collection = Collection.new(query, [ self ])
|
|
441
600
|
end
|
|
442
601
|
|
|
443
|
-
|
|
602
|
+
protected
|
|
603
|
+
|
|
604
|
+
# Method for hooking callbacks on resource creation
|
|
444
605
|
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
606
|
+
# @return [Boolean]
|
|
607
|
+
# true if the create was successful, false if not
|
|
447
608
|
#
|
|
448
|
-
#
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
reload_attributes(*loaded_attributes)
|
|
453
|
-
(parent_associations + child_associations).each { |association| association.reload }
|
|
454
|
-
end
|
|
609
|
+
# @api private
|
|
610
|
+
def create_hook
|
|
611
|
+
_create
|
|
612
|
+
end
|
|
455
613
|
|
|
456
|
-
|
|
614
|
+
# Method for hooking callbacks on resource updates
|
|
615
|
+
#
|
|
616
|
+
# @return [Boolean]
|
|
617
|
+
# true if the update was successful, false if not
|
|
618
|
+
#
|
|
619
|
+
# @api private
|
|
620
|
+
def update_hook
|
|
621
|
+
_update
|
|
457
622
|
end
|
|
458
623
|
|
|
459
|
-
|
|
624
|
+
private
|
|
625
|
+
|
|
626
|
+
# Initialize a new instance of this Resource using the provided values
|
|
460
627
|
#
|
|
461
|
-
#
|
|
462
|
-
#
|
|
628
|
+
# @param [Hash] attributes
|
|
629
|
+
# attribute values to use for the new instance
|
|
463
630
|
#
|
|
464
|
-
#
|
|
465
|
-
#
|
|
631
|
+
# @return [Hash]
|
|
632
|
+
# attribute values used in the new instance
|
|
466
633
|
#
|
|
467
|
-
# --
|
|
468
634
|
# @api public
|
|
469
|
-
def
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
end
|
|
635
|
+
def initialize(attributes = {}, &block) # :nodoc:
|
|
636
|
+
self.attributes = attributes
|
|
637
|
+
end
|
|
473
638
|
|
|
474
|
-
|
|
639
|
+
# Returns name of the repository this object
|
|
640
|
+
# was loaded from
|
|
641
|
+
#
|
|
642
|
+
# @return [String]
|
|
643
|
+
# name of the repository this object was loaded from
|
|
644
|
+
#
|
|
645
|
+
# @api private
|
|
646
|
+
def repository_name
|
|
647
|
+
repository.name
|
|
475
648
|
end
|
|
476
649
|
|
|
477
|
-
#
|
|
650
|
+
# Gets this instance's Model's properties
|
|
478
651
|
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
652
|
+
# @return [Array(Property)]
|
|
653
|
+
# List of this Resource's Model's properties
|
|
481
654
|
#
|
|
482
|
-
#
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
!defined?(@new_record) || @new_record
|
|
655
|
+
# @api private
|
|
656
|
+
def properties
|
|
657
|
+
model.properties(repository_name)
|
|
486
658
|
end
|
|
487
659
|
|
|
488
|
-
#
|
|
660
|
+
# Gets this instance's Model's relationships
|
|
489
661
|
#
|
|
490
|
-
#
|
|
491
|
-
#
|
|
662
|
+
# @return [Array(Associations::Relationship)]
|
|
663
|
+
# List of this instance's Model's Relationships
|
|
492
664
|
#
|
|
493
|
-
#
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
properties.map do |p|
|
|
497
|
-
[p.name, send(p.getter)] if p.reader_visibility == :public
|
|
498
|
-
end.compact.to_hash
|
|
665
|
+
# @api private
|
|
666
|
+
def relationships
|
|
667
|
+
model.relationships(repository_name)
|
|
499
668
|
end
|
|
500
669
|
|
|
501
|
-
#
|
|
670
|
+
# Returns the identity map for the model from the repository
|
|
502
671
|
#
|
|
503
|
-
#
|
|
504
|
-
#
|
|
672
|
+
# @return [IdentityMap]
|
|
673
|
+
# identity map of repository this object was loaded from
|
|
505
674
|
#
|
|
506
|
-
#
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
name = name.to_s.sub(/\?\z/, '')
|
|
675
|
+
# @api semipublic
|
|
676
|
+
def identity_map
|
|
677
|
+
repository.identity_map(model)
|
|
678
|
+
end
|
|
511
679
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
680
|
+
# Fetches all the names of the attributes that have been loaded,
|
|
681
|
+
# even if they are lazy but have been called
|
|
682
|
+
#
|
|
683
|
+
# @return [Array<Property>]
|
|
684
|
+
# names of attributes that have been loaded
|
|
685
|
+
#
|
|
686
|
+
# @api private
|
|
687
|
+
def loaded_properties
|
|
688
|
+
properties.select { |property| property.loaded?(self) }
|
|
518
689
|
end
|
|
519
690
|
|
|
520
|
-
#
|
|
691
|
+
# Lazy loads attributes not yet loaded
|
|
521
692
|
#
|
|
522
|
-
#
|
|
523
|
-
#
|
|
524
|
-
# keys<Symbol, String, Array> keys of Hash to update (others won't be updated)
|
|
693
|
+
# @param [Array<Property>] fields
|
|
694
|
+
# the properties to reload
|
|
525
695
|
#
|
|
526
|
-
#
|
|
527
|
-
# <TrueClass, FalseClass> if model got saved or not
|
|
696
|
+
# @return [self]
|
|
528
697
|
#
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
698
|
+
# @api private
|
|
699
|
+
def lazy_load(fields)
|
|
700
|
+
eager_load(fields - loaded_properties)
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
# Reloads specified attributes
|
|
704
|
+
#
|
|
705
|
+
# @param [Array<Property>] fields
|
|
706
|
+
# the properties to reload
|
|
707
|
+
#
|
|
708
|
+
# @return [Resource]
|
|
709
|
+
# the receiver, the current Resource instance
|
|
710
|
+
#
|
|
711
|
+
# @api private
|
|
712
|
+
def eager_load(fields)
|
|
713
|
+
unless fields.empty? || new?
|
|
714
|
+
collection.reload(:fields => fields)
|
|
535
715
|
end
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
save
|
|
716
|
+
|
|
717
|
+
self
|
|
539
718
|
end
|
|
540
719
|
|
|
541
|
-
#
|
|
542
|
-
|
|
543
|
-
|
|
720
|
+
# Gets a Query that will return this Resource instance
|
|
721
|
+
#
|
|
722
|
+
# @return [Query]
|
|
723
|
+
# Query that will retrieve this Resource instance
|
|
724
|
+
#
|
|
725
|
+
# @api private
|
|
726
|
+
def query
|
|
727
|
+
Query.new(repository, model, model.key_conditions(repository, key))
|
|
544
728
|
end
|
|
545
729
|
|
|
546
|
-
# TODO:
|
|
730
|
+
# TODO: document
|
|
547
731
|
# @api private
|
|
548
|
-
def
|
|
549
|
-
|
|
732
|
+
def parent_relationships
|
|
733
|
+
parent_relationships = []
|
|
550
734
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
next unless
|
|
554
|
-
ivars[property.instance_variable_name] = property.get!(self)
|
|
555
|
-
end
|
|
735
|
+
relationships.each_value do |relationship|
|
|
736
|
+
next unless relationship.respond_to?(:resource_for) && relationship.loaded?(self)
|
|
737
|
+
next unless relationship.get(self)
|
|
556
738
|
|
|
557
|
-
|
|
558
|
-
%w[ @new_record @original_values @readonly @repository ].each do |name|
|
|
559
|
-
ivars[name] = instance_variable_get(name)
|
|
739
|
+
parent_relationships << relationship
|
|
560
740
|
end
|
|
561
741
|
|
|
562
|
-
|
|
742
|
+
parent_relationships
|
|
563
743
|
end
|
|
564
744
|
|
|
565
|
-
|
|
745
|
+
# Returns loaded child relationships
|
|
746
|
+
#
|
|
747
|
+
# @return [Array<Associations::OneToMany::Relationship>]
|
|
748
|
+
# array of child relationships for which this resource is parent and is loaded
|
|
749
|
+
#
|
|
750
|
+
# @api private
|
|
751
|
+
def child_relationships
|
|
752
|
+
child_relationships = []
|
|
566
753
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
end
|
|
754
|
+
relationships.each_value do |relationship|
|
|
755
|
+
next unless relationship.respond_to?(:collection_for) && relationship.loaded?(self)
|
|
570
756
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
end
|
|
757
|
+
association = relationship.get!(self)
|
|
758
|
+
next unless association.loaded? || association.head.any? || association.tail.any?
|
|
574
759
|
|
|
575
|
-
|
|
576
|
-
|
|
760
|
+
child_relationships << relationship
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
many_to_many, other = child_relationships.partition do |relationship|
|
|
764
|
+
relationship.kind_of?(Associations::ManyToMany::Relationship)
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
many_to_many + other
|
|
577
768
|
end
|
|
578
769
|
|
|
770
|
+
# Creates the resource with default values
|
|
771
|
+
#
|
|
772
|
+
# If resource is not dirty or a new (not yet saved),
|
|
773
|
+
# this method returns false
|
|
774
|
+
#
|
|
775
|
+
# On successful save identity map of the repository is
|
|
776
|
+
# updated
|
|
777
|
+
#
|
|
579
778
|
# Needs to be a protected method so that it is hookable
|
|
580
|
-
|
|
779
|
+
#
|
|
780
|
+
# The primary purpose of this method is to allow before :create
|
|
781
|
+
# hooks to fire at a point just before/after resource creation
|
|
782
|
+
#
|
|
783
|
+
# @return [Boolean]
|
|
784
|
+
# true if the receiver was successfully created
|
|
785
|
+
#
|
|
786
|
+
# @api private
|
|
787
|
+
def _create
|
|
581
788
|
# Can't create a resource that is not dirty and doesn't have serial keys
|
|
582
|
-
return false if
|
|
789
|
+
return false if new? && !dirty?
|
|
790
|
+
|
|
583
791
|
# set defaults for new resource
|
|
584
792
|
properties.each do |property|
|
|
585
|
-
|
|
586
|
-
|
|
793
|
+
unless property.serial? || property.loaded?(self)
|
|
794
|
+
property.set(self, property.default_for(self))
|
|
795
|
+
end
|
|
587
796
|
end
|
|
588
797
|
|
|
589
|
-
|
|
798
|
+
repository.create([ self ])
|
|
590
799
|
|
|
591
800
|
@repository = repository
|
|
592
|
-
@
|
|
801
|
+
@saved = true
|
|
593
802
|
|
|
594
|
-
|
|
803
|
+
original_attributes.clear
|
|
804
|
+
|
|
805
|
+
identity_map[key] = self
|
|
595
806
|
|
|
596
807
|
true
|
|
597
808
|
end
|
|
598
809
|
|
|
599
|
-
#
|
|
600
|
-
|
|
810
|
+
# Updates resource state
|
|
811
|
+
#
|
|
812
|
+
# The primary purpose of this method is to allow before :update
|
|
813
|
+
# hooks to fire at a point just before/after resource update whether
|
|
814
|
+
# it is the result of Resource#save, or using Resource#update
|
|
815
|
+
#
|
|
816
|
+
# @return [Boolean]
|
|
817
|
+
# true if the receiver was successfully created
|
|
818
|
+
#
|
|
819
|
+
# @api private
|
|
820
|
+
def _update
|
|
601
821
|
dirty_attributes = self.dirty_attributes
|
|
602
|
-
return true if dirty_attributes.empty?
|
|
603
|
-
repository.update(dirty_attributes, to_query) == 1
|
|
604
|
-
end
|
|
605
822
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
def assert_valid_model # :nodoc:
|
|
614
|
-
return if self.class._valid_model
|
|
615
|
-
properties = self.properties
|
|
823
|
+
if dirty_attributes.empty?
|
|
824
|
+
true
|
|
825
|
+
elsif dirty_attributes.any? { |property, value| !property.nullable? && value.nil? }
|
|
826
|
+
false
|
|
827
|
+
else
|
|
828
|
+
# remove from the identity map
|
|
829
|
+
identity_map.delete(key)
|
|
616
830
|
|
|
617
|
-
|
|
618
|
-
raise IncompleteResourceError, "#{model.name} must have at least one property or relationship to be initialized."
|
|
619
|
-
end
|
|
831
|
+
return false unless repository.update(dirty_attributes, Collection.new(query, [ self ])) == 1
|
|
620
832
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
end
|
|
833
|
+
# remove the cached key in case it is updated
|
|
834
|
+
remove_instance_variable(:@key)
|
|
624
835
|
|
|
625
|
-
|
|
626
|
-
end
|
|
836
|
+
original_attributes.clear
|
|
627
837
|
|
|
628
|
-
|
|
629
|
-
# @api semipublic
|
|
630
|
-
def attribute_get!(name)
|
|
631
|
-
properties[name].get!(self)
|
|
632
|
-
end
|
|
838
|
+
identity_map[key] = self
|
|
633
839
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
def attribute_set!(name, value)
|
|
637
|
-
properties[name].set!(self, value)
|
|
840
|
+
true
|
|
841
|
+
end
|
|
638
842
|
end
|
|
639
843
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
844
|
+
# Return true if +other+'s is equivalent or equal to +self+'s
|
|
845
|
+
#
|
|
846
|
+
# @param [Resource] other
|
|
847
|
+
# The Resource whose attributes are to be compared with +self+'s
|
|
848
|
+
# @param [Symbol] operator
|
|
849
|
+
# The comparison operator to use to compare the attributes
|
|
850
|
+
#
|
|
851
|
+
# @return [Boolean]
|
|
852
|
+
# The result of the comparison of +other+'s attributes with +self+'s
|
|
853
|
+
#
|
|
854
|
+
# @api private
|
|
855
|
+
def cmp?(other, operator)
|
|
856
|
+
return false unless key.send(operator, other.key)
|
|
857
|
+
return true if repository.send(operator, other.repository) && !dirty? && !other.dirty?
|
|
643
858
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
859
|
+
# get all the loaded and non-loaded properties that are not keys,
|
|
860
|
+
# since the key comparison was performed earlier
|
|
861
|
+
loaded, not_loaded = properties.select { |property| !property.key? }.partition do |property|
|
|
862
|
+
property.loaded?(self) && property.loaded?(other)
|
|
863
|
+
end
|
|
647
864
|
|
|
648
|
-
|
|
649
|
-
|
|
865
|
+
# check all loaded properties, and then all unloaded properties
|
|
866
|
+
(loaded + not_loaded).all? { |property| property.get(self).send(operator, property.get(other)) }
|
|
650
867
|
end
|
|
651
868
|
|
|
652
|
-
#
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
869
|
+
# Raises an exception if #update is performed on a dirty resource
|
|
870
|
+
#
|
|
871
|
+
# @param [Symbol] method
|
|
872
|
+
# the name of the method to use in the exception
|
|
873
|
+
#
|
|
874
|
+
# @return [undefined]
|
|
875
|
+
#
|
|
876
|
+
# @raise [UpdateConflictError]
|
|
877
|
+
# raise if the resource is dirty
|
|
878
|
+
#
|
|
879
|
+
# @api private
|
|
880
|
+
def assert_update_clean_only(method)
|
|
881
|
+
if original_attributes.any?
|
|
882
|
+
raise UpdateConflictError, "#{model}##{method} cannot be called on a dirty resource"
|
|
666
883
|
end
|
|
667
|
-
end
|
|
668
|
-
|
|
669
|
-
include Transaction
|
|
884
|
+
end
|
|
670
885
|
end # module Resource
|
|
671
886
|
end # module DataMapper
|