Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created July 31, 2019 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/bef3f0767dd616c4f59aeca80110360b to your computer and use it in GitHub Desktop.
Save JoshCheek/bef3f0767dd616c4f59aeca80110360b to your computer and use it in GitHub Desktop.
ActiveRecord enum / PostgreSQL enum
# 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 :tasks, if_exists: true
execute "drop type if exists task_status;"
# create the type
execute "create type task_status as enum ('completed', 'in_progress', 'pending', 'open', 'rescheduled');"
# create the table
create_table :tasks do |t|
t.column :status, :task_status, null: false
end
end
# ===== Model =====
class Task < ActiveRecord::Base
enum status: {
completed: 'completed',
in_progress: 'in_progress',
pending: 'pending',
open: 'open',
rescheduled: 'rescheduled',
}
end
# ===== Create some tasks =====
Task.create! [
{status: 'completed'},
{status: 'completed'},
{status: 'pending'},
{status: 'open' },
]
# ===== Querying =====
Task.completed.to_sql
# => "SELECT \"tasks\".* FROM \"tasks\" WHERE \"tasks\".\"status\" = 'completed'"
Task.completed
# => [#<Task:0x00007ff3ae0e8090 id: 1, status: "completed">,
# #<Task:0x00007ff3ae0f3e18 id: 2, status: "completed">]
Task.pending
# => [#<Task:0x00007ff3ae10a960 id: 3, status: "pending">]
Task.open
# => [#<Task:0x00007ff3ada2bc70 id: 4, status: "open">]
# ===== Invalid Values =====
# All three of these seem like strange behaviour to me, feels like it should just add a validation
Task.new(status: 'something_else') rescue $! # => #<ArgumentError: 'something_else' is not a valid status>
Task.new.valid? # => true
Task.new.save rescue $! # => #<ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: null value in column "status" violates not-null constraint\nDETAIL: Failing row co...
# We can address the last two with our own validation
Task.validates :status, inclusion: { in: Task.statuses.keys }
Task.new(status: 'something_else') rescue $! # => #<ArgumentError: 'something_else' is not a valid status>
t = Task.new
t.valid? # => false
t.errors.full_messages # => ["Status is not included in the list"]
# >> D, [2019-07-31T10:39:04.582684 #89309] DEBUG -- : (4.4ms) DROP TABLE IF EXISTS "tasks"
# >> D, [2019-07-31T10:39:04.585073 #89309] DEBUG -- : (1.9ms) drop type if exists task_status;
# >> D, [2019-07-31T10:39:04.588222 #89309] DEBUG -- : (2.7ms) create type task_status as enum ('completed', 'in_progress', 'pending', 'open', 'rescheduled');
# >> D, [2019-07-31T10:39:04.594838 #89309] DEBUG -- : (5.9ms) CREATE TABLE "tasks" ("id" bigserial primary key, "status" task_status NOT NULL)
# >> D, [2019-07-31T10:39:04.634232 #89309] DEBUG -- : ActiveRecord::InternalMetadata Load (0.5ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key"...
# >> D, [2019-07-31T10:39:04.674247 #89309] DEBUG -- : (0.4ms) BEGIN
# >> D, [2019-07-31T10:39:04.675448 #89309] DEBUG -- : (0.2ms) COMMIT
# >> W, [2019-07-31T10:39:04.676516 #89309] WARN -- : Creating scope :open. Overwriting existing method Task.open.
# >> D, [2019-07-31T10:39:04.684591 #89309] DEBUG -- : (0.2ms) BEGIN
# >> D, [2019-07-31T10:39:04.686838 #89309] DEBUG -- : Task Create (0.7ms) INSERT INTO "tasks" ("status") VALUES ($1) RETURNING "id" [["status", "completed"]]
# >> D, [2019-07-31T10:39:04.688405 #89309] DEBUG -- : (1.2ms) COMMIT
# >> D, [2019-07-31T10:39:04.688896 #89309] DEBUG -- : (0.1ms) BEGIN
# >> D, [2019-07-31T10:39:04.689679 #89309] DEBUG -- : Task Create (0.3ms) INSERT INTO "tasks" ("status") VALUES ($1) RETURNING "id" [["status", "completed"]]
# >> D, [2019-07-31T10:39:04.691084 #89309] DEBUG -- : (1.2ms) COMMIT
# >> D, [2019-07-31T10:39:04.691541 #89309] DEBUG -- : (0.1ms) BEGIN
# >> D, [2019-07-31T10:39:04.692292 #89309] DEBUG -- : Task Create (0.3ms) INSERT INTO "tasks" ("status") VALUES ($1) RETURNING "id" [["status", "pending"]]
# >> D, [2019-07-31T10:39:04.693792 #89309] DEBUG -- : (1.2ms) COMMIT
# >> D, [2019-07-31T10:39:04.694248 #89309] DEBUG -- : (0.1ms) BEGIN
# >> D, [2019-07-31T10:39:04.694988 #89309] DEBUG -- : Task Create (0.3ms) INSERT INTO "tasks" ("status") VALUES ($1) RETURNING "id" [["status", "open"]]
# >> D, [2019-07-31T10:39:04.696737 #89309] DEBUG -- : (1.5ms) COMMIT
# >> D, [2019-07-31T10:39:04.698658 #89309] DEBUG -- : Task Load (0.3ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."status" = $1 [["status", "completed"]]
# >> D, [2019-07-31T10:39:04.699880 #89309] DEBUG -- : Task Load (0.3ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."status" = $1 [["status", "pending"]]
# >> D, [2019-07-31T10:39:04.700838 #89309] DEBUG -- : Task Load (0.3ms) SELECT "tasks".* FROM "tasks" WHERE "tasks"."status" = $1 [["status", "open"]]
# >> D, [2019-07-31T10:39:04.701720 #89309] DEBUG -- : (0.2ms) BEGIN
# >> D, [2019-07-31T10:39:04.702980 #89309] DEBUG -- : Task Create (0.6ms) INSERT INTO "tasks" DEFAULT VALUES RETURNING "id"
# >> D, [2019-07-31T10:39:04.703311 #89309] DEBUG -- : (0.2ms) ROLLBACK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment