Class: RailsConsoleAi::SafetyGuards
- Inherits:
-
Object
- Object
- RailsConsoleAi::SafetyGuards
- Defined in:
- lib/rails_console_ai/safety_guards.rb
Instance Attribute Summary collapse
-
#guards ⇒ Object
readonly
Returns the value of attribute guards.
Instance Method Summary collapse
- #add(name, &block) ⇒ Object
- #allow(guard_name, key) ⇒ Object
- #allowed?(guard_name, key) ⇒ Boolean
- #allowlist ⇒ Object
- #disable! ⇒ Object
- #empty? ⇒ Boolean
- #enable! ⇒ Object
- #enabled? ⇒ Boolean
-
#initialize ⇒ SafetyGuards
constructor
A new instance of SafetyGuards.
-
#install_bypass_method!(spec) ⇒ Object
Install a bypass shim for a single method spec (e.g. “ChangeApproval#approve_by!”).
- #names ⇒ Object
- #remove(name) ⇒ Object
-
#without_guards ⇒ Object
Bypass all safety guards for the duration of the block.
-
#wrap(channel_mode: nil, additional_bypass_methods: nil, &block) ⇒ Object
Compose all guards around a block of code.
Constructor Details
#initialize ⇒ SafetyGuards
Returns a new instance of SafetyGuards.
29 30 31 32 33 |
# File 'lib/rails_console_ai/safety_guards.rb', line 29 def initialize @guards = {} @enabled = true @allowlist = {} # { guard_name => [String or Regexp, ...] } end |
Instance Attribute Details
#guards ⇒ Object (readonly)
Returns the value of attribute guards.
27 28 29 |
# File 'lib/rails_console_ai/safety_guards.rb', line 27 def guards @guards end |
Instance Method Details
#add(name, &block) ⇒ Object
35 36 37 |
# File 'lib/rails_console_ai/safety_guards.rb', line 35 def add(name, &block) @guards[name.to_sym] = block end |
#allow(guard_name, key) ⇒ Object
63 64 65 66 67 |
# File 'lib/rails_console_ai/safety_guards.rb', line 63 def allow(guard_name, key) guard_name = guard_name.to_sym @allowlist[guard_name] ||= [] @allowlist[guard_name] << key unless @allowlist[guard_name].include?(key) end |
#allowed?(guard_name, key) ⇒ Boolean
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/rails_console_ai/safety_guards.rb', line 69 def allowed?(guard_name, key) entries = @allowlist[guard_name.to_sym] return false unless entries entries.any? do |entry| case entry when Regexp then key.match?(entry) else entry.to_s == key.to_s end end end |
#allowlist ⇒ Object
81 82 83 |
# File 'lib/rails_console_ai/safety_guards.rb', line 81 def allowlist @allowlist end |
#disable! ⇒ Object
51 52 53 |
# File 'lib/rails_console_ai/safety_guards.rb', line 51 def disable! @enabled = false end |
#empty? ⇒ Boolean
55 56 57 |
# File 'lib/rails_console_ai/safety_guards.rb', line 55 def empty? @guards.empty? end |
#enable! ⇒ Object
47 48 49 |
# File 'lib/rails_console_ai/safety_guards.rb', line 47 def enable! @enabled = true end |
#enabled? ⇒ Boolean
43 44 45 |
# File 'lib/rails_console_ai/safety_guards.rb', line 43 def enabled? @enabled end |
#install_bypass_method!(spec) ⇒ Object
Install a bypass shim for a single method spec (e.g. “ChangeApproval#approve_by!”). Prepends a module that checks the thread-local bypass set at runtime. Idempotent: tracks which specs have been installed to avoid double-prepending.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/rails_console_ai/safety_guards.rb', line 112 def install_bypass_method!(spec) @installed_bypass_specs ||= Set.new return if @installed_bypass_specs.include?(spec) class_name, method_name = spec.split('#') klass = Object.const_get(class_name) rescue return method_sym = method_name.to_sym bypass_mod = Module.new do define_method(method_sym) do |*args, &blk| if Thread.current[:rails_console_ai_bypass_methods]&.include?(spec) RailsConsoleAi.configuration.safety_guards.without_guards { super(*args, &blk) } else super(*args, &blk) end end end klass.prepend(bypass_mod) @installed_bypass_specs << spec end |
#names ⇒ Object
59 60 61 |
# File 'lib/rails_console_ai/safety_guards.rb', line 59 def names @guards.keys end |
#remove(name) ⇒ Object
39 40 41 |
# File 'lib/rails_console_ai/safety_guards.rb', line 39 def remove(name) @guards.delete(name.to_sym) end |
#without_guards ⇒ Object
Bypass all safety guards for the duration of the block. Thread-safe: uses a thread-local flag that is restored after the block, even if the block raises an exception.
163 164 165 166 167 168 169 |
# File 'lib/rails_console_ai/safety_guards.rb', line 163 def without_guards prev = Thread.current[:rails_console_ai_bypass_guards] Thread.current[:rails_console_ai_bypass_guards] = true yield ensure Thread.current[:rails_console_ai_bypass_guards] = prev end |
#wrap(channel_mode: nil, additional_bypass_methods: nil, &block) ⇒ Object
Compose all guards around a block of code. Each guard is an around-block: guard.call { inner } Result: guard_1 { guard_2 { guard_3 { yield } } }
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rails_console_ai/safety_guards.rb', line 88 def wrap(channel_mode: nil, additional_bypass_methods: nil, &block) return yield unless @enabled && !@guards.empty? install_skills_once! bypass_set = resolve_bypass_methods(channel_mode) Array(additional_bypass_methods).each { |m| bypass_set << m } prev_active = Thread.current[:rails_console_ai_session_active] prev_bypass = Thread.current[:rails_console_ai_bypass_methods] Thread.current[:rails_console_ai_session_active] = true Thread.current[:rails_console_ai_bypass_methods] = bypass_set begin @guards.values.reduce(block) { |inner, guard| -> { guard.call(&inner) } }.call ensure Thread.current[:rails_console_ai_session_active] = prev_active Thread.current[:rails_console_ai_bypass_methods] = prev_bypass end end |