ruby-on-railsactivesupport-concern

Creating the reverse of an association within a concern


I have a concern that creates an association:

# concerns/product.rb
module Product
  extend ActiveSupport::Concern

  included do
    has_many :products, class_name: "Product", foreign_key: :owner_id
  end
end

And an example model:

# models/user.rb
class User < ApplicationRecord
  include Product
end

If I do: User.products.last it works fine. I also want to be able to do Product.last.owner but it won't work without the association being defined. I can't define it in the Product model since I have no clue to what models will include the concern that creates the association.

I have tried creating the association using inverse_of:

class Product < ApplicationRecord
  belongs_to :owner, inverse_of: :products
end

... but it won't work, apparently it needs a class name.

I've also tried reflecting on which classes the concern gets included within but it raises some strange errors when the concern gets included into several different models.

How do I create the inverse of an association from within the concern?


Solution

  • As pointed out by @engineersmnky you can't actually setup an association that points to a bunch of different classes without using polymorphism:

    # you can't have a class and a module with the same name in Ruby
    # reopening the class/module will cause a TypeError
    module ProductOwner
      extend ActiveSupport::Concern
      included do
        has_many :products, as: :owner
      end
    end
    
    class Product < ApplicationRecord
      belongs_to :owner, 
        inverse_of: :products,
        polymorphic: true
    end
    
    class User < ApplicationRecord
      include ProductOwner
    end
    

    The information about what you're actually joining has to be stored somewhere on the products table. If you don't use polymorphism you just have an integer (or UUID) which will always refer to a single table.