Module: DenormalizeFields
- Defined in:
- lib/denormalize_fields.rb,
lib/denormalize_fields/version.rb,
lib/denormalize_fields/association_extension.rb
Defined Under Namespace
Modules: AssociationExtension
Classes: ArgumentError, RelatedRecordInvalid
Constant Summary
collapse
- CONDITIONAL_CLASSES =
[NilClass, TrueClass, FalseClass, Symbol, Proc]
- VERSION =
'1.3.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.
102
103
104
105
106
107
108
109
110
|
# File 'lib/denormalize_fields.rb', line 102
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 RelatedRecordInvalid.new(record: to, owner: owner, mapping: mapping)
end
|
.call(record:, relation_name:, mapping:, **options) ⇒ Object
58
59
60
61
62
63
64
65
66
67
68
69
70
|
# File 'lib/denormalize_fields.rb', line 58
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
49
50
51
52
53
54
55
56
|
# File 'lib/denormalize_fields.rb', line 49
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
87
88
89
90
91
92
93
94
95
96
97
98
|
# File 'lib/denormalize_fields.rb', line 87
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
# File 'lib/denormalize_fields.rb', line 72
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
113
114
115
116
117
118
119
120
121
|
# File 'lib/denormalize_fields.rb', line 113
def copy_errors(errors, to_record:, mapping:)
errors.details.each do |key, array|
Array(mapping.rassoc(key.to_sym)).compact.each do |field|
array.each do |details|
to_record.errors.add(field, details[:error], **details.except(:error))
end
end
end
end
|
.denormalize(fields:, from:, onto:, prefix: nil, **options) ⇒ Object
19
20
21
22
23
24
25
26
27
28
29
30
31
|
# File 'lib/denormalize_fields.rb', line 19
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
.validate_options(**options) ⇒ Object
33
34
35
36
37
38
|
# File 'lib/denormalize_fields.rb', line 33
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
|