Home > OS >  Rails has many through has one polymorphic
Rails has many through has one polymorphic

Time:12-15

I am trying to set up a polymorphic entity has_one location, but a location can belong to many polymorphic entities. The confusion I'm running into is where I need to specify polymorphic associations, and guess check isn't working, lol.

To clarify, any "locatable" should have one location, but a location should be able to have many locatables associated with it.

Current setup:

class User
  has_one :locatable_location, as: :locatable
  has_one :location, through: :locatable_locations
end

class Entity
  has_one :locatable_location, as: :locatable
  has_one :location, through: :locatable_locations
end

class LocatableLocation
  belongs_to :locatable, polymorphic: true
  belongs_to :location
end

class Location
  has_many :locatable_locations
  has_many :locatables, polymorphic: true, through: :locatable_locations
end

Any help greatly appreciated :)

CodePudding user response:

Gonna answer my own question since I ended up figuring it out.

The error was in the Location model. Change to

class Location
  has_many :locatable_locations
  has_many :users, through: :locatable_locations, source: :locatable, source_type: :User
  has_many :entities, through: :locatable_locations, source: :locatable, source_type: :Entity
end

and if you wanted to have a relation to "locatables", i.e. get all records associated you'll have to define a method in location like

def locatables
  users   entities
end

If your use case has many, many locatables like mine, so you can also do something like

class Location
  RELATIONS = %i[User Entity]
  has_many :locatable_locations
  RELATIONS.each do |associated_klass|
    has_many associated_klass.to_s.snake_case.to_sym, through: :locatable_locations, source: :locatable, source_type: associated_klass
  end

  def locatables
    RELATIONS.reduce([]) { |all, curr| all.append(send(curr.to_s.snake_case)) }
  end

  

CodePudding user response:

I would consider if you're not just overcomplicating this needlessly. The only actual advantage to using a polymorphic join table is that you make additional models "locatable" without adding a column to the table. You can't actually use it as a homogenious collection without risking N 1 queries as polymorphic assocations don't support eager loading.

The way this is implemented there is also no guarentee that a user can only have one "current location" - duplicates can occur due to simple race conditions. A has_one assocation is actually just putting a LIMIT 1 on the query. belongs_to on the other hand can only ever have a single value.

The simplest solution would be to simply add a foreign key column to the users table and a separate assocation:

module Locatable
  extend ActiveSupport::Concern
  included do
    belongs_to :location
  end
end 

class User < ApplicationRecord
  include Locatable
end

class Entity < ApplicationRecord
  include Locatable
end

class Location < ApplicationRecord
  has_many :users
  has_many :entities
end

Magic always comes at a price - in this case its completely crippling your database design.

  • Related