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

VERSION =
'1.1.1'

Class Method Summary collapse

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.

Raises:

  • (ActiveRecord::RecordInvalid)


50
51
52
53
54
55
56
57
58
# File 'lib/denormalize_fields.rb', line 50

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:) ⇒ Object



24
25
26
27
28
29
30
31
32
33
# File 'lib/denormalize_fields.rb', line 24

def call(record:, relation_name:, mapping:)
  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



15
16
17
18
19
20
21
22
# File 'lib/denormalize_fields.rb', line 15

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



35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/denormalize_fields.rb', line 35

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

.copy_errors(errors, to_record:, mapping:) ⇒ Object

TODO: use Errors#import when it becomes available in rails 6.1 or 6.2



61
62
63
64
65
66
67
68
# File 'lib/denormalize_fields.rb', line 61

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) ⇒ Object



7
8
9
10
11
12
13
# File 'lib/denormalize_fields.rb', line 7

def denormalize(fields:, from:, onto:, prefix: nil)
  mapping = cast_to_mapping(fields, prefix: prefix)

  from.after_save do
    DenormalizeFields.call(record: self, relation_name: onto, mapping: mapping)
  end
end