Module: ConsoleThinkTwice
- Defined in:
- lib/console_think_twice.rb,
lib/console_think_twice/railtie.rb,
lib/console_think_twice/version.rb,
lib/console_think_twice/record_guard.rb,
lib/console_think_twice/configuration.rb,
lib/console_think_twice/relation_guard.rb,
lib/console_think_twice/collection_guard.rb
Overview
Confirmation prompts for destructive Active Record calls typed into a console.
>> User.first.destroy!
This will permanently destroy User #1 in production.
Cascades to: sessions, company_memberships, comments, api_keys
Confirm? (y/N)
Every guarded call takes force: true to skip the prompt. In a Rails app the railtie
installs this on console boot only, so the web app, jobs and rails runner are untouched.
Defined Under Namespace
Modules: CollectionGuard, RecordGuard, RelationGuard Classes: Configuration, Railtie
Constant Summary collapse
- Aborted =
Raised when a destructive call is declined, or when there is nobody to ask.
Class.new(StandardError)
- CASCADING_STRATEGIES =
%i[destroy destroy_async delete_all].freeze
- SUPPRESSION_KEY =
:console_think_twice_suppressed- RAILS_SOURCE =
Source files belonging to Rails itself. Matched on the path rather than resolved from Gem.loaded_specs so that a vendored or git-checkout Rails is recognised too.
%r{/active_(?:record|support|model)/}- RAILS_INTERNAL_WORK =
The Active Record internals that destroy a record as one step of a larger operation: association bookkeeping, autosave, and nested attributes.
%r{/active_record/(?:associations/|autosave_association\.rb|nested_attributes\.rb)}- GEM_SOURCE =
This gem's own frames, skipped when looking for the caller of a guarded method.
__dir__- VERSION =
"0.2.0"
Class Method Summary collapse
- .active? ⇒ Boolean
- .configuration ⇒ Object
- .configure {|configuration| ... } ⇒ Object
- .disable! ⇒ Object
- .enable! ⇒ Object
-
.guard(target, action:, force:, count: nil) ⇒ Object
Confirms the call, then runs it with nested prompts suppressed so that a
dependent:cascade, or a relation destroying its records one by one, only ever asks once. - .install! ⇒ Object
Class Method Details
.active? ⇒ Boolean
74 75 76 |
# File 'lib/console_think_twice.rb', line 74 def active? !!@active && configuration.enabled? end |
.configuration ⇒ Object
40 41 42 |
# File 'lib/console_think_twice.rb', line 40 def configuration @configuration ||= Configuration.new end |
.configure {|configuration| ... } ⇒ Object
44 45 46 |
# File 'lib/console_think_twice.rb', line 44 def configure yield configuration end |
.disable! ⇒ Object
66 67 68 |
# File 'lib/console_think_twice.rb', line 66 def disable! @active = false end |
.enable! ⇒ Object
70 71 72 |
# File 'lib/console_think_twice.rb', line 70 def enable! @active = true end |
.guard(target, action:, force:, count: nil) ⇒ Object
Confirms the call, then runs it with nested prompts suppressed so that a dependent:
cascade, or a relation destroying its records one by one, only ever asks once.
count is passed when the caller already knows how many records are involved, which
saves a COUNT and is the only way to size a call like books.destroy(a, b) that names
its records rather than describing them.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/console_think_twice.rb', line 84 def guard(target, action:, force:, count: nil) return yield if !active? || suppressed? # Active Record's own work is confirmed by whatever the user typed to set it off, so it # runs suppressed rather than merely unguarded: `destroy!` re-enters as `destroy`, and # that second hop is dispatched from persistence.rb, where nothing marks it as internal. return suppressed { yield } if nested_in_active_record?(target) unless force count ||= affected_count(target) return yield if count.zero? confirm!(target, action, count) end suppressed { yield } end |
.install! ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/console_think_twice.rb', line 48 def install! return false unless configuration.enabled? unless @installed ActiveRecord::Base.prepend(RecordGuard) # CollectionProxy overrides destroy_all and delete_all rather than inheriting them, # so guarding Relation alone would miss every has_many collection. ActiveRecord::Relation.prepend(RelationGuard) ActiveRecord::Associations::CollectionProxy.prepend(RelationGuard) ActiveRecord::Associations::CollectionProxy.prepend(CollectionGuard) @installed = true end @active = true announce true end |