-
-
Save 3limin4t0r/7785aeaadcf1b0d72aa8e5a60f6016ed to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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