ghost_dm-core 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (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