STI polymorphic has_many uses wrong type value

Viewed 776

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.

1 Answers

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.

Related