rom-repository 1.4.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -6
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +18 -1
  5. data/lib/rom-repository.rb +1 -2
  6. data/lib/rom/repository.rb +9 -216
  7. data/lib/rom/repository/class_interface.rb +16 -33
  8. data/lib/rom/repository/relation_reader.rb +46 -0
  9. data/lib/rom/repository/root.rb +3 -59
  10. data/lib/rom/repository/version.rb +1 -1
  11. metadata +9 -98
  12. data/.gitignore +0 -3
  13. data/.rspec +0 -3
  14. data/.travis.yml +0 -27
  15. data/.yardopts +0 -2
  16. data/Gemfile +0 -38
  17. data/Rakefile +0 -19
  18. data/lib/rom/open_struct.rb +0 -35
  19. data/lib/rom/repository/changeset.rb +0 -155
  20. data/lib/rom/repository/changeset/associated.rb +0 -100
  21. data/lib/rom/repository/changeset/create.rb +0 -16
  22. data/lib/rom/repository/changeset/delete.rb +0 -17
  23. data/lib/rom/repository/changeset/pipe.rb +0 -97
  24. data/lib/rom/repository/changeset/restricted.rb +0 -28
  25. data/lib/rom/repository/changeset/stateful.rb +0 -282
  26. data/lib/rom/repository/changeset/update.rb +0 -82
  27. data/lib/rom/repository/command_compiler.rb +0 -257
  28. data/lib/rom/repository/command_proxy.rb +0 -26
  29. data/lib/rom/repository/header_builder.rb +0 -65
  30. data/lib/rom/repository/mapper_builder.rb +0 -23
  31. data/lib/rom/repository/relation_proxy.rb +0 -337
  32. data/lib/rom/repository/relation_proxy/combine.rb +0 -320
  33. data/lib/rom/repository/relation_proxy/wrap.rb +0 -78
  34. data/lib/rom/repository/struct_builder.rb +0 -83
  35. data/lib/rom/struct.rb +0 -113
  36. data/log/.gitkeep +0 -0
  37. data/rom-repository.gemspec +0 -23
  38. data/spec/integration/changeset_spec.rb +0 -193
  39. data/spec/integration/command_macros_spec.rb +0 -191
  40. data/spec/integration/command_spec.rb +0 -228
  41. data/spec/integration/multi_adapter_spec.rb +0 -73
  42. data/spec/integration/repository/aggregate_spec.rb +0 -58
  43. data/spec/integration/repository_spec.rb +0 -406
  44. data/spec/integration/root_repository_spec.rb +0 -106
  45. data/spec/integration/typed_structs_spec.rb +0 -64
  46. data/spec/shared/database.rb +0 -79
  47. data/spec/shared/mappers.rb +0 -35
  48. data/spec/shared/models.rb +0 -41
  49. data/spec/shared/plugins.rb +0 -66
  50. data/spec/shared/relations.rb +0 -115
  51. data/spec/shared/repo.rb +0 -86
  52. data/spec/shared/seeds.rb +0 -30
  53. data/spec/shared/structs.rb +0 -140
  54. data/spec/spec_helper.rb +0 -83
  55. data/spec/support/mapper_registry.rb +0 -9
  56. data/spec/support/mutant.rb +0 -10
  57. data/spec/unit/changeset/associate_spec.rb +0 -120
  58. data/spec/unit/changeset/map_spec.rb +0 -111
  59. data/spec/unit/changeset_spec.rb +0 -186
  60. data/spec/unit/relation_proxy_spec.rb +0 -202
  61. data/spec/unit/repository/changeset_spec.rb +0 -197
  62. data/spec/unit/repository/inspect_spec.rb +0 -18
  63. data/spec/unit/repository/session_spec.rb +0 -251
  64. data/spec/unit/repository/transaction_spec.rb +0 -42
  65. data/spec/unit/session_spec.rb +0 -46
  66. data/spec/unit/struct_builder_spec.rb +0 -128
@@ -1,337 +0,0 @@
1
- require 'dry/core/deprecations'
2
-
3
- require 'rom/initializer'
4
- require 'rom/relation/materializable'
5
-
6
- require 'rom/repository/relation_proxy/combine'
7
- require 'rom/repository/relation_proxy/wrap'
8
-
9
- module ROM
10
- class Repository
11
- # RelationProxy decorates a relation and automatically generates mappers that
12
- # will map raw tuples into rom structs
13
- #
14
- # Relation proxies are being registered within repositories so typically there's
15
- # no need to instantiate them manually.
16
- #
17
- # @api public
18
- class RelationProxy
19
- extend Initializer
20
- extend Dry::Core::Deprecations[:rom]
21
-
22
- include Relation::Materializable
23
-
24
- (Kernel.private_instance_methods - %i(raise)).each(&method(:undef_method))
25
-
26
- include RelationProxy::Combine
27
- include RelationProxy::Wrap
28
-
29
- deprecate :combine_parents
30
- deprecate :combine_children
31
- deprecate :wrap_parent
32
-
33
- RelationRegistryType = Types.Definition(RelationRegistry).constrained(type: RelationRegistry)
34
-
35
- # @!attribute [r] relation
36
- # @return [Relation, Relation::Composite, Relation::Graph, Relation::Curried] The decorated relation object
37
- param :relation
38
-
39
- option :name, type: Types::Strict::Symbol
40
- option :mappers, default: -> { MapperBuilder.new }
41
- option :meta, default: -> { EMPTY_HASH }
42
- option :registry, type: RelationRegistryType, default: -> { RelationRegistry.new }
43
- option :auto_struct, default: -> { true }
44
-
45
- # Relation name
46
- #
47
- # @return [ROM::Relation::Name]
48
- #
49
- # @api public
50
- def name
51
- @name ? relation.name.with(@name) : relation.name
52
- end
53
-
54
- # Materializes wrapped relation and sends it through a mapper
55
- #
56
- # For performance reasons a combined relation will skip mapping since
57
- # we only care about extracting key values for combining
58
- #
59
- # @api public
60
- def call(*args)
61
- ((combine? || composite?) ? relation : (relation >> mapper)).call(*args)
62
- end
63
-
64
- # Maps the wrapped relation with other mappers available in the registry
65
- #
66
- # @overload map_with(model)
67
- # Map tuples to the provided custom model class
68
- #
69
- # @example
70
- # users.as(MyUserModel)
71
- #
72
- # @param [Class>] model Your custom model class
73
- #
74
- # @overload map_with(*mappers)
75
- # Map tuples using registered mappers
76
- #
77
- # @example
78
- # users.map_with(:my_mapper, :my_other_mapper)
79
- #
80
- # @param [Array<Symbol>] mappers A list of mapper identifiers
81
- #
82
- # @overload map_with(*mappers, auto_map: true)
83
- # Map tuples using auto-mapping and custom registered mappers
84
- #
85
- # If `auto_map` is enabled, your mappers will be applied after performing
86
- # default auto-mapping. This means that you can compose complex relations
87
- # and have them auto-mapped, and use much simpler custom mappers to adjust
88
- # resulting data according to your requirements.
89
- #
90
- # @example
91
- # users.map_with(:my_mapper, :my_other_mapper, auto_map: true)
92
- #
93
- # @param [Array<Symbol>] mappers A list of mapper identifiers
94
- #
95
- # @return [RelationProxy] A new relation proxy with pipelined relation
96
- #
97
- # @api public
98
- def map_with(*names, **opts)
99
- if names.size == 1 && names[0].is_a?(Class)
100
- map_to(names[0])
101
- elsif names.size > 1 && names.any? { |name| name.is_a?(Class) }
102
- raise ArgumentError, 'using custom mappers and a model is not supported'
103
- else
104
- if opts[:auto_map] && !meta[:combine_type]
105
- mappers = [mapper, *names.map { |name| relation.mappers[name] }]
106
- mappers.reduce(self) { |a, e| a >> e }
107
- else
108
- names.reduce(self) { |a, e| a >> relation.mappers[e] }
109
- end
110
- end
111
- end
112
-
113
- # @api public
114
- def as(*names, **opts)
115
- if names.size == 1 && names[0].is_a?(Class)
116
- msg = <<-STR
117
- Relation#as will change behavior in 4.0. Use `map_to` instead
118
- => Called at:
119
- #{Kernel.caller[0..5].join("\n")}
120
- STR
121
-
122
- Dry::Core::Deprecations.warn(msg)
123
-
124
- map_to(names[0])
125
- elsif names.size > 1 && names.any? { |name| name.is_a?(Class) }
126
- raise ArgumentError, 'using custom mappers and a model is not supported'
127
- else
128
- if opts[:auto_map] && !meta[:combine_type]
129
- mappers = [mapper, *names.map { |name| relation.mappers[name] }]
130
- mappers.reduce(self) { |a, e| a >> e }
131
- else
132
- names.reduce(self) { |a, e| a >> relation.mappers[e] }
133
- end
134
- end
135
- end
136
-
137
- # @api public
138
- def map_to(model)
139
- with(meta: meta.merge(model: model))
140
- end
141
-
142
- # Return a new graph with adjusted node returned from a block
143
- #
144
- # @example with a node identifier
145
- # aggregate(:tasks).node(:tasks) { |tasks| tasks.prioritized }
146
- #
147
- # @example with a nested path
148
- # aggregate(tasks: :tags).node(tasks: :tags) { |tags| tags.where(name: 'red') }
149
- #
150
- # @param [Symbol] name The node relation name
151
- #
152
- # @yieldparam [RelationProxy] The relation node
153
- # @yieldreturn [RelationProxy] The new relation node
154
- #
155
- # @return [RelationProxy]
156
- #
157
- # @api public
158
- def node(name, &block)
159
- if name.is_a?(Symbol) && !nodes.map { |n| n.name.relation }.include?(name)
160
- raise ArgumentError, "#{name.inspect} is not a valid aggregate node name"
161
- end
162
-
163
- new_nodes = nodes.map { |node|
164
- case name
165
- when Symbol
166
- name == node.name.relation ? yield(node) : node
167
- when Hash
168
- other, *rest = name.flatten(1)
169
- if other == node.name.relation
170
- nodes.detect { |n| n.name.relation == other }.node(*rest, &block)
171
- else
172
- node
173
- end
174
- else
175
- node
176
- end
177
- }
178
-
179
- with_nodes(new_nodes)
180
- end
181
-
182
- # Return a string representation of this relation proxy
183
- #
184
- # @return [String]
185
- #
186
- # @api public
187
- def inspect
188
- %(#<#{relation.class} name=#{name} dataset=#{dataset.inspect}>)
189
- end
190
-
191
- # Infers a mapper for the wrapped relation
192
- #
193
- # @return [ROM::Mapper]
194
- #
195
- # @api private
196
- def mapper
197
- mappers[to_ast]
198
- end
199
-
200
- # Returns a new instance with new options
201
- #
202
- # @param new_options [Hash]
203
- #
204
- # @return [RelationProxy]
205
- #
206
- # @api private
207
- def with(new_options)
208
- __new__(relation, options.merge(new_options))
209
- end
210
-
211
- # Returns if this relation is combined aka a relation graph
212
- #
213
- # @return [Boolean]
214
- #
215
- # @api private
216
- def combine?
217
- meta[:combine_type]
218
- end
219
-
220
- # Return if this relation is a composite
221
- #
222
- # @return [Boolean]
223
- #
224
- # @api private
225
- def composite?
226
- relation.is_a?(Relation::Composite)
227
- end
228
-
229
- # @return [Symbol] The wrapped relation's adapter identifier ie :sql or :http
230
- #
231
- # @api private
232
- def adapter
233
- relation.class.adapter
234
- end
235
-
236
- # Returns AST for the wrapped relation
237
- #
238
- # @return [Array]
239
- #
240
- # @api private
241
- def to_ast
242
- @to_ast ||=
243
- begin
244
- attr_ast = schema.map { |attr| [:attribute, attr] }
245
-
246
- meta = self.meta.merge(dataset: base_name.dataset)
247
- meta.update(model: false) unless meta[:model] || auto_struct
248
- meta.delete(:wraps)
249
-
250
- header = attr_ast + nodes_ast + wraps_ast
251
-
252
- [:relation, [base_name.relation, meta, [:header, header]]]
253
- end
254
- end
255
-
256
- # @api private
257
- def respond_to_missing?(meth, _include_private = false)
258
- relation.respond_to?(meth) || super
259
- end
260
-
261
- private
262
-
263
- # @api private
264
- def schema
265
- if meta[:wrap]
266
- relation.schema.wrap
267
- else
268
- relation.schema.reject(&:wrapped?)
269
- end
270
- end
271
-
272
- # @api private
273
- def base_name
274
- relation.base_name
275
- end
276
-
277
- # @api private
278
- def nodes_ast
279
- @nodes_ast ||= nodes.map(&:to_ast)
280
- end
281
-
282
- # @api private
283
- def wraps_ast
284
- @wraps_ast ||= wraps.map(&:to_ast)
285
- end
286
-
287
- # Return a new instance with another relation and options
288
- #
289
- # @return [RelationProxy]
290
- #
291
- # @api private
292
- def __new__(relation, new_options = EMPTY_HASH)
293
- self.class.new(
294
- relation, new_options.size > 0 ? options.merge(new_options) : options
295
- )
296
- end
297
-
298
- # Return all nodes that this relation combines
299
- #
300
- # @return [Array<RelationProxy>]
301
- #
302
- # @api private
303
- def nodes
304
- relation.graph? ? relation.nodes : EMPTY_ARRAY
305
- end
306
-
307
- # Return all nodes that this relation wraps
308
- #
309
- # @return [Array<RelationProxy>]
310
- #
311
- # @api private
312
- def wraps
313
- meta.fetch(:wraps, EMPTY_ARRAY)
314
- end
315
-
316
- # Forward to relation and wrap it with proxy if response was a relation too
317
- #
318
- # TODO: this will be simplified once ROM::Relation has lazy-features built-in
319
- # and ROM::Lazy is gone
320
- #
321
- # @api private
322
- def method_missing(meth, *args, &block)
323
- if relation.respond_to?(meth)
324
- result = relation.__send__(meth, *args, &block)
325
-
326
- if result.kind_of?(Relation::Materializable) && !result.is_a?(Relation::Loaded)
327
- __new__(result)
328
- else
329
- result
330
- end
331
- else
332
- raise NoMethodError, "undefined method `#{meth}' for #{relation.class.name}"
333
- end
334
- end
335
- end
336
- end
337
- end
@@ -1,320 +0,0 @@
1
- require 'dry/core/inflector'
2
-
3
- module ROM
4
- class Repository
5
- class RelationProxy
6
- # Provides convenient methods for composing relations
7
- #
8
- # @api public
9
- module Combine
10
- # Returns a combine representation of a loading-proxy relation
11
- #
12
- # This will carry meta info used to produce a correct AST from a relation
13
- # so that correct mapper can be generated
14
- #
15
- # @return [RelationProxy]
16
- #
17
- # @api private
18
- def combined(name, keys, type)
19
- meta = { keys: keys, combine_type: type, combine_name: name }
20
- with(name: name, meta: self.meta.merge(meta))
21
- end
22
-
23
- # Combine with other relations
24
- #
25
- # @overload combine(*associations)
26
- # Composes relations using configured associations
27
- # @example
28
- # users.combine(:tasks, :posts)
29
- # @param *associations [Array<Symbol>] A list of association names
30
- #
31
- # @overload combine(options)
32
- # Composes relations based on options
33
- #
34
- # @example
35
- # # users have-many tasks (name and join-keys inferred, which needs associations in schema)
36
- # users.combine(many: tasks)
37
- #
38
- # # users have-many tasks with custom name (join-keys inferred, which needs associations in schema)
39
- # users.combine(many: { priority_tasks: tasks.priority })
40
- #
41
- # # users have-many tasks with custom view and join keys
42
- # users.combine(many: { tasks: [tasks.for_users, id: :task_id] })
43
- #
44
- # # users has-one task
45
- # users.combine(one: { task: tasks })
46
- #
47
- # @param options [Hash] Options for combine
48
- # @option :many [Hash] Sets options for "has-many" type of association
49
- # @option :one [Hash] Sets options for "has-one/belongs-to" type of association
50
- #
51
- # @return [RelationProxy]
52
- #
53
- # @api public
54
- def combine(*args)
55
- options = args[0].is_a?(Hash) ? args[0] : args
56
-
57
- combine_opts = Hash.new { |h, k| h[k] = {} }
58
-
59
- options.each do |key, value|
60
- if key == :one || key == :many
61
- if value.is_a?(Hash)
62
- value.each do |name, spec|
63
- if spec.is_a?(Array)
64
- combine_opts[key][name] = spec
65
- else
66
- _, (curried, keys) = combine_opts_from_relations(spec).to_a[0]
67
- combine_opts[key][name] = [curried, keys]
68
- end
69
- end
70
- else
71
- _, (curried, keys) = combine_opts_from_relations(value).to_a[0]
72
- combine_opts[key][curried.combine_tuple_key(key)] = [curried, keys]
73
- end
74
- else
75
- if value.is_a?(Array)
76
- other =
77
- if registry.key?(key)
78
- registry[key]
79
- else
80
- registry[associations[key].target]
81
- end
82
- curried = combine_from_assoc(key, other).combine(*value)
83
- result, _, keys = combine_opts_for_assoc(key)
84
- combine_opts[result][key] = [curried, keys]
85
- else
86
- result, curried, keys = combine_opts_for_assoc(key, value)
87
- combine_opts[result][key] = [curried, keys]
88
- end
89
- end
90
- end
91
-
92
- nodes = combine_opts.flat_map do |type, relations|
93
- relations.map { |name, (relation, keys)|
94
- relation.combined(name, keys, type)
95
- }
96
- end
97
-
98
- __new__(relation.graph(*nodes))
99
- end
100
-
101
- # Shortcut for combining with parents which infers the join keys
102
- #
103
- # @example
104
- # # tasks belong-to users
105
- # tasks.combine_parents(one: users)
106
- #
107
- # # tasks belong-to users with custom user view
108
- # tasks.combine_parents(one: users.task_owners)
109
- #
110
- # @param options [Hash] Combine options hash
111
- #
112
- # @return [RelationProxy]
113
- #
114
- # @api public
115
- def combine_parents(options)
116
- combine_opts = {}
117
-
118
- options.each do |type, parents|
119
- combine_opts[type] =
120
- case parents
121
- when Hash
122
- parents.each_with_object({}) { |(name, parent), r|
123
- keys = combine_keys(parent, relation, :parent)
124
- curried = combine_from_assoc_with_fallback(name, parent, keys)
125
- r[name] = [curried, keys]
126
- }
127
- when Array
128
- parents.each_with_object({}) { |parent, r|
129
- keys = combine_keys(parent, relation, :parent)
130
- tuple_key = parent.combine_tuple_key(type)
131
- curried = combine_from_assoc_with_fallback(parent.name, parent, keys)
132
- r[tuple_key] = [curried, keys]
133
- }
134
- else
135
- keys = combine_keys(parents, relation, :parent)
136
- tuple_key = parents.combine_tuple_key(type)
137
- curried = combine_from_assoc_with_fallback(parents.name, parents, keys)
138
- { tuple_key => [curried, keys] }
139
- end
140
- end
141
-
142
- combine(combine_opts)
143
- end
144
-
145
- # Shortcut for combining with children which infers the join keys
146
- #
147
- # @example
148
- # # users have-many tasks
149
- # users.combine_children(many: tasks)
150
- #
151
- # # users have-many tasks with custom mapping (requires associations)
152
- # users.combine_children(many: { priority_tasks: tasks.priority })
153
- #
154
- # @param [Hash] options
155
- #
156
- # @return [RelationProxy]
157
- #
158
- # @api public
159
- def combine_children(options)
160
- combine_opts = {}
161
-
162
- options.each do |type, children|
163
- combine_opts[type] =
164
- case children
165
- when Hash
166
- children.each_with_object({}) { |(name, child), r|
167
- keys = combine_keys(relation, child, :children)
168
- curried = combine_from_assoc_with_fallback(name, child, keys)
169
- r[name] = [curried, keys]
170
- }
171
- when Array
172
- children.each_with_object({}) { |child, r|
173
- keys = combine_keys(relation, child, :children)
174
- tuple_key = child.combine_tuple_key(type)
175
- curried = combine_from_assoc_with_fallback(child.name, child, keys)
176
- r[tuple_key] = [curried, keys]
177
- }
178
- else
179
- keys = combine_keys(relation, children, :children)
180
- curried = combine_from_assoc_with_fallback(children.name, children, keys)
181
- tuple_key = children.combine_tuple_key(type)
182
- { tuple_key => [curried, keys] }
183
- end
184
- end
185
-
186
- combine(combine_opts)
187
- end
188
-
189
- protected
190
-
191
- # Infer join/combine keys for a given relation and association type
192
- #
193
- # When source has association corresponding to target's name, it'll be
194
- # used to get the keys. Otherwise we fall back to using default keys based
195
- # on naming conventions.
196
- #
197
- # @param [Relation::Name] source The source relation name
198
- # @param [Relation::Name] target The target relation name
199
- # @param [Symbol] type The association type, can be either :parent or :children
200
- #
201
- # @return [Hash<Symbol=>Symbol>]
202
- #
203
- # @api private
204
- def combine_keys(source, target, type)
205
- source.associations.try(target.name) { |assoc|
206
- assoc.combine_keys(__registry__)
207
- } or infer_combine_keys(source, target, type)
208
- end
209
-
210
- # Build combine options from a relation mapping hash passed to `combine`
211
- #
212
- # This method will infer combine keys either from defined associations
213
- # or use the keys provided explicitly for ad-hoc combines
214
- #
215
- # It returns a mapping like `name => [preloadable_relation, combine_keys]`
216
- # and this mapping is used by `combine` to build a full relation graph
217
- #
218
- # @api private
219
- def combine_opts_from_relations(*relations)
220
- relations.each_with_object({}) do |spec, h|
221
- # We assume it's a child relation
222
- keys = combine_keys(relation, spec, :children)
223
- rel = combine_from_assoc_with_fallback(spec.name, spec, keys)
224
- h[spec.name.relation] = [rel, keys]
225
- end
226
- end
227
-
228
- # @api private
229
- def combine_from_assoc_with_fallback(name, other, keys)
230
- combine_from_assoc(name, other) do
231
- other.combine_method(relation, keys)
232
- end
233
- end
234
-
235
- # Try to get a preloadable relation from a defined association
236
- #
237
- # If association doesn't exist we call the fallback block
238
- #
239
- # @return [RelationProxy]
240
- #
241
- # @api private
242
- def combine_from_assoc(name, other, &fallback)
243
- return other if other.curried?
244
- associations.try(name) { |assoc| other.for_combine(assoc) } or fallback.call
245
- end
246
-
247
- # Extract result (either :one or :many), preloadable relation and its keys
248
- # by using given association name
249
- #
250
- # This is used when a flat list of association names was passed to `combine`
251
- #
252
- # @api private
253
- def combine_opts_for_assoc(name, opts = nil)
254
- assoc = relation.associations[name]
255
- curried = registry[assoc.target.relation].for_combine(assoc)
256
- curried = curried.combine(opts) unless opts.nil?
257
- keys = assoc.combine_keys(__registry__)
258
- [assoc.result, curried, keys]
259
- end
260
-
261
- # Build a preloadable relation for relation graph
262
- #
263
- # When a given relation defines `for_other_relation` then it will be used
264
- # to preload `other_relation`. ie `users` relation defines `for_tasks`
265
- # then when we preload tasks for users, this custom method will be used
266
- #
267
- # This *defaults* to the built-in `for_combine` with explicitly provided
268
- # keys
269
- #
270
- # @return [RelationProxy]
271
- #
272
- # @api private
273
- def combine_method(other, keys)
274
- custom_name = :"for_#{other.name.dataset}"
275
-
276
- if relation.respond_to?(custom_name)
277
- __send__(custom_name)
278
- else
279
- for_combine(keys)
280
- end
281
- end
282
-
283
- # Infer key under which a combine relation will be loaded
284
- #
285
- # This is used in cases like ad-hoc combines where relation was passed
286
- # in without specifying the key explicitly, ie:
287
- #
288
- # tasks.combine_parents(one: users)
289
- #
290
- # # ^^^ this will be expanded under-the-hood to:
291
- # tasks.combine(one: { user: users })
292
- #
293
- # @return [Symbol]
294
- #
295
- # @api private
296
- def combine_tuple_key(result)
297
- if result == :one
298
- Dry::Core::Inflector.singularize(base_name.relation).to_sym
299
- else
300
- base_name.relation
301
- end
302
- end
303
-
304
- # Fallback mechanism for `combine_keys` when there's no association defined
305
- #
306
- # @api private
307
- def infer_combine_keys(source, target, type)
308
- primary_key = source.primary_key
309
- foreign_key = target.foreign_key(source)
310
-
311
- if type == :parent
312
- { foreign_key => primary_key }
313
- else
314
- { primary_key => foreign_key }
315
- end
316
- end
317
- end
318
- end
319
- end
320
- end