3

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
Pavan
  • 33,316
  • 7
  • 50
  • 76
Kobius
  • 674
  • 7
  • 28
  • 1
    The error says there is no column `sub_place_id` in places tabel. Do you have it or not? – Pavan Oct 22 '18 at 06:40
  • @Pavan the column "sub_place_id" is in the Posts table, as a Post belongs to a SubPlace, or at least it should. For some reason Rails seems to be looking in the Place table for this column, I'm not quite sure why. – Kobius Oct 22 '18 at 15:37

2 Answers2

6

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 Post class also has the association you just write it as:

class Place < ApplicationRecord
  has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
max
  • 96,212
  • 14
  • 104
  • 165
  • Make sure you rename the column `sub_place_id` to `place_id`. – max Oct 22 '18 at 17:52
  • Thanks for explaining this, if I setup the associations this way, would it restrict a Post so that it may only belong_to a SubPlace or AnotherKindOfPlace, even though it is written belongs_to the base class? Ideally, Posts can only belong to SubPlaces, and never a base Place. – Kobius Oct 22 '18 at 18:12
  • No - not really. This kind of assumes that you're following the liskov substitably principle. The `belongs_to :place` assocation will allow you to assign any subtype of `Place`. – max Oct 22 '18 at 18:21
  • If I were to add the association to the base "Place" class, then surely I could simply put "has_many :posts", without specifying the foreign_key and inverse_of? Is that correct, just trying to ensure I've got it right. If I wanted then to ensure that a Post could ONLY belong to a "SubPlace", then I could specify this using class validations - is this a better approach, following LSP conventions? – Kobius Oct 23 '18 at 20:57
  • Yeah I don't think you need to specify the foreign key when it can be deduced from the name of the association, so `has_many :posts` will use `post_id` no matter what the class is named. `inverse_of` is not specified by default so it actually does make a difference. – max Oct 24 '18 at 15:08
  • Also yes, its quite simple to create a validation that ensures that a post can only belong to a subplace. – max Oct 24 '18 at 15:10
2

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

Your association in SubPlace is not valid. You should re-write that to just

class SubPlace < Place
  has_many :posts
end
Pavan
  • 33,316
  • 7
  • 50
  • 76
  • That did the trick - I also had a typo in a line in my SubPlace view that was causing an issue: place_path(post) - should be post_path(post) - combined, this has fixed the issue. Thanks Pavan! – Kobius Oct 22 '18 at 17:09