sbf-dm-core 1.3.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -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 +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -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 +388 -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 +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -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 +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,650 @@
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 Subject, DataMapper::Assertions
10
+
11
+ OPTIONS = %i(child_repository_name parent_repository_name child_key parent_key min max inverse reader_visibility writer_visibility
12
+ 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
+
171
+ child_model_name = self.child_model_name
172
+ @child_model = DataMapper::Ext::Module.find_const(@parent_model || Object, child_model_name)
173
+ rescue NameError
174
+ raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}"
175
+ end
176
+
177
+ # @api private
178
+ def child_model?
179
+ child_model
180
+ true
181
+ rescue NameError
182
+ false
183
+ end
184
+
185
+ # @api private
186
+ def child_model_name
187
+ @child_model ? child_model&.name : @child_model_name
188
+ end
189
+
190
+ # Returns a set of keys that identify the target model
191
+ #
192
+ # @return [PropertySet]
193
+ # a set of properties that identify the target model
194
+ #
195
+ # @api semipublic
196
+ def child_key
197
+ return @child_key if defined?(@child_key)
198
+
199
+ repository_name = child_repository_name || parent_repository_name
200
+ properties = child_model&.properties(repository_name)
201
+
202
+ @child_key = if @child_properties
203
+ child_key = properties&.values_at(*@child_properties)
204
+ properties.class.new(child_key).freeze
205
+ else
206
+ properties&.key
207
+ end
208
+ end
209
+
210
+ # Access Relationship#child_key directly
211
+ #
212
+ # @api private
213
+ alias_method :relationship_child_key, :child_key
214
+ private :relationship_child_key
215
+
216
+ # Returns model class used by parent side of the relationship
217
+ #
218
+ # @return [Resource]
219
+ # Class of association parent
220
+ #
221
+ # @api private
222
+ def parent_model
223
+ return @parent_model if defined?(@parent_model)
224
+
225
+ parent_model_name = self.parent_model_name
226
+ @parent_model = DataMapper::Ext::Module.find_const(@child_model || Object, parent_model_name)
227
+ rescue NameError
228
+ raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}"
229
+ end
230
+
231
+ # @api private
232
+ def parent_model?
233
+ parent_model
234
+ true
235
+ rescue NameError
236
+ false
237
+ end
238
+
239
+ # @api private
240
+ def parent_model_name
241
+ @parent_model ? parent_model&.name : @parent_model_name
242
+ end
243
+
244
+ # Returns a set of keys that identify parent model
245
+ #
246
+ # @return [PropertySet]
247
+ # a set of properties that identify parent model
248
+ #
249
+ # @api private
250
+ def parent_key
251
+ return @parent_key if defined?(@parent_key)
252
+
253
+ repository_name = parent_repository_name || child_repository_name
254
+ properties = parent_model&.properties(repository_name)
255
+
256
+ @parent_key = if @parent_properties
257
+ parent_key = properties&.values_at(*@parent_properties)
258
+ properties.class.new(parent_key).freeze
259
+ else
260
+ properties&.key
261
+ end
262
+ end
263
+
264
+ # Loads and returns "other end" of the association.
265
+ # Must be implemented in subclasses.
266
+ #
267
+ # @api semipublic
268
+ def get(resource, other_query = nil)
269
+ raise NotImplementedError, "#{self.class}#get not implemented"
270
+ end
271
+
272
+ # Gets "other end" of the association directly
273
+ # as @ivar on given resource. Subclasses usually
274
+ # use implementation of this class.
275
+ #
276
+ # @api semipublic
277
+ def get!(resource)
278
+ resource.instance_variable_get(instance_variable_name)
279
+ end
280
+
281
+ # Sets value of the "other end" of association
282
+ # on given resource. Must be implemented in subclasses.
283
+ #
284
+ # @api semipublic
285
+ def set(resource, association)
286
+ raise NotImplementedError, "#{self.class}#set not implemented"
287
+ end
288
+
289
+ # Sets "other end" of the association directly
290
+ # as @ivar on given resource. Subclasses usually
291
+ # use implementation of this class.
292
+ #
293
+ # @api semipublic
294
+ def set!(resource, association)
295
+ resource.instance_variable_set(instance_variable_name, association)
296
+ end
297
+
298
+ # Eager load the collection using the source as a base
299
+ #
300
+ # @param [Collection] source
301
+ # the source collection to query with
302
+ # @param [Query, Hash] query
303
+ # optional query to restrict the collection
304
+ #
305
+ # @return [Collection]
306
+ # the loaded collection for the source
307
+ #
308
+ # @api private
309
+ def eager_load(source, query = nil)
310
+ targets = source.model.all(query_for(source, query))
311
+
312
+ # FIXME: cannot associate targets to m:m collection yet
313
+ associate_targets(source, targets) if source.loaded? && !source.is_a?(ManyToMany::Collection)
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] value
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
+
357
+ instance_of?(other.class) && cmp?(other, :eql?)
358
+ end
359
+
360
+ # Compares another Relationship for equivalency
361
+ #
362
+ # @param [Relationship] other
363
+ # the other Relationship to compare with
364
+ #
365
+ # @return [Boolean]
366
+ # true if they are equal, false if not
367
+ #
368
+ # @api public
369
+ def ==(other)
370
+ return true if equal?(other)
371
+
372
+ other.respond_to?(:cmp_repository?, true) &&
373
+ other.respond_to?(:cmp_model?, true) &&
374
+ other.respond_to?(:cmp_key?, true) &&
375
+ other.respond_to?(:min) &&
376
+ other.respond_to?(:max) &&
377
+ other.respond_to?(:query) &&
378
+ cmp?(other, :==)
379
+ end
380
+
381
+ # Get the inverse relationship from the target model
382
+ #
383
+ # @api semipublic
384
+ def inverse
385
+ return @inverse if defined?(@inverse)
386
+
387
+ @inverse = options[:inverse]
388
+
389
+ return @inverse if kind_of_inverse?(@inverse)
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
+ [
418
+ self.class, name, child_repository_name, parent_repository_name, child_model,
419
+ parent_model, child_properties, parent_properties, min, max, query
420
+ ].hash
421
+ end
422
+
423
+ # @api private
424
+ attr_reader :child_properties
425
+
426
+ # @api private
427
+ attr_reader :parent_properties
428
+
429
+ # Initializes new Relationship: sets attributes of relationship
430
+ # from options as well as conventions: for instance, @ivar name
431
+ # for association is constructed by prefixing @ to association name.
432
+ #
433
+ # Once attributes are set, reader and writer are created for
434
+ # the resource association belongs to
435
+ #
436
+ # @api semipublic
437
+ private def initialize(name, child_model, parent_model, options = {})
438
+ initialize_object_ivar('child_model', child_model)
439
+ initialize_object_ivar('parent_model', parent_model)
440
+
441
+ @name = name
442
+ @instance_variable_name = "@#{@name}".freeze
443
+ @options = options.dup.freeze
444
+ @child_repository_name = @options[:child_repository_name]
445
+ @parent_repository_name = @options[:parent_repository_name]
446
+
447
+ @child_properties = DataMapper::Ext.try_dup(@options[:child_key]).freeze unless @options[:child_key].nil?
448
+ @parent_properties = DataMapper::Ext.try_dup(@options[:parent_key]).freeze unless @options[:parent_key].nil?
449
+
450
+ @min = @options[:min]
451
+ @max = @options[:max]
452
+ @reader_visibility = @options.fetch(:reader_visibility, :public)
453
+ @writer_visibility = @options.fetch(:writer_visibility, :public)
454
+ @default = @options.fetch(:default, nil)
455
+
456
+ # TODO: normalize the @query to become :conditions => AndOperation
457
+ # - Property/Relationship/Path should be left alone
458
+ # - Symbol/String keys should become a Property, scoped to the target_repository and target_model
459
+ # - Extract subject (target) from Operator
460
+ # - subject should be processed same as above
461
+ # - each subject should be transformed into AbstractComparison
462
+ # object with the subject, operator and value
463
+ # - transform into an AndOperation object, and return the
464
+ # query as :condition => and_object from self.query
465
+ # - this should provide the best performance
466
+
467
+ @query = DataMapper::Ext::Hash.except(@options, *self.class::OPTIONS).freeze
468
+ end
469
+
470
+ # Set the correct ivars for the named object
471
+ #
472
+ # This method should set the object in an ivar with the same name
473
+ # provided, plus it should set a String form of the object in
474
+ # a second ivar.
475
+ #
476
+ # @param [String]
477
+ # the name of the ivar to set
478
+ # @param [#name, #to_str, #to_sym] object
479
+ # the object to set in the ivar
480
+ #
481
+ # @return [String]
482
+ # the String value
483
+ #
484
+ # @raise [ArgumentError]
485
+ # raise when object does not respond to expected methods
486
+ #
487
+ # @api private
488
+ private def initialize_object_ivar(name, object)
489
+ if object.respond_to?(:name)
490
+ instance_variable_set("@#{name}", object)
491
+ initialize_object_ivar(name, object.name)
492
+ elsif object.respond_to?(:to_str)
493
+ instance_variable_set("@#{name}_name", object.to_str.dup.freeze)
494
+ elsif object.respond_to?(:to_sym)
495
+ instance_variable_set("@#{name}_name", object.to_sym)
496
+ else
497
+ raise ArgumentError, "#{name} does not respond to #to_str or #name"
498
+ end
499
+
500
+ object
501
+ end
502
+
503
+ # Sets the association targets in the resource
504
+ #
505
+ # @param [Resource] source
506
+ # the source to set
507
+ # @param [Array<Resource>] targets
508
+ # the targets for the association
509
+ # @param [Query, Hash] query
510
+ # the query to scope the association with
511
+ #
512
+ # @return [undefined]
513
+ #
514
+ # @api private
515
+ private def eager_load_targets(source, targets, query)
516
+ raise NotImplementedError, "#{self.class}#eager_load_targets not implemented"
517
+ end
518
+
519
+ # @api private
520
+ private def valid_target_collection?(collection, negated)
521
+ if collection.is_a?(Collection)
522
+ # TODO: move the check for model_key into Collection#reloadable?
523
+ # since what we're really checking is a Collection's ability
524
+ # to reload itself, which is (currently) only possible if the
525
+ # key was loaded.
526
+ model = target_model
527
+ model_key = model.key(repository&.name)
528
+
529
+ collection.model <= model &&
530
+ (collection.query.fields & model_key) == model_key &&
531
+ (collection.loaded? ? (collection.any? || negated) : true)
532
+ else
533
+ collection.all? { |resource| valid_target?(resource) }
534
+ end
535
+ end
536
+
537
+ # @api private
538
+ private def valid_target?(target)
539
+ target.is_a?(target_model) &&
540
+ source_key.valid?(target_key.get(target))
541
+ end
542
+
543
+ # @api private
544
+ private def valid_source?(source)
545
+ source.is_a?(source_model) &&
546
+ target_key.valid?(source_key.get(source))
547
+ end
548
+
549
+ # @api private
550
+ private def inverse?(other)
551
+ return true if @inverse.equal?(other)
552
+
553
+ other != self &&
554
+ kind_of_inverse?(other) &&
555
+ cmp_repository?(other, :==, :child) &&
556
+ cmp_repository?(other, :==, :parent) &&
557
+ cmp_model?(other, :==, :child) &&
558
+ cmp_model?(other, :==, :parent) &&
559
+ cmp_key?(other, :==, :child) &&
560
+ cmp_key?(other, :==, :parent)
561
+
562
+ # TODO: match only when the Query is empty, or is the same as the
563
+ # default scope for the target model
564
+ end
565
+
566
+ # @api private
567
+ private def inverse_name
568
+ inverse = options[:inverse]
569
+ if inverse.is_a?(Relationship)
570
+ inverse.name
571
+ else
572
+ inverse
573
+ end
574
+ end
575
+
576
+ # @api private
577
+ private def invert
578
+ inverse_class.new(inverse_name, child_model, parent_model, inverted_options)
579
+ end
580
+
581
+ # @api private
582
+ private def inverted_options
583
+ DataMapper::Ext::Hash.only(options, *OPTIONS - %i(min max)).update(inverse: self)
584
+ end
585
+
586
+ # @api private
587
+ private def kind_of_inverse?(other)
588
+ other.is_a?(inverse_class)
589
+ end
590
+
591
+ # @api private
592
+ private def cmp?(other, operator)
593
+ name.send(operator, other.name) &&
594
+ cmp_repository?(other, operator, :child) &&
595
+ cmp_repository?(other, operator, :parent) &&
596
+ cmp_model?(other, operator, :child) &&
597
+ cmp_model?(other, operator, :parent) &&
598
+ cmp_key?(other, operator, :child) &&
599
+ cmp_key?(other, operator, :parent) &&
600
+ min.send(operator, other.min) &&
601
+ max.send(operator, other.max) &&
602
+ query.send(operator, other.query)
603
+ end
604
+
605
+ # @api private
606
+ private def cmp_repository?(other, operator, type)
607
+ # if either repository is nil, then the relationship is relative,
608
+ # and the repositories are considered equivalent
609
+ return true unless (repository_name = send("#{type}_repository_name"))
610
+ return true unless (other_repository_name = other.send("#{type}_repository_name"))
611
+
612
+ repository_name.send(operator, other_repository_name)
613
+ end
614
+
615
+ # @api private
616
+ private def cmp_model?(other, operator, type)
617
+ send("#{type}_model?") &&
618
+ other.send("#{type}_model?") &&
619
+ send("#{type}_model").base_model.send(operator, other.send("#{type}_model").base_model)
620
+ end
621
+
622
+ # @api private
623
+ private def cmp_key?(other, operator, type)
624
+ property_method = "#{type}_properties"
625
+
626
+ self_key = send(property_method)
627
+ other_key = other.send(property_method)
628
+
629
+ self_key.send(operator, other_key)
630
+ end
631
+
632
+ private def associate_targets(source, targets)
633
+ # TODO: create an object that wraps this logic, and when the first
634
+ # kicker is fired, then it'll load up the collection, and then
635
+ # populate all the other methods
636
+
637
+ target_maps = Hash.new { |hash, key| hash[key] = [] }
638
+
639
+ targets.each do |target|
640
+ target_maps[target_key.get(target)] << target
641
+ end
642
+
643
+ Array(source).each do |s|
644
+ key = source_key.get(s)
645
+ eager_load_targets(s, target_maps[key], query)
646
+ end
647
+ end
648
+ end
649
+ end
650
+ end
@@ -0,0 +1,11 @@
1
+ require 'dm-core/support/deprecate'
2
+
3
+ module DataMapper
4
+ module Resource
5
+ extend Deprecate
6
+
7
+ deprecate :persisted_state, :persistence_state
8
+ deprecate :persisted_state=, :persistence_state=
9
+ deprecate :persisted_state?, :persistence_state?
10
+ end
11
+ end