console_think_twice

CI

Confirm before you destroy. console_think_twice intercepts destructive Active Record calls made from a console and asks first, so a stray User.destroy_all in production costs you a keystroke instead of a database.

>> User.first.destroy!

This will permanently destroy User #1 in production.
Cascades to: sessions, comments
Confirm? (y/N) n
ConsoleThinkTwice::Aborted: Aborted. Nothing was destroyed.

>> User.destroy_all

This will permanently destroy 200 Users in production.
Cascades to: sessions, comments
Confirm? (y/N) y
=> [#<User id: 1, ...>, ...]

>> User.first.destroy!(force: true)
=> #<User id: 2, ...>

Installation

gem "console_think_twice"

Not on RubyGems yet — until it is, point at the repository:

gem "console_think_twice", github: "agomezcampero/console-think-twice"

In a Rails app that is all — a railtie installs the guard from the console hook, which runs for rails console only. Web requests, background jobs and rails runner are untouched, and the gem never patches anything until a console actually boots.

Outside Rails, call ConsoleThinkTwice.install! once your models are loaded.

What is guarded

Call Prompt
record.destroy, record.destroy! This will permanently destroy User #1.
record.delete This will permanently delete, skipping callbacks, User #1.
Model.destroy_all, relation.destroy_all, company.users.destroy_all This will permanently destroy 200 Users.
Model.delete_all, relation.delete_all This will permanently delete, skipping callbacks, 200 Users.

Model.destroy(id), destroy_by and delete_by route through those methods, so they are covered too.

Anything other than y/yes raises ConsoleThinkTwice::Aborted, which also stops a loop like users.each(&:destroy!) on the first refusal rather than asking 200 times.

Every guarded method takes force: true to skip the prompt.

One prompt per call

A dependent: :destroy cascade destroys child records, and destroy_all destroys its records one at a time — both would otherwise prompt again for every record. Nested prompts are suppressed for the duration of a confirmed call, so you are asked exactly once.

Nobody to ask

When the input stream is not a terminal — piped input, a script fed to rails console — there is no one to answer, so the guard refuses the call instead of assuming yes. Pass force: true for destructive calls that are meant to run unattended.

Configuration

ConsoleThinkTwice.configure do |config|
  config.enabled = Rails.env.production?      # default: true, unless CONSOLE_THINK_TWICE is 0/false/no/off
  config.label = "production (europe)"        # shown in the prompt; defaults to Rails.env
  config.affirmative_answers = %w[y yes si]   # default: %w[y yes]
  config.interactive = true                   # default: auto-detected from config.input.tty?
  config.input = $stdin
  config.output = $stdout
end

Set CONSOLE_THINK_TWICE=0 in the environment to turn the guard off without touching code. ConsoleThinkTwice.disable! turns it off for the rest of the current session.

Development

bundle install
bundle exec rake        # specs and linter
bundle exec rspec
bundle exec standardrb

License

MIT. See LICENSE.