-
-
Notifications
You must be signed in to change notification settings - Fork 41
/
tuple_evaluator.rb
143 lines (117 loc) · 3.69 KB
/
tuple_evaluator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
require 'rom/factory/sequences'
module ROM
module Factory
# @api private
class TupleEvaluator
# @api private
attr_reader :attributes
# @api private
attr_reader :relation
# @api private
attr_reader :traits
# @api private
attr_reader :model
# @api private
attr_reader :sequence
# @api private
def initialize(attributes, relation, traits = {})
@attributes = attributes
@relation = relation.with(auto_struct: true)
@traits = traits
@model = @relation.combine(*assoc_names).mapper.model
@sequence = Sequences[relation]
end
# @api private
def defaults(traits, attrs, opts = EMPTY_HASH)
evaluate(traits, attrs, opts).merge(attrs)
end
# @api private
def struct(*traits, attrs)
merged_attrs = struct_attrs.merge(defaults(traits, attrs, persist: false))
is_callable = proc { |_name, value| value.respond_to?(:call) }
callables = merged_attrs.select(&is_callable)
attributes = merged_attrs.reject(&is_callable)
materialized_callables = {}
callables.each do |_name, callable|
materialized_callables.merge!(callable.call(attributes, persist: false))
end
attributes.merge!(materialized_callables)
attributes = relation.output_schema.call(attributes)
model.new(attributes)
end
# @api private
def persist_associations(tuple, parent, traits = [])
assoc_names(traits).each do |name|
assoc = tuple[name]
assoc.call(parent, persist: true) if assoc.is_a?(Proc)
end
end
# @api private
def assoc_names(traits = [])
assocs(traits).map(&:name)
end
def assocs(traits_names = [])
traits
.values_at(*traits_names)
.map(&:associations).flat_map(&:elements)
.inject(AttributeRegistry.new(attributes.associations.elements), :<<)
end
# @api private
def has_associations?(traits = [])
!assoc_names(traits).empty?
end
# @api private
def primary_key
relation.primary_key
end
private
# @api private
def evaluate(traits, attrs, opts)
evaluate_values(attrs, opts)
.merge(evaluate_associations(attrs, opts))
.merge(evaluate_traits(traits, attrs, opts))
end
# @api private
def evaluate_values(attrs, opts)
attributes.values.tsort.each_with_object({}) do |attr, h|
deps = attr.dependency_names.map { |k| h[k] }.compact
result = attr.(attrs, *deps)
if result
h.update(result)
end
end
end
def evaluate_traits(traits, attrs, opts)
return {} if traits.empty?
traits_attrs = self.traits.values_at(*traits).flat_map(&:elements)
registry = AttributeRegistry.new(traits_attrs)
self.class.new(registry, relation).defaults([], attrs, opts)
end
# @api private
def evaluate_associations(attrs, opts)
attributes.associations.each_with_object({}) do |assoc, h|
if assoc.dependency?(relation)
h[assoc.name] = ->(parent, call_opts) do
assoc.call(parent, opts.merge(call_opts))
end
else
result = assoc.(attrs, opts)
h.update(result) if result
end
end
end
# @api private
def struct_attrs
relation.schema.
reject(&:primary_key?).
map { |attr| [attr.name, nil] }.
to_h.
merge(primary_key => next_id)
end
# @api private
def next_id
sequence.()
end
end
end
end