ruby-on-railsassociationssingle-table-inheritance

Can model belong to STI child?


I have a base class Place and multiple sub-classes using STI conventions. I have a separate model Post, which belongs_to one of the sub-classes of Place:

class Place < ApplicationRecord
end

class SubPlace < Place
  has_many :posts, class_name: "SubPlace", foreign_key: "sub_place_id"
end

class Post < ApplicationRecord
  belongs_to :sub_place, class_name: "SubPlace", foreign_key: "sub_place_id"
end

It is possible to save a new Post record using Rails console, but I get the following error when trying to find Posts for a specific SubPlace:

ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column places.sub_place_id does not exist)

Is there a way to make this work, or must my associations relate to the base class only?

Added Schema:

create_table "posts", force: :cascade do |t|
    t.string "title"
    t.bigint "sub_place_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["sub_place_id"], name: "index_posts_on_sub_place_id"
end

create_table "places", force: :cascade do |t|
    t.string "name"
    t.string "type"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
end

Solution

  • A better way to handle assocations and STI is to just setup the assocations to the base class:

    class Place < ApplicationRecord
    end
    
    class SubPlace < Place
      has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
    end
    
    class AnotherKindOfPlace < Place
      has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
    end
    
    class Post < ApplicationRecord
      belongs_to :place
    end
    

    This keeps things nice and simple since Post does not know or care that there are different kinds of places. When you access @post.place ActiveRecord reads the places.type column and will instanciate the correct subtype.

    If the base Place class also has the association you just write it as:

    class Place < ApplicationRecord
      has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
    end