ardm-core 1.2.1

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