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