sbf-dm-core 1.3.0.beta

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