Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active October 11, 2018 16:35
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save JoshCheek/c28a374a607fb8fe850c07feff939002 to your computer and use it in GitHub Desktop.
ActiveRecord + PostgreSQL enums
# require 'pg'; PG.connect(dbname: 'postgres').exec('create database josh_testing') # <-- uncomment to create the db (only needs to run once)
# ===== Setup ActiveRecord =====
require 'active_record'
require 'logger'
ActiveRecord::Base.establish_connection adapter: 'postgresql', database: 'josh_testing'
ActiveRecord::Base.logger = Logger.new $stdout
ActiveSupport::LogSubscriber.colorize_logging = false
# ===== Migration =====
ActiveRecord::Schema.define do
self.verbose = false
# reset db
drop_table :workers, if_exists: true
execute "drop type if exists worker_type;"
# create the type
execute "create type worker_type as enum ('person', 'script', 'model');"
# create the table
create_table :workers do |t|
t.column :type, :worker_type, null: false
end
end
# ===== Model =====
class Worker < ActiveRecord::Base
# Rails convention is for the `type` column to be used for STI
# we aren't using STI and we have a type column,
# so we need to override the convention to use some nonexistant column
self.inheritance_column = :_sti_disabled
enum type: {
people: 'person',
scripts: 'script',
models: 'model',
}
end
# ===== Creating Data =====
Worker.create! [
{type: 'person'},
{type: 'person'},
{type: 'script'},
{type: 'model' },
]
# ===== Querying =====
Worker.people.to_sql
# => "SELECT \"workers\".* FROM \"workers\" WHERE \"workers\".\"type\" = 'person'"
Worker.people
# => [#<Worker:0x00007f9684bf2558 id: 1, type: "people">,
# #<Worker:0x00007f9684bf2378 id: 2, type: "people">]
Worker.scripts
# => [#<Worker:0x00007f96868361b0 id: 3, type: "scripts">]
Worker.models
# => [#<Worker:0x00007f9684bf6180 id: 4, type: "models">]
# ===== Invalid Values =====
# All three of these seem like strange behaviour to me, feels like it should just add a validation
Worker.new(type: 'something_else') rescue $! # => #<ArgumentError: 'something_else' is not a valid type>
Worker.new.valid? # => true
Worker.new.save rescue $! # => #<ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: null value in column "type" violates not-null constraint\nDETAIL: Failing row contains (5, null).\n: INSERT INTO "workers" DEFAULT VALUES RETURNING "id">
# We can address the last two with our own validation
Worker.validates :type, inclusion: { in: %w[person script model] }
Worker.new(type: 'something_else') rescue $! # => #<ArgumentError: 'something_else' is not a valid type>
w = Worker.new
w.valid? # => false
w.errors.full_messages # => ["Type is not included in the list"]
# >> D, [2018-10-11T11:35:22.832577 #55812] DEBUG -- : (3.7ms) DROP TABLE IF EXISTS "workers"
# >> D, [2018-10-11T11:35:22.834320 #55812] DEBUG -- : (1.5ms) drop type if exists worker_type;
# >> D, [2018-10-11T11:35:22.836611 #55812] DEBUG -- : (2.1ms) create type worker_type as enum ('person', 'script', 'model');
# >> D, [2018-10-11T11:35:22.842889 #55812] DEBUG -- : (5.4ms) CREATE TABLE "workers" ("id" bigserial primary key, "type" worker_type NOT NULL)
# >> D, [2018-10-11T11:35:22.885089 #55812] DEBUG -- : ActiveRecord::InternalMetadata Load (0.5ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]]
# >> D, [2018-10-11T11:35:22.891333 #55812] DEBUG -- : (0.2ms) BEGIN
# >> D, [2018-10-11T11:35:22.892533 #55812] DEBUG -- : (0.2ms) COMMIT
# >> D, [2018-10-11T11:35:22.900873 #55812] DEBUG -- : (0.2ms) BEGIN
# >> D, [2018-10-11T11:35:22.902100 #55812] DEBUG -- : Worker Create (0.6ms) INSERT INTO "workers" ("type") VALUES ($1) RETURNING "id" [["type", "person"]]
# >> D, [2018-10-11T11:35:22.903688 #55812] DEBUG -- : (1.3ms) COMMIT
# >> D, [2018-10-11T11:35:22.904200 #55812] DEBUG -- : (0.2ms) BEGIN
# >> D, [2018-10-11T11:35:22.904931 #55812] DEBUG -- : Worker Create (0.3ms) INSERT INTO "workers" ("type") VALUES ($1) RETURNING "id" [["type", "person"]]
# >> D, [2018-10-11T11:35:22.906561 #55812] DEBUG -- : (1.4ms) COMMIT
# >> D, [2018-10-11T11:35:22.907113 #55812] DEBUG -- : (0.2ms) BEGIN
# >> D, [2018-10-11T11:35:22.908105 #55812] DEBUG -- : Worker Create (0.4ms) INSERT INTO "workers" ("type") VALUES ($1) RETURNING "id" [["type", "script"]]
# >> D, [2018-10-11T11:35:22.909961 #55812] DEBUG -- : (1.4ms) COMMIT
# >> D, [2018-10-11T11:35:22.910556 #55812] DEBUG -- : (0.2ms) BEGIN
# >> D, [2018-10-11T11:35:22.911279 #55812] DEBUG -- : Worker Create (0.3ms) INSERT INTO "workers" ("type") VALUES ($1) RETURNING "id" [["type", "model"]]
# >> D, [2018-10-11T11:35:22.913072 #55812] DEBUG -- : (1.5ms) COMMIT
# >> D, [2018-10-11T11:35:22.915092 #55812] DEBUG -- : Worker Load (0.3ms) SELECT "workers".* FROM "workers" WHERE "workers"."type" = $1 [["type", "person"]]
# >> D, [2018-10-11T11:35:22.916361 #55812] DEBUG -- : Worker Load (0.3ms) SELECT "workers".* FROM "workers" WHERE "workers"."type" = $1 [["type", "script"]]
# >> D, [2018-10-11T11:35:22.917269 #55812] DEBUG -- : Worker Load (0.3ms) SELECT "workers".* FROM "workers" WHERE "workers"."type" = $1 [["type", "model"]]
# >> D, [2018-10-11T11:35:22.918083 #55812] DEBUG -- : (0.2ms) BEGIN
# >> D, [2018-10-11T11:35:22.919092 #55812] DEBUG -- : Worker Create (0.6ms) INSERT INTO "workers" DEFAULT VALUES RETURNING "id"
# >> D, [2018-10-11T11:35:22.919416 #55812] DEBUG -- : (0.1ms) ROLLBACK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment