ardm-core 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +35 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +63 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +237 -0
  10. data/Rakefile +4 -0
  11. data/VERSION +1 -0
  12. data/ardm-core.gemspec +25 -0
  13. data/lib/ardm-core.rb +1 -0
  14. data/lib/dm-core.rb +285 -0
  15. data/lib/dm-core/adapters.rb +222 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +236 -0
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  18. data/lib/dm-core/associations/many_to_many.rb +496 -0
  19. data/lib/dm-core/associations/many_to_one.rb +296 -0
  20. data/lib/dm-core/associations/one_to_many.rb +345 -0
  21. data/lib/dm-core/associations/one_to_one.rb +86 -0
  22. data/lib/dm-core/associations/relationship.rb +663 -0
  23. data/lib/dm-core/backwards.rb +13 -0
  24. data/lib/dm-core/collection.rb +1514 -0
  25. data/lib/dm-core/core_ext/kernel.rb +23 -0
  26. data/lib/dm-core/core_ext/pathname.rb +6 -0
  27. data/lib/dm-core/core_ext/symbol.rb +10 -0
  28. data/lib/dm-core/identity_map.rb +7 -0
  29. data/lib/dm-core/model.rb +869 -0
  30. data/lib/dm-core/model/hook.rb +102 -0
  31. data/lib/dm-core/model/is.rb +32 -0
  32. data/lib/dm-core/model/property.rb +253 -0
  33. data/lib/dm-core/model/relationship.rb +377 -0
  34. data/lib/dm-core/model/scope.rb +89 -0
  35. data/lib/dm-core/property.rb +839 -0
  36. data/lib/dm-core/property/binary.rb +22 -0
  37. data/lib/dm-core/property/boolean.rb +31 -0
  38. data/lib/dm-core/property/class.rb +24 -0
  39. data/lib/dm-core/property/date.rb +45 -0
  40. data/lib/dm-core/property/date_time.rb +44 -0
  41. data/lib/dm-core/property/decimal.rb +50 -0
  42. data/lib/dm-core/property/discriminator.rb +46 -0
  43. data/lib/dm-core/property/float.rb +28 -0
  44. data/lib/dm-core/property/integer.rb +32 -0
  45. data/lib/dm-core/property/lookup.rb +29 -0
  46. data/lib/dm-core/property/numeric.rb +40 -0
  47. data/lib/dm-core/property/object.rb +28 -0
  48. data/lib/dm-core/property/serial.rb +13 -0
  49. data/lib/dm-core/property/string.rb +50 -0
  50. data/lib/dm-core/property/text.rb +12 -0
  51. data/lib/dm-core/property/time.rb +46 -0
  52. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  53. data/lib/dm-core/property/typecast/time.rb +33 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query.rb +1444 -0
  56. data/lib/dm-core/query/conditions/comparison.rb +910 -0
  57. data/lib/dm-core/query/conditions/operation.rb +720 -0
  58. data/lib/dm-core/query/direction.rb +36 -0
  59. data/lib/dm-core/query/operator.rb +35 -0
  60. data/lib/dm-core/query/path.rb +114 -0
  61. data/lib/dm-core/query/sort.rb +39 -0
  62. data/lib/dm-core/relationship_set.rb +72 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource.rb +1228 -0
  65. data/lib/dm-core/resource/persistence_state.rb +75 -0
  66. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  67. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  68. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  69. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  70. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  71. data/lib/dm-core/resource/persistence_state/transient.rb +78 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +54 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +173 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +326 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1236 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +134 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +402 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +12 -0
  102. data/lib/dm-core/support/logger.rb +199 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +90 -0
  105. data/lib/dm-core/support/ordered_set.rb +380 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +250 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/script/performance.rb +275 -0
  110. data/script/profile.rb +218 -0
  111. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  112. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  113. data/spec/public/associations/many_to_many_spec.rb +197 -0
  114. data/spec/public/associations/many_to_one_spec.rb +83 -0
  115. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  116. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  117. data/spec/public/associations/one_to_many_spec.rb +81 -0
  118. data/spec/public/associations/one_to_one_spec.rb +176 -0
  119. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  120. data/spec/public/collection_spec.rb +69 -0
  121. data/spec/public/finalize_spec.rb +76 -0
  122. data/spec/public/model/hook_spec.rb +246 -0
  123. data/spec/public/model/property_spec.rb +88 -0
  124. data/spec/public/model/relationship_spec.rb +1040 -0
  125. data/spec/public/model_spec.rb +458 -0
  126. data/spec/public/property/binary_spec.rb +41 -0
  127. data/spec/public/property/boolean_spec.rb +22 -0
  128. data/spec/public/property/class_spec.rb +28 -0
  129. data/spec/public/property/date_spec.rb +22 -0
  130. data/spec/public/property/date_time_spec.rb +22 -0
  131. data/spec/public/property/decimal_spec.rb +23 -0
  132. data/spec/public/property/discriminator_spec.rb +135 -0
  133. data/spec/public/property/float_spec.rb +22 -0
  134. data/spec/public/property/integer_spec.rb +22 -0
  135. data/spec/public/property/object_spec.rb +107 -0
  136. data/spec/public/property/serial_spec.rb +22 -0
  137. data/spec/public/property/string_spec.rb +22 -0
  138. data/spec/public/property/text_spec.rb +63 -0
  139. data/spec/public/property/time_spec.rb +22 -0
  140. data/spec/public/property_spec.rb +341 -0
  141. data/spec/public/resource_spec.rb +284 -0
  142. data/spec/public/sel_spec.rb +53 -0
  143. data/spec/public/setup_spec.rb +145 -0
  144. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  145. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  146. data/spec/public/shared/collection_shared_spec.rb +1669 -0
  147. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  148. data/spec/rcov.opts +6 -0
  149. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  150. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  151. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  152. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  153. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  154. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  155. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  156. data/spec/semipublic/associations_spec.rb +177 -0
  157. data/spec/semipublic/collection_spec.rb +110 -0
  158. data/spec/semipublic/model_spec.rb +96 -0
  159. data/spec/semipublic/property/binary_spec.rb +13 -0
  160. data/spec/semipublic/property/boolean_spec.rb +47 -0
  161. data/spec/semipublic/property/class_spec.rb +33 -0
  162. data/spec/semipublic/property/date_spec.rb +43 -0
  163. data/spec/semipublic/property/date_time_spec.rb +46 -0
  164. data/spec/semipublic/property/decimal_spec.rb +83 -0
  165. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  166. data/spec/semipublic/property/float_spec.rb +82 -0
  167. data/spec/semipublic/property/integer_spec.rb +82 -0
  168. data/spec/semipublic/property/lookup_spec.rb +29 -0
  169. data/spec/semipublic/property/serial_spec.rb +13 -0
  170. data/spec/semipublic/property/string_spec.rb +13 -0
  171. data/spec/semipublic/property/text_spec.rb +31 -0
  172. data/spec/semipublic/property/time_spec.rb +50 -0
  173. data/spec/semipublic/property_spec.rb +114 -0
  174. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  175. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  176. data/spec/semipublic/query/path_spec.rb +471 -0
  177. data/spec/semipublic/query_spec.rb +3777 -0
  178. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  179. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  180. data/spec/semipublic/resource/state/dirty_spec.rb +156 -0
  181. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  182. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  183. data/spec/semipublic/resource/state_spec.rb +230 -0
  184. data/spec/semipublic/resource_spec.rb +23 -0
  185. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  186. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  187. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  188. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  189. data/spec/spec.opts +5 -0
  190. data/spec/spec_helper.rb +37 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +28 -0
  248. data/spec/unit/hook_spec.rb +1235 -0
  249. data/spec/unit/lazy_array_spec.rb +1949 -0
  250. data/spec/unit/mash_spec.rb +312 -0
  251. data/spec/unit/module_spec.rb +71 -0
  252. data/spec/unit/object_spec.rb +38 -0
  253. data/spec/unit/try_dup_spec.rb +46 -0
  254. data/tasks/ci.rake +1 -0
  255. data/tasks/db.rake +11 -0
  256. data/tasks/spec.rake +38 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +491 -0
