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,23 @@
1
+ module Kernel
2
+
3
+ # Returns the object's singleton class.
4
+ #
5
+ # @return [Class]
6
+ #
7
+ # @api private
8
+ def singleton_class
9
+ class << self
10
+ self
11
+ end
12
+ end unless respond_to?(:singleton_class) # exists in 1.9.2
13
+
14
+ private
15
+
16
+ # Delegates to DataMapper.repository()
17
+ #
18
+ # @api public
19
+ def repository(*args, &block)
20
+ DataMapper.repository(*args, &block)
21
+ end
22
+
23
+ end # module Kernel
@@ -0,0 +1,6 @@
1
+ class Pathname
2
+ # alias_method :to_s, :to to_str when to_str not defined
3
+ unless public_instance_methods(false).any? { |m| m.to_sym == :to_str }
4
+ alias_method :to_str, :to_s
5
+ end
6
+ end # class Pathname
@@ -0,0 +1,10 @@
1
+ class Symbol
2
+ (DataMapper::Query::Conditions::Comparison.slugs | [ :not, :asc, :desc ]).each do |sym|
3
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
4
+ def #{sym}
5
+ #{"raise \"explicit use of '#{sym}' operator is deprecated (#{caller.first})\"" if sym == :eql || sym == :in}
6
+ DataMapper::Query::Operator.new(self, #{sym.inspect})
7
+ end
8
+ RUBY
9
+ end
10
+ end # class Symbol
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+
3
+ # Tracks objects to help ensure that each object gets loaded only once.
4
+ # See: http://www.martinfowler.com/eaaCatalog/identityMap.html
5
+ class IdentityMap < Hash
6
+ end # class IdentityMap
7
+ end # module DataMapper
@@ -0,0 +1,869 @@
1
+ module DataMapper
2
+ module Model
3
+ include Enumerable
4
+
5
+ WRITER_METHOD_REGEXP = /=\z/.freeze
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 [Model]
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 [Set]
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 [undefined]
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
+ end
143
+
144
+ # Appends a module for inclusion into the model class after Resource.
145
+ #
146
+ # This is a useful way to extend Resource while still retaining a
147
+ # self.included method.
148
+ #
149
+ # @param [Module] inclusions
150
+ # the module that is to be appended to the module after Resource
151
+ #
152
+ # @return [Boolean]
153
+ # true if the inclusions have been successfully appended to the list
154
+ #
155
+ # @api semipublic
156
+ def self.append_inclusions(*inclusions)
157
+ extra_inclusions.concat inclusions
158
+
159
+ # Add the inclusion to existing descendants
160
+ descendants.each do |model|
161
+ inclusions.each { |inclusion| model.send :include, inclusion }
162
+ end
163
+
164
+ true
165
+ end
166
+
167
+ # The current registered extra inclusions
168
+ #
169
+ # @return [Set]
170
+ #
171
+ # @api private
172
+ def self.extra_inclusions
173
+ @extra_inclusions ||= []
174
+ end
175
+
176
+ # Extends the model with this module after Resource has been included.
177
+ #
178
+ # This is a useful way to extend Model while still retaining a self.extended method.
179
+ #
180
+ # @param [Module] extensions
181
+ # List of modules that will extend the model after it is extended by Model
182
+ #
183
+ # @return [Boolean]
184
+ # whether or not the inclusions have been successfully appended to the list
185
+ #
186
+ # @api semipublic
187
+ def self.append_extensions(*extensions)
188
+ extra_extensions.concat extensions
189
+
190
+ # Add the extension to existing descendants
191
+ descendants.each do |model|
192
+ extensions.each { |extension| model.extend(extension) }
193
+ end
194
+
195
+ true
196
+ end
197
+
198
+ # The current registered extra extensions
199
+ #
200
+ # @return [Set]
201
+ #
202
+ # @api private
203
+ def self.extra_extensions
204
+ @extra_extensions ||= []
205
+ end
206
+
207
+ # @api private
208
+ def self.extended(descendant)
209
+ descendants << descendant
210
+
211
+ descendant.instance_variable_set(:@valid, false)
212
+ descendant.instance_variable_set(:@base_model, descendant)
213
+ descendant.instance_variable_set(:@storage_names, {})
214
+ descendant.instance_variable_set(:@default_order, {})
215
+
216
+ descendant.extend(Chainable)
217
+
218
+ extra_extensions.each { |mod| descendant.extend(mod) }
219
+ extra_inclusions.each { |mod| descendant.send(:include, mod) }
220
+ end
221
+
222
+ # @api private
223
+ def inherited(descendant)
224
+ descendants << descendant
225
+
226
+ descendant.instance_variable_set(:@valid, false)
227
+ descendant.instance_variable_set(:@base_model, base_model)
228
+ descendant.instance_variable_set(:@storage_names, @storage_names.dup)
229
+ descendant.instance_variable_set(:@default_order, @default_order.dup)
230
+ end
231
+
232
+ # Gets the name of the storage receptacle for this resource in the given
233
+ # Repository (ie., table name, for database stores).
234
+ #
235
+ # @return [String]
236
+ # the storage name (ie., table name, for database stores) associated with
237
+ # this resource in the given repository
238
+ #
239
+ # @api public
240
+ def storage_name(repository_name = default_repository_name)
241
+ storage_names[repository_name] ||= repository(repository_name).adapter.resource_naming_convention.call(default_storage_name).freeze
242
+ end
243
+
244
+ # the names of the storage receptacles for this resource across all repositories
245
+ #
246
+ # @return [Hash(Symbol => String)]
247
+ # All available names of storage receptacles
248
+ #
249
+ # @api public
250
+ def storage_names
251
+ @storage_names
252
+ end
253
+
254
+ # Grab a single record by its key. Supports natural and composite key
255
+ # lookups as well.
256
+ #
257
+ # Zoo.get(1) # get the zoo with primary key of 1.
258
+ # Zoo.get!(1) # Or get! if you want an ObjectNotFoundError on failure
259
+ # Zoo.get('DFW') # wow, support for natural primary keys
260
+ # Zoo.get('Metro', 'DFW') # more wow, composite key look-up
261
+ #
262
+ # @param [Object] *key
263
+ # The primary key or keys to use for lookup
264
+ #
265
+ # @return [Resource, nil]
266
+ # A single model that was found
267
+ # If no instance was found matching +key+
268
+ #
269
+ # @api public
270
+ def get(*key)
271
+ assert_valid_key_size(key)
272
+
273
+ repository = self.repository
274
+ key = self.key(repository.name).typecast(key)
275
+
276
+ repository.identity_map(self)[key] || first(key_conditions(repository, key).update(:order => nil))
277
+ end
278
+
279
+ # Grab a single record just like #get, but raise an ObjectNotFoundError
280
+ # if the record doesn't exist.
281
+ #
282
+ # @param [Object] *key
283
+ # The primary key or keys to use for lookup
284
+ # @return [Resource]
285
+ # A single model that was found
286
+ # @raise [ObjectNotFoundError]
287
+ # The record was not found
288
+ #
289
+ # @api public
290
+ def get!(*key)
291
+ get(*key) || raise(ObjectNotFoundError, "Could not find #{self.name} with key #{key.inspect}")
292
+ end
293
+
294
+ def [](*args)
295
+ all[*args]
296
+ end
297
+
298
+ alias_method :slice, :[]
299
+
300
+ def at(*args)
301
+ all.at(*args)
302
+ end
303
+
304
+ def fetch(*args, &block)
305
+ all.fetch(*args, &block)
306
+ end
307
+
308
+ def values_at(*args)
309
+ all.values_at(*args)
310
+ end
311
+
312
+ def reverse
313
+ all.reverse
314
+ end
315
+
316
+ def each(&block)
317
+ all.each(&block)
318
+ self
319
+ end
320
+
321
+ # Find a set of records matching an optional set of conditions. Additionally,
322
+ # specify the order that the records are return.
323
+ #
324
+ # Zoo.all # all zoos
325
+ # Zoo.all(:open => true) # all zoos that are open
326
+ # Zoo.all(:opened_on => start..end) # all zoos that opened on a date in the date-range
327
+ # Zoo.all(:order => [ :tiger_count.desc ]) # Ordered by tiger_count
328
+ #
329
+ # @param [Hash] query
330
+ # A hash describing the conditions and order for the query
331
+ # @return [Collection]
332
+ # A set of records found matching the conditions in +query+
333
+ # @see Collection
334
+ #
335
+ # @api public
336
+ def all(query = Undefined)
337
+ if query.equal?(Undefined) || (query.kind_of?(Hash) && query.empty?)
338
+ # TODO: after adding Enumerable methods to Model, try to return self here
339
+ new_collection(self.query.dup)
340
+ else
341
+ new_collection(scoped_query(query))
342
+ end
343
+ end
344
+
345
+ # Return the first Resource or the first N Resources for the Model with an optional query
346
+ #
347
+ # When there are no arguments, return the first Resource in the
348
+ # Model. When the first argument is an Integer, return a
349
+ # Collection containing the first N Resources. When the last
350
+ # (optional) argument is a Hash scope the results to the query.
351
+ #
352
+ # @param [Integer] limit (optional)
353
+ # limit the returned Collection to a specific number of entries
354
+ # @param [Hash] query (optional)
355
+ # scope the returned Resource or Collection to the supplied query
356
+ #
357
+ # @return [Resource, Collection]
358
+ # The first resource in the entries of this collection,
359
+ # or a new collection whose query has been merged
360
+ #
361
+ # @api public
362
+ def first(*args)
363
+ first_arg = args.first
364
+ last_arg = args.last
365
+
366
+ limit_specified = first_arg.kind_of?(Integer)
367
+ with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query)
368
+
369
+ limit = limit_specified ? first_arg : 1
370
+ query = with_query ? last_arg : {}
371
+
372
+ query = self.query.slice(0, limit).update(query)
373
+
374
+ if limit_specified
375
+ all(query)
376
+ else
377
+ query.repository.read(query).first
378
+ end
379
+ end
380
+
381
+ # Return the last Resource or the last N Resources for the Model with an optional query
382
+ #
383
+ # When there are no arguments, return the last Resource for the
384
+ # Model. When the first argument is an Integer, return a
385
+ # Collection containing the last N Resources. When the last
386
+ # (optional) argument is a Hash scope the results to the query.
387
+ #
388
+ # @param [Integer] limit (optional)
389
+ # limit the returned Collection to a specific number of entries
390
+ # @param [Hash] query (optional)
391
+ # scope the returned Resource or Collection to the supplied query
392
+ #
393
+ # @return [Resource, Collection]
394
+ # The last resource in the entries of this collection,
395
+ # or a new collection whose query has been merged
396
+ #
397
+ # @api public
398
+ def last(*args)
399
+ first_arg = args.first
400
+ last_arg = args.last
401
+
402
+ limit_specified = first_arg.kind_of?(Integer)
403
+ with_query = (last_arg.kind_of?(Hash) && !last_arg.empty?) || last_arg.kind_of?(Query)
404
+
405
+ limit = limit_specified ? first_arg : 1
406
+ query = with_query ? last_arg : {}
407
+
408
+ query = self.query.slice(0, limit).update(query).reverse!
409
+
410
+ if limit_specified
411
+ all(query)
412
+ else
413
+ query.repository.read(query).last
414
+ end
415
+ end
416
+
417
+ # Finds the first Resource by conditions, or initializes a new
418
+ # Resource with the attributes if none found
419
+ #
420
+ # @param [Hash] conditions
421
+ # The conditions to be used to search
422
+ # @param [Hash] attributes
423
+ # The attributes to be used to create the record of none is found.
424
+ # @return [Resource]
425
+ # The instance found by +query+, or created with +attributes+ if none found
426
+ #
427
+ # @api public
428
+ def first_or_new(conditions = {}, attributes = {})
429
+ first(conditions) || new(conditions.merge(attributes))
430
+ end
431
+
432
+ # Finds the first Resource by conditions, or creates a new
433
+ # Resource with the attributes if none found
434
+ #
435
+ # @param [Hash] conditions
436
+ # The conditions to be used to search
437
+ # @param [Hash] attributes
438
+ # The attributes to be used to create the record of none is found.
439
+ # @return [Resource]
440
+ # The instance found by +query+, or created with +attributes+ if none found
441
+ #
442
+ # @api public
443
+ def first_or_create(conditions = {}, attributes = {})
444
+ first(conditions) || create(conditions.merge(attributes))
445
+ end
446
+
447
+ # Create a Resource
448
+ #
449
+ # @param [Hash(Symbol => Object)] attributes
450
+ # attributes to set
451
+ #
452
+ # @return [Resource]
453
+ # the newly created Resource instance
454
+ #
455
+ # @api public
456
+ def create(attributes = {})
457
+ _create(attributes)
458
+ end
459
+
460
+ # Create a Resource, bypassing hooks
461
+ #
462
+ # @param [Hash(Symbol => Object)] attributes
463
+ # attributes to set
464
+ #
465
+ # @return [Resource]
466
+ # the newly created Resource instance
467
+ #
468
+ # @api public
469
+ def create!(attributes = {})
470
+ _create(attributes, false)
471
+ end
472
+
473
+ # Update every Resource
474
+ #
475
+ # Person.update(:allow_beer => true)
476
+ #
477
+ # @param [Hash] attributes
478
+ # attributes to update with
479
+ #
480
+ # @return [Boolean]
481
+ # true if the resources were successfully updated
482
+ #
483
+ # @api public
484
+ def update(attributes)
485
+ all.update(attributes)
486
+ end
487
+
488
+ # Update every Resource, bypassing validations
489
+ #
490
+ # Person.update!(:allow_beer => true)
491
+ #
492
+ # @param [Hash] attributes
493
+ # attributes to update with
494
+ #
495
+ # @return [Boolean]
496
+ # true if the resources were successfully updated
497
+ #
498
+ # @api public
499
+ def update!(attributes)
500
+ all.update!(attributes)
501
+ end
502
+
503
+ # Remove all Resources from the repository
504
+ #
505
+ # @return [Boolean]
506
+ # true if the resources were successfully destroyed
507
+ #
508
+ # @api public
509
+ def destroy
510
+ all.destroy
511
+ end
512
+
513
+ # Remove all Resources from the repository, bypassing validation
514
+ #
515
+ # @return [Boolean]
516
+ # true if the resources were successfully destroyed
517
+ #
518
+ # @api public
519
+ def destroy!
520
+ all.destroy!
521
+ end
522
+
523
+ # Copy a set of records from one repository to another.
524
+ #
525
+ # @param [String] source_repository_name
526
+ # The name of the Repository the resources should be copied _from_
527
+ # @param [String] target_repository_name
528
+ # The name of the Repository the resources should be copied _to_
529
+ # @param [Hash] query
530
+ # The conditions with which to find the records to copy. These
531
+ # conditions are merged with Model.query
532
+ #
533
+ # @return [Collection]
534
+ # A Collection of the Resource instances created in the operation
535
+ #
536
+ # @api public
537
+ def copy(source_repository_name, target_repository_name, query = {})
538
+ target_properties = properties(target_repository_name)
539
+
540
+ query[:fields] ||= properties(source_repository_name).select do |property|
541
+ target_properties.include?(property)
542
+ end
543
+
544
+ repository(target_repository_name) do |repository|
545
+ resources = []
546
+
547
+ all(query.merge(:repository => source_repository_name)).each do |resource|
548
+ new_resource = new
549
+ query[:fields].each { |property| new_resource.__send__("#{property.name}=", property.get(resource)) }
550
+ resources << new_resource if new_resource.save
551
+ end
552
+
553
+ all(Query.target_query(repository, self, resources))
554
+ end
555
+ end
556
+
557
+ # Loads an instance of this Model, taking into account IdentityMap lookup,
558
+ # inheritance columns(s) and Property typecasting.
559
+ #
560
+ # @param [Enumerable(Object)] records
561
+ # an Array of Resource or Hashes to load a Resource with
562
+ #
563
+ # @return [Resource]
564
+ # the loaded Resource instance
565
+ #
566
+ # @api semipublic
567
+ def load(records, query)
568
+ repository = query.repository
569
+ repository_name = repository.name
570
+ fields = query.fields
571
+ discriminator = properties(repository_name).discriminator
572
+ no_reload = !query.reload?
573
+
574
+ field_map = Hash[ fields.map { |property| [ property, property.field ] } ]
575
+
576
+ records.map do |record|
577
+ identity_map = nil
578
+ key_values = nil
579
+ resource = nil
580
+
581
+ case record
582
+ when Hash
583
+ # remap fields to use the Property object
584
+ record = record.dup
585
+ field_map.each { |property, field| record[property] = record.delete(field) if record.key?(field) }
586
+
587
+ model = discriminator && discriminator.load(record[discriminator]) || self
588
+ model_key = model.key(repository_name)
589
+
590
+ resource = if model_key.valid?(key_values = record.values_at(*model_key))
591
+ identity_map = repository.identity_map(model)
592
+ identity_map[key_values]
593
+ end
594
+
595
+ resource ||= model.allocate
596
+
597
+ fields.each do |property|
598
+ next if no_reload && property.loaded?(resource)
599
+
600
+ value = record[property]
601
+
602
+ # TODO: typecasting should happen inside the Adapter
603
+ # and all values should come back as expected objects
604
+ value = property.load(value)
605
+
606
+ property.set!(resource, value)
607
+ end
608
+
609
+ when Resource
610
+ model = record.model
611
+ model_key = model.key(repository_name)
612
+
613
+ resource = if model_key.valid?(key_values = record.key)
614
+ identity_map = repository.identity_map(model)
615
+ identity_map[key_values]
616
+ end
617
+
618
+ resource ||= model.allocate
619
+
620
+ fields.each do |property|
621
+ next if no_reload && property.loaded?(resource)
622
+
623
+ property.set!(resource, property.get!(record))
624
+ end
625
+ end
626
+
627
+ resource.instance_variable_set(:@_repository, repository)
628
+
629
+ if identity_map
630
+ resource.persistence_state = Resource::PersistenceState::Clean.new(resource) unless resource.persistence_state?
631
+
632
+ # defer setting the IdentityMap so second level caches can
633
+ # record the state of the resource after loaded
634
+ identity_map[key_values] = resource
635
+ else
636
+ resource.persistence_state = Resource::PersistenceState::Immutable.new(resource)
637
+ end
638
+
639
+ resource
640
+ end
641
+ end
642
+
643
+ # @api semipublic
644
+ attr_reader :base_model
645
+
646
+ # The list of writer methods that can be mass-assigned to in #attributes=
647
+ #
648
+ # @return [Set]
649
+ #
650
+ # @api private
651
+ attr_reader :allowed_writer_methods
652
+
653
+ # @api semipublic
654
+ def default_repository_name
655
+ Repository.default_name
656
+ end
657
+
658
+ # @api semipublic
659
+ def default_order(repository_name = default_repository_name)
660
+ @default_order[repository_name] ||= key(repository_name).map { |property| Query::Direction.new(property) }.freeze
661
+ end
662
+
663
+ # Get the repository with a given name, or the default one for the current
664
+ # context, or the default one for this class.
665
+ #
666
+ # @param [Symbol] name
667
+ # the name of the repository wanted
668
+ # @param [Block] block
669
+ # block to execute with the fetched repository as parameter
670
+ #
671
+ # @return [Object, Respository]
672
+ # whatever the block returns, if given a block,
673
+ # otherwise the requested repository.
674
+ #
675
+ # @api private
676
+ def repository(name = nil, &block)
677
+ #
678
+ # There has been a couple of different strategies here, but me (zond) and dkubb are at least
679
+ # united in the concept of explicitness over implicitness. That is - the explicit wish of the
680
+ # caller (+name+) should be given more priority than the implicit wish of the caller (Repository.context.last).
681
+ #
682
+
683
+ DataMapper.repository(name || repository_name, &block)
684
+ end
685
+
686
+ # Get the current +repository_name+ for this Model.
687
+ #
688
+ # If there are any Repository contexts, the name of the last one will
689
+ # be returned, else the +default_repository_name+ of this model will be
690
+ #
691
+ # @return [String]
692
+ # the current repository name to use for this Model
693
+ #
694
+ # @api private
695
+ def repository_name
696
+ context = Repository.context
697
+ context.any? ? context.last.name : default_repository_name
698
+ end
699
+
700
+ # Gets the current Set of repositories for which
701
+ # this Model has been defined (beyond default)
702
+ #
703
+ # @return [Set]
704
+ # The Set of repositories for which this Model
705
+ # has been defined (beyond default)
706
+ #
707
+ # @api private
708
+ def repositories
709
+ [ repository ].to_set + @properties.keys.map { |repository_name| DataMapper.repository(repository_name) }
710
+ end
711
+
712
+ private
713
+
714
+ # @api private
715
+ def _create(attributes, execute_hooks = true)
716
+ resource = new(attributes)
717
+ resource.__send__(execute_hooks ? :save : :save!)
718
+ resource
719
+ end
720
+
721
+ # @api private
722
+ def const_missing(name)
723
+ if name == :DM
724
+ raise "#{name} prefix deprecated and no longer necessary (#{caller.first})"
725
+ elsif name == :Resource
726
+ Resource
727
+ else
728
+ super
729
+ end
730
+ end
731
+
732
+ # @api private
733
+ def default_storage_name
734
+ base_model.name
735
+ end
736
+
737
+ # Initializes a new Collection
738
+ #
739
+ # @return [Collection]
740
+ # A new Collection object
741
+ #
742
+ # @api private
743
+ def new_collection(query, resources = nil, &block)
744
+ Collection.new(query, resources, &block)
745
+ end
746
+
747
+ # @api private
748
+ # TODO: move the logic to create relative query into Query
749
+ def scoped_query(query)
750
+ if query.kind_of?(Query)
751
+ query.dup
752
+ else
753
+ repository = if query.key?(:repository)
754
+ query = query.dup
755
+ repository = query.delete(:repository)
756
+
757
+ if repository.kind_of?(Symbol)
758
+ DataMapper.repository(repository)
759
+ else
760
+ repository
761
+ end
762
+ else
763
+ self.repository
764
+ end
765
+
766
+ query = self.query.merge(query)
767
+
768
+ if self.query.repository == repository
769
+ query
770
+ else
771
+ repository.new_query(self, query.options)
772
+ end
773
+ end
774
+ end
775
+
776
+ # Initialize all foreign key properties established by relationships
777
+ #
778
+ # @return [undefined]
779
+ #
780
+ # @api private
781
+ def finalize_relationships
782
+ relationships(repository_name).each { |relationship| relationship.finalize }
783
+ end
784
+
785
+ # Initialize the list of allowed writer methods
786
+ #
787
+ # @return [undefined]
788
+ #
789
+ # @api private
790
+ def finalize_allowed_writer_methods
791
+ @allowed_writer_methods = public_instance_methods.map { |method| method.to_s }.grep(WRITER_METHOD_REGEXP).to_set
792
+ @allowed_writer_methods -= INVALID_WRITER_METHODS
793
+ @allowed_writer_methods.freeze
794
+ end
795
+
796
+ # @api private
797
+ # TODO: Remove this once appropriate warnings can be added.
798
+ def assert_valid(force = false) # :nodoc:
799
+ return if @valid && !force
800
+ @valid = true
801
+ finalize
802
+ end
803
+
804
+ # Raises an exception if #get receives the wrong number of arguments
805
+ #
806
+ # @param [Array] key
807
+ # the key value
808
+ #
809
+ # @return [undefined]
810
+ #
811
+ # @raise [UpdateConflictError]
812
+ # raise if the resource is dirty
813
+ #
814
+ # @api private
815
+ def assert_valid_key_size(key)
816
+ expected_key_size = self.key(repository_name).size
817
+ actual_key_size = key.size
818
+
819
+ if actual_key_size != expected_key_size
820
+ raise ArgumentError, "The number of arguments for the key is invalid, expected #{expected_key_size} but was #{actual_key_size}"
821
+ end
822
+ end
823
+
824
+ # Test if the model name is valid
825
+ #
826
+ # @return [undefined]
827
+ #
828
+ # @api private
829
+ def assert_valid_name
830
+ if name.to_s.strip.empty?
831
+ raise IncompleteModelError, "#{inspect} must have a name"
832
+ end
833
+ end
834
+
835
+ # Test if the model has properties
836
+ #
837
+ # A model may also be valid if it has at least one m:1 relationships which
838
+ # will add inferred foreign key properties.
839
+ #
840
+ # @return [undefined]
841
+ #
842
+ # @raise [IncompleteModelError]
843
+ # raised if the model has no properties
844
+ #
845
+ # @api private
846
+ def assert_valid_properties
847
+ repository_name = self.repository_name
848
+ if properties(repository_name).empty? &&
849
+ !relationships(repository_name).any? { |relationship| relationship.kind_of?(Associations::ManyToOne::Relationship) }
850
+ raise IncompleteModelError, "#{name} must have at least one property or many to one relationship to be valid"
851
+ end
852
+ end
853
+
854
+ # Test if the model has a valid key
855
+ #
856
+ # @return [undefined]
857
+ #
858
+ # @raise [IncompleteModelError]
859
+ # raised if the model does not have a valid key
860
+ #
861
+ # @api private
862
+ def assert_valid_key
863
+ if key(repository_name).empty?
864
+ raise IncompleteModelError, "#{name} must have a key to be valid"
865
+ end
866
+ end
867
+
868
+ end # module Model
869
+ end # module DataMapper