ardm-core 1.2.1

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