sbf-dm-core 1.3.0.beta

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 +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