ghost_dm-core 1.3.0.beta

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