@@ -6,15 +6,21 @@ module Denormalizable
66 DENORMALIZE_ASSOCIATION_ASYNC_BATCH_SIZE = 1_000
77
88 class_methods do
9- def denormalizes ( *attribute_names , with : nil , from : nil , to : nil , prefix : nil )
9+ def denormalizes ( *attribute_names , with : nil , from : nil , to : nil , prefix : nil , as : nil )
1010 raise ArgumentError , 'must provide :from, :to, or :with (but not multiple)' unless
1111 from . present? ^ to . present? ^ with . present?
1212
13+ raise ArgumentError , 'must provide either :prefix or :as (but not both)' if
14+ prefix . present? && as . present?
15+
16+ raise ArgumentError , 'must provide a single attribute when using :as' if
17+ as . present? && attribute_names . many?
18+
1319 case
1420 when from . present?
15- attribute_names . each { instrument_denormalized_attribute_from ( it , from :, prefix :) }
21+ attribute_names . each { instrument_denormalized_attribute_from ( it , from :, prefix :, as : ) }
1622 when to . present?
17- attribute_names . each { instrument_denormalized_attribute_to ( it , to :, prefix :) }
23+ attribute_names . each { instrument_denormalized_attribute_to ( it , to :, prefix :, as : ) }
1824 when with . present?
1925 raise NotImplementedError , 'denormalizes :with is not supported yet'
2026 else
@@ -24,14 +30,15 @@ def denormalizes(*attribute_names, with: nil, from: nil, to: nil, prefix: nil)
2430
2531 private
2632
27- def instrument_denormalized_attribute_from ( attribute_name , from :, prefix :)
33+ def instrument_denormalized_attribute_from ( attribute_name , from :, prefix :, as : nil )
2834 case from
2935 in Symbol => association_name if reflection = reflect_on_association ( association_name )
30- prefixed_attribute_name = case prefix
31- when true
36+ prefixed_attribute_name = case
37+ when as . present?
38+ as . to_s
39+ when prefix == true
3240 "#{ association_name } _#{ attribute_name } "
33- when Symbol ,
34- String
41+ when ( prefix in Symbol | String )
3542 "#{ prefix } _#{ attribute_name } "
3643 else
3744 attribute_name . to_s
@@ -41,28 +48,32 @@ def instrument_denormalized_attribute_from(attribute_name, from:, prefix:)
4148 raise ArgumentError , "must be a singular association: #{ association_name . inspect } "
4249 end
4350
51+ # NB(ezekg) supports composite foreign keys, e.g. %i[bearer_type bearer_id]
52+ association_changed = -> { Array ( reflection . foreign_key ) . any? { send ( :"#{ it } _changed?" ) } || send ( :"#{ reflection . name } _changed?" ) }
53+
4454 # FIXME(ezekg) after_initialize ignores prepend: false
45- set_callback :initialize , :after , -> { write_denormalized_attribute_from_schrodingers_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : -> { send ( :" #{ reflection . foreign_key } _changed?" ) || send ( :" #{ reflection . name } _changed?" ) } , unless : :persisted? , prepend : false
46- before_validation -> { write_denormalized_attribute_from_schrodingers_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : -> { send ( :" #{ reflection . foreign_key } _changed?" ) || send ( :" #{ reflection . name } _changed?" ) } , on : :create
47- before_update -> { write_denormalized_attribute_from_persisted_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : -> { send ( :" #{ reflection . foreign_key } _changed?" ) || send ( :" #{ reflection . name } _changed?" ) }
55+ set_callback :initialize , :after , -> { write_denormalized_attribute_from_schrodingers_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : association_changed , unless : :persisted? , prepend : false
56+ before_validation -> { write_denormalized_attribute_from_schrodingers_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : association_changed , on : :create
57+ before_update -> { write_denormalized_attribute_from_persisted_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : association_changed
4858
4959 # make sure validation fails if our denormalized column is modified directly
5060 validate -> { validate_denormalized_attribute_from_persisted_record ( association_name , attribute_name , prefixed_attribute_name ) } , if : :"#{ prefixed_attribute_name } _changed?" , on : :update
5161
52- denormalized_attributes << attribute_name
62+ denormalized_attributes << prefixed_attribute_name . to_sym
5363 else
5464 raise ArgumentError , "invalid :from association: #{ from . inspect } "
5565 end
5666 end
5767
58- def instrument_denormalized_attribute_to ( attribute_name , to :, prefix :)
68+ def instrument_denormalized_attribute_to ( attribute_name , to :, prefix :, as : nil )
5969 case to
6070 in Symbol => association_name if reflection = reflect_on_association ( association_name )
61- prefixed_attribute_name = case prefix
62- when true
71+ prefixed_attribute_name = case
72+ when as . present?
73+ as . to_s
74+ when prefix == true
6375 "#{ association_name } _#{ attribute_name } "
64- when Symbol ,
65- String
76+ when ( prefix in Symbol | String )
6677 "#{ prefix } _#{ attribute_name } "
6778 else
6879 attribute_name . to_s
0 commit comments