sbf-dm-core 1.3.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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