Module: DenormalizeFields
- Defined in:
- lib/denormalize_fields.rb,
lib/denormalize_fields/version.rb,
lib/denormalize_fields/association_extension.rb
Defined Under Namespace
Modules: AssociationExtension
Constant Summary
collapse
- CONDITIONAL_CLASSES =
[NilClass, TrueClass, FalseClass, Symbol, Proc]
- VERSION =
'1.2.0'
Class Method Summary
collapse
-
.apply(changeset, to:, owner:, mapping:) ⇒ Object
Note: missing related records are ignored, and new related records are not persisted.
-
.call(record:, relation_name:, mapping:, **options) ⇒ Object
-
.cast_to_mapping(fields, prefix: nil) ⇒ Object
-
.changeset(record:, mapping:) ⇒ Object
-
.conditional_passes?(conditional, record, inverted) ⇒ Boolean
-
.copy_errors(errors, to_record:, mapping:) ⇒ Object
TODO: use Errors#import when it becomes available in rails 6.1 or 6.2.
-
.denormalize(fields:, from:, onto:, prefix: nil, **options) ⇒ Object
-
.validate_conditional(arg) ⇒ Object
-
.validate_options(**options) ⇒ Object
Class Method Details
.apply(changeset, to:, owner:, mapping:) ⇒ Object
Note: missing related records are ignored, and new related records are not persisted. Extra options to raise/create/persist in this case might be nice.
90
91
92
93
94
95
96
97
98
|
# File 'lib/denormalize_fields.rb', line 90
def apply(changeset, to:, owner:, mapping:)
return if to.nil?
to.assign_attributes(changeset)
return if to.new_record? ? to.valid? : to.save
DenormalizeFields.copy_errors(to.errors, to_record: owner, mapping: mapping)
raise(ActiveRecord::RecordInvalid, to)
end
|
.call(record:, relation_name:, mapping:, **options) ⇒ Object
46
47
48
49
50
51
52
53
54
55
56
57
58
|
# File 'lib/denormalize_fields.rb', line 46
def call(record:, relation_name:, mapping:, **options)
return unless conditional_passes?(options[:if], record, false)
return unless conditional_passes?(options[:unless], record, true)
changeset = DenormalizeFields.changeset(record: record, mapping: mapping)
return if changeset.empty?
Array(record.send(relation_name)).each do |related_record|
DenormalizeFields.apply(
changeset, to: related_record, owner: record, mapping: mapping
)
end
end
|
.cast_to_mapping(fields, prefix: nil) ⇒ Object
37
38
39
40
41
42
43
44
|
# File 'lib/denormalize_fields.rb', line 37
def cast_to_mapping(fields, prefix: nil)
if fields.is_a?(Hash)
prefix && raise(ArgumentError, 'pass EITHER a fields Hash OR a prefix')
fields
else
Array(fields).map { |e| [e.to_sym, [prefix, e].join.to_sym] }.to_h
end
end
|
.changeset(record:, mapping:) ⇒ Object
75
76
77
78
79
80
81
82
83
84
85
86
|
# File 'lib/denormalize_fields.rb', line 75
def changeset(record:, mapping:)
mapping.each.with_object({}) do |(source, dest), hash|
if source.is_a?(Array)
if source.any? { |field| record.saved_change_to_attribute?(field) }
current_values = record.attributes.values_at(*source.map(&:to_s))
hash[dest] = current_values.join(' ')
end
elsif change = record.saved_change_to_attribute(source)
hash[dest] = change.last
end
end
end
|
.conditional_passes?(conditional, record, inverted) ⇒ Boolean
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
# File 'lib/denormalize_fields.rb', line 60
def conditional_passes?(conditional, record, inverted)
return true if conditional.nil?
result =
if conditional.respond_to?(:call)
record.instance_exec(&conditional)
elsif conditional.class == Symbol
record.send(conditional)
else conditional
end
inverted ? !result : !!result
end
|
.copy_errors(errors, to_record:, mapping:) ⇒ Object
TODO: use Errors#import when it becomes available in rails 6.1 or 6.2
101
102
103
104
105
106
107
108
|
# File 'lib/denormalize_fields.rb', line 101
def copy_errors(errors, to_record:, mapping:)
errors.details.each do |key, array|
field = mapping.rassoc(key.to_sym).first
array.each do |details|
to_record.errors.add(field, details[:error], **details.except(:error))
end
end
end
|
.denormalize(fields:, from:, onto:, prefix: nil, **options) ⇒ Object
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# File 'lib/denormalize_fields.rb', line 7
def denormalize(fields:, from:, onto:, prefix: nil, **options)
mapping = cast_to_mapping(fields, prefix: prefix)
validate_options(**options)
from.after_save do
DenormalizeFields.call(
record: self,
relation_name: onto,
mapping: mapping,
**options,
)
end
end
|
.validate_conditional(arg) ⇒ Object
30
31
32
33
34
35
|
# File 'lib/denormalize_fields.rb', line 30
def validate_conditional(arg)
CONDITIONAL_CLASSES.include?(arg.class) || raise(
ArgumentError,
"`if:` option must be a #{CONDITIONAL_CLASSES.join('/')}, got: #{arg.class}"
)
end
|
.validate_options(**options) ⇒ Object
21
22
23
24
25
26
|
# File 'lib/denormalize_fields.rb', line 21
def validate_options(**options)
validate_conditional(options[:if])
validate_conditional(options[:unless])
unsupported = (options.keys - %i[if unless]).empty? ||
raise(ArgumentError, "unsupported denormalize options: #{unsupported}")
end
|