Class: RailsConsoleAi::SafetyGuards

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_console_ai/safety_guards.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSafetyGuards

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

#guardsObject (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

Returns:

  • (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

#allowlistObject



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

Returns:

  • (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

Returns:

  • (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

#namesObject



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_guardsObject

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