Modularisierte has_many Proxy Erweiterung

Es gibt unterschiedliche Gründe, eine has_many oder has_and_belongs_to_many Assoziation zu erweitern.
Die Proxy Logik kann auch wiederverwendet und über verschiedene Assoziationen geteilt werden. Dafür notwendig ist lediglich die Verlagerung der Proxy Erweiterung in ein Module.

Assoziation mit Proxy Erweiterung

Das Beispiel ist ein sehr vereinfachter Bestellprozess, bestehend aus einem Model für die Bestellung und den bestellbaren Produkten, wobei die m:n Beziehung über ein has_many through abgebildet ist:

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

Die beiden Models Product und ProductOrder haben jeweils eine Serializer Methode (#to_s).

Assoziation mit Proxy Erweiterung

Eine sehr einfache Proxy Erweiterung der .products Assoziation könnte ebenfalls eine to_s Methode sein:

class Order < ApplicationRecord
  has_many :product_orders
  has_many :products, through: :product_orders do
    def to_s
      map(&:to_s).join(', ')
    end
  end
end

Die Liste der Produkte einer Bestellung:

order = Order.first
order.products
# => #<ActiveRecord::Associations::CollectionProxy [#<Product id: 1, name: "TV">, #<Product id: 4, name: "Chair">]>

und mithilfe der Proxy Extension:

order.products.to_s # => "TV, Chair"

Modularisierung einer has_many Proxy Erweiterung

Im Falle, dass die has_many Proxy Erweiterung auch für andere Assoziationen angewendet werden soll, macht es Sinn die Logik in ein Module auszulagern und die entsprechenden Assoziationen mit dem Module zu erweitern.
Das Module:

# app/models/concerns/association_proxy/humanizable.rb
module AssociationProxy
  module Humanizable
    def to_s
      map(&:to_s).join(', ')
    end
  end
end

und zwei Assoziationen, die mit dem selben Module erweitert wurden:

class Order < ApplicationRecord
  has_many :product_orders,
           extend: AssociationProxy::Humanizable
  has_many :products, through: :product_orders,
           extend: AssociationProxy::Humanizable
end

Die Verwendung der modularisierten Proxy Erweiterung:

order.products.to_s # => "TV, Chair"
order.product_orders.to_s # => "00001, 00002"

Proxy Erweiterungen kombinieren

Eine has_many Assoziation kann auch mit einer Module Collection erweitert werden:

class Order < ApplicationRecord
  has_many :products, through: :product_orders,
           extend: [AssociationProxy::Humanizable, AssociationProxy::OtherModule]
end