Search code examples
ruby-on-railspolymorphic-associations

STI polymorphic has_many uses wrong type value


I have the following STI models, they have a polymorphic association, whose query is being wrongly constructed

class Product < ApplicationRecord
  has_many :images, as: :imageable
end

class OneProduct < Product
end

class Image < ApplicationRecord
  belongs_to :imageable
end

In a rails console, when I do

> OneProduct.last.icon_images

The query being fired is

SELECT  * FROM images WHERE imageable_id = id AND imageable_type = 'Product'

I was expecting:

SELECT * from images WHERE imageable_id = id AND imageable_type = 'OneProduct'

Am I expecting something wrong?

Side Info: database is postgres.


Solution

  • From the Rails docs:

    Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order for the associations to work as expected, ensure that you store the base model for the STI models in the type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts and member posts that use the posts table for STI. In this case, there must be a type column in the posts table.

    Note: The attachable_type= method is being called when assigning an attachable. The class_name of the attachable is passed as a String.

    class Asset < ActiveRecord::Base   
      belongs_to :attachable, polymorphic: true
    
      def attachable_type=(class_name)
         super(class_name.constantize.base_class.to_s)   
      end 
    end
    
    class Post < ActiveRecord::Base   
      # because we store "Post" in attachable_type now dependent: :destroy will work   
      has_many :assets,as: :attachable, dependent: :destroy 
    end
    
    class GuestPost < Post end
    
    class MemberPost < Post end
    

    Source: https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations

    So it says that instead of storing the imageable_type = OneProduct you need to store it as Product only and you can add a type column in the Product table. But that depends completely on what you need from the OneProduct model, if a default_scope on that model will make it work for you without adding the type column then don't add it on the Product table and if that doesn't work then you can add the column and then add the default_scope to fetch products.type = OneProduct when querying on OneProduct model.