@@ -0,0 +1,102 @@
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
+ end
12
+
13
+ module Methods
14
+ def inherited(model)
15
+ copy_hooks(model)
16
+ super
17
+ end
18
+
19
+ # @api public
20
+ def before(target_method, method_sym = nil, &block)
21
+ setup_hook(:before, target_method, method_sym, block) { super }
22
+ end
23
+
24
+ # @api public
25
+ def after(target_method, method_sym = nil, &block)
26
+ setup_hook(:after, target_method, method_sym, block) { super }
27
+ end
28
+
29
+ # @api private
30
+ def hooks
31
+ @hooks ||= {
32
+ :save => { :before => [], :after => [] },
33
+ :create => { :before => [], :after => [] },
34
+ :update => { :before => [], :after => [] },
35
+ :destroy => { :before => [], :after => [] },
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def setup_hook(type, name, method, proc)
42
+ types = hooks[name]
43
+ if types && types[type]
44
+ types[type] << if proc
45
+ ProcCommand.new(proc)
46
+ else
47
+ MethodCommand.new(self, method)
48
+ end
49
+ else
50
+ yield
51
+ end
52
+ end
53
+
54
+ # deep copy hooks from the parent model
55
+ def copy_hooks(model)
56
+ hooks = Hash.new do |hooks, name|
57
+ hooks[name] = Hash.new do |types, type|
58
+ if self.hooks[name]
59
+ types[type] = self.hooks[name][type].map do |command|
60
+ command.copy(model)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ model.instance_variable_set(:@hooks, hooks)
67
+ end
68
+
69
+ end
70
+
71
+ class ProcCommand
72
+ def initialize(proc)
73
+ @proc = proc.to_proc
74
+ end
75
+
76
+ def call(resource)
77
+ resource.instance_eval(&@proc)
78
+ end
79
+
80
+ def copy(model)
81
+ self
82
+ end
83
+ end
84
+
85
+ class MethodCommand
86
+ def initialize(model, method)
87
+ @model, @method = model, method.to_sym
88
+ end
89
+
90
+ def call(resource)
91
+ resource.__send__(@method)
92
+ end
93
+
94
+ def copy(model)
95
+ self.class.new(model, @method)
96
+ end
97
+
98
+ end
99
+
100
+ end # module Hook
101
+ end # module Model
102
+ 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,253 @@
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
+ end
13
+
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 TrueClass == type
44
+ raise "#{type} is deprecated, use Boolean instead at #{caller[2]}"
45
+ elsif BigDecimal == type
46
+ raise "#{type} is deprecated, use Decimal instead at #{caller[2]}"
47
+ end
48
+
49
+ # if the type can be found within Property then
50
+ # use that class rather than the primitive
51
+ unless klass = DataMapper::Property.determine_class(type)
52
+ raise ArgumentError, "+type+ was #{type.inspect}, which is not a supported type"
53
+ end
54
+
55
+ property = klass.new(self, name, options)
56
+
57
+ repository_name = self.repository_name
58
+ properties = properties(repository_name)
59
+
60
+ properties << property
61
+
62
+ # Add property to the other mappings as well if this is for the default
63
+ # repository.
64
+
65
+ if repository_name == default_repository_name
66
+ other_repository_properties = DataMapper::Ext::Hash.except(@properties, default_repository_name)
67
+
68
+ other_repository_properties.each do |other_repository_name, properties|
69
+ next if properties.named?(name)
70
+
71
+ # make sure the property is created within the correct repository scope
72
+ DataMapper.repository(other_repository_name) do
73
+ properties << klass.new(self, name, options)
74
+ end
75
+ end
76
+ end
77
+
78
+ # Add the property to the lazy_loads set for this resources repository
79
+ # only.
80
+ # TODO Is this right or should we add the lazy contexts to all
81
+ # repositories?
82
+ if property.lazy?
83
+ context = options.fetch(:lazy, :default)
84
+ context = :default if context == true
85
+
86
+ Array(context).each do |context|
87
+ properties.lazy_context(context) << property
88
+ end
89
+ end
90
+
91
+ # add the property to the child classes only if the property was
92
+ # added after the child classes' properties have been copied from
93
+ # the parent
94
+ descendants.each do |descendant|
95
+ descendant.properties(repository_name) << property
96
+ end
97
+
98
+ create_reader_for(property)
99
+ create_writer_for(property)
100
+
101
+ # FIXME: explicit return needed for YARD to parse this properly
102
+ return property
103
+ end
104
+
105
+ # Gets a list of all properties that have been defined on this Model in
106
+ # the requested repository
107
+ #
108
+ # @param [Symbol, String] repository_name
109
+ # The name of the repository to use. Uses the default Repository
110
+ # if none is specified.
111
+ #
112
+ # @return [PropertySet]
113
+ # A list of Properties defined on this Model in the given Repository
114
+ #
115
+ # @api public
116
+ def properties(repository_name = default_repository_name)
117
+ # TODO: create PropertySet#copy that will copy the properties, but assign the
118
+ # new Relationship objects to a supplied repository and model. dup does not really
119
+ # do what is needed
120
+ repository_name = repository_name.to_sym
121
+
122
+ default_repository_name = self.default_repository_name
123
+
124
+ @properties[repository_name] ||= if repository_name == default_repository_name
125
+ PropertySet.new
126
+ else
127
+ properties(default_repository_name).dup
128
+ end
129
+ end
130
+
131
+ # Gets the list of key fields for this Model in +repository_name+
132
+ #
133
+ # @param [String] repository_name
134
+ # The name of the Repository for which the key is to be reported
135
+ #
136
+ # @return [Array]
137
+ # The list of key fields for this Model in +repository_name+
138
+ #
139
+ # @api public
140
+ def key(repository_name = default_repository_name)
141
+ properties(repository_name).key
142
+ end
143
+
144
+ # @api public
145
+ def serial(repository_name = default_repository_name)
146
+ key(repository_name).detect { |property| property.serial? }
147
+ end
148
+
149
+ # Gets the field naming conventions for this resource in the given Repository
150
+ #
151
+ # @param [String, Symbol] repository_name
152
+ # the name of the Repository for which the field naming convention
153
+ # will be retrieved
154
+ #
155
+ # @return [#call]
156
+ # The naming convention for the given Repository
157
+ #
158
+ # @api public
159
+ def field_naming_convention(repository_name = default_storage_name)
160
+ @field_naming_conventions[repository_name] ||= repository(repository_name).adapter.field_naming_convention
161
+ end
162
+
163
+ # @api private
164
+ def properties_with_subclasses(repository_name = default_repository_name)
165
+ properties = properties(repository_name).dup
166
+
167
+ descendants.each do |model|
168
+ model.properties(repository_name).each do |property|
169
+ properties << property
170
+ end
171
+ end
172
+
173
+ properties
174
+ end
175
+
176
+ # @api private
177
+ def key_conditions(repository, key)
178
+ Hash[ self.key(repository.name).zip(key.nil? ? [] : key) ]
179
+ end
180
+
181
+ private
182
+
183
+ # Defines the anonymous module that is used to add properties.
184
+ # Using a single module here prevents having a very large number
185
+ # of anonymous modules, where each property has their own module.
186
+ # @api private
187
+ def property_module
188
+ @property_module ||= begin
189
+ mod = Module.new
190
+ class_eval do
191
+ include mod
192
+ end
193
+ mod
194
+ end
195
+ end
196
+
197
+ # defines the reader method for the property
198
+ #
199
+ # @api private
200
+ def create_reader_for(property)
201
+ name = property.name.to_s
202
+ reader_visibility = property.reader_visibility
203
+ instance_variable_name = property.instance_variable_name
204
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
205
+ #{reader_visibility}
206
+ def #{name}
207
+ return #{instance_variable_name} if defined?(#{instance_variable_name})
208
+ property = properties[#{name.inspect}]
209
+ #{instance_variable_name} = property ? persistence_state.get(property) : nil
210
+ end
211
+ RUBY
212
+
213
+ boolean_reader_name = "#{name}?"
214
+
215
+ if property.kind_of?(DataMapper::Property::Boolean)
216
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
217
+ #{reader_visibility}
218
+ def #{boolean_reader_name}
219
+ #{name}
220
+ end
221
+ RUBY
222
+ end
223
+ end
224
+
225
+ # defines the setter for the property
226
+ #
227
+ # @api private
228
+ def create_writer_for(property)
229
+ name = property.name
230
+ writer_visibility = property.writer_visibility
231
+
232
+ writer_name = "#{name}="
233
+ property_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
234
+ #{writer_visibility}
235
+ def #{writer_name}(value)
236
+ property = properties[#{name.inspect}]
237
+ self.persistence_state = persistence_state.set(property, value)
238
+ persistence_state.get(property)
239
+ end
240
+ RUBY
241
+ end
242
+
243
+ # @api public
244
+ def method_missing(method, *args, &block)
245
+ if property = properties(repository_name)[method]
246
+ return property
247
+ end
248
+
249
+ super
250
+ end
251
+ end # module Property
252
+ end # module Model
253
+ end # module DataMapper
@@ -0,0 +1,377 @@
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
+ end
22
+
23
+ # When DataMapper model is inherited, relationships
24
+ # of parent are duplicated and copied to subclass model
25
+ #
26
+ # @api private
27
+ def inherited(model)
28
+ model.instance_variable_set(:@relationships, {})
29
+
30
+ @relationships.each do |repository_name, relationships|
31
+ model_relationships = model.relationships(repository_name)
32
+ relationships.each { |relationship| model_relationships << relationship }
33
+ end
34
+
35
+ super
36
+ end
37
+
38
+ # Returns copy of relationships set in given repository.
39
+ #
40
+ # @param [Symbol] repository_name
41
+ # Name of the repository for which relationships set is returned
42
+ # @return [RelationshipSet] relationships set for given repository
43
+ #
44
+ # @api semipublic
45
+ def relationships(repository_name = default_repository_name)
46
+ # TODO: create RelationshipSet#copy that will copy the relationships, but assign the
47
+ # new Relationship objects to a supplied repository and model. dup does not really
48
+ # do what is needed
49
+
50
+ default_repository_name = self.default_repository_name
51
+
52
+ @relationships[repository_name] ||= if repository_name == default_repository_name
53
+ RelationshipSet.new
54
+ else
55
+ relationships(default_repository_name).dup
56
+ end
57
+ end
58
+
59
+ # Used to express unlimited cardinality of association,
60
+ # see +has+
61
+ #
62
+ # @api public
63
+ def n
64
+ Infinity
65
+ end
66
+
67
+ # A shorthand, clear syntax for defining one-to-one, one-to-many and
68
+ # many-to-many resource relationships.
69
+ #
70
+ # * has 1, :friend # one friend
71
+ # * has n, :friends # many friends
72
+ # * has 1..3, :friends # many friends (at least 1, at most 3)
73
+ # * has 3, :friends # many friends (exactly 3)
74
+ # * has 1, :friend, 'User' # one friend with the class User
75
+ # * has 3, :friends, :through => :friendships # many friends through the friendships relationship
76
+ #
77
+ # @param cardinality [Integer, Range, Infinity]
78
+ # cardinality that defines the association type and constraints
79
+ # @param name [Symbol]
80
+ # the name that the association will be referenced by
81
+ # @param *args [Model, Hash] model and/or options hash
82
+ #
83
+ # @option *args :through[Symbol] A association that this join should go through to form
84
+ # a many-to-many association
85
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
86
+ # then the association name is assumed to match the class name
87
+ # @option *args :repository[Symbol] name of child model repository
88
+ #
89
+ # @return [Association::Relationship] the relationship that was
90
+ # created to reflect either a one-to-one, one-to-many or many-to-many
91
+ # relationship
92
+ # @raise [ArgumentError] if the cardinality was not understood. Should be a
93
+ # Integer, Range or Infinity(n)
94
+ #
95
+ # @api public
96
+ def has(cardinality, name, *args)
97
+ name = name.to_sym
98
+ model = extract_model(args)
99
+ options = extract_options(args)
100
+
101
+ min, max = extract_min_max(cardinality)
102
+ options.update(:min => min, :max => max)
103
+
104
+ assert_valid_options(options)
105
+
106
+ if options.key?(:model) && model
107
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
108
+ end
109
+
110
+ model ||= options.delete(:model)
111
+
112
+ repository_name = repository.name
113
+
114
+ # TODO: change to :target_respository_name and :source_repository_name
115
+ options[:child_repository_name] = options.delete(:repository)
116
+ options[:parent_repository_name] = repository_name
117
+
118
+ klass = if max > 1
119
+ options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
120
+ else
121
+ Associations::OneToOne::Relationship
122
+ end
123
+
124
+ relationship = klass.new(name, model, self, options)
125
+
126
+ relationships(repository_name) << relationship
127
+
128
+ descendants.each do |descendant|
129
+ descendant.relationships(repository_name) << relationship
130
+ end
131
+
132
+ create_relationship_reader(relationship)
133
+ create_relationship_writer(relationship)
134
+
135
+ relationship
136
+ end
137
+
138
+ # A shorthand, clear syntax for defining many-to-one resource relationships.
139
+ #
140
+ # * belongs_to :user # many to one user
141
+ # * belongs_to :friend, :model => 'User' # many to one friend
142
+ # * belongs_to :reference, :repository => :pubmed # association for repository other than default
143
+ #
144
+ # @param name [Symbol]
145
+ # the name that the association will be referenced by
146
+ # @param *args [Model, Hash] model and/or options hash
147
+ #
148
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
149
+ # then the association name is assumed to match the class name
150
+ # @option *args :repository[Symbol] name of child model repository
151
+ #
152
+ # @return [Association::Relationship] The association created
153
+ # should not be accessed directly
154
+ #
155
+ # @api public
156
+ def belongs_to(name, *args)
157
+ name = name.to_sym
158
+ model_name = self.name
159
+ model = extract_model(args)
160
+ options = extract_options(args)
161
+
162
+ if options.key?(:through)
163
+ raise "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{model_name} instead (#{caller.first})"
164
+ elsif options.key?(:model) && model
165
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
166
+ end
167
+
168
+ assert_valid_options(options)
169
+
170
+ model ||= options.delete(:model)
171
+
172
+ repository_name = repository.name
173
+
174
+ # TODO: change to source_repository_name and target_respository_name
175
+ options[:child_repository_name] = repository_name
176
+ options[:parent_repository_name] = options.delete(:repository)
177
+
178
+ relationship = Associations::ManyToOne::Relationship.new(name, self, model, options)
179
+
180
+ relationships(repository_name) << relationship
181
+
182
+ descendants.each do |descendant|
183
+ descendant.relationships(repository_name) << relationship
184
+ end
185
+
186
+ create_relationship_reader(relationship)
187
+ create_relationship_writer(relationship)
188
+
189
+ relationship
190
+ end
191
+
192
+ private
193
+
194
+ # Extract the model from an Array of arguments
195
+ #
196
+ # @param [Array(Model, String, Hash)]
197
+ # The arguments passed to an relationship declaration
198
+ #
199
+ # @return [Model, #to_str]
200
+ # target model for the association
201
+ #
202
+ # @api private
203
+ def extract_model(args)
204
+ model = args.first
205
+
206
+ if model.kind_of?(Model)
207
+ model
208
+ elsif model.respond_to?(:to_str)
209
+ model.to_str
210
+ else
211
+ nil
212
+ end
213
+ end
214
+
215
+ # Extract the model from an Array of arguments
216
+ #
217
+ # @param [Array(Model, String, Hash)]
218
+ # The arguments passed to an relationship declaration
219
+ #
220
+ # @return [Hash]
221
+ # options for the association
222
+ #
223
+ # @api private
224
+ def extract_options(args)
225
+ options = args.last
226
+ options.respond_to?(:to_hash) ? options.to_hash.dup : {}
227
+ end
228
+
229
+ # A support method for converting Integer, Range or Infinity values into two
230
+ # values representing the minimum and maximum cardinality of the association
231
+ #
232
+ # @return [Array] A pair of integers, min and max
233
+ #
234
+ # @api private
235
+ def extract_min_max(cardinality)
236
+ case cardinality
237
+ when Integer then [ cardinality, cardinality ]
238
+ when Range then [ cardinality.first, cardinality.last ]
239
+ when Infinity then [ 0, Infinity ]
240
+ else
241
+ assert_kind_of 'options', options, Integer, Range, Infinity.class
242
+ end
243
+ end
244
+
245
+ # Validates options of association method like belongs_to or has:
246
+ # verifies types of cardinality bounds, repository, association class,
247
+ # keys and possible values of :through option.
248
+ #
249
+ # @api private
250
+ def assert_valid_options(options)
251
+ # TODO: update to match Query#assert_valid_options
252
+ # - perform options normalization elsewhere
253
+
254
+ if options.key?(:min) && options.key?(:max)
255
+ min = options[:min]
256
+ max = options[:max]
257
+
258
+ min = min.to_int unless min == Infinity
259
+ max = max.to_int unless max == Infinity
260
+
261
+ if min == Infinity && max == Infinity
262
+ raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
263
+ elsif min > max
264
+ raise ArgumentError, "Cardinality min (#{min}) cannot be larger than the max (#{max})"
265
+ elsif min < 0
266
+ raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{min}"
267
+ elsif max < 1
268
+ raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{max}"
269
+ end
270
+ end
271
+
272
+ if options.key?(:repository)
273
+ options[:repository] = options[:repository].to_sym
274
+ end
275
+
276
+ if options.key?(:class_name)
277
+ raise "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
278
+ elsif options.key?(:remote_name)
279
+ raise "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
280
+ end
281
+
282
+ if options.key?(:through)
283
+ assert_kind_of 'options[:through]', options[:through], Symbol, Module
284
+ end
285
+
286
+ [ :via, :inverse ].each do |key|
287
+ if options.key?(key)
288
+ assert_kind_of "options[#{key.inspect}]", options[key], Symbol, Associations::Relationship
289
+ end
290
+ end
291
+
292
+ # TODO: deprecate :child_key and :parent_key in favor of :source_key and
293
+ # :target_key (will mean something different for each relationship)
294
+
295
+ [ :child_key, :parent_key ].each do |key|
296
+ if options.key?(key)
297
+ options[key] = Array(options[key])
298
+ end
299
+ end
300
+
301
+ if options.key?(:limit)
302
+ raise ArgumentError, '+options[:limit]+ should not be specified on a relationship'
303
+ end
304
+ end
305
+
306
+ # Defines the anonymous module that is used to add relationships.
307
+ # Using a single module here prevents having a very large number
308
+ # of anonymous modules, where each property has their own module.
309
+ # @api private
310
+ def relationship_module
311
+ @relationship_module ||= begin
312
+ mod = Module.new
313
+ class_eval do
314
+ include mod
315
+ end
316
+ mod
317
+ end
318
+ end
319
+
320
+ # Dynamically defines reader method
321
+ #
322
+ # @api private
323
+ def create_relationship_reader(relationship)
324
+ name = relationship.name
325
+ reader_name = name.to_s
326
+
327
+ return if method_defined?(reader_name)
328
+
329
+ reader_visibility = relationship.reader_visibility
330
+
331
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
332
+ #{reader_visibility}
333
+ def #{reader_name}(query = nil)
334
+ # TODO: when no query is passed in, return the results from
335
+ # the ivar directly. This will require that the ivar
336
+ # actually hold the resource/collection, and in the case
337
+ # of 1:1, the underlying collection is hidden in a
338
+ # private ivar, and the resource is in a known ivar
339
+
340
+ persistence_state.get(relationships[#{name.inspect}], query)
341
+ end
342
+ RUBY
343
+ end
344
+
345
+ # Dynamically defines writer method
346
+ #
347
+ # @api private
348
+ def create_relationship_writer(relationship)
349
+ name = relationship.name
350
+ writer_name = "#{name}="
351
+
352
+ return if method_defined?(writer_name)
353
+
354
+ writer_visibility = relationship.writer_visibility
355
+
356
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
357
+ #{writer_visibility}
358
+ def #{writer_name}(target)
359
+ relationship = relationships[#{name.inspect}]
360
+ self.persistence_state = persistence_state.set(relationship, target)
361
+ persistence_state.get(relationship)
362
+ end
363
+ RUBY
364
+ end
365
+
366
+ # @api public
367
+ def method_missing(method, *args, &block)
368
+ if relationship = relationships(repository_name)[method]
369
+ return Query::Path.new([ relationship ])
370
+ end
371
+
372
+ super
373
+ end
374
+
375
+ end # module Relationship
376
+ end # module Model
377
+ end # module DataMapper