sbf-dm-core 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +388 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,1220 @@
1
+ module DataMapper
2
+ module Resource
3
+ include DataMapper::Assertions
4
+
5
+ # Return if Resource#save should raise an exception on save failures (per-resource)
6
+ #
7
+ # This delegates to model.raise_on_save_failure by default.
8
+ #
9
+ # user.raise_on_save_failure # => false
10
+ #
11
+ # @return [Boolean]
12
+ # true if a failure in Resource#save should raise an exception
13
+ #
14
+ # @api public
15
+ def raise_on_save_failure
16
+ if defined?(@raise_on_save_failure)
17
+ @raise_on_save_failure
18
+ else
19
+ model.raise_on_save_failure
20
+ end
21
+ end
22
+
23
+ # Specify if Resource#save should raise an exception on save failures (per-resource)
24
+ #
25
+ # @param [Boolean]
26
+ # a boolean that if true will cause Resource#save to raise an exception
27
+ #
28
+ # @return [Boolean]
29
+ # true if a failure in Resource#save should raise an exception
30
+ #
31
+ # @api public
32
+ def raise_on_save_failure=(raise_on_save_failure)
33
+ @raise_on_save_failure = raise_on_save_failure
34
+ end
35
+
36
+ # Deprecated API for updating attributes and saving Resource
37
+ #
38
+ # @see #update
39
+ #
40
+ # @deprecated
41
+ def update_attributes(_attributes = {}, *_allowed)
42
+ raise "#{model}#update_attributes is deprecated, use #{model}#update instead (#{caller.first})"
43
+ end
44
+
45
+ # Makes sure a class gets all the methods when it includes Resource
46
+ #
47
+ # Note that including this module into an anonymous class will leave
48
+ # the model descendant tracking mechanism with no possibility to reliably
49
+ # track the anonymous model across code reloads. This means that
50
+ # {DataMapper::DescendantSet} will currently leak memory in scenarios where
51
+ # anonymous models are reloaded multiple times (as is the case in dm-rails
52
+ # development mode for example).
53
+ #
54
+ # @api private
55
+ def self.included(model)
56
+ model.extend Model
57
+ super
58
+ end
59
+
60
+ # @api public
61
+ alias_method :model, :class
62
+
63
+ # Get the persisted state for the resource
64
+ #
65
+ # @return [Resource::PersistenceState]
66
+ # the current persisted state for the resource
67
+ #
68
+ # @api private
69
+ def persistence_state
70
+ @_persistence_state ||= Resource::PersistenceState::Transient.new(self)
71
+ end
72
+
73
+ # Set the persisted state for the resource
74
+ #
75
+ # @param [Resource::PersistenceState]
76
+ # the new persisted state for the resource
77
+ #
78
+ # @return [undefined]
79
+ #
80
+ # @api private
81
+ def persistence_state=(state)
82
+ @_persistence_state = state
83
+ end
84
+
85
+ # Test if the persisted state is set
86
+ #
87
+ # @return [Boolean]
88
+ # true if the persisted state is set
89
+ #
90
+ # @api private
91
+ def persistence_state?
92
+ defined?(@_persistence_state) ? true : false
93
+ end
94
+
95
+ # Repository this resource belongs to in the context of this collection
96
+ # or of the resource's class.
97
+ #
98
+ # @return [Repository]
99
+ # the repository this resource belongs to, in the context of
100
+ # a collection OR in the instance's Model's context
101
+ #
102
+ # @api semipublic
103
+ def repository
104
+ # only set @_repository explicitly when persisted
105
+ defined?(@_repository) ? @_repository : model.repository
106
+ end
107
+
108
+ # Retrieve the key(s) for this resource.
109
+ #
110
+ # This always returns the persisted key value,
111
+ # even if the key is changed and not yet persisted.
112
+ # This is done so all relations still work.
113
+ #
114
+ # @return [Array(Key)]
115
+ # the key(s) identifying this resource
116
+ #
117
+ # @api public
118
+ def key
119
+ return @_key if defined?(@_key)
120
+
121
+ model_key = model.key(repository_name)
122
+
123
+ key = model_key.map do |property|
124
+ original_attributes[property] || (property.loaded?(self) ? property.get!(self) : nil)
125
+ end
126
+
127
+ # only memoize a valid key
128
+ @_key = key if model_key.valid?(key)
129
+ end
130
+
131
+ # Checks if this Resource instance is new
132
+ #
133
+ # @return [Boolean]
134
+ # true if the resource is new and not saved
135
+ #
136
+ # @api public
137
+ def new?
138
+ persistence_state.is_a?(PersistenceState::Transient)
139
+ end
140
+
141
+ # Checks if this Resource instance is saved
142
+ #
143
+ # @return [Boolean]
144
+ # true if the resource has been saved
145
+ #
146
+ # @api public
147
+ def saved?
148
+ persistence_state.is_a?(PersistenceState::Persisted)
149
+ end
150
+
151
+ # Checks if this Resource instance is destroyed
152
+ #
153
+ # @return [Boolean]
154
+ # true if the resource has been destroyed
155
+ #
156
+ # @api public
157
+ def destroyed?
158
+ readonly? && !key.nil?
159
+ end
160
+
161
+ # Checks if the resource has no changes to save
162
+ #
163
+ # @return [Boolean]
164
+ # true if the resource may not be persisted
165
+ #
166
+ # @api public
167
+ def clean?
168
+ persistence_state.is_a?(PersistenceState::Clean) ||
169
+ persistence_state.is_a?(PersistenceState::Immutable)
170
+ end
171
+
172
+ # Checks if the resource has unsaved changes
173
+ #
174
+ # @return [Boolean]
175
+ # true if resource may be persisted
176
+ #
177
+ # @api public
178
+ def dirty?
179
+ run_once(true) do
180
+ dirty_self? || dirty_parents? || dirty_children?
181
+ end
182
+ end
183
+
184
+ # Checks if this Resource instance is readonly
185
+ #
186
+ # @return [Boolean]
187
+ # true if the resource cannot be persisted
188
+ #
189
+ # @api public
190
+ def readonly?
191
+ persistence_state.is_a?(PersistenceState::Immutable)
192
+ end
193
+
194
+ # Returns the value of the attribute.
195
+ #
196
+ # Do not read from instance variables directly, but use this method.
197
+ # This method handles lazy loading the attribute and returning of
198
+ # defaults if necessary.
199
+ #
200
+ # @example
201
+ # class Foo
202
+ # include DataMapper::Resource
203
+ #
204
+ # property :first_name, String
205
+ # property :last_name, String
206
+ #
207
+ # def full_name
208
+ # "#{attribute_get(:first_name)} #{attribute_get(:last_name)}"
209
+ # end
210
+ #
211
+ # # using the shorter syntax
212
+ # def name_for_address_book
213
+ # "#{last_name}, #{first_name}"
214
+ # end
215
+ # end
216
+ #
217
+ # @param [Symbol] name
218
+ # name of attribute to retrieve
219
+ #
220
+ # @return [Object]
221
+ # the value stored at that given attribute
222
+ # (nil if none, and default if necessary)
223
+ #
224
+ # @api public
225
+ def attribute_get(name)
226
+ property = properties[name]
227
+ persistence_state.get(property) if property
228
+ end
229
+
230
+ alias_method :[], :attribute_get
231
+
232
+ # Sets the value of the attribute and marks the attribute as dirty
233
+ # if it has been changed so that it may be saved. Do not set from
234
+ # instance variables directly, but use this method. This method
235
+ # handles the lazy loading the property and returning of defaults
236
+ # if necessary.
237
+ #
238
+ # @example
239
+ # class Foo
240
+ # include DataMapper::Resource
241
+ #
242
+ # property :first_name, String
243
+ # property :last_name, String
244
+ #
245
+ # def full_name(name)
246
+ # name = name.split(' ')
247
+ # attribute_set(:first_name, name[0])
248
+ # attribute_set(:last_name, name[1])
249
+ # end
250
+ #
251
+ # # using the shorter syntax
252
+ # def name_from_address_book(name)
253
+ # name = name.split(', ')
254
+ # self.first_name = name[1]
255
+ # self.last_name = name[0]
256
+ # end
257
+ # end
258
+ #
259
+ # @param [Symbol] name
260
+ # name of attribute to set
261
+ # @param [Object] value
262
+ # value to store
263
+ #
264
+ # @return [undefined]
265
+ #
266
+ # @api public
267
+ def attribute_set(name, value)
268
+ property = properties[name]
269
+ return unless property
270
+
271
+ value = property.typecast(value)
272
+ self.persistence_state = persistence_state.set(property, value)
273
+ end
274
+
275
+ alias_method :[]=, :attribute_set
276
+
277
+ # Gets all the attributes of the Resource instance
278
+ #
279
+ # @param [Symbol] key_on
280
+ # Use this attribute of the Property as keys.
281
+ # defaults to :name. :field is useful for adapters
282
+ # :property or nil use the actual Property object.
283
+ #
284
+ # @return [Hash]
285
+ # All the attributes
286
+ #
287
+ # @api public
288
+ def attributes(key_on = :name)
289
+ attributes = {}
290
+
291
+ lazy_load(properties)
292
+ fields.each do |property|
293
+ next unless model.public_method_defined?((name = property.name))
294
+
295
+ key = case key_on
296
+ when :name then name
297
+ when :field then property.field
298
+ else property
299
+ end
300
+
301
+ attributes[key] = __send__(name)
302
+ end
303
+
304
+ attributes
305
+ end
306
+
307
+ # Assign values to multiple attributes in one call (mass assignment)
308
+ #
309
+ # @param [Hash] attributes
310
+ # names and values of attributes to assign
311
+ #
312
+ # @return [Hash]
313
+ # names and values of attributes assigned
314
+ #
315
+ # @api public
316
+ def attributes=(attributes)
317
+ model = self.model
318
+ attributes.each do |name, value|
319
+ case name
320
+ when String, Symbol
321
+ unless model.allowed_writer_methods.include?((setter = "#{name}="))
322
+ raise ArgumentError, "The attribute '#{name}' is not accessible in #{model}"
323
+ end
324
+
325
+ __send__(setter, value)
326
+ when Associations::Relationship, Property
327
+ # only call a public #typecast (e.g. on Property instances)
328
+ value = name.typecast(value) if name.respond_to?(:typecast)
329
+ self.persistence_state = persistence_state.set(name, value)
330
+ end
331
+ end
332
+ end
333
+
334
+ # Reloads association and all child association
335
+ #
336
+ # This is accomplished by resetting the Resource key to it's
337
+ # original value, and then removing all the ivars for properties
338
+ # and relationships. On the next access of those ivars, the
339
+ # resource will eager load what it needs. While this is more of
340
+ # a lazy reload, it should result in more consistent behavior
341
+ # since no cached results will remain from the initial load.
342
+ #
343
+ # @return [Resource]
344
+ # the receiver, the current Resource instance
345
+ #
346
+ # @api public
347
+ def reload
348
+ if key
349
+ reset_key
350
+ clear_subjects
351
+ end
352
+
353
+ self.persistence_state = persistence_state.rollback
354
+
355
+ self
356
+ end
357
+
358
+ # Updates attributes and saves this Resource instance
359
+ #
360
+ # @param [Hash] attributes
361
+ # attributes to be updated
362
+ #
363
+ # @return [Boolean]
364
+ # true if resource and storage state match
365
+ #
366
+ # @api public
367
+ def update(attributes)
368
+ assert_update_clean_only(:update)
369
+ self.attributes = attributes
370
+ save
371
+ end
372
+
373
+ # Updates attributes and saves this Resource instance, bypassing hooks
374
+ #
375
+ # @param [Hash] attributes
376
+ # attributes to be updated
377
+ #
378
+ # @return [Boolean]
379
+ # true if resource and storage state match
380
+ #
381
+ # @api public
382
+ def update!(attributes)
383
+ assert_update_clean_only(:update!)
384
+ self.attributes = attributes
385
+ save!
386
+ end
387
+
388
+ # Save the instance and loaded, dirty associations to the data-store
389
+ #
390
+ # @return [Boolean]
391
+ # true if Resource instance and all associations were saved
392
+ #
393
+ # @api public
394
+ def save
395
+ assert_not_destroyed(:save)
396
+ retval = _save
397
+ assert_save_successful(:save, retval)
398
+ retval
399
+ end
400
+
401
+ # Save the instance and loaded, dirty associations to the data-store, bypassing hooks
402
+ #
403
+ # @return [Boolean]
404
+ # true if Resource instance and all associations were saved
405
+ #
406
+ # @api public
407
+ def save!
408
+ assert_not_destroyed(:save!)
409
+ retval = _save(false)
410
+ assert_save_successful(:save!, retval)
411
+ retval
412
+ end
413
+
414
+ # Destroy the instance, remove it from the repository
415
+ #
416
+ # @return [Boolean]
417
+ # true if resource was destroyed
418
+ #
419
+ # @api public
420
+ def destroy
421
+ return true if destroyed?
422
+
423
+ catch :halt do
424
+ before_destroy_hook
425
+ _destroy
426
+ after_destroy_hook
427
+ end
428
+ destroyed?
429
+ end
430
+
431
+ # Destroy the instance, remove it from the repository, bypassing hooks
432
+ #
433
+ # @return [Boolean]
434
+ # true if resource was destroyed
435
+ #
436
+ # @api public
437
+ def destroy!
438
+ return true if destroyed?
439
+
440
+ _destroy(false)
441
+ destroyed?
442
+ end
443
+
444
+ # Compares another Resource for equality
445
+ #
446
+ # Resource is equal to +other+ if they are the same object
447
+ # (identical object_id) or if they are both of the *same model* and
448
+ # all of their attributes are equivalent
449
+ #
450
+ # @param [Resource] other
451
+ # the other Resource to compare with
452
+ #
453
+ # @return [Boolean]
454
+ # true if they are equal, false if not
455
+ #
456
+ # @api public
457
+ def eql?(other)
458
+ return true if equal?(other)
459
+
460
+ instance_of?(other.class) && cmp?(other, :eql?)
461
+ end
462
+
463
+ # Compares another Resource for equivalency
464
+ #
465
+ # Resource is equivalent to +other+ if they are the same object
466
+ # (identical object_id) or all of their attribute are equivalent
467
+ #
468
+ # @param [Resource] other
469
+ # the other Resource to compare with
470
+ #
471
+ # @return [Boolean]
472
+ # true if they are equivalent, false if not
473
+ #
474
+ # @api public
475
+ def ==(other)
476
+ return true if equal?(other)
477
+ return false unless other.is_a?(Resource) && model.base_model.equal?(other.model.base_model)
478
+
479
+ cmp?(other, :==)
480
+ end
481
+
482
+ # Compares two Resources to allow them to be sorted
483
+ #
484
+ # @param [Resource] other
485
+ # The other Resource to compare with
486
+ #
487
+ # @return [Integer]
488
+ # Return 0 if Resources should be sorted as the same, -1 if the
489
+ # other Resource should be after self, and 1 if the other Resource
490
+ # should be before self
491
+ #
492
+ # @api public
493
+ def <=>(other)
494
+ model = self.model
495
+ raise ArgumentError, "Cannot compare a #{other.class} instance with a #{model} instance" unless other.is_a?(model.base_model)
496
+
497
+ model.default_order(repository_name).each do |direction|
498
+ cmp = direction.get(self) <=> direction.get(other)
499
+ return cmp if cmp.nonzero?
500
+ end
501
+ 0
502
+ end
503
+
504
+ # Returns hash value of the object.
505
+ # Two objects with the same hash value assumed equal (using eql? method)
506
+ #
507
+ # DataMapper resources are equal when their models have the same hash
508
+ # and they have the same set of properties
509
+ #
510
+ # When used as key in a Hash or Hash subclass, objects are compared
511
+ # by eql? and thus hash value has direct effect on lookup
512
+ #
513
+ # @api private
514
+ def hash
515
+ [model, key].hash
516
+ end
517
+
518
+ # Get a Human-readable representation of this Resource instance
519
+ #
520
+ # Foo.new #=> #<Foo name=nil updated_at=nil created_at=nil id=nil>
521
+ #
522
+ # @return [String]
523
+ # Human-readable representation of this Resource instance
524
+ #
525
+ # @api public
526
+ def inspect
527
+ # TODO: display relationship values
528
+ attrs = properties.map do |property|
529
+ value = if new? || property.loaded?(self)
530
+ property.get!(self).inspect
531
+ else
532
+ '<not loaded>'
533
+ end
534
+
535
+ "#{property.instance_variable_name}=#{value}"
536
+ end
537
+
538
+ "#<#{model.name} #{attrs.join(' ')}>"
539
+ end
540
+
541
+ # Hash of original values of attributes that have unsaved changes
542
+ #
543
+ # @return [Hash]
544
+ # original values of attributes that have unsaved changes
545
+ #
546
+ # @api semipublic
547
+ def original_attributes
548
+ if persistence_state.respond_to?(:original_attributes)
549
+ persistence_state.original_attributes.dup.freeze
550
+ else
551
+ {}.freeze
552
+ end
553
+ end
554
+
555
+ # Checks if an attribute has been loaded from the repository
556
+ #
557
+ # @example
558
+ # class Foo
559
+ # include DataMapper::Resource
560
+ #
561
+ # property :name, String
562
+ # property :description, Text, :lazy => false
563
+ # end
564
+ #
565
+ # Foo.new.attribute_loaded?(:description) #=> false
566
+ #
567
+ # @return [Boolean]
568
+ # true if ivar +name+ has been loaded
569
+ #
570
+ # @return [Boolean]
571
+ # true if ivar +name+ has been loaded
572
+ #
573
+ # @api private
574
+ def attribute_loaded?(name)
575
+ properties[name]&.loaded?(self) || false
576
+ end
577
+
578
+ # Checks if an attribute has unsaved changes
579
+ #
580
+ # @param [Symbol] name
581
+ # name of attribute to check for unsaved changes
582
+ #
583
+ # @return [Boolean]
584
+ # true if attribute has unsaved changes
585
+ #
586
+ # @api semipublic
587
+ def attribute_dirty?(name)
588
+ dirty_attributes.key?(properties[name])
589
+ end
590
+
591
+ # Hash of attributes that have unsaved changes
592
+ #
593
+ # @return [Hash]
594
+ # attributes that have unsaved changes
595
+ #
596
+ # @api semipublic
597
+ def dirty_attributes
598
+ dirty_attributes = {}
599
+
600
+ original_attributes.each_key do |property|
601
+ next unless property.respond_to?(:dump)
602
+
603
+ dirty_attributes[property] = property.dump(property.get!(self))
604
+ end
605
+
606
+ dirty_attributes
607
+ end
608
+
609
+ # Returns the Collection the Resource is associated with
610
+ #
611
+ # @return [nil]
612
+ # nil if this is a new record
613
+ # @return [Collection]
614
+ # a Collection that self belongs to
615
+ #
616
+ # @api private
617
+ def collection
618
+ return @_collection if @_collection || new? || readonly?
619
+
620
+ collection_for_self
621
+ end
622
+
623
+ # Associates a Resource to a Collection
624
+ #
625
+ # @param [Collection, nil] collection
626
+ # the collection to associate the resource with
627
+ #
628
+ # @return [nil]
629
+ # nil if this is a new record
630
+ # @return [Collection]
631
+ # a Collection that self belongs to
632
+ #
633
+ # @api private
634
+ def collection=(collection)
635
+ @_collection = collection
636
+ end
637
+
638
+ # Return a collection including the current resource only
639
+ #
640
+ # @return [Collection]
641
+ # a collection containing self
642
+ #
643
+ # @api private
644
+ def collection_for_self
645
+ Collection.new(query, [self])
646
+ end
647
+
648
+ # Returns a Query that will match the resource
649
+ #
650
+ # @return [Query]
651
+ # Query that will match the resource
652
+ #
653
+ # @api semipublic
654
+ def query
655
+ repository.new_query(model, fields: fields, conditions: conditions)
656
+ end
657
+
658
+ # Method for hooking callbacks before resource saving
659
+ #
660
+ # @return [undefined]
661
+ #
662
+ # @api private
663
+ protected def before_save_hook
664
+ execute_hooks_for(:before, :save)
665
+ end
666
+
667
+ # Method for hooking callbacks after resource saving
668
+ #
669
+ # @return [undefined]
670
+ #
671
+ # @api private
672
+ protected def after_save_hook
673
+ execute_hooks_for(:after, :save)
674
+ end
675
+
676
+ # Method for hooking callbacks before resource creation
677
+ #
678
+ # @return [undefined]
679
+ #
680
+ # @api private
681
+ protected def before_create_hook
682
+ execute_hooks_for(:before, :create)
683
+ end
684
+
685
+ # Method for hooking callbacks after resource creation
686
+ #
687
+ # @return [undefined]
688
+ #
689
+ # @api private
690
+ protected def after_create_hook
691
+ execute_hooks_for(:after, :create)
692
+ end
693
+
694
+ # Method for hooking callbacks before resource updating
695
+ #
696
+ # @return [undefined]
697
+ #
698
+ # @api private
699
+ protected def before_update_hook
700
+ execute_hooks_for(:before, :update)
701
+ end
702
+
703
+ # Method for hooking callbacks after resource updating
704
+ #
705
+ # @return [undefined]
706
+ #
707
+ # @api private
708
+ protected def after_update_hook
709
+ execute_hooks_for(:after, :update)
710
+ end
711
+
712
+ # Method for hooking callbacks before resource destruction
713
+ #
714
+ # @return [undefined]
715
+ #
716
+ # @api private
717
+ protected def before_destroy_hook
718
+ execute_hooks_for(:before, :destroy)
719
+ end
720
+
721
+ # Method for hooking callbacks after resource destruction
722
+ #
723
+ # @return [undefined]
724
+ #
725
+ # @api private
726
+ protected def after_destroy_hook
727
+ execute_hooks_for(:after, :destroy)
728
+ end
729
+
730
+ # Initialize a new instance of this Resource using the provided values
731
+ #
732
+ # @param [Hash] attributes
733
+ # attribute values to use for the new instance
734
+ #
735
+ # @return [Hash]
736
+ # attribute values used in the new instance
737
+ #
738
+ # @api public
739
+ private def initialize(attributes = nil) # :nodoc:
740
+ self.attributes = attributes if attributes
741
+ end
742
+
743
+ # @api private
744
+ private def initialize_copy(_original)
745
+ instance_variables.each do |ivar|
746
+ instance_variable_set(ivar, DataMapper::Ext.try_dup(instance_variable_get(ivar)))
747
+ end
748
+
749
+ self.persistence_state = persistence_state.class.new(self)
750
+ end
751
+
752
+ # Returns name of the repository this object
753
+ # was loaded from
754
+ #
755
+ # @return [String]
756
+ # name of the repository this object was loaded from
757
+ #
758
+ # @api private
759
+ private def repository_name
760
+ repository.name
761
+ end
762
+
763
+ # Gets this instance's Model's properties
764
+ #
765
+ # @return [PropertySet]
766
+ # List of this Resource's Model's properties
767
+ #
768
+ # @api private
769
+ private def properties
770
+ model.properties(repository_name)
771
+ end
772
+
773
+ # Gets this instance's Model's relationships
774
+ #
775
+ # @return [RelationshipSet]
776
+ # List of this instance's Model's Relationships
777
+ #
778
+ # @api private
779
+ private def relationships
780
+ model.relationships(repository_name)
781
+ end
782
+
783
+ # Returns the identity map for the model from the repository
784
+ #
785
+ # @return [IdentityMap]
786
+ # identity map of repository this object was loaded from
787
+ #
788
+ # @api private
789
+ private def identity_map
790
+ repository.identity_map(model)
791
+ end
792
+
793
+ # @api private
794
+ private def add_to_identity_map
795
+ identity_map[key] = self
796
+ end
797
+
798
+ # @api private
799
+ private def remove_from_identity_map
800
+ identity_map.delete(key)
801
+ end
802
+
803
+ # Fetches all the names of the attributes that have been loaded,
804
+ # even if they are lazy but have been called
805
+ #
806
+ # @return [Array<Property>]
807
+ # names of attributes that have been loaded
808
+ #
809
+ # @api private
810
+ private def fields
811
+ properties.select do |property|
812
+ property.loaded?(self) || (new? && property.default?)
813
+ end
814
+ end
815
+
816
+ # Reset the key to the original value
817
+ #
818
+ # @return [undefined]
819
+ #
820
+ # @api private
821
+ private def reset_key
822
+ properties.key.zip(key) do |property, value|
823
+ property.set!(self, value)
824
+ end
825
+ end
826
+
827
+ # Remove all the ivars for properties and relationships
828
+ #
829
+ # @return [undefined]
830
+ #
831
+ # @api private
832
+ private def clear_subjects
833
+ model_properties = properties
834
+
835
+ ((model_properties - model_properties.key) | relationships).each do |subject|
836
+ next unless subject.loaded?(self)
837
+
838
+ remove_instance_variable(subject.instance_variable_name)
839
+ end
840
+ end
841
+
842
+ # Lazy loads attributes not yet loaded
843
+ #
844
+ # @param [Array<Property>] properties
845
+ # the properties to reload
846
+ #
847
+ # @return [self]
848
+ #
849
+ # @api private
850
+ private def lazy_load(properties)
851
+ eager_load(properties - fields)
852
+ end
853
+
854
+ # Reloads specified attributes
855
+ #
856
+ # @param [Array<Property>] properties
857
+ # the properties to reload
858
+ #
859
+ # @return [Resource]
860
+ # the receiver, the current Resource instance
861
+ #
862
+ # @api private
863
+ private def eager_load(properties)
864
+ unless properties.empty? || key.nil? || collection.nil?
865
+ # set an initial value to prevent recursive lazy loads
866
+ properties.each { |property| property.set!(self, nil) }
867
+
868
+ collection&.reload(fields: properties)
869
+ end
870
+
871
+ self
872
+ end
873
+
874
+ # Return conditions to match the Resource
875
+ #
876
+ # @return [Hash]
877
+ # query conditions
878
+ #
879
+ # @api private
880
+ private def conditions
881
+ key = self.key
882
+ if key
883
+ model.key_conditions(repository, key)
884
+ else
885
+ conditions = {}
886
+ properties.each do |property|
887
+ next unless property.loaded?(self)
888
+
889
+ conditions[property] = property.get!(self)
890
+ end
891
+ conditions
892
+ end
893
+ end
894
+
895
+ # @api private
896
+ private def parent_relationships
897
+ parent_relationships = []
898
+
899
+ relationships.each do |relationship|
900
+ next unless relationship.respond_to?(:resource_for)
901
+
902
+ set_default_value(relationship)
903
+ next unless relationship.loaded?(self) && relationship.get!(self)
904
+
905
+ parent_relationships << relationship
906
+ end
907
+
908
+ parent_relationships
909
+ end
910
+
911
+ # Returns loaded child relationships
912
+ #
913
+ # @return [Array<Associations::OneToMany::Relationship>]
914
+ # array of child relationships for which this resource is parent and is loaded
915
+ #
916
+ # @api private
917
+ private def child_relationships
918
+ child_relationships = []
919
+
920
+ relationships.each do |relationship|
921
+ next unless relationship.respond_to?(:collection_for)
922
+
923
+ set_default_value(relationship)
924
+ next unless relationship.loaded?(self)
925
+
926
+ child_relationships << relationship
927
+ end
928
+
929
+ many_to_many, other = child_relationships.partition do |relationship|
930
+ relationship.is_a?(Associations::ManyToMany::Relationship)
931
+ end
932
+
933
+ many_to_many + other
934
+ end
935
+
936
+ # @api private
937
+ private def parent_associations
938
+ parent_relationships.map { |relationship| relationship.get!(self) }
939
+ end
940
+
941
+ # @api private
942
+ private def child_associations
943
+ child_relationships.map { |relationship| relationship.get_collection(self) }
944
+ end
945
+
946
+ # Commit the persisted state
947
+ #
948
+ # @return [undefined]
949
+ #
950
+ # @api private
951
+ private def _persist
952
+ self.persistence_state = persistence_state.commit
953
+ end
954
+
955
+ # This method executes the hooks before and after resource creation
956
+ #
957
+ # @return [Boolean]
958
+ #
959
+ # @see Resource#_create
960
+ #
961
+ # @api private
962
+ private def create_with_hooks
963
+ catch :halt do
964
+ before_save_hook
965
+ before_create_hook
966
+ _persist
967
+ after_create_hook
968
+ after_save_hook
969
+ end
970
+ end
971
+
972
+ # This method executes the hooks before and after resource updating
973
+ #
974
+ # @return [Boolean]
975
+ #
976
+ # @see Resource#_update
977
+ #
978
+ # @api private
979
+ private def update_with_hooks
980
+ catch :halt do
981
+ before_save_hook
982
+ before_update_hook
983
+ _persist
984
+ after_update_hook
985
+ after_save_hook
986
+ end
987
+ end
988
+
989
+ # Destroy the resource
990
+ #
991
+ # @return [undefined]
992
+ #
993
+ # @api private
994
+ private def _destroy(_execute_hooks = true)
995
+ self.persistence_state = persistence_state.delete
996
+ _persist
997
+ end
998
+
999
+ # @api private
1000
+ private def _save(execute_hooks = true)
1001
+ run_once(true) do
1002
+ save_parents(execute_hooks) && save_self(execute_hooks) && save_children(execute_hooks)
1003
+ end
1004
+ end
1005
+
1006
+ # Saves the resource
1007
+ #
1008
+ # @return [Boolean]
1009
+ # true if the resource was successfully saved
1010
+ #
1011
+ # @api semipublic
1012
+ private def save_self(execute_hooks = true)
1013
+ # short-circuit if the resource is not dirty
1014
+ return saved? unless dirty_self?
1015
+
1016
+ if execute_hooks
1017
+ new? ? create_with_hooks : update_with_hooks
1018
+ else
1019
+ _persist
1020
+ end
1021
+ clean?
1022
+ end
1023
+
1024
+ # Saves the parent resources
1025
+ #
1026
+ # @return [Boolean]
1027
+ # true if the parents were successfully saved
1028
+ #
1029
+ # @api private
1030
+ private def save_parents(execute_hooks)
1031
+ run_once(true) do
1032
+ parent_relationships.map do |relationship|
1033
+ parent = relationship.get(self)
1034
+
1035
+ if parent.__send__(:save_parents, execute_hooks) && parent.__send__(:save_self, execute_hooks)
1036
+ relationship.set(self, parent) # set the FK values
1037
+ end
1038
+ end.all?
1039
+ end
1040
+ end
1041
+
1042
+ # Saves the children resources
1043
+ #
1044
+ # @return [Boolean]
1045
+ # true if the children were successfully saved
1046
+ #
1047
+ # @api private
1048
+ private def save_children(execute_hooks)
1049
+ child_associations.map do |association|
1050
+ association.__send__(execute_hooks ? :save : :save!)
1051
+ end.all?
1052
+ end
1053
+
1054
+ # Checks if the resource has unsaved changes
1055
+ #
1056
+ # @return [Boolean]
1057
+ # true if the resource has unsaved changes
1058
+ #
1059
+ # @api semipublic
1060
+ private def dirty_self?
1061
+ if original_attributes.any?
1062
+ true
1063
+ elsif new?
1064
+ !model.serial.nil? || properties.any?(&:default?)
1065
+ else
1066
+ false
1067
+ end
1068
+ end
1069
+
1070
+ # Checks if the parents have unsaved changes
1071
+ #
1072
+ # @return [Boolean]
1073
+ # true if the parents have unsaved changes
1074
+ #
1075
+ # @api private
1076
+ private def dirty_parents?
1077
+ run_once(false) do
1078
+ parent_associations.any? do |association|
1079
+ association.__send__(:dirty_self?) || association.__send__(:dirty_parents?)
1080
+ end
1081
+ end
1082
+ end
1083
+
1084
+ # Checks if the children have unsaved changes
1085
+ #
1086
+ # @return [Boolean]
1087
+ # true if the children have unsaved changes
1088
+ #
1089
+ # @api private
1090
+ private def dirty_children?
1091
+ child_associations.any?(&:dirty?)
1092
+ end
1093
+
1094
+ # Return true if +other+'s is equivalent or equal to +self+'s
1095
+ #
1096
+ # @param [Resource] other
1097
+ # The Resource whose attributes are to be compared with +self+'s
1098
+ # @param [Symbol] operator
1099
+ # The comparison operator to use to compare the attributes
1100
+ #
1101
+ # @return [Boolean]
1102
+ # The result of the comparison of +other+'s attributes with +self+'s
1103
+ #
1104
+ # @api private
1105
+ private def cmp?(other, operator)
1106
+ return false unless repository.send(operator, other.repository) &&
1107
+ key.send(operator, other.key)
1108
+
1109
+ if saved? && other.saved?
1110
+ # if dirty attributes match then they are the same resource
1111
+ dirty_attributes == other.dirty_attributes
1112
+ else
1113
+ # compare properties for unsaved resources
1114
+ properties.all? do |property|
1115
+ __send__(property.name).send(operator, other.__send__(property.name))
1116
+ end
1117
+ end
1118
+ end
1119
+
1120
+ # @api private
1121
+ private def set_default_value(subject)
1122
+ return unless persistence_state.respond_to?(:set_default_value, true)
1123
+
1124
+ persistence_state.__send__(:set_default_value, subject)
1125
+ end
1126
+
1127
+ # Execute all the queued up hooks for a given type and name
1128
+ #
1129
+ # @param [Symbol] type
1130
+ # the type of hook to execute (before or after)
1131
+ # @param [Symbol] name
1132
+ # the name of the hook to execute
1133
+ #
1134
+ # @return [undefined]
1135
+ #
1136
+ # @api private
1137
+ private def execute_hooks_for(type, name)
1138
+ model.hooks[name][type].each { |hook| hook.call(self) }
1139
+ end
1140
+
1141
+ # Raises an exception if #update is performed on a dirty resource
1142
+ #
1143
+ # @param [Symbol] method
1144
+ # the name of the method to use in the exception
1145
+ #
1146
+ # @return [undefined]
1147
+ #
1148
+ # @raise [UpdateConflictError]
1149
+ # raise if the resource is dirty
1150
+ #
1151
+ # @api private
1152
+ private def assert_update_clean_only(method)
1153
+ return unless dirty?
1154
+
1155
+ raise UpdateConflictError, "#{model}##{method} cannot be called on a #{new? ? 'new' : 'dirty'} resource"
1156
+ end
1157
+
1158
+ # Raises an exception if #save is performed on a destroyed resource
1159
+ #
1160
+ # @param [Symbol] method
1161
+ # the name of the method to use in the exception
1162
+ #
1163
+ # @return [undefined]
1164
+ #
1165
+ # @raise [PersistenceError]
1166
+ # raise if the resource is destroyed
1167
+ #
1168
+ # @api private
1169
+ private def assert_not_destroyed(method)
1170
+ raise PersistenceError, "#{model}##{method} cannot be called on a destroyed resource" if destroyed?
1171
+ end
1172
+
1173
+ # Raises an exception if #save returns false
1174
+ #
1175
+ # @param [Symbol] method
1176
+ # the name of the method to use in the exception
1177
+ # @param [Boolean] save_retval
1178
+ # the result of the #save call
1179
+ #
1180
+ # @return [undefined]
1181
+ #
1182
+ # @raise [SaveFailureError]
1183
+ # raise if the resource was not saved
1184
+ #
1185
+ # @api private
1186
+ private def assert_save_successful(method, save_retval)
1187
+ return unless save_retval != true && raise_on_save_failure
1188
+
1189
+ raise SaveFailureError.new("#{model}##{method} returned #{save_retval.inspect}, #{model} was not saved", self)
1190
+ end
1191
+
1192
+ # Prevent a method from being in the stack more than once
1193
+ #
1194
+ # The purpose of this method is to prevent SystemStackError from
1195
+ # being thrown from methods from encountering infinite recursion
1196
+ # when called on resources having circular dependencies.
1197
+ #
1198
+ # @param [Object] default
1199
+ # default return value
1200
+ #
1201
+ # @yield The block of code to run once
1202
+ #
1203
+ # @return [Object]
1204
+ # block return value
1205
+ #
1206
+ # @api private
1207
+ private def run_once(default)
1208
+ caller_method = Kernel.caller(1).first[/`([^'?!]+)[?!]?'/, 1]
1209
+ sentinel = "@_#{caller_method}_sentinel"
1210
+ return instance_variable_get(sentinel) if instance_variable_defined?(sentinel)
1211
+
1212
+ begin
1213
+ instance_variable_set(sentinel, default)
1214
+ yield
1215
+ ensure
1216
+ remove_instance_variable(sentinel)
1217
+ end
1218
+ end
1219
+ end
1220
+ end