DEV Community

Seiei Miyagi
Seiei Miyagi

Posted on

Apply Reference table to the Rails STI type/Polymorphic type/enum/inclusion validated columns

Applying Reference table1 to the Rails application by following 2 steps.

  1. Create reference tables that contains STI types / Polymorphic types / possible enum values / inclusion values.
  2. Add foreign key constraint from the column to the reference table to prevent to set a wrong name to STI type / set a wrong name to Polymorphic type / set a invalid value to the enum / set a excluded values to inclusion validated column.

Sample repository

https://github.com/hanachin/iikanji_enum/

How to do

Set same type to a id column of a reference table and type column of a model table, or enum value column of the model table.
Then add foreign key constraint.

Sample migration file is looks like following:

# db/migrate/20200627151958_create_posts.rb class CreatePosts < ActiveRecord::Migration[6.0] def change create_table :posts do |t| t.string :type t.integer :state t.string :title t.text :body t.timestamps end create_table :post_states do |t| t.string :name t.timestamps end add_foreign_key :posts, :post_states, column: :state create_table :post_types, id: :string do |t| t.timestamps end add_foreign_key :posts, :post_types, column: :type end end 

After a rails db:migrate, the db/schema.rb will looks like following:

# db/schema.rb # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # This file is the source Rails uses to define your schema when running `rails # db:schema:load`. When creating a new database, `rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 2020_06_27_160353) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "post_states", force: :cascade do |t| t.string "name" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end create_table "post_types", id: :string, force: :cascade do |t| t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end create_table "posts", force: :cascade do |t| t.string "type" t.integer "state" t.string "title" t.text "body" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end add_foreign_key "posts", "post_states", column: "state" add_foreign_key "posts", "post_types", column: "type" end 

When there are STI models that uses ActiveRecord::Enum like following:

# app/models/post.rb class Post < ApplicationRecord enum state: { draft: 0, published: 1 } end 
# app/models/draft_post.rb class DraftPost < Post end 
# app/models/published_post.rb class PublishedPost < Post end 

Create reference table records that possible values of STI types / enum values like following:

# app/models/post/state.rb class Post < ApplicationRecord class State < ApplicationRecord class << self def seed Post.states.each do |state, id| find_or_create_by!(id: id, name: state) end end end end end 
# app/models/post/type.rb class Post < ApplicationRecord class Type < ApplicationRecord class << self def seed [PublishedPost, DraftPost].each do |klass| find_or_create_by!(id: klass.name) end end end end end 

Then run rails runner Post::Type.seed rails runner Post::State.seed.

Once reference tables and foreign key constraint set up, then you can see You can not save the model with type that does not exists on a reference table post_types.

Loading development environment (Rails 6.0.3.2) irb(main):001:0> post = Post.first  Post Load (0.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1 [["LIMIT", 1]] irb(main):002:0> post => #<DraftPost id: 3, type: "DraftPost", state: "draft", title: "test", body: "test", created_at: "2020-06-27 16:14:49", updated_at: "2020-06-27 16:14:49"> irb(main):003:0> post.type = "YavayPost" irb(main):004:0> post.save!  (0.3ms) BEGIN DraftPost Update (1.2ms) UPDATE "posts" SET "type" = $1, "updated_at" = $2 WHERE "posts"."id" = $3 [["type", "YavayPost"], ["updated_at", "2020-06-27 16:40:40.492136"], ["id", 3]] (0.2ms) ROLLBACK Traceback (most recent call last): 1: from (irb):4 ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR: insert or update on table "posts" violates foreign key constraint "fk_rails_43c128f7b9") DETAIL: Key (type)=(YavayPost) is not present in table "post_types". irb(main):005:0> 

The enum column is also the same, You can not save the enum value that does not exists on a reference table post_states.

Loading development environment (Rails 6.0.3.2) irb(main):001:0> class Post; enum state: { amasawa: 4423 }; end => {:state=>{:amasawa=>4423}} irb(main):002:0> post = Post.first  Post Load (0.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT $1 [["LIMIT", 1]] irb(main):003:0> post => #<DraftPost id: 3, type: "DraftPost", state: nil, title: "test", body: "test", created_at: "2020-06-27 16:14:49", updated_at: "2020-06-27 16:14:49"> irb(main):004:0> post.state = :amasawa irb(main):005:0> post.save!  (0.3ms) BEGIN DraftPost Update (1.3ms) UPDATE "posts" SET "state" = $1, "updated_at" = $2 WHERE "posts"."id" = $3 [["state", 4423], ["updated_at", "2020-06-27 16:43:03.201733"], ["id", 3]] (0.2ms) ROLLBACK Traceback (most recent call last): 1: from (irb):5 ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR: insert or update on table "posts" violates foreign key constraint "fk_rails_93ccb3c476") DETAIL: Key (state)=(4423) is not present in table "post_states". irb(main):006:0> 

Conclusion

Database constraint is awesome. Let's use reference table in the Rails apps.

You can apply the Reference table to STI types and possible enum values.
Also You can apply the Reference table to Polymorphic types and valid inclusion values.


  1. https://en.wikipedia.org/wiki/Reference_table 

Top comments (0)