Modularized has_many proxy extension

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