ardm-core 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +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