Search code examples
ruby-on-railsrubysti

Stop rails from including type in belongs_to with Single Table Inheritance


I have a problem where rails is adding unnecessary (I think) clauses to my belongs_to association - restricting on type even though my association is using a foreign key.

The STI setup looks like this:

class Foo < ActiveRecord::Base
  belongs_to :apple
end

class Apple < Fruit
  has_many :foos
end

So Foo has an apple_id column which links to the primary key column ID in fruits, and Apple is STI under Fruit. I'm happy with all of this.

Now:

> Foo.joins(:apple).to_sql

SELECT "foos".* FROM "foos" INNER JOIN "fruits" ON "fruits"."id" = 
"foos"."apple_id" AND "fruits"."type" IN ('Apple')

Why is rails adding AND "fruits"."type" IN ('Apple')? It's a belongs_to using a primary key of the fruits table, so the type part seems redundant. Can I stop rails from adding that part to the lookups and just get this:

SELECT "foos".* FROM "foos" INNER JOIN "fruits" ON "fruits"."id" = 
"foos"."apple_id"

I know I could do belongs_to :apple, :class_name => "Fruit" in Foo, but I want the objects to auto-become Apples when they're returned.

In case someone questions my motives... I want to do this because the type clause is messing up the query plan postgres chooses when I'm doing a query through fruits to other tables (yes, I have an index on type and even tried a multi-column one on [type,id]). That's a little complicated / irrelevant to describe fully here.


Solution

  • Basically AM is adding the ('Apple') part because in order to respect the inheritance chain.

    For example if you'd have

    class GreenApple < Apple
    end
    

    You'll get.

    Foo.joins(:apple).to_sql SELECT "foos".* FROM "foos" INNER JOIN "fruits" ON "fruits"."id" = "foos"."apple_id" AND "fruits"."type" IN ('Apple', 'GreenApple')

    If for whatever reason you end up having on your fruits tabel a fruit with a referenced pk but with a different type you could end up instantiating a different kind of object without knowing it and after that things can go wrong without getting an exception.