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,103 @@
1
+ module DataMapper
2
+ module Model
3
+ module Hook
4
+ Model.append_inclusions self
5
+
6
+ extend Chainable
7
+
8
+ def self.included(model)
9
+ model.send(:include, DataMapper::Hook)
10
+ model.extend Methods
11
+ super
12
+ end
13
+
14
+ module Methods
15
+ def inherited(model)
16
+ copy_hooks(model)
17
+ super
18
+ end
19
+
20
+ # @api public
21
+ def before(target_method, method_sym = nil, &block)
22
+ setup_hook(:before, target_method, method_sym, block) { super }
23
+ end
24
+
25
+ # @api public
26
+ def after(target_method, method_sym = nil, &block)
27
+ setup_hook(:after, target_method, method_sym, block) { super }
28
+ end
29
+
30
+ # @api private
31
+ def hooks
32
+ @hooks ||= {
33
+ :save => { :before => [], :after => [] },
34
+ :create => { :before => [], :after => [] },
35
+ :update => { :before => [], :after => [] },
36
+ :destroy => { :before => [], :after => [] },
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ def setup_hook(type, name, method, proc)
43
+ types = hooks[name]
44
+ if types && types[type]
45
+ types[type] << if proc
46
+ ProcCommand.new(proc)
47
+ else
48
+ MethodCommand.new(self, method)
49
+ end
50
+ else
51
+ yield
52
+ end
53
+ end
54
+
55
+ # deep copy hooks from the parent model
56
+ def copy_hooks(model)
57
+ hooks = Hash.new do |hooks, name|
58
+ hooks[name] = Hash.new do |types, type|
59
+ if self.hooks[name]
60
+ types[type] = self.hooks[name][type].map do |command|
61
+ command.copy(model)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ model.instance_variable_set(:@hooks, hooks)
68
+ end
69
+
70
+ end
71
+
72
+ class ProcCommand
73
+ def initialize(proc)
74
+ @proc = proc.to_proc
75
+ end
76
+
77
+ def call(resource)
78
+ resource.instance_eval(&@proc)
79
+ end
80
+
81
+ def copy(model)
82
+ self
83
+ end
84
+ end
85
+
86
+ class MethodCommand
87
+ def initialize(model, method)
88
+ @model, @method = model, method.to_sym
89
+ end
90
+
91
+ def call(resource)
92
+ resource.__send__(@method)
93
+ end
94
+
95
+ def copy(model)
96
+ self.class.new(model, @method)
97
+ end
98
+
99
+ end
100
+
101
+ end # module Hook
102
+ end # module Model
103
+ end # module DataMapper
@@ -0,0 +1,32 @@
1
+ module DataMapper
2
+ module Model
3
+ # Module that provides a common way for plugin authors
4
+ # to implement "is ... " traits (object behaviors that can be shared)
5
+ module Is
6
+ # A common interface to activate plugins for a resource. For instance:
7
+ #
8
+ # class Widget
9
+ # include DataMapper::Resource
10
+ #
11
+ # is :list
12
+ # end
13
+ #
14
+ # adds list item behavior to the model. Plugin that wants to conform
15
+ # to "is API" of DataMapper must supply is_+behavior name+ method,
16
+ # for example above it would be is_list.
17
+ #
18
+ # @api public
19
+ def is(plugin, *args, &block)
20
+ generator_method = "is_#{plugin}".to_sym
21
+
22
+ if respond_to?(generator_method)
23
+ send(generator_method, *args, &block)
24
+ else
25
+ raise PluginNotFoundError, "could not find plugin named #{plugin}"
26
+ end
27
+ end
28
+ end # module Is
29
+
30
+ include Is
31
+ end # module Model
32
+ end # module DataMapper
@@ -0,0 +1,249 @@
1
+ # TODO: update Model#respond_to? to return true if method_method missing
2
+ # would handle the message
3
+
4
+ module DataMapper
5
+ module Model
6
+ module Property
7
+ Model.append_extensions self, DataMapper::Property::Lookup
8
+
9
+ def self.extended(model)
10
+ model.instance_variable_set(:@properties, {})
11
+ model.instance_variable_set(:@field_naming_conventions, {})
12
+ super
13
+ end
14
+
15
+ def inherited(model)
16
+ model.instance_variable_set(:@properties, {})
17
+ model.instance_variable_set(:@field_naming_conventions, @field_naming_conventions.dup)
18
+
19
+ @properties.each do |repository_name, properties|
20
+ model_properties = model.properties(repository_name)
21
+ properties.each { |property| model_properties << property }
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ # Defines a Property on the Resource
28
+ #
29
+ # @param [Symbol] name
30
+ # the name for which to call this property
31
+ # @param [Class] type
32
+ # the ruby type to define this property as
33
+ # @param [Hash(Symbol => String)] options
34
+ # a hash of available options
35
+ #
36
+ # @return [Property]
37
+ # the created Property
38
+ #
39
+ # @see Property
40
+ #
41
+ # @api public
42
+ def property(name, type, options = {})
43
+ # if the type can be found within Property then
44
+ # use that class rather than the primitive
45
+ klass = DataMapper::Property.determine_class(type)
46
+
47
+ unless klass
48
+ raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
49
+ end
50
+
51
+ property = klass.new(self, name, options)
52
+
53
+ repository_name = self.repository_name
54
+ properties = properties(repository_name)
55
+
56
+ properties << property
57
+
58
+ # Add property to the other mappings as well if this is for the default
59
+ # repository.
60
+
61
+ if repository_name == default_repository_name
62
+ other_repository_properties = DataMapper::Ext::Hash.except(@properties, default_repository_name)
63
+
64
+ other_repository_properties.each do |other_repository_name, properties|
65
+ next if properties.named?(name)
66
+
67
+ # make sure the property is created within the correct repository scope
68
+ DataMapper.repository(other_repository_name) do
69
+ properties << klass.new(self, name, options)
70
+ end
71
+ end
72
+ end
73
+
74
+ # Add the property to the lazy_loads set for this resources repository
75
+ # only.
76
+ # TODO Is this right or should we add the lazy contexts to all
77
+ # repositories?
78
+ if property.lazy?
79
+ context = options.fetch(:lazy, :default)
80
+ context = :default if context == true
81
+
82
+ Array(context).each do |context|
83
+ properties.lazy_context(context) << property
84
+ end
85
+ end
86
+
87
+ # add the property to the child classes only if the property was
88
+ # added after the child classes' properties have been copied from
89
+ # the parent
90
+ descendants.each do |descendant|
91
+ descendant.properties(repository_name) << property
92
+ end
93
+
94
+ create_reader_for(property)
95
+ create_writer_for(property)
96
+
97
+ # FIXME: explicit return needed for YARD to parse this properly
98
+ return property
99
+ end
100
+
101
+ # Gets a list of all properties that have been defined on this Model in
102
+ # the requested repository
103
+ #
104
+ # @param [Symbol, String] repository_name
105
+ # The name of the repository to use. Uses the default Repository
106
+ # if none is specified.
107
+ #
108
+ # @return [PropertySet]
109
+ # A list of Properties defined on this Model in the given Repository
110
+ #
111
+ # @api public
112
+ def properties(repository_name = default_repository_name)
113
+ # TODO: create PropertySet#copy that will copy the properties, but assign the
114
+ # new Relationship objects to a supplied repository and model. dup does not really
115
+ # do what is needed
116
+ repository_name = repository_name.to_sym
117
+
118
+ default_repository_name = self.default_repository_name
119
+
120
+ @properties[repository_name] ||= if repository_name == default_repository_name
121
+ PropertySet.new
122
+ else
123
+ properties(default_repository_name).dup
124
+ end
125
+ end
126
+
127
+ # Gets the list of key fields for this Model in +repository_name+
128
+ #
129
+ # @param [String] repository_name
130
+ # The name of the Repository for which the key is to be reported
131
+ #
132
+ # @return [Array]
133
+ # The list of key fields for this Model in +repository_name+
134
+ #
135
+ # @api public
136
+ def key(repository_name = default_repository_name)
137
+ properties(repository_name).key
138
+ end
139
+
140
+ # @api public
141
+ def serial(repository_name = default_repository_name)
142
+ key(repository_name).detect { |property| property.serial? }
143
+ end
144
+
145
+ # Gets the field naming conventions for this resource in the given Repository
146
+ #
147
+ # @param [String, Symbol] repository_name
148
+ # the name of the Repository for which the field naming convention
149
+ # will be retrieved
150
+ #
151
+ # @return [#call]
152
+ # The naming convention for the given Repository
153
+ #
154
+ # @api public
155
+ def field_naming_convention(repository_name = default_storage_name)
156
+ @field_naming_conventions[repository_name] ||= repository(repository_name).adapter.field_naming_convention
157
+ end
158
+
159
+ # @api private
160
+ def properties_with_subclasses(repository_name = default_repository_name)
161
+ properties = properties(repository_name).dup
162
+
163
+ descendants.each do |model|
164
+ model.properties(repository_name).each do |property|
165
+ properties << property
166
+ end
167
+ end
168
+
169
+ properties
170
+ end
171
+
172
+ # @api private
173
+ def key_conditions(repository, key)
174
+ Hash[ self.key(repository.name).zip(key.nil? ? [] : key) ]
175
+ end
176
+
177
+ private
178
+
179
+ # Defines the anonymous module that is used to add properties.
180
+ # Using a single module here prevents having a very large number
181
+ # of anonymous modules, where each property has their own module.
182
+ # @api private
183
+ def property_module
184
+ @property_module ||= begin
185
+ mod = Module.new
186
+ class_eval do
187
+ include mod
188
+ end
189
+ mod
190
+ end
191
+ end
192
+
193
+ # defines the reader method for the property
194
+ #
195
+ # @api private
196
+ def create_reader_for(property)
197
+ name = property.name.to_s
198
+ reader_visibility = property.reader_visibility
199
+ instance_variable_name = property.instance_variable_name
200
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
201
+ #{reader_visibility}
202
+ def #{name}
203
+ return #{instance_variable_name} if defined?(#{instance_variable_name})
204
+ property = properties[#{name.inspect}]
205
+ #{instance_variable_name} = property ? persistence_state.get(property) : nil
206
+ end
207
+ RUBY
208
+
209
+ boolean_reader_name = "#{name}?"
210
+
211
+ if property.kind_of?(DataMapper::Property::Boolean)
212
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
213
+ #{reader_visibility}
214
+ def #{boolean_reader_name}
215
+ #{name}
216
+ end
217
+ RUBY
218
+ end
219
+ end
220
+
221
+ # defines the setter for the property
222
+ #
223
+ # @api private
224
+ def create_writer_for(property)
225
+ name = property.name
226
+ writer_visibility = property.writer_visibility
227
+
228
+ writer_name = "#{name}="
229
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
230
+ #{writer_visibility}
231
+ def #{writer_name}(value)
232
+ property = properties[#{name.inspect}]
233
+ self.persistence_state = persistence_state.set(property, value)
234
+ persistence_state.get(property)
235
+ end
236
+ RUBY
237
+ end
238
+
239
+ # @api public
240
+ def method_missing(method, *args, &block)
241
+ if property = properties(repository_name)[method]
242
+ return property
243
+ end
244
+
245
+ super
246
+ end
247
+ end # module Property
248
+ end # module Model
249
+ end # module DataMapper
@@ -0,0 +1,378 @@
1
+ # TODO: update Model#respond_to? to return true if method_method missing
2
+ # would handle the message
3
+
4
+ module DataMapper
5
+ module Model
6
+ module Relationship
7
+ Model.append_extensions self
8
+
9
+ include DataMapper::Assertions
10
+
11
+ # Initializes relationships hash for extended model
12
+ # class.
13
+ #
14
+ # When model calls has n, has 1 or belongs_to, relationships
15
+ # are stored in that hash: keys are repository names and
16
+ # values are relationship sets.
17
+ #
18
+ # @api private
19
+ def self.extended(model)
20
+ model.instance_variable_set(:@relationships, {})
21
+ super
22
+ end
23
+
24
+ # When DataMapper model is inherited, relationships
25
+ # of parent are duplicated and copied to subclass model
26
+ #
27
+ # @api private
28
+ def inherited(model)
29
+ model.instance_variable_set(:@relationships, {})
30
+
31
+ @relationships.each do |repository_name, relationships|
32
+ model_relationships = model.relationships(repository_name)
33
+ relationships.each { |relationship| model_relationships << relationship }
34
+ end
35
+
36
+ super
37
+ end
38
+
39
+ # Returns copy of relationships set in given repository.
40
+ #
41
+ # @param [Symbol] repository_name
42
+ # Name of the repository for which relationships set is returned
43
+ # @return [RelationshipSet] relationships set for given repository
44
+ #
45
+ # @api semipublic
46
+ def relationships(repository_name = default_repository_name)
47
+ # TODO: create RelationshipSet#copy that will copy the relationships, but assign the
48
+ # new Relationship objects to a supplied repository and model. dup does not really
49
+ # do what is needed
50
+
51
+ default_repository_name = self.default_repository_name
52
+
53
+ @relationships[repository_name] ||= if repository_name == default_repository_name
54
+ RelationshipSet.new
55
+ else
56
+ relationships(default_repository_name).dup
57
+ end
58
+ end
59
+
60
+ # Used to express unlimited cardinality of association,
61
+ # see +has+
62
+ #
63
+ # @api public
64
+ def n
65
+ Infinity
66
+ end
67
+
68
+ # A shorthand, clear syntax for defining one-to-one, one-to-many and
69
+ # many-to-many resource relationships.
70
+ #
71
+ # * has 1, :friend # one friend
72
+ # * has n, :friends # many friends
73
+ # * has 1..3, :friends # many friends (at least 1, at most 3)
74
+ # * has 3, :friends # many friends (exactly 3)
75
+ # * has 1, :friend, 'User' # one friend with the class User
76
+ # * has 3, :friends, :through => :friendships # many friends through the friendships relationship
77
+ #
78
+ # @param cardinality [Integer, Range, Infinity]
79
+ # cardinality that defines the association type and constraints
80
+ # @param name [Symbol]
81
+ # the name that the association will be referenced by
82
+ # @param *args [Model, Hash] model and/or options hash
83
+ #
84
+ # @option *args :through[Symbol] A association that this join should go through to form
85
+ # a many-to-many association
86
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
87
+ # then the association name is assumed to match the class name
88
+ # @option *args :repository[Symbol] name of child model repository
89
+ #
90
+ # @return [Association::Relationship] the relationship that was
91
+ # created to reflect either a one-to-one, one-to-many or many-to-many
92
+ # relationship
93
+ # @raise [ArgumentError] if the cardinality was not understood. Should be a
94
+ # Integer, Range or Infinity(n)
95
+ #
96
+ # @api public
97
+ def has(cardinality, name, *args)
98
+ name = name.to_sym
99
+ model = extract_model(args)
100
+ options = extract_options(args)
101
+
102
+ min, max = extract_min_max(cardinality)
103
+ options.update(:min => min, :max => max)
104
+
105
+ assert_valid_options(options)
106
+
107
+ if options.key?(:model) && model
108
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
109
+ end
110
+
111
+ model ||= options.delete(:model)
112
+
113
+ repository_name = repository.name
114
+
115
+ # TODO: change to :target_respository_name and :source_repository_name
116
+ options[:child_repository_name] = options.delete(:repository)
117
+ options[:parent_repository_name] = repository_name
118
+
119
+ klass = if max > 1
120
+ options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
121
+ else
122
+ Associations::OneToOne::Relationship
123
+ end
124
+
125
+ relationship = klass.new(name, model, self, options)
126
+
127
+ relationships(repository_name) << relationship
128
+
129
+ descendants.each do |descendant|
130
+ descendant.relationships(repository_name) << relationship
131
+ end
132
+
133
+ create_relationship_reader(relationship)
134
+ create_relationship_writer(relationship)
135
+
136
+ relationship
137
+ end
138
+
139
+ # A shorthand, clear syntax for defining many-to-one resource relationships.
140
+ #
141
+ # * belongs_to :user # many to one user
142
+ # * belongs_to :friend, :model => 'User' # many to one friend
143
+ # * belongs_to :reference, :repository => :pubmed # association for repository other than default
144
+ #
145
+ # @param name [Symbol]
146
+ # the name that the association will be referenced by
147
+ # @param *args [Model, Hash] model and/or options hash
148
+ #
149
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
150
+ # then the association name is assumed to match the class name
151
+ # @option *args :repository[Symbol] name of child model repository
152
+ #
153
+ # @return [Association::Relationship] The association created
154
+ # should not be accessed directly
155
+ #
156
+ # @api public
157
+ def belongs_to(name, *args)
158
+ name = name.to_sym
159
+ model_name = self.name
160
+ model = extract_model(args)
161
+ options = extract_options(args)
162
+
163
+ if options.key?(:through)
164
+ raise "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{model_name} instead (#{caller.first})"
165
+ elsif options.key?(:model) && model
166
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
167
+ end
168
+
169
+ assert_valid_options(options)
170
+
171
+ model ||= options.delete(:model)
172
+
173
+ repository_name = repository.name
174
+
175
+ # TODO: change to source_repository_name and target_respository_name
176
+ options[:child_repository_name] = repository_name
177
+ options[:parent_repository_name] = options.delete(:repository)
178
+
179
+ relationship = Associations::ManyToOne::Relationship.new(name, self, model, options)
180
+
181
+ relationships(repository_name) << relationship
182
+
183
+ descendants.each do |descendant|
184
+ descendant.relationships(repository_name) << relationship
185
+ end
186
+
187
+ create_relationship_reader(relationship)
188
+ create_relationship_writer(relationship)
189
+
190
+ relationship
191
+ end
192
+
193
+ private
194
+
195
+ # Extract the model from an Array of arguments
196
+ #
197
+ # @param [Array(Model, String, Hash)]
198
+ # The arguments passed to an relationship declaration
199
+ #
200
+ # @return [Model, #to_str]
201
+ # target model for the association
202
+ #
203
+ # @api private
204
+ def extract_model(args)
205
+ model = args.first
206
+
207
+ if model.kind_of?(Model)
208
+ model
209
+ elsif model.respond_to?(:to_str)
210
+ model.to_str
211
+ else
212
+ nil
213
+ end
214
+ end
215
+
216
+ # Extract the model from an Array of arguments
217
+ #
218
+ # @param [Array(Model, String, Hash)]
219
+ # The arguments passed to an relationship declaration
220
+ #
221
+ # @return [Hash]
222
+ # options for the association
223
+ #
224
+ # @api private
225
+ def extract_options(args)
226
+ options = args.last
227
+ options.respond_to?(:to_hash) ? options.to_hash.dup : {}
228
+ end
229
+
230
+ # A support method for converting Integer, Range or Infinity values into two
231
+ # values representing the minimum and maximum cardinality of the association
232
+ #
233
+ # @return [Array] A pair of integers, min and max
234
+ #
235
+ # @api private
236
+ def extract_min_max(cardinality)
237
+ case cardinality
238
+ when Integer then [ cardinality, cardinality ]
239
+ when Range then [ cardinality.first, cardinality.last ]
240
+ when Infinity then [ 0, Infinity ]
241
+ else
242
+ assert_kind_of 'options', options, Integer, Range, Infinity.class
243
+ end
244
+ end
245
+
246
+ # Validates options of association method like belongs_to or has:
247
+ # verifies types of cardinality bounds, repository, association class,
248
+ # keys and possible values of :through option.
249
+ #
250
+ # @api private
251
+ def assert_valid_options(options)
252
+ # TODO: update to match Query#assert_valid_options
253
+ # - perform options normalization elsewhere
254
+
255
+ if options.key?(:min) && options.key?(:max)
256
+ min = options[:min]
257
+ max = options[:max]
258
+
259
+ min = min.to_int unless min == Infinity
260
+ max = max.to_int unless max == Infinity
261
+
262
+ if min == Infinity && max == Infinity
263
+ raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
264
+ elsif min > max
265
+ raise ArgumentError, "Cardinality min (#{min}) cannot be larger than the max (#{max})"
266
+ elsif min < 0
267
+ raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{min}"
268
+ elsif max < 1
269
+ raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{max}"
270
+ end
271
+ end
272
+
273
+ if options.key?(:repository)
274
+ options[:repository] = options[:repository].to_sym
275
+ end
276
+
277
+ if options.key?(:class_name)
278
+ raise "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
279
+ elsif options.key?(:remote_name)
280
+ raise "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
281
+ end
282
+
283
+ if options.key?(:through)
284
+ assert_kind_of 'options[:through]', options[:through], Symbol, Module
285
+ end
286
+
287
+ [ :via, :inverse ].each do |key|
288
+ if options.key?(key)
289
+ assert_kind_of "options[#{key.inspect}]", options[key], Symbol, Associations::Relationship
290
+ end
291
+ end
292
+
293
+ # TODO: deprecate :child_key and :parent_key in favor of :source_key and
294
+ # :target_key (will mean something different for each relationship)
295
+
296
+ [ :child_key, :parent_key ].each do |key|
297
+ if options.key?(key)
298
+ options[key] = Array(options[key])
299
+ end
300
+ end
301
+
302
+ if options.key?(:limit)
303
+ raise ArgumentError, '+options[:limit]+ should not be specified on a relationship'
304
+ end
305
+ end
306
+
307
+ # Defines the anonymous module that is used to add relationships.
308
+ # Using a single module here prevents having a very large number
309
+ # of anonymous modules, where each property has their own module.
310
+ # @api private
311
+ def relationship_module
312
+ @relationship_module ||= begin
313
+ mod = Module.new
314
+ class_eval do
315
+ include mod
316
+ end
317
+ mod
318
+ end
319
+ end
320
+
321
+ # Dynamically defines reader method
322
+ #
323
+ # @api private
324
+ def create_relationship_reader(relationship)
325
+ name = relationship.name
326
+ reader_name = name.to_s
327
+
328
+ return if method_defined?(reader_name)
329
+
330
+ reader_visibility = relationship.reader_visibility
331
+
332
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
333
+ #{reader_visibility}
334
+ def #{reader_name}(query = nil)
335
+ # TODO: when no query is passed in, return the results from
336
+ # the ivar directly. This will require that the ivar
337
+ # actually hold the resource/collection, and in the case
338
+ # of 1:1, the underlying collection is hidden in a
339
+ # private ivar, and the resource is in a known ivar
340
+
341
+ persistence_state.get(relationships[#{name.inspect}], query)
342
+ end
343
+ RUBY
344
+ end
345
+
346
+ # Dynamically defines writer method
347
+ #
348
+ # @api private
349
+ def create_relationship_writer(relationship)
350
+ name = relationship.name
351
+ writer_name = "#{name}="
352
+
353
+ return if method_defined?(writer_name)
354
+
355
+ writer_visibility = relationship.writer_visibility
356
+
357
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
358
+ #{writer_visibility}
359
+ def #{writer_name}(target)
360
+ relationship = relationships[#{name.inspect}]
361
+ self.persistence_state = persistence_state.set(relationship, target)
362
+ persistence_state.get(relationship)
363
+ end
364
+ RUBY
365
+ end
366
+
367
+ # @api public
368
+ def method_missing(method, *args, &block)
369
+ if relationship = relationships(repository_name)[method]
370
+ return Query::Path.new([ relationship ])
371
+ end
372
+
373
+ super
374
+ end
375
+
376
+ end # module Relationship
377
+ end # module Model
378
+ end # module DataMapper