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.
- checksums.yaml +4 -4
- data/.devtools/templates/changelog.erb +3 -0
- data/.devtools/templates/release.erb +36 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/{---bug-report.md → bug-report.md} +6 -10
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/SUPPORT.md +3 -0
- data/.github/workflows/ci.yml +17 -25
- data/.github/workflows/docsite.yml +6 -6
- data/.github/workflows/rubocop.yml +46 -0
- data/.github/workflows/sync_configs.yml +7 -13
- data/.repobot.yml +24 -0
- data/.rubocop.yml +135 -16
- data/CHANGELOG.md +64 -0
- data/CODEOWNERS +1 -0
- data/CONTRIBUTING.md +3 -3
- data/Gemfile +20 -19
- data/Gemfile.devtools +6 -5
- data/LICENSE +1 -1
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/benchmarks/basic.rb +10 -10
- data/changelog.yml +35 -0
- data/docsite/source/index.html.md +162 -1
- data/lib/rom/factory/attribute_registry.rb +6 -2
- data/lib/rom/factory/attributes/association.rb +127 -29
- data/lib/rom/factory/attributes/callable.rb +1 -1
- data/lib/rom/factory/attributes/value.rb +1 -1
- data/lib/rom/factory/attributes.rb +4 -6
- data/lib/rom/factory/builder/persistable.rb +21 -6
- data/lib/rom/factory/builder.rb +17 -19
- data/lib/rom/factory/constants.rb +1 -1
- data/lib/rom/factory/dsl.rb +43 -25
- data/lib/rom/factory/factories.rb +13 -15
- data/lib/rom/factory/registry.rb +2 -2
- data/lib/rom/factory/sequences.rb +4 -3
- data/lib/rom/factory/tuple_evaluator.rb +92 -43
- data/lib/rom/factory/version.rb +1 -1
- data/lib/rom/factory.rb +8 -5
- data/lib/rom-factory.rb +1 -1
- data/project.yml +1 -0
- data/rom-factory.gemspec +9 -10
- metadata +40 -26
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
- data/.github/ISSUE_TEMPLATE/----please-don-t-report-feature-requests-via-issues.md +0 -10
- data/.github/ISSUE_TEMPLATE/---a-detailed-bug-report.md +0 -30
- data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
- data/.github/workflows/custom/ci.yml +0 -26
- data/Appraisals +0 -9
- data/LICENSE.txt +0 -21
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
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(*
|
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
|
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
|
data/lib/rom/factory/builder.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
|
5
|
-
require
|
6
|
-
require
|
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
|
-
#
|
34
|
-
|
35
|
-
|
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(*
|
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(*
|
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(
|
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
|
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
|
data/lib/rom/factory/dsl.rb
CHANGED
@@ -1,26 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require 'dry/core/inflector'
|
3
|
+
require "faker"
|
5
4
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
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(
|
15
|
-
api =
|
15
|
+
def fake(*args, **options)
|
16
|
+
api = fetch_or_store(:faker, *args) do
|
17
|
+
*ns, method_name = args
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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,
|
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(
|
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(
|
96
|
+
# @overload fake(genre, type)
|
87
97
|
# @example
|
88
98
|
# f.email { fake(:internet, :email) }
|
89
99
|
#
|
90
|
-
# @param [Symbol]
|
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(
|
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]
|
107
|
+
# @param [Symbol] genre The faker API identifier ie. :internet, :product etc.
|
98
108
|
# @param [Symbol] type The value type to generate
|
99
|
-
# @param [
|
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/
|
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,
|
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
|
4
|
-
require
|
5
|
-
|
6
|
-
require
|
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:
|
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 [
|
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, *
|
173
|
-
registry[name].struct_namespace(struct_namespace).persistable.create(*
|
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
|
208
|
-
ns ? {
|
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 =
|
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,
|
data/lib/rom/factory/registry.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
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
|
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
|
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
|
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
|
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
|
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
|
35
|
-
|
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(*
|
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.
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
60
|
-
attributes.
|
96
|
+
def build_assoc?(name, attributes)
|
97
|
+
attributes.key?(name) && attributes[name] != [] && !attributes[name].nil?
|
98
|
+
end
|
61
99
|
|
62
|
-
|
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
|
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
|
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(
|
128
|
-
return {} if
|
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.
|
169
|
+
traits_attrs = self.traits.select { |key, _value| traits[key] }.values.flat_map(&:elements)
|
131
170
|
registry = AttributeRegistry.new(traits_attrs)
|
132
|
-
|
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
|
-
|
138
|
-
if
|
139
|
-
|
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
|
-
|
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
|
data/lib/rom/factory/version.rb
CHANGED
data/lib/rom/factory.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
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 =
|
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 |
|
27
|
-
|
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
data/project.yml
CHANGED