There are different reasons to extend a has_many or has_and_belongs_to_many association.
It is possible to reuse the proxy logic and share it with different associations. It can be achieved by moving the proxy extension into a module.
Association with proxy extension
As an example there is a very simplified product ordering process. It is implemented with a model for the order, the available products and their m:n relationship, being mapped by a has_many through association:
class Order < ApplicationRecord
has_many :product_orders
has_many :products, through: :product_orders
end
class ProductOrder < ApplicationRecord
belongs_to :product
belongs_to :order
def to_s
id.to_s.rjust(5, '0')
end
end
class Product < ApplicationRecord
delegate :to_s, to: :name
end
Both models Product and ProductOrder respond to a serializing method (#to_s).
Association with proxy extension
A very simple proxy extension of the .products association might be a to_s method:
class Order < ApplicationRecord
has_many :product_orders
has_many :products, through: :product_orders do
def to_s
map(&:to_s).join(', ')
end
end
end
A list of ordered products:
order = Order.first
order.products
# => #<ActiveRecord::Associations::CollectionProxy [#<Product id: 1, name: "TV">, #<Product id: 4, name: "Chair">]>
and using the proxy extension:
order.products.to_s # => "TV, Chair"
Modularization of a has_many proxy extension
In case the has_many proxy extension has to be applied to other associations as well, it makes sense to move the logic into a module and extend the corresponding associations with the module.
The module:
# app/models/concerns/association_proxy/humanizable.rb
module AssociationProxy
module Humanizable
def to_s
map(&:to_s).join(', ')
end
end
end
And two associations, which were extended with the same module:
class Order < ApplicationRecord
has_many :product_orders,
extend: AssociationProxy::Humanizable
has_many :products, through: :product_orders,
extend: AssociationProxy::Humanizable
end
Using the modularized proxy extension:
order.products.to_s # => "TV, Chair"
order.product_orders.to_s # => "00001, 00002"
Combining proxy extensions
A has_many association can also be extended with a Module collection:
class Order < ApplicationRecord
has_many :products, through: :product_orders,
extend: [AssociationProxy::Humanizable, AssociationProxy::OtherModule]
end