0

I want to use STI in Rails 4. I already had a model Boilerplate and now I want to inherit a BoilerplateOriginal and a BoilerplateCopy model from it. So I added a column type to my table:

class AddTypeToBoilerplates < ActiveRecord::Migration
  def up
    add_column :boilerplates, :type, :string
    Boilerplate.update_all type: 'BoilerplateOriginal'
    change_column :boilerplates, :type, :string, null: false
  end

  def down
    remove_column :boilerplates, :type
  end
end

Sadly, this column doesn't seem to be filled by Rails automatically:

[1] a4aa2 »  x = Boilerplate.new
=> #<Boilerplate:0x00000101609580> {
               :id => nil,
            :title => nil,
             :type => nil
}
[2] a4aa2 »  x.valid?
  Boilerplate Exists (0.4ms)  SELECT  1 AS one FROM "boilerplates" WHERE "boilerplates"."title" IS NULL LIMIT 1
=> false
[3] a4aa2 »  x.errors
=> {
  :title => [
    [0] "must not be empty"
  ]
}
[4] a4aa2 »  x.title = 'test'
=> "test"
[5] a4aa2 »  x.valid?
  Boilerplate Exists (0.1ms)  SELECT  1 AS one FROM "boilerplates" WHERE "boilerplates"."title" = 'test' LIMIT 1
=> true
[6] a4aa2 »  x.save
   (0.1ms)  begin transaction
  Boilerplate Exists (0.2ms)  SELECT  1 AS one FROM "boilerplates" WHERE "boilerplates"."title" = 'test' LIMIT 1
  SQL (1.4ms)  INSERT INTO "boilerplates" ("title") VALUES (?)  [["title", "test"]]
SQLite3::ConstraintException: NOT NULL constraint failed: boilerplates.type: INSERT INTO "boilerplates" ("title") VALUES (?)
   (0.1ms)  rollback transaction
from /Users/josh/.rvm/gems/ruby-2.1.0@a4aa2/gems/sqlite3-1.3.10/lib/sqlite3/statement.rb:108:in `step'

Did I miss something important? I thought Rails simply fills the column :type when it's available?

Joshua Muheim
  • 12,617
  • 9
  • 76
  • 152
  • 2
    Yes, you are missing. `type` will be set when you will use `BoilerplateOriginal.new` or `BoilerplateCopy.new`.. That means any subclass which is a child of `Boilerplate`. But of you do `Boilerplate.new` it wouldn't set the `type` column. – Arup Rakshit Aug 14 '15 at 08:49
  • 1
    @ArupRakshit post the answer, please! – Roman Kiselenko Aug 14 '15 at 08:53

1 Answers1

5

Well. You are doing it wrong of-course. The type column will be set, only when you will be creating the objects by using any of the child class of the parent Boilerplate. But if you use Boilerplate.new, you need to pass the type value manually. On the other hand, when you will do BoilerplateCopy.new or BoilerplateOriginal.new, the type will be set by ActiveRecord by default for you to the class name of the child class.

Read the official documentation of Single table inheritance.

Active Record allows inheritance by storing the name of the class in a column that by default is named type (can be changed by overwriting Base.inheritance_column). This means that an inheritance looking like this:

class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end

When you do Firm.create(name: "37signals"), this record will be saved in the companies table with type = “Firm”. You can then fetch this row again using Company.where(name: '37signals').first and it will return a Firm object.

Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
  • Thank you! But why's that? Does this mean, STI only supports one level of inheritance and the base class is never meant to be instantiated? – Joshua Muheim Aug 14 '15 at 10:06
  • 2
    @JoshuaMuheim Yes. it is one level AFAIK. You can use base class, but then what is the point of using STI. Anyway, if you base class, you need to supply the `type` if you want to set it. – Arup Rakshit Aug 14 '15 at 10:09