sbf-dm-core 1.3.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +388 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,876 @@
1
+ module DataMapper
2
+ module Model
3
+ include Enumerable
4
+
5
+ WRITER_METHOD_REGEXP = /=\z/
6
+ INVALID_WRITER_METHODS = %w[== != === []= taguri= attributes= collection= persistence_state= raise_on_save_failure=].to_set.freeze
7
+
8
+ # Creates a new Model class with its constant already set
9
+ #
10
+ # If a block is passed, it will be eval'd in the context of the new Model
11
+ #
12
+ # @param [#to_s] name
13
+ # the name of the new model
14
+ # @param [Object] namespace
15
+ # the namespace that will hold the new model
16
+ # @param [Proc] block
17
+ # a block that will be eval'd in the context of the new Model class
18
+ #
19
+ # @return [Class]
20
+ # the newly created Model class
21
+ #
22
+ # @api private
23
+ def self.new(name = nil, namespace = Object, &block)
24
+ model = name ? namespace.const_set(name, Class.new) : Class.new
25
+
26
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
27
+ include DataMapper::Resource
28
+ RUBY
29
+
30
+ model.instance_eval(&block) if block
31
+ model
32
+ end
33
+
34
+ # Return all models that extend the Model module
35
+ #
36
+ # class Foo
37
+ # include DataMapper::Resource
38
+ # end
39
+ #
40
+ # DataMapper::Model.descendants.first #=> Foo
41
+ #
42
+ # @return [DescendantSet]
43
+ # Set containing the descendant models
44
+ #
45
+ # @api semipublic
46
+ def self.descendants
47
+ @descendants ||= DescendantSet.new
48
+ end
49
+
50
+ # Return all models that inherit from a Model
51
+ #
52
+ # class Foo
53
+ # include DataMapper::Resource
54
+ # end
55
+ #
56
+ # class Bar < Foo
57
+ # end
58
+ #
59
+ # Foo.descendants.first #=> Bar
60
+ #
61
+ # @return [DescendantSet]
62
+ # Set containing the descendant classes
63
+ #
64
+ # @api semipublic
65
+ def descendants
66
+ @descendants ||= DescendantSet.new
67
+ end
68
+
69
+ # Return if Resource#save should raise an exception on save failures (globally)
70
+ #
71
+ # This is false by default.
72
+ #
73
+ # DataMapper::Model.raise_on_save_failure # => false
74
+ #
75
+ # @return [Boolean]
76
+ # true if a failure in Resource#save should raise an exception
77
+ #
78
+ # @api public
79
+ def self.raise_on_save_failure
80
+ if defined?(@raise_on_save_failure)
81
+ @raise_on_save_failure
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ # Specify if Resource#save should raise an exception on save failures (globally)
88
+ #
89
+ # @param [Boolean]
90
+ # a boolean that if true will cause Resource#save to raise an exception
91
+ #
92
+ # @return [Boolean]
93
+ # true if a failure in Resource#save should raise an exception
94
+ #
95
+ # @api public
96
+ def self.raise_on_save_failure=(raise_on_save_failure)
97
+ @raise_on_save_failure = raise_on_save_failure
98
+ end
99
+
100
+ # Return if Resource#save should raise an exception on save failures (per-model)
101
+ #
102
+ # This delegates to DataMapper::Model.raise_on_save_failure by default.
103
+ #
104
+ # User.raise_on_save_failure # => false
105
+ #
106
+ # @return [Boolean]
107
+ # true if a failure in Resource#save should raise an exception
108
+ #
109
+ # @api public
110
+ def raise_on_save_failure
111
+ if defined?(@raise_on_save_failure)
112
+ @raise_on_save_failure
113
+ else
114
+ DataMapper::Model.raise_on_save_failure
115
+ end
116
+ end
117
+
118
+ # Specify if Resource#save should raise an exception on save failures (per-model)
119
+ #
120
+ # @param [Boolean]
121
+ # a boolean that if true will cause Resource#save to raise an exception
122
+ #
123
+ # @return [Boolean]
124
+ # true if a failure in Resource#save should raise an exception
125
+ #
126
+ # @api public
127
+ def raise_on_save_failure=(raise_on_save_failure)
128
+ @raise_on_save_failure = raise_on_save_failure
129
+ end
130
+
131
+ # Finish model setup and verify it is valid
132
+ #
133
+ # @return [self]
134
+ #
135
+ # @api public
136
+ def finalize
137
+ finalize_relationships
138
+ finalize_allowed_writer_methods
139
+ assert_valid_name
140
+ assert_valid_properties
141
+ assert_valid_key
142
+ self
143
+ end
144
+
145
+ # Appends a module for inclusion into the model class after Resource.
146
+ #
147
+ # This is a useful way to extend Resource while still retaining a
148
+ # self.included method.
149
+ #
150
+ # @param [Module] inclusions
151
+ # the module that is to be appended to the module after Resource
152
+ #
153
+ # @return [Boolean]
154
+ # true if the inclusions have been successfully appended to the list
155
+ #
156
+ # @api semipublic
157
+ def self.append_inclusions(*inclusions)
158
+ extra_inclusions.concat inclusions
159
+
160
+ # Add the inclusion to existing descendants
161
+ descendants.each do |model|
162
+ inclusions.each { |inclusion| model.send :include, inclusion }
163
+ end
164
+
165
+ true
166
+ end
167
+
168
+ # The current registered extra inclusions
169
+ #
170
+ # @return [Mixed]
171
+ # DescendantSet or Array
172
+ # @api private
173
+ def self.extra_inclusions
174
+ @extra_inclusions ||= []
175
+ end
176
+
177
+ # Extends the model with this module after Resource has been included.
178
+ #
179
+ # This is a useful way to extend Model while still retaining a self.extended method.
180
+ #
181
+ # @param [Module] extensions
182
+ # List of modules that will extend the model after it is extended by Model
183
+ #
184
+ # @return [Boolean]
185
+ # whether or not the inclusions have been successfully appended to the list
186
+ #
187
+ # @api semipublic
188
+ def self.append_extensions(*extensions)
189
+ extra_extensions.concat extensions
190
+
191
+ # Add the extension to existing descendants
192
+ descendants.each do |model|
193
+ extensions.each { |extension| model.extend(extension) }
194
+ end
195
+
196
+ true
197
+ end
198
+
199
+ # The current registered extra extensions
200
+ #
201
+ # @return [Mixed]
202
+ # DescendantSet or Array
203
+ # @api private
204
+ def self.extra_extensions
205
+ @extra_extensions ||= []
206
+ end
207
+
208
+ # @api private
209
+ def self.extended(descendant)
210
+ descendants << descendant
211
+
212
+ descendant.instance_variable_set(:@valid, false)
213
+ descendant.instance_variable_set(:@base_model, descendant)
214
+ descendant.instance_variable_set(:@storage_names, {})
215
+ descendant.instance_variable_set(:@default_order, {})
216
+
217
+ descendant.extend(Chainable)
218
+
219
+ extra_extensions.each { |mod| descendant.extend(mod) }
220
+ extra_inclusions.each { |mod| descendant.send(:include, mod) }
221
+
222
+ super
223
+ end
224
+
225
+ # @api private
226
+ def inherited(descendant)
227
+ descendants << descendant
228
+
229
+ descendant.instance_variable_set(:@valid, false)
230
+ descendant.instance_variable_set(:@base_model, base_model)
231
+ descendant.instance_variable_set(:@storage_names, @storage_names.dup)
232
+ descendant.instance_variable_set(:@default_order, @default_order.dup)
233
+
234
+ super
235
+ end
236
+
237
+ # Gets the name of the storage receptacle for this resource in the given
238
+ # Repository (ie., table name, for database stores).
239
+ #
240
+ # @return [String]
241
+ # the storage name (ie., table name, for database stores) associated with
242
+ # this resource in the given repository
243
+ #
244
+ # @api public
245
+ def storage_name(repository_name = default_repository_name)
246
+ storage_names[repository_name] ||= repository(repository_name)&.adapter&.resource_naming_convention&.call(default_storage_name).freeze
247
+ end
248
+
249
+ # the names of the storage receptacles for this resource across all repositories
250
+ #
251
+ # @return [Hash(Symbol => String)]
252
+ # All available names of storage receptacles
253
+ #
254
+ # @api public
255
+ def storage_names
256
+ @storage_names
257
+ end
258
+
259
+ # Grab a single record by its key. Supports natural and composite key
260
+ # lookups as well.
261
+ #
262
+ # Zoo.get(1) # get the zoo with primary key of 1.
263
+ # Zoo.get!(1) # Or get! if you want an ObjectNotFoundError on failure
264
+ # Zoo.get('DFW') # wow, support for natural primary keys
265
+ # Zoo.get('Metro', 'DFW') # more wow, composite key look-up
266
+ #
267
+ # @param [Object] *key
268
+ # The primary key or keys to use for lookup
269
+ #
270
+ # @return [Resource, nil]
271
+ # A single model that was found
272
+ # If no instance was found matching +key+
273
+ #
274
+ # @api public
275
+ def get(*key)
276
+ assert_valid_key_size(key)
277
+
278
+ repository = self.repository
279
+ key = self.key(repository&.name).typecast(key)
280
+
281
+ id_map = repository&.identity_map(self)
282
+ id_map[key] || first(key_conditions(repository, key).update(order: nil))
283
+ end
284
+
285
+ # Grab a single record just like #get, but raise an ObjectNotFoundError
286
+ # if the record doesn't exist.
287
+ #
288
+ # @param [Object] *key
289
+ # The primary key or keys to use for lookup
290
+ # @return [Resource]
291
+ # A single model that was found
292
+ # @raise [ObjectNotFoundError]
293
+ # The record was not found
294
+ #
295
+ # @api public
296
+ def get!(*key)
297
+ get(*key) || raise(ObjectNotFoundError, "Could not find #{name} with key #{key.inspect}")
298
+ end
299
+
300
+ def [](*args)
301
+ all[*args]
302
+ end
303
+
304
+ alias_method :slice, :[]
305
+
306
+ def at(*args)
307
+ all.at(*args)
308
+ end
309
+
310
+ def fetch(*args, &block)
311
+ all.fetch(*args, &block)
312
+ end
313
+
314
+ def values_at(*args)
315
+ all.values_at(*args)
316
+ end
317
+
318
+ def reverse
319
+ all.reverse
320
+ end
321
+
322
+ def each(&block)
323
+ return to_enum unless block_given?
324
+
325
+ all.each(&block)
326
+ self
327
+ end
328
+
329
+ # Find a set of records matching an optional set of conditions. Additionally,
330
+ # specify the order that the records are return.
331
+ #
332
+ # Zoo.all # all zoos
333
+ # Zoo.all(:open => true) # all zoos that are open
334
+ # Zoo.all(:opened_on => start..end) # all zoos that opened on a date in the date-range
335
+ # Zoo.all(:order => [ :tiger_count.desc ]) # Ordered by tiger_count
336
+ #
337
+ # @param [Mixed] query
338
+ # [Hash] A hash describing the conditions and order for the query
339
+ # [DataMapper::Query] A query
340
+ # @return [Collection]
341
+ # A set of records found matching the conditions in +query+
342
+ # @see Collection
343
+ #
344
+ # @api public
345
+ def all(query = Undefined)
346
+ if query.equal?(Undefined) || (query.is_a?(Hash) && query.empty?)
347
+ # TODO: after adding Enumerable methods to Model, try to return self here
348
+ new_collection(self.query.dup)
349
+ else
350
+ new_collection(scoped_query(query))
351
+ end
352
+ end
353
+
354
+ # Return the first Resource or the first N Resources for the Model with an optional query
355
+ #
356
+ # When there are no arguments, return the first Resource in the
357
+ # Model. When the first argument is an Integer, return a
358
+ # Collection containing the first N Resources. When the last
359
+ # (optional) argument is a Hash scope the results to the query.
360
+ #
361
+ # @param [Mixed] args(optional)
362
+ # [Integer] limit the returned Collection to a specific number of entries
363
+ # [Hash] query (optional)
364
+ # scope the returned Resource or Collection to the supplied query
365
+ #
366
+ # @return [Resource, Collection]
367
+ # The first resource in the entries of this collection,
368
+ # or a new collection whose query has been merged
369
+ #
370
+ # @api public
371
+ def first(*args)
372
+ first_arg = args.first
373
+ last_arg = args.last
374
+
375
+ limit_specified = first_arg.is_a?(Integer)
376
+ with_query = (last_arg.is_a?(Hash) && !last_arg.empty?) || last_arg.is_a?(Query)
377
+
378
+ limit = limit_specified ? first_arg : 1
379
+ query = with_query ? last_arg : {}
380
+
381
+ query = self.query.slice(0, limit).update(query)
382
+
383
+ if limit_specified
384
+ all(query)
385
+ else
386
+ query.repository.read(query).first
387
+ end
388
+ end
389
+
390
+ # Return the last Resource or the last N Resources for the Model with an optional query
391
+ #
392
+ # When there are no arguments, return the last Resource for the
393
+ # Model. When the first argument is an Integer, return a
394
+ # Collection containing the last N Resources. When the last
395
+ # (optional) argument is a Hash scope the results to the query.
396
+ #
397
+ # @param [Mixed] args (optional)
398
+ # [Integer] limit the returned Collection to a specific number of entries
399
+ # [Hash] query (optional)
400
+ # scope the returned Resource or Collection to the supplied query
401
+ #
402
+ # @return [Resource, Collection]
403
+ # The last resource in the entries of this collection,
404
+ # or a new collection whose query has been merged
405
+ #
406
+ # @api public
407
+ def last(*args)
408
+ first_arg = args.first
409
+ last_arg = args.last
410
+
411
+ limit_specified = first_arg.is_a?(Integer)
412
+ with_query = (last_arg.is_a?(Hash) && !last_arg.empty?) || last_arg.is_a?(Query)
413
+
414
+ limit = limit_specified ? first_arg : 1
415
+ query = with_query ? last_arg : {}
416
+
417
+ query = self.query.slice(0, limit).update(query).reverse!
418
+
419
+ if limit_specified
420
+ all(query)
421
+ else
422
+ query.repository.read(query).last
423
+ end
424
+ end
425
+
426
+ # Finds the first Resource by conditions, or initializes a new
427
+ # Resource with the attributes if none found
428
+ #
429
+ # @param [Hash] conditions
430
+ # The conditions to be used to search
431
+ # @param [Hash] attributes
432
+ # The attributes to be used to create the record of none is found.
433
+ # @return [Resource]
434
+ # The instance found by +query+, or created with +attributes+ if none found
435
+ #
436
+ # @api public
437
+ def first_or_new(conditions = {}, attributes = {})
438
+ first(conditions) || new(conditions.merge(attributes))
439
+ end
440
+
441
+ # Finds the first Resource by conditions, or creates a new
442
+ # Resource with the attributes if none found
443
+ #
444
+ # @param [Hash] conditions
445
+ # The conditions to be used to search
446
+ # @param [Hash] attributes
447
+ # The attributes to be used to create the record of none is found.
448
+ # @return [Resource]
449
+ # The instance found by +query+, or created with +attributes+ if none found
450
+ #
451
+ # @api public
452
+ def first_or_create(conditions = {}, attributes = {})
453
+ first(conditions) || create(conditions.merge(attributes))
454
+ end
455
+
456
+ # Create a Resource
457
+ #
458
+ # @param [Hash(Symbol => Object)] attributes
459
+ # attributes to set
460
+ #
461
+ # @return [Resource]
462
+ # the newly created Resource instance
463
+ #
464
+ # @api public
465
+ def create(attributes = {})
466
+ _create(attributes)
467
+ end
468
+
469
+ # Create a Resource, bypassing hooks
470
+ #
471
+ # @param [Hash(Symbol => Object)] attributes
472
+ # attributes to set
473
+ #
474
+ # @return [Resource]
475
+ # the newly created Resource instance
476
+ #
477
+ # @api public
478
+ def create!(attributes = {})
479
+ _create(attributes, false)
480
+ end
481
+
482
+ # Update every Resource
483
+ #
484
+ # Person.update(:allow_beer => true)
485
+ #
486
+ # @param [Hash] attributes
487
+ # attributes to update with
488
+ #
489
+ # @return [Boolean]
490
+ # true if the resources were successfully updated
491
+ #
492
+ # @api public
493
+ def update(attributes)
494
+ all.update(attributes)
495
+ end
496
+
497
+ # Update every Resource, bypassing validations
498
+ #
499
+ # Person.update!(:allow_beer => true)
500
+ #
501
+ # @param [Hash] attributes
502
+ # attributes to update with
503
+ #
504
+ # @return [Boolean]
505
+ # true if the resources were successfully updated
506
+ #
507
+ # @api public
508
+ def update!(attributes)
509
+ all.update!(attributes)
510
+ end
511
+
512
+ # Remove all Resources from the repository
513
+ #
514
+ # @return [Boolean]
515
+ # true if the resources were successfully destroyed
516
+ #
517
+ # @api public
518
+ def destroy
519
+ all.destroy
520
+ end
521
+
522
+ # Remove all Resources from the repository, bypassing validation
523
+ #
524
+ # @return [Boolean]
525
+ # true if the resources were successfully destroyed
526
+ #
527
+ # @api public
528
+ def destroy!
529
+ all.destroy!
530
+ end
531
+
532
+ # Copy a set of records from one repository to another.
533
+ #
534
+ # @param [String] source_repository_name
535
+ # The name of the Repository the resources should be copied _from_
536
+ # @param [String] target_repository_name
537
+ # The name of the Repository the resources should be copied _to_
538
+ # @param [Hash] query
539
+ # The conditions with which to find the records to copy. These
540
+ # conditions are merged with Model.query
541
+ #
542
+ # @return [DataMapper::Repository]
543
+ # A Collection of the Resource instances created in the operation
544
+ #
545
+ # @api public
546
+ def copy(source_repository_name, target_repository_name, query = {})
547
+ target_properties = properties(target_repository_name)
548
+
549
+ query[:fields] ||= properties(source_repository_name).select do |property|
550
+ target_properties.include?(property)
551
+ end
552
+
553
+ repository(target_repository_name) do |repository|
554
+ resources = []
555
+
556
+ all(query.merge(repository: source_repository_name)).each do |resource|
557
+ new_resource = new
558
+ query[:fields].each { |property| new_resource.__send__("#{property.name}=", property.get(resource)) }
559
+ resources << new_resource if new_resource.save
560
+ end
561
+
562
+ all(Query.target_query(repository, self, resources))
563
+ end
564
+ end
565
+
566
+ # Loads an instance of this Model, taking into account IdentityMap lookup,
567
+ # inheritance columns(s) and Property typecasting.
568
+ #
569
+ # @param [Enumerable(Object)] records
570
+ # an Array of Resource or Hashes to load a Resource with
571
+ #
572
+ # @return [Resource]
573
+ # the loaded Resource instance
574
+ #
575
+ # @api semipublic
576
+ def load(records, query)
577
+ repository = query.repository
578
+ repository_name = repository.name
579
+ fields = query.fields
580
+ discriminator = properties(repository_name).discriminator
581
+ no_reload = !query.reload?
582
+
583
+ field_map = fields.to_h { |property| [property, property.field] }
584
+
585
+ records.map do |record|
586
+ identity_map = nil
587
+ key_values = nil
588
+ resource = nil
589
+
590
+ case record
591
+ when Hash
592
+ # remap fields to use the Property object
593
+ record = record.dup
594
+ field_map.each { |property, field| record[property] = record.delete(field) if record.key?(field) }
595
+
596
+ model = discriminator&.load(record[discriminator]) || self
597
+ model_key = model.key(repository_name)
598
+
599
+ resource = if model_key.valid?((key_values = record.values_at(*model_key)))
600
+ identity_map = repository.identity_map(model)
601
+ identity_map[key_values]
602
+ end
603
+
604
+ resource ||= model.allocate
605
+
606
+ fields.each do |property|
607
+ next if no_reload && property.loaded?(resource)
608
+
609
+ value = record[property]
610
+
611
+ # TODO: typecasting should happen inside the Adapter
612
+ # and all values should come back as expected objects
613
+ value = property.load(value)
614
+
615
+ property.set!(resource, value)
616
+ end
617
+ when Resource
618
+ model = record.model
619
+ model_key = model.key(repository_name)
620
+
621
+ resource = if model_key.valid?((key_values = record.key))
622
+ identity_map = repository.identity_map(model)
623
+ identity_map[key_values]
624
+ end
625
+
626
+ resource ||= model.allocate
627
+
628
+ fields.each do |property|
629
+ next if no_reload && property.loaded?(resource)
630
+
631
+ property.set!(resource, property.get!(record))
632
+ end
633
+ end
634
+
635
+ resource.instance_variable_set(:@_repository, repository)
636
+
637
+ if identity_map
638
+ resource&.persistence_state = Resource::PersistenceState::Clean.new(resource) unless resource&.persistence_state?
639
+
640
+ # defer setting the IdentityMap so second level caches can
641
+ # record the state of the resource after loaded
642
+ identity_map[key_values] = resource
643
+ else
644
+ resource&.persistence_state = Resource::PersistenceState::Immutable.new(resource)
645
+ end
646
+
647
+ resource
648
+ end
649
+ end
650
+
651
+ # @api semipublic
652
+ attr_reader :base_model
653
+
654
+ # The list of writer methods that can be mass-assigned to in #attributes=
655
+ #
656
+ # @return [Set]
657
+ #
658
+ # @api private
659
+ attr_reader :allowed_writer_methods
660
+
661
+ # @api semipublic
662
+ def default_repository_name
663
+ Repository.default_name
664
+ end
665
+
666
+ # @api semipublic
667
+ def default_order(repository_name = default_repository_name)
668
+ @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }.freeze
669
+ end
670
+
671
+ # Get the repository with a given name, or the default one for the current
672
+ # context, or the default one for this class.
673
+ #
674
+ # @param [Symbol] name
675
+ # the name of the repository wanted
676
+ # @param [Block] block
677
+ # block to execute with the fetched repository as parameter
678
+ #
679
+ # @return [Object, Repository]
680
+ # whatever the block returns, if given a block,
681
+ # otherwise the requested repository.
682
+ #
683
+ # @api private
684
+ def repository(name = nil, &block)
685
+ #
686
+ # There has been a couple of different strategies here, but me (zond) and dkubb are at least
687
+ # united in the concept of explicitness over implicitness. That is - the explicit wish of the
688
+ # caller (+name+) should be given more priority than the implicit wish of the caller (Repository.context.last).
689
+ #
690
+
691
+ DataMapper.repository(name || repository_name, &block)
692
+ end
693
+
694
+ # Get the current +repository_name+ for this Model.
695
+ #
696
+ # If there are any Repository contexts, the name of the last one will
697
+ # be returned, else the +default_repository_name+ of this model will be
698
+ #
699
+ # @return [String]
700
+ # the current repository name to use for this Model
701
+ #
702
+ # @api private
703
+ def repository_name
704
+ context = Repository.context
705
+ context.any? ? context.last&.name : default_repository_name
706
+ end
707
+
708
+ # Gets the current Set of repositories for which
709
+ # this Model has been defined (beyond default)
710
+ #
711
+ # @return [Set]
712
+ # The Set of repositories for which this Model
713
+ # has been defined (beyond default)
714
+ #
715
+ # @api private
716
+ def repositories
717
+ [repository].to_set + @properties.keys.map { |repository_name| DataMapper.repository(repository_name) }
718
+ end
719
+
720
+ # @api private
721
+ def const_missing(name)
722
+ if name == :DM
723
+ raise "#{name} prefix deprecated and no longer necessary (#{caller.first})"
724
+ elsif name == :Resource
725
+ Resource
726
+ else
727
+ super
728
+ end
729
+ end
730
+
731
+ # @api private
732
+ private def _create(attributes, execute_hooks = true)
733
+ resource = new(attributes)
734
+ resource.__send__(execute_hooks ? :save : :save!)
735
+ resource
736
+ end
737
+
738
+ # @api private
739
+ private def default_storage_name
740
+ base_model.name
741
+ end
742
+
743
+ # Initializes a new Collection
744
+ #
745
+ # @return [Collection]
746
+ # A new Collection object
747
+ #
748
+ # @api private
749
+ private def new_collection(query, resources = nil, &block)
750
+ Collection.new(query, resources, &block)
751
+ end
752
+
753
+ # @api private
754
+ # TODO: move the logic to create relative query into Query
755
+ private def scoped_query(query)
756
+ if query.is_a?(Query)
757
+ query.dup
758
+ else
759
+ repository = if query.key?(:repository)
760
+ query = query.dup
761
+ repository = query.delete(:repository)
762
+
763
+ if repository.is_a?(Symbol)
764
+ DataMapper.repository(repository)
765
+ else
766
+ repository
767
+ end
768
+ else
769
+ self.repository
770
+ end
771
+
772
+ query = self.query.merge(query)
773
+
774
+ if self.query.repository == repository
775
+ query
776
+ else
777
+ repository.new_query(self, query.options)
778
+ end
779
+ end
780
+ end
781
+
782
+ # Initialize all foreign key properties established by relationships
783
+ #
784
+ # @return [undefined]
785
+ #
786
+ # @api private
787
+ private def finalize_relationships
788
+ relationships(repository_name).each(&:finalize)
789
+ end
790
+
791
+ # Initialize the list of allowed writer methods
792
+ #
793
+ # @return [undefined]
794
+ #
795
+ # @api private
796
+ private def finalize_allowed_writer_methods
797
+ @allowed_writer_methods = public_instance_methods.map(&:to_s).grep(WRITER_METHOD_REGEXP).to_set
798
+ @allowed_writer_methods -= INVALID_WRITER_METHODS
799
+ @allowed_writer_methods.freeze
800
+ end
801
+
802
+ # @api private
803
+ # TODO: Remove this once appropriate warnings can be added.
804
+ private def assert_valid(force = false) # :nodoc:
805
+ return if @valid && !force
806
+
807
+ @valid = true
808
+ finalize
809
+ end
810
+
811
+ # Raises an exception if #get receives the wrong number of arguments
812
+ #
813
+ # @param [Array] key
814
+ # the key value
815
+ #
816
+ # @return [undefined]
817
+ #
818
+ # @raise [UpdateConflictError]
819
+ # raise if the resource is dirty
820
+ #
821
+ # @api private
822
+ private def assert_valid_key_size(key)
823
+ expected_key_size = self.key(repository_name).size
824
+ actual_key_size = key.size
825
+
826
+ return unless actual_key_size != expected_key_size
827
+
828
+ raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}"
829
+ end
830
+
831
+ # Test if the model name is valid
832
+ #
833
+ # @return [undefined]
834
+ #
835
+ # @api private
836
+ private def assert_valid_name
837
+ return unless name.to_s.strip.empty?
838
+
839
+ raise IncompleteModelError, "#{inspect} must have a name"
840
+ end
841
+
842
+ # Test if the model has properties
843
+ #
844
+ # A model may also be valid if it has at least one m:1 relationships which
845
+ # will add inferred foreign key properties.
846
+ #
847
+ # @return [undefined]
848
+ #
849
+ # @raise [IncompleteModelError]
850
+ # raised if the model has no properties
851
+ #
852
+ # @api private
853
+ private def assert_valid_properties
854
+ repository_name = self.repository_name
855
+ if properties(repository_name).empty? &&
856
+ relationships(repository_name).none? { |relationship| relationship.is_a?(Associations::ManyToOne::Relationship) }
857
+
858
+ raise IncompleteModelError, "#{name} must have at least one property or many to one relationship in #{repository_name} to be valid"
859
+ end
860
+ end
861
+
862
+ # Test if the model has a valid key
863
+ #
864
+ # @return [undefined]
865
+ #
866
+ # @raise [IncompleteModelError]
867
+ # raised if the model does not have a valid key
868
+ #
869
+ # @api private
870
+ private def assert_valid_key
871
+ return unless key(repository_name).empty?
872
+
873
+ raise IncompleteModelError, "#{name} must have a key in #{repository_name} to be valid"
874
+ end
875
+ end
876
+ end