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,282 @@
1
+ module DataMapper
2
+ module Associations
3
+ module ManyToOne # :nodoc:
4
+ # Relationship class with implementation specific
5
+ # to n side of 1 to n association
6
+ class Relationship < Associations::Relationship
7
+ OPTIONS = superclass::OPTIONS.dup << :required << :key << :unique
8
+
9
+ # @api semipublic
10
+ alias_method :source_repository_name, :child_repository_name
11
+
12
+ # @api semipublic
13
+ alias_method :source_model, :child_model
14
+
15
+ # @api semipublic
16
+ alias_method :target_repository_name, :parent_repository_name
17
+
18
+ # @api semipublic
19
+ alias_method :target_model, :parent_model
20
+
21
+ # @api semipublic
22
+ alias_method :target_key, :parent_key
23
+
24
+ # @api semipublic
25
+ def required?
26
+ @required
27
+ end
28
+
29
+ # @api semipublic
30
+ def key?
31
+ @key
32
+ end
33
+
34
+ # @api semipublic
35
+ def unique?
36
+ !!@unique
37
+ end
38
+
39
+ # @deprecated
40
+ def nullable?
41
+ raise "#{self.class}#nullable? is deprecated, use #{self.class}#required? instead (#{caller.first})"
42
+ end
43
+
44
+ # Returns a set of keys that identify source model
45
+ #
46
+ # @return [DataMapper::PropertySet] a set of properties that identify source model
47
+ # @api private
48
+ def child_key
49
+ return @child_key if defined?(@child_key)
50
+
51
+ model = source_model
52
+ repository_name = source_repository_name || target_repository_name
53
+ properties = model&.properties(repository_name)
54
+
55
+ source_key = target_key.zip(@child_properties || []).map do |target_property, property_name|
56
+ property_name ||= "#{name}_#{target_property.name}".to_sym
57
+
58
+ properties[property_name] || begin
59
+ # create the property within the correct repository
60
+ DataMapper.repository(repository_name) do
61
+ model&.property(property_name, target_property.to_child_key, source_key_options(target_property))
62
+ end
63
+ end
64
+ end
65
+
66
+ @child_key = properties.class.new(source_key).freeze
67
+ end
68
+
69
+ # @api semipublic
70
+ alias_method :source_key, :child_key
71
+
72
+ # Initialize the foreign key property this "many to one"
73
+ # relationship uses to persist itself
74
+ #
75
+ # @return [self]
76
+ #
77
+ # @api public
78
+ def finalize
79
+ child_key
80
+ self
81
+ end
82
+
83
+ # Returns a hash of conditions that scopes query that fetches
84
+ # target object
85
+ #
86
+ # @return [Hash]
87
+ # Hash of conditions that scopes query
88
+ #
89
+ # @api private
90
+ def source_scope(source)
91
+ if source.is_a?(Resource)
92
+ Query.target_conditions(source, source_key, target_key)
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+ # Returns a Resource for this relationship with a given source
99
+ #
100
+ # @param [Resource] source
101
+ # A Resource to scope the collection with
102
+ # @param [Query] other_query (optional)
103
+ # A Query to further scope the collection with
104
+ #
105
+ # @return [Resource]
106
+ # The resource scoped to the relationship, source and query
107
+ #
108
+ # @api private
109
+ def resource_for(source, other_query = nil)
110
+ query = query_for(source, other_query)
111
+
112
+ # If the target key is equal to the model key, we can use the
113
+ # Model#get so the IdentityMap is used
114
+ if target_key == target_model&.key
115
+ target = target_model&.get(*source_key.get!(source))
116
+ target if query.conditions.matches?(target)
117
+ else
118
+ target_model&.first(query)
119
+ end
120
+ end
121
+
122
+ # Loads and returns association target (ex.: author) for given source resource
123
+ # (ex.: article)
124
+ #
125
+ # @param source [DataMapper::Resource]
126
+ # source object (ex.: instance of article)
127
+ # @param query [DataMapper::Query]
128
+ # Query options
129
+ #
130
+ # @api semipublic
131
+ def get(source, query = nil)
132
+ lazy_load(source)
133
+
134
+ if query
135
+ collection = get_collection(source)
136
+ collection&.first(query)
137
+ else
138
+ get!(source)
139
+ end
140
+ end
141
+
142
+ def get_collection(source)
143
+ target = get!(source)
144
+ target&.collection_for_self
145
+ end
146
+
147
+ # Sets value of association target (ex.: author) for given source resource
148
+ # (ex.: article)
149
+ #
150
+ # @param source [DataMapper::Resource]
151
+ # source object (ex.: instance of article)
152
+ #
153
+ # @param target [DataMapper::Resource]
154
+ # target object (ex.: instance of author)
155
+ #
156
+ # @api semipublic
157
+ def set(source, target)
158
+ target = typecast(target)
159
+ source_key.set(source, target_key.get(target))
160
+ set!(source, target)
161
+ end
162
+
163
+ # @api semipublic
164
+ def default_for(source)
165
+ typecast(super)
166
+ end
167
+
168
+ # Loads association target and sets resulting value on
169
+ # given source resource
170
+ #
171
+ # @param [Resource] source
172
+ # the source resource for the association
173
+ #
174
+ # @return [undefined]
175
+ #
176
+ # @api private
177
+ def lazy_load(source)
178
+ source_key_different = source_key_different?(source)
179
+
180
+ return if (loaded?(source) && !source_key_different) || !valid_source?(source)
181
+
182
+ # SEL: load all related resources in the source collection
183
+ if source.saved? && ((collection = source.collection)&.size&.> 1)
184
+ eager_load(collection)
185
+ end
186
+
187
+ if !loaded?(source) || (source_key_dirty?(source) && source.saved?)
188
+ set!(source, resource_for(source))
189
+ elsif loaded?(source) && source_key_different
190
+ source_key.set(source, target_key.get!(get!(source)))
191
+ end
192
+ end
193
+
194
+ # Initializes the relationship, always using max cardinality of 1.
195
+ #
196
+ # @api semipublic
197
+ private def initialize(name, source_model, target_model, options = {})
198
+ raise ":nullable is deprecated, use :required instead (#{caller[2]})" if options.key?(:nullable)
199
+
200
+ @required = options.fetch(:required, true)
201
+ @key = options.fetch(:key, false)
202
+ @unique = options.fetch(:unique, false)
203
+ @unique_index = options.fetch(:unique_index, @unique)
204
+ target_model ||= DataMapper::Inflector.camelize(name)
205
+ options = {min: @required ? 1 : 0, max: 1}.update(options)
206
+ super
207
+ end
208
+
209
+ # Sets the association targets in the resource
210
+ #
211
+ # @param [Resource] source
212
+ # the source to set
213
+ # @param [Array(Resource)] targets
214
+ # the target resource for the association
215
+ # @param [Query, Hash] _query
216
+ # not used
217
+ #
218
+ # @return [undefined]
219
+ #
220
+ # @api private
221
+ private def eager_load_targets(source, targets, _query)
222
+ set(source, targets.first)
223
+ end
224
+
225
+ # @api private
226
+ private def typecast(target)
227
+ if target.is_a?(Hash)
228
+ target_model&.new(target)
229
+ else
230
+ target
231
+ end
232
+ end
233
+
234
+ # Returns the inverse relationship class
235
+ #
236
+ # @api private
237
+ private def inverse_class
238
+ OneToMany::Relationship
239
+ end
240
+
241
+ # Returns the inverse relationship name
242
+ #
243
+ # @api private
244
+ private def inverse_name
245
+ name = super
246
+ return name if name
247
+
248
+ name = DataMapper::Inflector.demodulize(source_model&.name)
249
+ name = DataMapper::Inflector.underscore(name)
250
+ name = DataMapper::Inflector.pluralize(name)
251
+ name.to_sym
252
+ end
253
+
254
+ # @api private
255
+ private def source_key_options(target_property)
256
+ DataMapper::Ext::Hash.only(target_property.options, :length, :precision, :scale).update(
257
+ index: name,
258
+ required: required?,
259
+ key: key?,
260
+ unique: @unique,
261
+ unique_index: @unique_index
262
+ )
263
+ end
264
+
265
+ # @api private
266
+ private def child_properties
267
+ source_key.map(&:name)
268
+ end
269
+
270
+ # @api private
271
+ private def source_key_different?(source)
272
+ source_key.get!(source) != target_key.get!(get!(source))
273
+ end
274
+
275
+ # @api private
276
+ private def source_key_dirty?(source)
277
+ source.dirty_attributes.keys.any? { |property| source_key.include?(property) }
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,332 @@
1
+ module DataMapper
2
+ module Associations
3
+ module OneToMany # :nodoc:
4
+ class Relationship < Associations::Relationship
5
+ # @api semipublic
6
+ alias_method :target_repository_name, :child_repository_name
7
+
8
+ # @api semipublic
9
+ alias_method :target_model, :child_model
10
+
11
+ # @api semipublic
12
+ alias_method :source_repository_name, :parent_repository_name
13
+
14
+ # @api semipublic
15
+ alias_method :source_model, :parent_model
16
+
17
+ # @api semipublic
18
+ alias_method :source_key, :parent_key
19
+
20
+ # @api semipublic
21
+ def child_key
22
+ inverse.child_key
23
+ end
24
+
25
+ # @api semipublic
26
+ alias_method :target_key, :child_key
27
+
28
+ # Returns a Collection for this relationship with a given source
29
+ #
30
+ # @param [Resource] source
31
+ # A Resource to scope the collection with
32
+ # @param [Query] other_query (optional)
33
+ # A Query to further scope the collection with
34
+ #
35
+ # @return [Collection]
36
+ # The collection scoped to the relationship, source and query
37
+ #
38
+ # @api private
39
+ def collection_for(source, other_query = nil)
40
+ query = query_for(source, other_query)
41
+
42
+ collection = collection_class.new(query)
43
+ collection.relationship = self
44
+ collection.source = source
45
+
46
+ # make the collection empty if the source is new
47
+ collection.replace([]) if source.new?
48
+
49
+ collection
50
+ end
51
+
52
+ # Loads and returns association targets (ex.: articles) for given source resource
53
+ # (ex.: author)
54
+ #
55
+ # @api semipublic
56
+ def get(source, query = nil)
57
+ lazy_load(source)
58
+ collection = get_collection(source)
59
+ query ? collection.all(query) : collection
60
+ end
61
+
62
+ # @api private
63
+ def get_collection(source)
64
+ get!(source)
65
+ end
66
+
67
+ # Sets value of association targets (ex.: paragraphs) for given source resource
68
+ # (ex.: article)
69
+ #
70
+ # @api semipublic
71
+ def set(source, targets)
72
+ lazy_load(source)
73
+ get!(source).replace(targets)
74
+ end
75
+
76
+ # @api private
77
+ def set_collection(source, target)
78
+ set!(source, target)
79
+ end
80
+
81
+ # Loads association targets and sets resulting value on
82
+ # given source resource
83
+ #
84
+ # @param [Resource] source
85
+ # the source resource for the association
86
+ #
87
+ # @return [undefined]
88
+ #
89
+ # @api private
90
+ def lazy_load(source)
91
+ return if loaded?(source)
92
+
93
+ # SEL: load all related resources in the source collection
94
+ if source.saved? && ((collection = source.collection)&.size&.> 1)
95
+ eager_load(collection)
96
+ end
97
+
98
+ set!(source, collection_for(source)) unless loaded?(source)
99
+ end
100
+
101
+ # initialize the inverse "many to one" relationships explicitly before
102
+ # initializing other relationships. This makes sure that foreign key
103
+ # properties always appear in the order they were declared.
104
+ #
105
+ # @return [self]
106
+ #
107
+ # @api public
108
+ def finalize
109
+ child_model&.relationships&.each do |relationship|
110
+ # TODO: should this check #inverse?
111
+ # relationship.child_key if inverse?(relationship)
112
+ relationship.finalize if relationship.is_a?(Associations::ManyToOne::Relationship)
113
+ end
114
+ inverse.finalize
115
+ self
116
+ end
117
+
118
+ # @api semipublic
119
+ def default_for(source)
120
+ collection_for(source).replace(Array(super))
121
+ end
122
+
123
+ # @api semipublic
124
+ private def initialize(name, target_model, source_model, options = {})
125
+ target_model ||= DataMapper::Inflector.camelize(DataMapper::Inflector.singularize(name.to_s))
126
+ options = {min: 0, max: source_model.n}.update(options)
127
+ super
128
+ end
129
+
130
+ # Sets the association targets in the resource
131
+ #
132
+ # @param [Resource] source
133
+ # the source to set
134
+ # @param [Array<Resource>] targets
135
+ # the target collection for the association
136
+ # @param [Query, Hash] query
137
+ # the query to scope the association with
138
+ #
139
+ # @return [undefined]
140
+ #
141
+ # @api private
142
+ private def eager_load_targets(source, targets, query)
143
+ set!(source, collection_for(source, query).set(targets))
144
+ end
145
+
146
+ # Returns collection class used by this type of
147
+ # relationship
148
+ #
149
+ # @api private
150
+ private def collection_class
151
+ OneToMany::Collection
152
+ end
153
+
154
+ # Returns the inverse relationship class
155
+ #
156
+ # @api private
157
+ private def inverse_class
158
+ ManyToOne::Relationship
159
+ end
160
+
161
+ # Returns the inverse relationship name
162
+ #
163
+ # @api private
164
+ private def inverse_name
165
+ super || DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(source_model&.name)).to_sym
166
+ end
167
+
168
+ # @api private
169
+ private def child_properties
170
+ super || parent_key.map do |parent_property|
171
+ "#{inverse_name}_#{parent_property.name}".to_sym
172
+ end
173
+ end
174
+ end
175
+
176
+ class Collection < DataMapper::Collection
177
+ # @api private
178
+ attr_accessor :relationship
179
+
180
+ # @api private
181
+ attr_accessor :source
182
+
183
+ # @api public
184
+ def reload(*)
185
+ assert_source_saved 'The source must be saved before reloading the collection'
186
+ super
187
+ end
188
+
189
+ # Replace the Resources within the 1:m Collection
190
+ #
191
+ # @param [Enumerable] *
192
+ # List of other Resources to replace with
193
+ #
194
+ # @return [Collection]
195
+ # self
196
+ #
197
+ # @api public
198
+ def replace(*)
199
+ lazy_load # lazy load so that targets are always orphaned
200
+ super
201
+ end
202
+
203
+ # Removes all Resources from the 1:m Collection
204
+ #
205
+ # This should remove and orphan each Resource from the 1:m Collection.
206
+ #
207
+ # @return [Collection]
208
+ # self
209
+ #
210
+ # @api public
211
+ def clear
212
+ lazy_load # lazy load so that targets are always orphaned
213
+ super
214
+ end
215
+
216
+ # Update every Resource in the 1:m Collection
217
+ #
218
+ # @param [Hash] *
219
+ # attributes to update with
220
+ #
221
+ # @return [Boolean]
222
+ # true if the resources were successfully updated
223
+ #
224
+ # @api public
225
+ def update(*)
226
+ assert_source_saved 'The source must be saved before mass-updating the collection'
227
+ super
228
+ end
229
+
230
+ # Update every Resource in the 1:m Collection, bypassing validation
231
+ #
232
+ # @param [Hash] *
233
+ # attributes to update
234
+ #
235
+ # @return [Boolean]
236
+ # true if the resources were successfully updated
237
+ #
238
+ # @api public
239
+ def update!(*)
240
+ assert_source_saved 'The source must be saved before mass-updating the collection'
241
+ super
242
+ end
243
+
244
+ # Remove every Resource in the 1:m Collection from the repository
245
+ #
246
+ # This performs a deletion of each Resource in the Collection from
247
+ # the repository and clears the Collection.
248
+ #
249
+ # @return [Boolean]
250
+ # true if the resources were successfully destroyed
251
+ #
252
+ # @api public
253
+ def destroy
254
+ assert_source_saved 'The source must be saved before mass-deleting the collection'
255
+ super
256
+ end
257
+
258
+ # Remove every Resource in the 1:m Collection from the repository, bypassing validation
259
+ #
260
+ # This performs a deletion of each Resource in the Collection from
261
+ # the repository and clears the Collection while skipping
262
+ # validation.
263
+ #
264
+ # @return [Boolean]
265
+ # true if the resources were successfully destroyed
266
+ #
267
+ # @api public
268
+ def destroy!
269
+ assert_source_saved 'The source must be saved before mass-deleting the collection'
270
+ super
271
+ end
272
+
273
+ # @api private
274
+ private def _create(*)
275
+ assert_source_saved 'The source must be saved before creating a resource'
276
+ super
277
+ end
278
+
279
+ # @api private
280
+ private def _save(execute_hooks = true)
281
+ assert_source_saved 'The source must be saved before saving the collection'
282
+
283
+ # update removed resources to not reference the source
284
+ @removed.all? { |resource| resource.destroyed? || resource.__send__(execute_hooks ? :save : :save!) } && super
285
+ end
286
+
287
+ # @api private
288
+ private def lazy_load
289
+ super if source.saved?
290
+ end
291
+
292
+ # @api private
293
+ private def new_collection(query, resources = nil, &block)
294
+ collection = self.class.new(query, &block)
295
+
296
+ collection.relationship = relationship
297
+ collection.source = source
298
+
299
+ resources ||= filter(query) if loaded?
300
+
301
+ # set the resources after the relationship and source are set
302
+ collection.set(resources) if resources
303
+
304
+ collection
305
+ end
306
+
307
+ # @api private
308
+ private def resource_added(resource)
309
+ resource = initialize_resource(resource)
310
+ inverse_set(resource, source)
311
+ super
312
+ end
313
+
314
+ # @api private
315
+ private def resource_removed(resource)
316
+ inverse_set(resource, nil)
317
+ super
318
+ end
319
+
320
+ # @api private
321
+ private def inverse_set(source, target)
322
+ relationship.inverse.set(source, target) unless source.readonly?
323
+ end
324
+
325
+ # @api private
326
+ private def assert_source_saved(message)
327
+ raise UnsavedParentError, message unless source.saved?
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,84 @@
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
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.is_a?(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
+ attr_reader :relationship
66
+
67
+ # Initializes the relationship. Always assumes target model class is
68
+ # a camel cased association name.
69
+ #
70
+ # @api semipublic
71
+ private def initialize(name, target_model, source_model, options = {})
72
+ klass = options.key?(:through) ? ManyToMany::Relationship : OneToMany::Relationship
73
+ target_model ||= DataMapper::Inflector.camelize(name).freeze
74
+ @relationship = klass.new(name, target_model, source_model, options)
75
+ end
76
+
77
+ # @api private
78
+ private def method_missing(method, *args, &block)
79
+ relationship.send(method, *args, &block)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end