Skip to content

Instantly share code, notes, and snippets.

@3limin4t0r
Last active November 8, 2019 22:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 3limin4t0r/7785aeaadcf1b0d72aa8e5a60f6016ed to your computer and use it in GitHub Desktop.
Save 3limin4t0r/7785aeaadcf1b0d72aa8e5a60f6016ed to your computer and use it in GitHub Desktop.
module PolicyEnforcer
class MethodWrapper
attr_reader :klass
def initialize(klass)
@klass = klass
end
def wrap(method_name)
# Assign the policy level to a variable. If this is not done invoking
# a wrapped method will retreive the current value (last value). Not
# the value when defined.
policy_level = klass.policy_level
method = klass.instance_method(method_name)
return unless policy_level # don't wrap is there is no policy level
return if @wrapping # don't wrap if the method in question is the result of wrapping a method
@wrapping = true
klass.send(:define_method, method_name) do |*args, &blk|
result = method.bind(self).call(*args, &blk)
if current_user_level >= policy_level
result
else
secure(result)
end
end
@wrapping = false
end
end
##
# Make sure only one instance of MethodWrapper is used for a class.
# This is needed because the wrapper defines methods which will
# trigger the #method_added callback. By using the same wrapper it
# will see that @wrapping is set and return early. Preventing what
# might be an infinit loop.
#
# method defined (by user) -> wrap method -> method defined (by wrapper) -> wrap method -> ...
def __policy_enforcer_method_wrapper__
@__policy_enforcer_method_wrapping__ ||= MethodWrapper.new(self)
end
##
# Callback to wrap newly defined methods.
def method_added(method)
super
wrapper = __policy_enforcer_method_wrapper__
wrapper.wrap(method)
end
##
# Without arguments calling this method functions as a getter.
# Returning the current value of the policy level.
#
# With one argument (the policy level) the method the policy
# level for newly defined methods to that level.
#
# With two or more arguments (the first one being policy level
# the others being method names) the provided methods will be
# wrapped with that specific policy level, but doesn't set the
# policy level of newly defined methods.
def policy_level(*args)
return @policy_level if args.empty?
level, *methods = args
old_policy_level = @policy_level
@policy_level = level
return @policy_level if methods.empty?
wrapper = __policy_enforcer_method_wrapper__
methods.each(&wrapper.method(:wrap))
ensure
if methods && !methods.empty?
@policy_level = old_policy_level
end
end
end
class Car
extend PolicyEnforcer
def brand
'Car Brand'
end
policy_level 3, :brand # set policy level for only the :brand method
def manifacturer
'Car Manifacturer'
end
policy_level 2 # set policy level to 2 for newly defined methods
def title
'Car Title'
end
private
policy_level nil # disable policies for newly defined methods
def current_user_level
level = (1..5).to_a.sample
puts "using current_user_level #{level}"
level
end
def secure(obj)
case obj
when String
'X' * obj.length
else
raise ArgumentError, "unsupported object #{obj}:#{obj.class}"
end
end
end
car = Car.new
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment