rom-factory 0.10.2 → 0.12.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.devtools/templates/changelog.erb +3 -0
  3. data/.devtools/templates/release.erb +36 -0
  4. data/.github/FUNDING.yml +1 -0
  5. data/.github/ISSUE_TEMPLATE/{---bug-report.md → bug-report.md} +6 -10
  6. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  7. data/.github/SUPPORT.md +3 -0
  8. data/.github/workflows/ci.yml +17 -25
  9. data/.github/workflows/docsite.yml +6 -6
  10. data/.github/workflows/rubocop.yml +46 -0
  11. data/.github/workflows/sync_configs.yml +7 -13
  12. data/.repobot.yml +24 -0
  13. data/.rubocop.yml +135 -16
  14. data/CHANGELOG.md +64 -0
  15. data/CODEOWNERS +1 -0
  16. data/CONTRIBUTING.md +3 -3
  17. data/Gemfile +20 -19
  18. data/Gemfile.devtools +6 -5
  19. data/LICENSE +1 -1
  20. data/README.md +3 -3
  21. data/Rakefile +1 -1
  22. data/benchmarks/basic.rb +10 -10
  23. data/changelog.yml +35 -0
  24. data/docsite/source/index.html.md +162 -1
  25. data/lib/rom/factory/attribute_registry.rb +6 -2
  26. data/lib/rom/factory/attributes/association.rb +127 -29
  27. data/lib/rom/factory/attributes/callable.rb +1 -1
  28. data/lib/rom/factory/attributes/value.rb +1 -1
  29. data/lib/rom/factory/attributes.rb +4 -6
  30. data/lib/rom/factory/builder/persistable.rb +21 -6
  31. data/lib/rom/factory/builder.rb +17 -19
  32. data/lib/rom/factory/constants.rb +1 -1
  33. data/lib/rom/factory/dsl.rb +43 -25
  34. data/lib/rom/factory/factories.rb +13 -15
  35. data/lib/rom/factory/registry.rb +2 -2
  36. data/lib/rom/factory/sequences.rb +4 -3
  37. data/lib/rom/factory/tuple_evaluator.rb +92 -43
  38. data/lib/rom/factory/version.rb +1 -1
  39. data/lib/rom/factory.rb +8 -5
  40. data/lib/rom-factory.rb +1 -1
  41. data/project.yml +1 -0
  42. data/rom-factory.gemspec +9 -10
  43. metadata +40 -26
  44. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  45. data/.github/ISSUE_TEMPLATE/----please-don-t-report-feature-requests-via-issues.md +0 -10
  46. data/.github/ISSUE_TEMPLATE/---a-detailed-bug-report.md +0 -30
  47. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  48. data/.github/workflows/custom/ci.yml +0 -26
  49. data/Appraisals +0 -9
  50. data/LICENSE.txt +0 -21
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require "delegate"
4
4
 
5
5
  module ROM
6
6
  module Factory
@@ -21,11 +21,10 @@ module ROM
21
21
  end
22
22
 
23
23
  # @api private
24
- def create(*args)
25
- traits, attrs = builder.extract_tuple(args)
26
-
27
- tuple = tuple(*traits, attrs)
24
+ def create(*traits, **attrs)
28
25
  validate_keys(traits, attrs)
26
+
27
+ tuple = tuple(*traits, **attrs)
29
28
  persisted = persist(tuple)
30
29
 
31
30
  if tuple_evaluator.has_associations?(traits)
@@ -43,13 +42,29 @@ module ROM
43
42
 
44
43
  # @api private
45
44
  def persist(attrs)
46
- relation.with(auto_struct: !tuple_evaluator.has_associations?).command(:create).call(attrs)
45
+ result = relation
46
+ .with(auto_struct: !tuple_evaluator.has_associations?)
47
+ .command(:create)
48
+ .call(attrs)
49
+
50
+ # Handle PK values generated by the factory
51
+ if pk? && (pks = attrs.values_at(*primary_key_names)).compact.size == primary_key_names.size
52
+ relation.by_pk(*pks).one!
53
+ elsif result
54
+ result
55
+ else
56
+ relation.where(attrs).one!
57
+ end
47
58
  end
48
59
 
49
60
  # @api private
50
61
  def primary_key_names
51
62
  relation.schema.primary_key.map(&:name)
52
63
  end
64
+
65
+ def pk?
66
+ primary_key_names.any?
67
+ end
53
68
  end
54
69
  end
55
70
  end
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/constants'
4
-
5
- require 'rom/struct'
6
- require 'rom/initializer'
7
- require 'rom/factory/tuple_evaluator'
8
- require 'rom/factory/builder/persistable'
3
+ require "rom/struct"
4
+ require "rom/initializer"
5
+ require "rom/factory/tuple_evaluator"
6
+ require "rom/factory/builder/persistable"
9
7
 
10
8
  module ROM::Factory
11
9
  # @api private
@@ -30,19 +28,20 @@ module ROM::Factory
30
28
  # @return [Module] Custom struct namespace
31
29
  option :struct_namespace, reader: false
32
30
 
33
- # @api private
34
- def tuple(*args)
35
- traits, attrs = extract_tuple(args)
31
+ # @!attribute [r] factories
32
+ # @return [Module] Factories with other builders
33
+ option :factories, reader: true, optional: true
36
34
 
35
+ # @api private
36
+ def tuple(*traits, **attrs)
37
37
  tuple_evaluator.defaults(traits, attrs)
38
38
  end
39
39
 
40
40
  # @api private
41
- def struct(*args)
42
- traits, attrs = extract_tuple(args)
41
+ def struct(*traits, **attrs)
43
42
  validate_keys(traits, attrs, allow_associations: true)
44
43
 
45
- tuple_evaluator.struct(*args)
44
+ tuple_evaluator.struct(*traits, **attrs)
46
45
  end
47
46
  alias_method :create, :struct
48
47
 
@@ -62,7 +61,11 @@ module ROM::Factory
62
61
 
63
62
  # @api private
64
63
  def tuple_evaluator
65
- @__tuple_evaluator__ ||= TupleEvaluator.new(attributes, tuple_evaluator_relation, traits)
64
+ @__tuple_evaluator__ ||= TupleEvaluator.new(
65
+ attributes,
66
+ tuple_evaluator_relation,
67
+ traits
68
+ )
66
69
  end
67
70
 
68
71
  # @api private
@@ -75,18 +78,13 @@ module ROM::Factory
75
78
  tuple_evaluator.relation
76
79
  end
77
80
 
78
- # @api private
79
- def extract_tuple(args)
80
- tuple_evaluator.extract_tuple(args)
81
- end
82
-
83
81
  # @api private
84
82
  def validate_keys(traits, tuple, allow_associations: false)
85
83
  schema_keys = relation.schema.attributes.map(&:name)
86
84
  assoc_keys = tuple_evaluator.assoc_names(traits)
87
85
  unknown_keys = tuple.keys - schema_keys - assoc_keys
88
86
 
89
- unknown_keys = unknown_keys - relation.schema.associations.to_h.keys if allow_associations
87
+ unknown_keys -= relation.schema.associations.to_h.keys if allow_associations
90
88
 
91
89
  raise UnknownFactoryAttributes, unknown_keys unless unknown_keys.empty?
92
90
  end
@@ -10,7 +10,7 @@ module ROM
10
10
 
11
11
  class UnknownFactoryAttributes < StandardError
12
12
  def initialize(attrs)
13
- super("Unknown attributes: #{attrs.join(', ')}")
13
+ super("Unknown attributes: #{attrs.join(", ")}")
14
14
  end
15
15
  end
16
16
  end
@@ -1,26 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'faker'
4
- require 'dry/core/inflector'
3
+ require "faker"
5
4
 
6
- require 'rom/factory/builder'
7
- require 'rom/factory/attribute_registry'
8
- require 'rom/factory/attributes'
5
+ require "rom/factory/builder"
6
+ require "rom/factory/attribute_registry"
7
+ require "rom/factory/attributes"
9
8
 
10
9
  module ROM
11
10
  module Factory
11
+ extend ::Dry::Core::Cache
12
+
12
13
  class << self
13
14
  # @api private
14
- def fake(type, *args)
15
- api = ::Faker.const_get(::Dry::Core::Inflector.classify(type.to_s))
15
+ def fake(*args, **options)
16
+ api = fetch_or_store(:faker, *args) do
17
+ *ns, method_name = args
16
18
 
17
- if args[0].is_a?(Symbol)
18
- api.public_send(*args)
19
- else
20
- api.public_send(type, *args)
19
+ const = ns.reduce(::Faker) do |obj, name|
20
+ obj.const_get(::Dry::Core::Inflector.camelize(name))
21
+ end
22
+
23
+ const.method(method_name)
21
24
  end
25
+
26
+ api.(**options)
22
27
  end
23
- ruby2_keywords(:fake) if respond_to?(:ruby2_keywords, true)
24
28
  end
25
29
 
26
30
  # Factory builder DSL
@@ -33,7 +37,7 @@ module ROM
33
37
  attr_reader :_traits
34
38
 
35
39
  # @api private
36
- def initialize(name, attributes: AttributeRegistry.new, relation:, factories:, struct_namespace:)
40
+ def initialize(name, relation:, factories:, struct_namespace:, attributes: AttributeRegistry.new)
37
41
  @_name = name
38
42
  @_relation = relation
39
43
  @_factories = factories
@@ -46,7 +50,13 @@ module ROM
46
50
 
47
51
  # @api private
48
52
  def call
49
- ::ROM::Factory::Builder.new(_attributes, _traits, relation: _relation, struct_namespace: _struct_namespace)
53
+ ::ROM::Factory::Builder.new(
54
+ _attributes,
55
+ _traits,
56
+ relation: _relation,
57
+ struct_namespace: _struct_namespace,
58
+ factories: _factories
59
+ )
50
60
  end
51
61
 
52
62
  # Delegate to a builder and persist a struct
@@ -83,28 +93,36 @@ module ROM
83
93
  #
84
94
  # @param [Symbol] type The value type to generate
85
95
  #
86
- # @overload fake(api, type)
96
+ # @overload fake(genre, type)
87
97
  # @example
88
98
  # f.email { fake(:internet, :email) }
89
99
  #
90
- # @param [Symbol] api The faker API identifier ie. :internet, :product etc.
100
+ # @param [Symbol] genre The faker API identifier ie. :internet, :product etc.
91
101
  # @param [Symbol] type The value type to generate
92
102
  #
93
- # @overload fake(api, type, *args)
103
+ # @overload fake(genre, type, **options)
94
104
  # @example
95
- # f.email { fake(:number, :between, 10, 100) }
105
+ # f.email { fake(:number, :between, from: 10, to: 100) }
96
106
  #
97
- # @param [Symbol] api The faker API identifier ie. :internet, :product etc.
107
+ # @param [Symbol] genre The faker API identifier ie. :internet, :product etc.
98
108
  # @param [Symbol] type The value type to generate
99
- # @param [Array] args Additional arguments
109
+ # @param [Hash] options Additional arguments
110
+ #
111
+ # @overload fake(genre, subgenre, type, **options)
112
+ # @example
113
+ # f.quote { fake(:books, :dune, :quote, character: 'stilgar') }
114
+ #
115
+ # @param [Symbol] genre The Faker genre of API i.e. :books, :creature, :games etc
116
+ # @param [Symbol] subgenre The subgenre of API i.e. :dune, :bird, :myst etc
117
+ # @param [Symbol] type the value type to generate
118
+ # @param [Hash] options Additional arguments
100
119
  #
101
- # @see https://github.com/stympy/faker/tree/master/doc
120
+ # @see https://github.com/faker-ruby/faker/tree/master/doc
102
121
  #
103
122
  # @api public
104
- def fake(*args)
105
- ::ROM::Factory.fake(*args)
123
+ def fake(type, *args, **options)
124
+ ::ROM::Factory.fake(type, *args, **options)
106
125
  end
107
- ruby2_keywords(:fake) if respond_to?(:ruby2_keywords, true)
108
126
 
109
127
  def trait(name, parents = [], &block)
110
128
  _traits[name] = DSL.new(
@@ -136,7 +154,7 @@ module ROM
136
154
  assoc = _relation.associations[name]
137
155
 
138
156
  if assoc.is_a?(::ROM::SQL::Associations::OneToOne) && options.fetch(:count, 1) > 1
139
- ::Kernel.raise ::ArgumentError, 'count cannot be greater than 1 on a OneToOne'
157
+ ::Kernel.raise ::ArgumentError, "count cannot be greater than 1 on a OneToOne"
140
158
  end
141
159
 
142
160
  builder = -> { _factories.for_relation(assoc.target) }
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/configurable'
4
- require 'dry/core/inflector'
5
-
6
- require 'rom/initializer'
7
- require 'rom/struct'
8
- require 'rom/factory/dsl'
9
- require 'rom/factory/registry'
3
+ require "rom/initializer"
4
+ require "rom/struct"
5
+ require "rom/factory/dsl"
6
+ require "rom/factory/registry"
10
7
 
11
8
  module ROM::Factory
12
9
  # In-memory builder API
@@ -42,7 +39,7 @@ module ROM::Factory
42
39
  #
43
40
  # @api public
44
41
  def [](name, *traits, **attrs)
45
- registry[name].struct_namespace(struct_namespace).create(*traits, attrs)
42
+ registry[name].struct_namespace(struct_namespace).create(*traits, **attrs)
46
43
  end
47
44
  end
48
45
 
@@ -147,7 +144,7 @@ module ROM::Factory
147
144
  name,
148
145
  relation: relation,
149
146
  factories: self,
150
- struct_namespace: builder_sturct_namespace(namespace),
147
+ struct_namespace: builder_struct_namespace(namespace),
151
148
  &block
152
149
  ).call
153
150
  end
@@ -164,13 +161,14 @@ module ROM::Factory
164
161
  # MyFactory[:user, name: "Jane"]
165
162
  #
166
163
  # @param [Symbol] name The name of the registered factory
167
- # @param [Hash] attrs An optional hash with attributes
164
+ # @param [Array<Symbol>] traits List of traits to apply
165
+ # @param [Hash] attrs optional attributes to override the defaults
168
166
  #
169
167
  # @return [ROM::Struct]
170
168
  #
171
169
  # @api public
172
- def [](name, *args)
173
- registry[name].struct_namespace(struct_namespace).persistable.create(*args)
170
+ def [](name, *traits, **attrs)
171
+ registry[name].struct_namespace(struct_namespace).persistable.create(*traits, **attrs)
174
172
  end
175
173
 
176
174
  # Return in-memory struct builder
@@ -204,8 +202,8 @@ module ROM::Factory
204
202
  end
205
203
 
206
204
  # @api private
207
- def builder_sturct_namespace(ns)
208
- ns ? { namespace: ns, overridable: false } : { namespace: struct_namespace, overridable: true }
205
+ def builder_struct_namespace(ns)
206
+ ns ? {namespace: ns, overridable: false} : {namespace: struct_namespace, overridable: true}
209
207
  end
210
208
 
211
209
  # @api private
@@ -226,7 +224,7 @@ module ROM::Factory
226
224
  # @api private
227
225
  def extend_builder(name, parent, relation_name, ns, &block)
228
226
  namespace = parent.options[:struct_namespace]
229
- namespace = builder_sturct_namespace(ns) if ns
227
+ namespace = builder_struct_namespace(ns) if ns
230
228
  relation = rom.relations.fetch(relation_name) { parent.relation }
231
229
  DSL.new(
232
230
  name,
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/factory/constants'
3
+ require "rom/factory/constants"
4
4
 
5
5
  module ROM
6
6
  module Factory
@@ -28,7 +28,7 @@ module ROM
28
28
  # @api private
29
29
  def [](name)
30
30
  elements.fetch(name) do
31
- raise FactoryNotDefinedError.new(name)
31
+ raise FactoryNotDefinedError, name
32
32
  end
33
33
  end
34
34
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
3
+ require "concurrent/map"
4
+ require "singleton"
4
5
 
5
6
  module ROM
6
7
  module Factory
@@ -24,12 +25,12 @@ module ROM
24
25
 
25
26
  # @api private
26
27
  def next(key)
27
- registry[key] += 1
28
+ registry.compute(key) { |v| (v || 0).succ }
28
29
  end
29
30
 
30
31
  # @api private
31
32
  def reset
32
- @registry = Concurrent::Map.new { |h, k| h[k] = 0 }
33
+ @registry = Concurrent::Map.new
33
34
  self
34
35
  end
35
36
  end
@@ -1,11 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/factory/sequences'
3
+ require "rom/factory/sequences"
4
4
 
5
5
  module ROM
6
6
  module Factory
7
7
  # @api private
8
8
  class TupleEvaluator
9
+ class TupleEvaluatorError < StandardError
10
+ attr_reader :original_exception
11
+
12
+ def initialize(relation, original_exception, attrs, traits, assoc_attrs)
13
+ super(<<~STR)
14
+ Failed to build attributes for #{relation.name}
15
+
16
+ Attributes:
17
+ #{attrs.inspect}
18
+
19
+ Associations:
20
+ #{assoc_attrs}
21
+
22
+ Traits:
23
+ #{traits.inspect}
24
+
25
+ Original exception: #{original_exception.message}
26
+ STR
27
+
28
+ set_backtrace(original_exception.backtrace)
29
+ end
30
+ end
31
+
9
32
  # @api private
10
33
  attr_reader :attributes
11
34
 
@@ -15,9 +38,6 @@ module ROM
15
38
  # @api private
16
39
  attr_reader :traits
17
40
 
18
- # @api private
19
- attr_reader :model
20
-
21
41
  # @api private
22
42
  attr_reader :sequence
23
43
 
@@ -26,18 +46,21 @@ module ROM
26
46
  @attributes = attributes
27
47
  @relation = relation.with(auto_struct: true)
28
48
  @traits = traits
29
- @model = @relation.combine(*assoc_names).mapper.model
30
49
  @sequence = Sequences[relation]
31
50
  end
32
51
 
52
+ def model(traits, combine: assoc_names(traits))
53
+ @relation.combine(*combine).mapper.model
54
+ end
55
+
33
56
  # @api private
34
- def defaults(traits, attrs, opts = EMPTY_HASH)
35
- evaluate(traits, attrs, opts).merge(attrs)
57
+ def defaults(traits, attrs, **opts)
58
+ mergeable_attrs = select_mergeable_attrs(traits, attrs)
59
+ evaluate(traits, attrs, opts).merge(mergeable_attrs)
36
60
  end
37
61
 
38
62
  # @api private
39
- def struct(*args)
40
- traits, attrs = extract_tuple(args)
63
+ def struct(*traits, **attrs)
41
64
  merged_attrs = struct_attrs.merge(defaults(traits, attrs, persist: false))
42
65
  is_callable = proc { |_name, value| value.respond_to?(:call) }
43
66
 
@@ -45,21 +68,41 @@ module ROM
45
68
  attributes = merged_attrs.reject(&is_callable)
46
69
 
47
70
  materialized_callables = {}
48
- callables.each do |_name, callable|
71
+ callables.each_value do |callable|
49
72
  materialized_callables.merge!(callable.call(attributes, persist: false))
50
73
  end
51
74
 
52
75
  attributes.merge!(materialized_callables)
53
76
 
54
- associations = assoc_names
55
- .map { |key| [key, attributes[key]] if attributes.key?(key) }
56
- .compact
77
+ assoc_attrs = attributes.slice(*assoc_names(traits)).merge(
78
+ assoc_names(traits)
79
+ .select { |key|
80
+ build_assoc?(key, attributes)
81
+ }
82
+ .map { |key|
83
+ [key, build_assoc_attrs(key, attributes[relation.primary_key], attributes[key])]
84
+ }
57
85
  .to_h
86
+ )
87
+
88
+ model_attrs = relation.output_schema[attributes]
89
+ model_attrs.update(assoc_attrs)
90
+
91
+ model(traits).new(**model_attrs)
92
+ rescue StandardError => e
93
+ raise TupleEvaluatorError.new(relation, e, attrs, traits, assoc_attrs)
94
+ end
58
95
 
59
- attributes = relation.output_schema[attributes]
60
- attributes.update(associations)
96
+ def build_assoc?(name, attributes)
97
+ attributes.key?(name) && attributes[name] != [] && !attributes[name].nil?
98
+ end
61
99
 
62
- model.new(attributes)
100
+ def build_assoc_attrs(key, fk, value)
101
+ if value.is_a?(Array)
102
+ value.map { |el| build_assoc_attrs(key, fk, el) }
103
+ else
104
+ {attributes[key].foreign_key => fk}.merge(value.to_h)
105
+ end
63
106
  end
64
107
 
65
108
  # @api private
@@ -76,10 +119,15 @@ module ROM
76
119
  end
77
120
 
78
121
  def assocs(traits_names = [])
79
- traits
122
+ found_assocs = traits
80
123
  .values_at(*traits_names)
124
+ .compact
81
125
  .map(&:associations).flat_map(&:elements)
82
126
  .inject(AttributeRegistry.new(attributes.associations.elements), :<<)
127
+
128
+ exclude = traits_names.select { |t| t.is_a?(Hash) }.reduce(:merge) || EMPTY_HASH
129
+
130
+ found_assocs.reject { |a| exclude[a.name] == false }
83
131
  end
84
132
 
85
133
  # @api private
@@ -92,28 +140,17 @@ module ROM
92
140
  relation.primary_key
93
141
  end
94
142
 
95
- # @api private
96
- def extract_tuple(args)
97
- if !args.empty? && args.last.is_a?(::Hash)
98
- *traits, attrs = args
99
-
100
- [traits, attrs]
101
- else
102
- [args, EMPTY_HASH]
103
- end
104
- end
105
-
106
143
  private
107
144
 
108
145
  # @api private
109
146
  def evaluate(traits, attrs, opts)
110
- evaluate_values(attrs, opts)
111
- .merge(evaluate_associations(attrs, opts))
147
+ evaluate_values(attrs)
148
+ .merge(evaluate_associations(traits, attrs, opts))
112
149
  .merge(evaluate_traits(traits, attrs, opts))
113
150
  end
114
151
 
115
152
  # @api private
116
- def evaluate_values(attrs, opts)
153
+ def evaluate_values(attrs)
117
154
  attributes.values.tsort.each_with_object({}) do |attr, h|
118
155
  deps = attr.dependency_names.map { |k| h[k] }.compact
119
156
  result = attr.(attrs, *deps)
@@ -124,33 +161,38 @@ module ROM
124
161
  end
125
162
  end
126
163
 
127
- def evaluate_traits(traits, attrs, opts)
128
- return {} if traits.empty?
164
+ def evaluate_traits(trait_list, attrs, opts)
165
+ return {} if trait_list.empty?
166
+
167
+ traits = trait_list.map { |v| v.is_a?(Hash) ? v : {v => true} }.reduce(:merge)
129
168
 
130
- traits_attrs = self.traits.values_at(*traits).flat_map(&:elements)
169
+ traits_attrs = self.traits.select { |key, _value| traits[key] }.values.flat_map(&:elements)
131
170
  registry = AttributeRegistry.new(traits_attrs)
132
- self.class.new(registry, relation).defaults([], attrs, opts)
171
+
172
+ self.class.new(registry, relation).defaults([], attrs, **opts)
133
173
  end
134
174
 
135
175
  # @api private
136
- def evaluate_associations(attrs, opts)
137
- attributes.associations.each_with_object({}) do |assoc, h|
138
- if assoc.dependency?(relation)
139
- h[assoc.name] = ->(parent, call_opts) do
176
+ def evaluate_associations(traits, attrs, opts)
177
+ assocs(traits).associations.each_with_object({}) do |assoc, memo|
178
+ if attrs.key?(assoc.name) && attrs[assoc.name].nil?
179
+ memo
180
+ elsif assoc.dependency?(relation)
181
+ memo[assoc.name] = ->(parent, call_opts) do
140
182
  assoc.call(parent, **opts, **call_opts)
141
183
  end
142
184
  else
143
185
  result = assoc.(attrs, **opts)
144
- h.update(result) if result
186
+ memo.update(result) if result
145
187
  end
146
188
  end
147
189
  end
148
190
 
149
191
  # @api private
150
192
  def struct_attrs
151
- struct_attrs = relation.schema.
152
- reject(&:primary_key?).
153
- map { |attr| [attr.name, nil] }.to_h
193
+ struct_attrs = relation.schema
194
+ .reject(&:primary_key?)
195
+ .map { |attr| [attr.name, nil] }.to_h
154
196
 
155
197
  if primary_key
156
198
  struct_attrs.merge(primary_key => next_id)
@@ -163,6 +205,13 @@ module ROM
163
205
  def next_id
164
206
  sequence.()
165
207
  end
208
+
209
+ def select_mergeable_attrs(traits, attrs)
210
+ unmergeable = assocs(traits).select(&:through?).map do |a|
211
+ Dry::Core::Inflector.singularize(a.assoc.target.name.to_sym).to_sym
212
+ end
213
+ attrs.dup.delete_if { |key, _| unmergeable.include?(key) }
214
+ end
166
215
  end
167
216
  end
168
217
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ROM
4
4
  module Factory
5
- VERSION = '0.10.2'
5
+ VERSION = "0.12.0"
6
6
  end
7
7
  end
data/lib/rom/factory.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/class_builder'
4
- require 'rom/factory/factories'
3
+ require "dry/core"
4
+ require "dry/configurable"
5
+ require "dry/struct"
6
+
7
+ require "rom/factory/factories"
5
8
 
6
9
  module ROM
7
10
  # Main ROM::Factory API
8
11
  #
9
12
  # @api public
10
13
  module Factory
11
- DEFAULT_NAME = 'Factories'.freeze
14
+ DEFAULT_NAME = "Factories"
12
15
 
13
16
  # Configure a new factory
14
17
  #
@@ -23,8 +26,8 @@ module ROM
23
26
  #
24
27
  # @api public
25
28
  def self.configure(name = DEFAULT_NAME, &block)
26
- klass = Dry::Core::ClassBuilder.new(name: name, parent: Factories).call do |klass|
27
- klass.configure(&block)
29
+ klass = Dry::Core::ClassBuilder.new(name: name, parent: Factories).call do |c|
30
+ c.configure(&block)
28
31
  end
29
32
 
30
33
  klass.new(klass.config.rom)
data/lib/rom-factory.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom/factory'
3
+ require "rom/factory"
data/project.yml CHANGED
@@ -1,2 +1,3 @@
1
1
  name: rom-factory
2
+ custom_ci: true
2
3
  codacy_id: 5fd26fae687549218458879b1a607e18