ardm-core 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +35 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +63 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +237 -0
  10. data/Rakefile +4 -0
  11. data/VERSION +1 -0
  12. data/ardm-core.gemspec +25 -0
  13. data/lib/ardm-core.rb +1 -0
  14. data/lib/dm-core.rb +285 -0
  15. data/lib/dm-core/adapters.rb +222 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +236 -0
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  18. data/lib/dm-core/associations/many_to_many.rb +496 -0
  19. data/lib/dm-core/associations/many_to_one.rb +296 -0
  20. data/lib/dm-core/associations/one_to_many.rb +345 -0
  21. data/lib/dm-core/associations/one_to_one.rb +86 -0
  22. data/lib/dm-core/associations/relationship.rb +663 -0
  23. data/lib/dm-core/backwards.rb +13 -0
  24. data/lib/dm-core/collection.rb +1514 -0
  25. data/lib/dm-core/core_ext/kernel.rb +23 -0
  26. data/lib/dm-core/core_ext/pathname.rb +6 -0
  27. data/lib/dm-core/core_ext/symbol.rb +10 -0
  28. data/lib/dm-core/identity_map.rb +7 -0
  29. data/lib/dm-core/model.rb +869 -0
  30. data/lib/dm-core/model/hook.rb +102 -0
  31. data/lib/dm-core/model/is.rb +32 -0
  32. data/lib/dm-core/model/property.rb +253 -0
  33. data/lib/dm-core/model/relationship.rb +377 -0
  34. data/lib/dm-core/model/scope.rb +89 -0
  35. data/lib/dm-core/property.rb +839 -0
  36. data/lib/dm-core/property/binary.rb +22 -0
  37. data/lib/dm-core/property/boolean.rb +31 -0
  38. data/lib/dm-core/property/class.rb +24 -0
  39. data/lib/dm-core/property/date.rb +45 -0
  40. data/lib/dm-core/property/date_time.rb +44 -0
  41. data/lib/dm-core/property/decimal.rb +50 -0
  42. data/lib/dm-core/property/discriminator.rb +46 -0
  43. data/lib/dm-core/property/float.rb +28 -0
  44. data/lib/dm-core/property/integer.rb +32 -0
  45. data/lib/dm-core/property/lookup.rb +29 -0
  46. data/lib/dm-core/property/numeric.rb +40 -0
  47. data/lib/dm-core/property/object.rb +28 -0
  48. data/lib/dm-core/property/serial.rb +13 -0
  49. data/lib/dm-core/property/string.rb +50 -0
  50. data/lib/dm-core/property/text.rb +12 -0
  51. data/lib/dm-core/property/time.rb +46 -0
  52. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  53. data/lib/dm-core/property/typecast/time.rb +33 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query.rb +1444 -0
  56. data/lib/dm-core/query/conditions/comparison.rb +910 -0
  57. data/lib/dm-core/query/conditions/operation.rb +720 -0
  58. data/lib/dm-core/query/direction.rb +36 -0
  59. data/lib/dm-core/query/operator.rb +35 -0
  60. data/lib/dm-core/query/path.rb +114 -0
  61. data/lib/dm-core/query/sort.rb +39 -0
  62. data/lib/dm-core/relationship_set.rb +72 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource.rb +1228 -0
  65. data/lib/dm-core/resource/persistence_state.rb +75 -0
  66. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  67. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  68. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  69. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  70. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  71. data/lib/dm-core/resource/persistence_state/transient.rb +78 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +54 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +173 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +326 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1236 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +134 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +402 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +12 -0
  102. data/lib/dm-core/support/logger.rb +199 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +90 -0
  105. data/lib/dm-core/support/ordered_set.rb +380 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +250 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/script/performance.rb +275 -0
  110. data/script/profile.rb +218 -0
  111. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  112. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  113. data/spec/public/associations/many_to_many_spec.rb +197 -0
  114. data/spec/public/associations/many_to_one_spec.rb +83 -0
  115. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  116. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  117. data/spec/public/associations/one_to_many_spec.rb +81 -0
  118. data/spec/public/associations/one_to_one_spec.rb +176 -0
  119. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  120. data/spec/public/collection_spec.rb +69 -0
  121. data/spec/public/finalize_spec.rb +76 -0
  122. data/spec/public/model/hook_spec.rb +246 -0
  123. data/spec/public/model/property_spec.rb +88 -0
  124. data/spec/public/model/relationship_spec.rb +1040 -0
  125. data/spec/public/model_spec.rb +458 -0
  126. data/spec/public/property/binary_spec.rb +41 -0
  127. data/spec/public/property/boolean_spec.rb +22 -0
  128. data/spec/public/property/class_spec.rb +28 -0
  129. data/spec/public/property/date_spec.rb +22 -0
  130. data/spec/public/property/date_time_spec.rb +22 -0
  131. data/spec/public/property/decimal_spec.rb +23 -0
  132. data/spec/public/property/discriminator_spec.rb +135 -0
  133. data/spec/public/property/float_spec.rb +22 -0
  134. data/spec/public/property/integer_spec.rb +22 -0
  135. data/spec/public/property/object_spec.rb +107 -0
  136. data/spec/public/property/serial_spec.rb +22 -0
  137. data/spec/public/property/string_spec.rb +22 -0
  138. data/spec/public/property/text_spec.rb +63 -0
  139. data/spec/public/property/time_spec.rb +22 -0
  140. data/spec/public/property_spec.rb +341 -0
  141. data/spec/public/resource_spec.rb +284 -0
  142. data/spec/public/sel_spec.rb +53 -0
  143. data/spec/public/setup_spec.rb +145 -0
  144. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  145. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  146. data/spec/public/shared/collection_shared_spec.rb +1669 -0
  147. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  148. data/spec/rcov.opts +6 -0
  149. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  150. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  151. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  152. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  153. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  154. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  155. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  156. data/spec/semipublic/associations_spec.rb +177 -0
  157. data/spec/semipublic/collection_spec.rb +110 -0
  158. data/spec/semipublic/model_spec.rb +96 -0
  159. data/spec/semipublic/property/binary_spec.rb +13 -0
  160. data/spec/semipublic/property/boolean_spec.rb +47 -0
  161. data/spec/semipublic/property/class_spec.rb +33 -0
  162. data/spec/semipublic/property/date_spec.rb +43 -0
  163. data/spec/semipublic/property/date_time_spec.rb +46 -0
  164. data/spec/semipublic/property/decimal_spec.rb +83 -0
  165. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  166. data/spec/semipublic/property/float_spec.rb +82 -0
  167. data/spec/semipublic/property/integer_spec.rb +82 -0
  168. data/spec/semipublic/property/lookup_spec.rb +29 -0
  169. data/spec/semipublic/property/serial_spec.rb +13 -0
  170. data/spec/semipublic/property/string_spec.rb +13 -0
  171. data/spec/semipublic/property/text_spec.rb +31 -0
  172. data/spec/semipublic/property/time_spec.rb +50 -0
  173. data/spec/semipublic/property_spec.rb +114 -0
  174. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  175. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  176. data/spec/semipublic/query/path_spec.rb +471 -0
  177. data/spec/semipublic/query_spec.rb +3777 -0
  178. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  179. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  180. data/spec/semipublic/resource/state/dirty_spec.rb +156 -0
  181. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  182. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  183. data/spec/semipublic/resource/state_spec.rb +230 -0
  184. data/spec/semipublic/resource_spec.rb +23 -0
  185. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  186. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  187. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  188. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  189. data/spec/spec.opts +5 -0
  190. data/spec/spec_helper.rb +37 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +28 -0
  248. data/spec/unit/hook_spec.rb +1235 -0
  249. data/spec/unit/lazy_array_spec.rb +1949 -0
  250. data/spec/unit/mash_spec.rb +312 -0
  251. data/spec/unit/module_spec.rb +71 -0
  252. data/spec/unit/object_spec.rb +38 -0
  253. data/spec/unit/try_dup_spec.rb +46 -0
  254. data/tasks/ci.rake +1 -0
  255. data/tasks/db.rake +11 -0
  256. data/tasks/spec.rake +38 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +491 -0
@@ -0,0 +1,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