module NoGo
class ProxyAdapter
# Valid values for @strategy and arguments when setting strategy
StrategyOptions = [:raise, :warn, :pass_through].freeze
ErrorMessageForRaiseStrategy = <<-EOM.freeze
Access to the database adapter is currently restricted (strategy set to :raise).
Set strategy to :pass_through or :warn to allow database access. See NoGo::Connection#strategy=.
EOM
# Most methods calls should simply be passed on to the proxied adapter. By undefining most methods we can use
# method_missing to accomplish this.
instance_methods.each do |method_name|
undef_method method_name unless method_name =~ /^__|^send$|^object_id$|^extend|^tap|^instance_variable_set|^instance_variable_get/
end
attr_accessor :enabled
# Include overriden AbstractAdapter methods
include NoGo::AbstractMethodOverrides
# Initializes an instance and sets the proxied adapter and default strategy. An exception is raised if the argument to adapter is
# not an instance of ActiveRecord::ConnectionAdapters::AbstractAdapter.
def initialize(adapter)
raise ArgumentError.new(
"Expected an instance of ActiveRecord::ConnectionAdapters::AbstractAdapter, but received #{adapter.class.name}"
) unless adapter.is_a?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
@adapter = adapter
@strategy = :raise
self.enabled = false
@enabled_state_stack = []
end
# Pops the last value from enabled_state_stack and assigns it to enabled.
def pop_enabled_state
self.enabled = @enabled_state_stack.pop || false
end
# Pushes the current value of enabled onto enabled_state_stack.
def push_enabled_state
@enabled_state_stack.push(enabled)
end
# Returns the adapter which is being proxied by the current object.
def proxied_adapter
@adapter
end
# Returns the current strategy assigned to this adapter, which can any of the StrategyOptions.
def strategy
@strategy
end
# Sets the current strategy for this adapter. Raises an ArgumentErrer if strategy_option is not
# a value from StrategyOptions
def strategy=(strategy_option)
raise ArgumentError.new(
"Expected strategy to be set to one of [#{StrategyOptions.map{|opt| ":#{opt}"}.join(', ')}], but received #{strategy_option}"
) unless StrategyOptions.include?(strategy_option.to_sym)
@strategy = strategy_option
end
private
# Pass through any undefined instance method calls.
def method_missing(method_name, *args, &block) # :doc:
pass_through(method_name, *args, &block)
end
# Passes a method call to the proxied adapter.
# If the strategy is set to :warn then #warn will be invoked.
def pass_through(method_name, *args, &block) # :doc:
warn(method_name, *args, &block) if enabled && @strategy == :warn
proxied_adapter.send(method_name, *args, &block)
end
# Raises an error if the current strategy is :raise and otherwise defers the method call to #pass_through.
def raise_or_pass_through(method_name, *args, &block) # :doc:
raise ErrorMessageForRaiseStrategy if enabled && @strategy == :raise
pass_through(method_name, *args, &block)
end
# Placeholder for #warn method. This is probably a place to trigger hooks once they are supported
def warn(method_name, *args, &block) # :doc:
puts "\nDatabase adapter accessed from: " + Kernel.caller[0..12].join("\n")
end
end
end