lib/consul/controller.rb
module Consul
module Controller
def self.included(base)
base.send :include, InstanceMethods
base.send :extend, ClassMethods
if ensure_power_initializer_present?
Util.before_action(base, :ensure_power_initializer_present)
end
end
private
def self.ensure_power_initializer_present?
['development', 'test', 'cucumber', 'in_memory'].include?(Rails.env)
end
module ClassMethods
def current_power_initializer
@current_power_initializer || (superclass.respond_to?(:current_power_initializer) && superclass.current_power_initializer)
end
def current_power_initializer=(initializer)
@current_power_initializer = initializer
end
private
def require_power_check(options = {})
Util.before_action(self, :unchecked_power, options)
end
# This is badly named, since it doesn't actually skip the :check_power filter
def skip_power_check(options = {})
Util.skip_before_action(self, :unchecked_power, options)
end
def current_power(&initializer)
self.current_power_initializer = initializer
Util.around_action(self, :with_current_power)
if respond_to?(:helper_method)
helper_method :current_power
end
end
def power(*args)
guard = Consul::Guard.new(*args)
controller = self
# One .power directive will skip the check for all actions, even
# if that .power directive has :only or :except options.
skip_power_check
# Store arguments for testing
consul_power_args << args
Util.before_action(self, guard.filter_options) do |controller|
guard.ensure!(controller, controller.action_name)
end
if guard.direct_access_method
consul_features_module.module_eval do
# It's dangerous to re-define direct access methods like this:
#
# power :one, as: :my_power
# power :two, as: :my_power
#
# The method would always check the last power only.
# To prevent this we're raising an error.
if method_defined?(guard.direct_access_method)
raise DuplicateMethod, "Method #{direct_access_method} is already defined on #{controller.name}"
end
define_method guard.direct_access_method do
guard.power_value(self, action_name)
end
private guard.direct_access_method
end
end
end
# Instead of using define_method on the controller we're enhancing,
# we define dynamic method in a module and have the controller include that.
# This way the controller can override our generated method and access
# the original implenentation with super().
#
# See https://thepugautomatic.com/2013/07/dsom/ for more examples on this
# technique.
def consul_features_module
name = :ConsulFeatures
# Each controller class should get its own FeatureModule, even when
# we already inherit one from our parent.
if const_defined?(name, (_search_ancestors = false))
const_get(name, _search_ancestors = false)
else
mod = Module.new
const_set(name, mod)
include(mod)
mod
end
end
# On first access we inherit .consul_power_args from our ancestor classes.
# We also copy inherited args so we don't change our parent's .consul_power_args
def consul_power_args
unless @consul_power_args_initialized
if superclass && superclass.respond_to?(:consul_power_args, true)
@consul_power_args = superclass.send(:consul_power_args).dup
else
@consul_power_args = []
end
@consul_power_args_initialized = true
end
@consul_power_args
end
end
module InstanceMethods
private
def unchecked_power
raise Consul::UncheckedPower, "This controller does not check against a power"
end
def current_power
@current_power_class && @current_power_class.current
end
def with_current_power(&action)
power = instance_eval(&self.class.current_power_initializer) or raise Consul::Error, 'current_power initializer returned nil'
@current_power_class = power.class
@current_power_class.current = power
action.call
ensure
if @current_power_class
@current_power_class.current = nil
end
end
def ensure_power_initializer_present
unless self.class.current_power_initializer.present?
raise Consul::UnreachablePower, 'You included Consul::Controller but forgot to define a power using current_power do ... end'
end
end
end
end
end