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