ghost_dm-core 1.3.0.beta

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