Search code examples
rubyrubygemsruby-on-rails-5metaprogramming

DRY Multiple same associations in rails Model


In my model, I have multiple has_one associations like

has_one  :t1_for_self_order, -> { t1_for_self_order }, as: :source, dependent: :destroy, inverse_of: :source,
          class_name: 'Spree::PriceMapping'
has_one  :shipping_charges_for_t1, -> { shipping_charges_for_t1 }, as: :source, dependent: :destroy, inverse_of: :source,
          class_name: 'Spree::PriceMapping'
has_one  :t2_for_self_order, -> { t2_for_self_order }, as: :source, dependent: :destroy, inverse_of: :source,
          class_name: 'Spree::PriceMapping'
has_one  :shipping_charges_for_t2, -> { shipping_charges_for_t2 }, as: :source, dependent: :destroy, inverse_of: :source,
          class_name: 'Spree::PriceMapping'
has_one  :minimum_value_for_gifting, -> { minimum_value_for_gifting }, as: :source, dependent: :destroy, inverse_of: :source,
          class_name: 'Spree::PriceMapping'
has_one  :international_fulfillment_fees, -> { international_fulfillment_fees }, as: :source, dependent: :destroy, inverse_of: :source,
          class_name: 'Spree::PriceMapping'

you notice that only the association name with the same scope name is different in all the associations but the rest of the content is same.

So I want to write a function that will remove all these repetitions. I think it can be possible with Meta programming but I'm not sure how to do this

Moreover, I also have an array of nested attributes which we can use.

    THRESHOLD_INTENT = ["t1_for_self_order", "shipping_charges_for_t1", "t2_for_self_order", "shipping_charges_for_t2",
                    "minimum_value_for_gifting", "international_fulfillment_fees", "menu_customization_fee",
                    "total_menu_customization_fee_cap", "video_message_fee", "total_video_message_fee_cap",
                    "swag_price", "box_types_with_price", "customization_box_types_with_price", "custom_note_fee",
                    "non_us_fees"]

I like to do it in this way

THRESHOLD_INTENT.each do |t_intent|
  has_one  t_intent.to_sym, as: :source, dependent: :destroy, inverse_of: :source, class_name: 'Spree::PriceMapping'
end

But how can i pas the scope in this like

-> { t1_for_self_order }


Solution

  • You can iterate over a list of association names and call has_one with all the parameters on each item. send(association) is needed to call an association scope as a method.

    ASSOCIATIONS = [:t1_for_self_order, :shipping_charges_for_t1, :t2_for_self_order, :shipping_charges_for_t2, :minimum_value_for_gifting, :international_fulfillment_fees]
    
    ASSOCIATIONS.each do |association|
      has_one association, -> { send(association) }, as: :source, dependent: :destroy, inverse_of: :source, class_name: 'Spree::PriceMapping'
    end