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,86 @@
1
+ module DataMapper
2
+ module Associations
3
+ module OneToOne #:nodoc:
4
+ class Relationship < Associations::Relationship
5
+ %w[ public protected private ].map do |visibility|
6
+ methods = superclass.send("#{visibility}_instance_methods", false) |
7
+ DataMapper::Subject.send("#{visibility}_instance_methods", false)
8
+
9
+ methods.each do |method|
10
+ undef_method method.to_sym unless method.to_s == 'initialize'
11
+ end
12
+ end
13
+
14
+ # Loads (if necessary) and returns association target
15
+ # for given source
16
+ #
17
+ # @api semipublic
18
+ def get(source, query = nil)
19
+ relationship.get(source, query).first
20
+ end
21
+
22
+ # Get the resource directly
23
+ #
24
+ # @api semipublic
25
+ def get!(source)
26
+ collection = relationship.get!(source)
27
+ collection.first if collection
28
+ end
29
+
30
+ # Sets and returns association target
31
+ # for given source
32
+ #
33
+ # @api semipublic
34
+ def set(source, target)
35
+ relationship.set(source, [ target ].compact).first
36
+ end
37
+
38
+ # Sets the resource directly
39
+ #
40
+ # @api semipublic
41
+ def set!(source, target)
42
+ set(source, target)
43
+ end
44
+
45
+ # @api semipublic
46
+ def default_for(source)
47
+ relationship.default_for(source).first
48
+ end
49
+
50
+ # @api public
51
+ def kind_of?(klass)
52
+ super || relationship.kind_of?(klass)
53
+ end
54
+
55
+ # @api public
56
+ def instance_of?(klass)
57
+ super || relationship.instance_of?(klass)
58
+ end
59
+
60
+ # @api public
61
+ def respond_to?(method, include_private = false)
62
+ super || relationship.respond_to?(method, include_private)
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :relationship
68
+
69
+ # Initializes the relationship. Always assumes target model class is
70
+ # a camel cased association name.
71
+ #
72
+ # @api semipublic
73
+ def initialize(name, target_model, source_model, options = {})
74
+ klass = options.key?(:through) ? ManyToMany::Relationship : OneToMany::Relationship
75
+ target_model ||= DataMapper::Inflector.camelize(name).freeze
76
+ @relationship = klass.new(name, target_model, source_model, options)
77
+ end
78
+
79
+ # @api private
80
+ def method_missing(method, *args, &block)
81
+ relationship.send(method, *args, &block)
82
+ end
83
+ end # class Relationship
84
+ end # module HasOne
85
+ end # module Associations
86
+ end # module DataMapper
@@ -0,0 +1,663 @@
1
+ # TODO: move argument and option validation into the class
2
+
3
+ module DataMapper
4
+ module Associations
5
+ # Base class for relationships. Each type of relationship
6
+ # (1 to 1, 1 to n, n to m) implements a subclass of this class
7
+ # with methods like get and set overridden.
8
+ class Relationship
9
+ include DataMapper::Assertions
10
+ include Subject
11
+
12
+ OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse, :reader_visibility, :writer_visibility, :default ].to_set
13
+
14
+ # Relationship name
15
+ #
16
+ # @example for :parent association in
17
+ #
18
+ # class VersionControl::Commit
19
+ # # ...
20
+ #
21
+ # belongs_to :parent
22
+ # end
23
+ #
24
+ # name is :parent
25
+ #
26
+ # @api semipublic
27
+ attr_reader :name
28
+
29
+ # Options used to set up association of this relationship
30
+ #
31
+ # @example for :author association in
32
+ #
33
+ # class VersionControl::Commit
34
+ # # ...
35
+ #
36
+ # belongs_to :author, :model => 'Person'
37
+ # end
38
+ #
39
+ # options is a hash with a single key, :model
40
+ #
41
+ # @api semipublic
42
+ attr_reader :options
43
+
44
+ # ivar used to store collection of child options in source
45
+ #
46
+ # @example for :commits association in
47
+ #
48
+ # class VersionControl::Branch
49
+ # # ...
50
+ #
51
+ # has n, :commits
52
+ # end
53
+ #
54
+ # instance variable name for source will be @commits
55
+ #
56
+ # @api semipublic
57
+ attr_reader :instance_variable_name
58
+
59
+ # Repository from where child objects are loaded
60
+ #
61
+ # @api semipublic
62
+ attr_reader :child_repository_name
63
+
64
+ # Repository from where parent objects are loaded
65
+ #
66
+ # @api semipublic
67
+ attr_reader :parent_repository_name
68
+
69
+ # Minimum number of child objects for relationship
70
+ #
71
+ # @example for :cores association in
72
+ #
73
+ # class CPU::Multicore
74
+ # # ...
75
+ #
76
+ # has 2..n, :cores
77
+ # end
78
+ #
79
+ # minimum is 2
80
+ #
81
+ # @api semipublic
82
+ attr_reader :min
83
+
84
+ # Maximum number of child objects for
85
+ # relationship
86
+ #
87
+ # @example for :fouls association in
88
+ #
89
+ # class Basketball::Player
90
+ # # ...
91
+ #
92
+ # has 0..5, :fouls
93
+ # end
94
+ #
95
+ # maximum is 5
96
+ #
97
+ # @api semipublic
98
+ attr_reader :max
99
+
100
+ # Returns the visibility for the source accessor
101
+ #
102
+ # @return [Symbol]
103
+ # the visibility for the accessor added to the source
104
+ #
105
+ # @api semipublic
106
+ attr_reader :reader_visibility
107
+
108
+ # Returns the visibility for the source mutator
109
+ #
110
+ # @return [Symbol]
111
+ # the visibility for the mutator added to the source
112
+ #
113
+ # @api semipublic
114
+ attr_reader :writer_visibility
115
+
116
+ # Returns query options for relationship.
117
+ #
118
+ # For this base class, always returns query options
119
+ # has been initialized with.
120
+ # Overriden in subclasses.
121
+ #
122
+ # @api private
123
+ attr_reader :query
124
+
125
+ # Returns the String the Relationship would use in a Hash
126
+ #
127
+ # @return [String]
128
+ # String name for the Relationship
129
+ #
130
+ # @api private
131
+ def field
132
+ name.to_s
133
+ end
134
+
135
+ # Returns a hash of conditions that scopes query that fetches
136
+ # target object
137
+ #
138
+ # @return [Hash]
139
+ # Hash of conditions that scopes query
140
+ #
141
+ # @api private
142
+ def source_scope(source)
143
+ { inverse => source }
144
+ end
145
+
146
+ # Creates and returns Query instance that fetches
147
+ # target resource(s) (ex.: articles) for given target resource (ex.: author)
148
+ #
149
+ # @api semipublic
150
+ def query_for(source, other_query = nil)
151
+ repository_name = relative_target_repository_name_for(source)
152
+
153
+ DataMapper.repository(repository_name).scope do
154
+ query = target_model.query.dup
155
+ query.update(self.query)
156
+ query.update(:conditions => source_scope(source))
157
+ query.update(other_query) if other_query
158
+ query.update(:fields => query.fields | target_key)
159
+ end
160
+ end
161
+
162
+ # Returns model class used by child side of the relationship
163
+ #
164
+ # @return [Resource]
165
+ # Model for association child
166
+ #
167
+ # @api private
168
+ def child_model
169
+ return @child_model if defined?(@child_model)
170
+ child_model_name = self.child_model_name
171
+ @child_model = DataMapper::Ext::Module.find_const(@parent_model || Object, child_model_name)
172
+ rescue NameError
173
+ raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}"
174
+ end
175
+
176
+ # @api private
177
+ def child_model?
178
+ child_model
179
+ true
180
+ rescue NameError
181
+ false
182
+ end
183
+
184
+ # @api private
185
+ def child_model_name
186
+ @child_model ? child_model.name : @child_model_name
187
+ end
188
+
189
+ # Returns a set of keys that identify the target model
190
+ #
191
+ # @return [PropertySet]
192
+ # a set of properties that identify the target model
193
+ #
194
+ # @api semipublic
195
+ def child_key
196
+ return @child_key if defined?(@child_key)
197
+
198
+ repository_name = child_repository_name || parent_repository_name
199
+ properties = child_model.properties(repository_name)
200
+
201
+ @child_key = if @child_properties
202
+ child_key = properties.values_at(*@child_properties)
203
+ properties.class.new(child_key).freeze
204
+ else
205
+ properties.key
206
+ end
207
+ end
208
+
209
+ # Access Relationship#child_key directly
210
+ #
211
+ # @api private
212
+ alias_method :relationship_child_key, :child_key
213
+ private :relationship_child_key
214
+
215
+ # Returns model class used by parent side of the relationship
216
+ #
217
+ # @return [Resource]
218
+ # Class of association parent
219
+ #
220
+ # @api private
221
+ def parent_model
222
+ return @parent_model if defined?(@parent_model)
223
+ parent_model_name = self.parent_model_name
224
+ @parent_model = DataMapper::Ext::Module.find_const(@child_model || Object, parent_model_name)
225
+ rescue NameError
226
+ raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}"
227
+ end
228
+
229
+ # @api private
230
+ def parent_model?
231
+ parent_model
232
+ true
233
+ rescue NameError
234
+ false
235
+ end
236
+
237
+ # @api private
238
+ def parent_model_name
239
+ @parent_model ? parent_model.name : @parent_model_name
240
+ end
241
+
242
+ # Returns a set of keys that identify parent model
243
+ #
244
+ # @return [PropertySet]
245
+ # a set of properties that identify parent model
246
+ #
247
+ # @api private
248
+ def parent_key
249
+ return @parent_key if defined?(@parent_key)
250
+
251
+ repository_name = parent_repository_name || child_repository_name
252
+ properties = parent_model.properties(repository_name)
253
+
254
+ @parent_key = if @parent_properties
255
+ parent_key = properties.values_at(*@parent_properties)
256
+ properties.class.new(parent_key).freeze
257
+ else
258
+ properties.key
259
+ end
260
+ end
261
+
262
+ # Loads and returns "other end" of the association.
263
+ # Must be implemented in subclasses.
264
+ #
265
+ # @api semipublic
266
+ def get(resource, other_query = nil)
267
+ raise NotImplementedError, "#{self.class}#get not implemented"
268
+ end
269
+
270
+ # Gets "other end" of the association directly
271
+ # as @ivar on given resource. Subclasses usually
272
+ # use implementation of this class.
273
+ #
274
+ # @api semipublic
275
+ def get!(resource)
276
+ resource.instance_variable_get(instance_variable_name)
277
+ end
278
+
279
+ # Sets value of the "other end" of association
280
+ # on given resource. Must be implemented in subclasses.
281
+ #
282
+ # @api semipublic
283
+ def set(resource, association)
284
+ raise NotImplementedError, "#{self.class}#set not implemented"
285
+ end
286
+
287
+ # Sets "other end" of the association directly
288
+ # as @ivar on given resource. Subclasses usually
289
+ # use implementation of this class.
290
+ #
291
+ # @api semipublic
292
+ def set!(resource, association)
293
+ resource.instance_variable_set(instance_variable_name, association)
294
+ end
295
+
296
+ # Eager load the collection using the source as a base
297
+ #
298
+ # @param [Collection] source
299
+ # the source collection to query with
300
+ # @param [Query, Hash] query
301
+ # optional query to restrict the collection
302
+ #
303
+ # @return [Collection]
304
+ # the loaded collection for the source
305
+ #
306
+ # @api private
307
+ def eager_load(source, query = nil)
308
+ targets = source.model.all(query_for(source, query))
309
+
310
+ # FIXME: cannot associate targets to m:m collection yet
311
+ if source.loaded? && !source.kind_of?(ManyToMany::Collection)
312
+ associate_targets(source, targets)
313
+ end
314
+
315
+ targets
316
+ end
317
+
318
+ # Checks if "other end" of association is loaded on given
319
+ # resource.
320
+ #
321
+ # @api semipublic
322
+ def loaded?(resource)
323
+ resource.instance_variable_defined?(instance_variable_name)
324
+ end
325
+
326
+ # Test the resource to see if it is a valid target
327
+ #
328
+ # @param [Object] source
329
+ # the resource or collection to be tested
330
+ #
331
+ # @return [Boolean]
332
+ # true if the resource is valid
333
+ #
334
+ # @api semipulic
335
+ def valid?(value, negated = false)
336
+ case value
337
+ when Enumerable then valid_target_collection?(value, negated)
338
+ when Resource then valid_target?(value)
339
+ when nil then true
340
+ else
341
+ raise ArgumentError, "+value+ should be an Enumerable, Resource or nil, but was a #{value.class.name}"
342
+ end
343
+ end
344
+
345
+ # Compares another Relationship for equality
346
+ #
347
+ # @param [Relationship] other
348
+ # the other Relationship to compare with
349
+ #
350
+ # @return [Boolean]
351
+ # true if they are equal, false if not
352
+ #
353
+ # @api public
354
+ def eql?(other)
355
+ return true if equal?(other)
356
+ instance_of?(other.class) && cmp?(other, :eql?)
357
+ end
358
+
359
+ # Compares another Relationship for equivalency
360
+ #
361
+ # @param [Relationship] other
362
+ # the other Relationship to compare with
363
+ #
364
+ # @return [Boolean]
365
+ # true if they are equal, false if not
366
+ #
367
+ # @api public
368
+ def ==(other)
369
+ return true if equal?(other)
370
+ other.respond_to?(:cmp_repository?, true) &&
371
+ other.respond_to?(:cmp_model?, true) &&
372
+ other.respond_to?(:cmp_key?, true) &&
373
+ other.respond_to?(:min) &&
374
+ other.respond_to?(:max) &&
375
+ other.respond_to?(:query) &&
376
+ cmp?(other, :==)
377
+ end
378
+
379
+ # Get the inverse relationship from the target model
380
+ #
381
+ # @api semipublic
382
+ def inverse
383
+ return @inverse if defined?(@inverse)
384
+
385
+ @inverse = options[:inverse]
386
+
387
+ if kind_of_inverse?(@inverse)
388
+ return @inverse
389
+ end
390
+
391
+ relationships = target_model.relationships(relative_target_repository_name)
392
+
393
+ @inverse = relationships.detect { |relationship| inverse?(relationship) } ||
394
+ invert
395
+
396
+ @inverse.child_key
397
+
398
+ @inverse
399
+ end
400
+
401
+ # @api private
402
+ def relative_target_repository_name
403
+ target_repository_name || source_repository_name
404
+ end
405
+
406
+ # @api private
407
+ def relative_target_repository_name_for(source)
408
+ target_repository_name || if source.respond_to?(:repository)
409
+ source.repository.name
410
+ else
411
+ source_repository_name
412
+ end
413
+ end
414
+
415
+ # @api private
416
+ def hash
417
+ self.class.hash ^
418
+ name.hash ^
419
+ child_repository_name.hash ^
420
+ parent_repository_name.hash ^
421
+ child_model.hash ^
422
+ parent_model.hash ^
423
+ child_properties.hash ^
424
+ parent_properties.hash ^
425
+ min.hash ^
426
+ max.hash ^
427
+ query.hash
428
+ end
429
+
430
+ private
431
+
432
+ # @api private
433
+ attr_reader :child_properties
434
+
435
+ # @api private
436
+ attr_reader :parent_properties
437
+
438
+ # Initializes new Relationship: sets attributes of relationship
439
+ # from options as well as conventions: for instance, @ivar name
440
+ # for association is constructed by prefixing @ to association name.
441
+ #
442
+ # Once attributes are set, reader and writer are created for
443
+ # the resource association belongs to
444
+ #
445
+ # @api semipublic
446
+ def initialize(name, child_model, parent_model, options = {})
447
+ initialize_object_ivar('child_model', child_model)
448
+ initialize_object_ivar('parent_model', parent_model)
449
+
450
+ @name = name
451
+ @instance_variable_name = "@#{@name}".freeze
452
+ @options = options.dup.freeze
453
+ @child_repository_name = @options[:child_repository_name]
454
+ @parent_repository_name = @options[:parent_repository_name]
455
+
456
+ unless @options[:child_key].nil?
457
+ @child_properties = DataMapper::Ext.try_dup(@options[:child_key]).freeze
458
+ end
459
+ unless @options[:parent_key].nil?
460
+ @parent_properties = DataMapper::Ext.try_dup(@options[:parent_key]).freeze
461
+ end
462
+
463
+ @min = @options[:min]
464
+ @max = @options[:max]
465
+ @reader_visibility = @options.fetch(:reader_visibility, :public)
466
+ @writer_visibility = @options.fetch(:writer_visibility, :public)
467
+ @default = @options.fetch(:default, nil)
468
+
469
+ # TODO: normalize the @query to become :conditions => AndOperation
470
+ # - Property/Relationship/Path should be left alone
471
+ # - Symbol/String keys should become a Property, scoped to the target_repository and target_model
472
+ # - Extract subject (target) from Operator
473
+ # - subject should be processed same as above
474
+ # - each subject should be transformed into AbstractComparison
475
+ # object with the subject, operator and value
476
+ # - transform into an AndOperation object, and return the
477
+ # query as :condition => and_object from self.query
478
+ # - this should provide the best performance
479
+
480
+ @query = DataMapper::Ext::Hash.except(@options, *self.class::OPTIONS).freeze
481
+ end
482
+
483
+ # Set the correct ivars for the named object
484
+ #
485
+ # This method should set the object in an ivar with the same name
486
+ # provided, plus it should set a String form of the object in
487
+ # a second ivar.
488
+ #
489
+ # @param [String]
490
+ # the name of the ivar to set
491
+ # @param [#name, #to_str, #to_sym] object
492
+ # the object to set in the ivar
493
+ #
494
+ # @return [String]
495
+ # the String value
496
+ #
497
+ # @raise [ArgumentError]
498
+ # raise when object does not respond to expected methods
499
+ #
500
+ # @api private
501
+ def initialize_object_ivar(name, object)
502
+ if object.respond_to?(:name)
503
+ instance_variable_set("@#{name}", object)
504
+ initialize_object_ivar(name, object.name)
505
+ elsif object.respond_to?(:to_str)
506
+ instance_variable_set("@#{name}_name", object.to_str.dup.freeze)
507
+ elsif object.respond_to?(:to_sym)
508
+ instance_variable_set("@#{name}_name", object.to_sym)
509
+ else
510
+ raise ArgumentError, "#{name} does not respond to #to_str or #name"
511
+ end
512
+
513
+ object
514
+ end
515
+
516
+ # Sets the association targets in the resource
517
+ #
518
+ # @param [Resource] source
519
+ # the source to set
520
+ # @param [Array<Resource>] targets
521
+ # the targets for the association
522
+ # @param [Query, Hash] query
523
+ # the query to scope the association with
524
+ #
525
+ # @return [undefined]
526
+ #
527
+ # @api private
528
+ def eager_load_targets(source, targets, query)
529
+ raise NotImplementedError, "#{self.class}#eager_load_targets not implemented"
530
+ end
531
+
532
+ # @api private
533
+ def valid_target_collection?(collection, negated)
534
+ if collection.kind_of?(Collection)
535
+ # TODO: move the check for model_key into Collection#reloadable?
536
+ # since what we're really checking is a Collection's ability
537
+ # to reload itself, which is (currently) only possible if the
538
+ # key was loaded.
539
+ model = target_model
540
+ model_key = model.key(repository.name)
541
+
542
+ collection.model <= model &&
543
+ (collection.query.fields & model_key) == model_key &&
544
+ (collection.loaded? ? (collection.any? || negated) : true)
545
+ else
546
+ collection.all? { |resource| valid_target?(resource) }
547
+ end
548
+ end
549
+
550
+ # @api private
551
+ def valid_target?(target)
552
+ target.kind_of?(target_model) &&
553
+ source_key.valid?(target_key.get(target))
554
+ end
555
+
556
+ # @api private
557
+ def valid_source?(source)
558
+ source.kind_of?(source_model) &&
559
+ target_key.valid?(source_key.get(source))
560
+ end
561
+
562
+ # @api private
563
+ def inverse?(other)
564
+ return true if @inverse.equal?(other)
565
+
566
+ other != self &&
567
+ kind_of_inverse?(other) &&
568
+ cmp_repository?(other, :==, :child) &&
569
+ cmp_repository?(other, :==, :parent) &&
570
+ cmp_model?(other, :==, :child) &&
571
+ cmp_model?(other, :==, :parent) &&
572
+ cmp_key?(other, :==, :child) &&
573
+ cmp_key?(other, :==, :parent)
574
+
575
+ # TODO: match only when the Query is empty, or is the same as the
576
+ # default scope for the target model
577
+ end
578
+
579
+ # @api private
580
+ def inverse_name
581
+ inverse = options[:inverse]
582
+ if inverse.kind_of?(Relationship)
583
+ inverse.name
584
+ else
585
+ inverse
586
+ end
587
+ end
588
+
589
+ # @api private
590
+ def invert
591
+ inverse_class.new(inverse_name, child_model, parent_model, inverted_options)
592
+ end
593
+
594
+ # @api private
595
+ def inverted_options
596
+ DataMapper::Ext::Hash.only(options, *OPTIONS - [ :min, :max ]).update(:inverse => self)
597
+ end
598
+
599
+ # @api private
600
+ def kind_of_inverse?(other)
601
+ other.kind_of?(inverse_class)
602
+ end
603
+
604
+ # @api private
605
+ def cmp?(other, operator)
606
+ name.send(operator, other.name) &&
607
+ cmp_repository?(other, operator, :child) &&
608
+ cmp_repository?(other, operator, :parent) &&
609
+ cmp_model?(other, operator, :child) &&
610
+ cmp_model?(other, operator, :parent) &&
611
+ cmp_key?(other, operator, :child) &&
612
+ cmp_key?(other, operator, :parent) &&
613
+ min.send(operator, other.min) &&
614
+ max.send(operator, other.max) &&
615
+ query.send(operator, other.query)
616
+ end
617
+
618
+ # @api private
619
+ def cmp_repository?(other, operator, type)
620
+ # if either repository is nil, then the relationship is relative,
621
+ # and the repositories are considered equivalent
622
+ return true unless repository_name = send("#{type}_repository_name")
623
+ return true unless other_repository_name = other.send("#{type}_repository_name")
624
+
625
+ repository_name.send(operator, other_repository_name)
626
+ end
627
+
628
+ # @api private
629
+ def cmp_model?(other, operator, type)
630
+ send("#{type}_model?") &&
631
+ other.send("#{type}_model?") &&
632
+ send("#{type}_model").base_model.send(operator, other.send("#{type}_model").base_model)
633
+ end
634
+
635
+ # @api private
636
+ def cmp_key?(other, operator, type)
637
+ property_method = "#{type}_properties"
638
+
639
+ self_key = send(property_method)
640
+ other_key = other.send(property_method)
641
+
642
+ self_key.send(operator, other_key)
643
+ end
644
+
645
+ def associate_targets(source, targets)
646
+ # TODO: create an object that wraps this logic, and when the first
647
+ # kicker is fired, then it'll load up the collection, and then
648
+ # populate all the other methods
649
+
650
+ target_maps = Hash.new { |hash, key| hash[key] = [] }
651
+
652
+ targets.each do |target|
653
+ target_maps[target_key.get(target)] << target
654
+ end
655
+
656
+ Array(source).each do |source|
657
+ key = source_key.get(source)
658
+ eager_load_targets(source, target_maps[key], query)
659
+ end
660
+ end
661
+ end # class Relationship
662
+ end # module Associations
663
+ end # module DataMapper