Search code examples
ruby-on-railspolymorphic-associationssoftware-design

Rails: Polymorphic Relationship needed multiple times for one model. How to differentiate? Is there a better way?


I have a complicated relationship where I have multiple models require addresses. This usually means using a polymorphic relationship like so:

class Address < ApplicationRecord
  belongs_to :addressable, polymorphic: true
end

class User < ApplicationRecord
  # no issue, its "addressable" so just use this line of code
  has_one :address, as: :addressable
end

class Account < ApplicationRecord
  # Ok the issue here is that I need exactly TWO addresses though
  # One is for billing and one if a physical address where an event will
  # physically take place.
  has_one :billing_address, class_name: "Address", as: :addressable
  has_one :site_address, class_name: "Address", as: :addressable
end

The problem with this is ...

a = Account.first
a.billing_address #returns an address
a.site_address #returns the same address

How can I get the account to differentiate between two addresses? I know this isn't really a limitation of polymorphism but rather a software design problem that I need to solve. I'm wondering if maybe I need to treat Address as an abstract model and derive BillingAddress and SiteAddress from it and maybe have something like this:

class Address < ApplicationRecord
  # see active_record-acts_as gem for how this mixin works
  # https://github.com/hzamani/active_record-acts_as
  actable
  belongs_to :addressable, polymorphic: true
end

class User < ApplicationRecord
  # no issue, its "addressable" so just use this line of code
  has_one :address, as: :addressable
end

class BillingAddress < ApplicationRecord
  acts_as :address
end

class SiteAddress < ApplicationRecord
  acts_as :address
end

class Account < ApplicationRecord
  has_one :billing_address
  has_one :site_address
end

This might be good to do because I also have an Event model which requires a site address so I could do this as well:

class Event < ApplicationRecord
  has_one :site_address
end

Is this over engineering? At the risk of sounding too subjective, what are your thoughts on this? Is there a better way to do this?


Solution

  • What separates the address categories? You mention that you may have a billing address and a site address.

    If for example the categories are determined by an attribute called 'category', then all you have to do is set a condition on the association declarations on the addressable:

    class Account < ApplicationRecord
      has_one :billing_address, -> { where category: 'billing' }, class_name: "Address", as: :addressable
      has_one :site_address, -> { where category: 'site' }, class_name: "Address", as: :addressable
    end