-
-
Save sdeming/1696658 to your computer and use it in GitHub Desktop.
module PredicateMethods | |
module Firewalls | |
class Allow | |
def initialize(what) | |
@wrappee = what | |
end | |
def method_missing(name, *args, &blk) | |
begin | |
@wrappee.send(name, *args, &blk) | |
rescue NoMethodError | |
eval "#{name} *args, &blk" | |
end | |
end | |
end | |
class Deny | |
def method_missing name, *args, &blk | |
self | |
end | |
end | |
end | |
def define_predicate name, predicate | |
name = name.to_s.sub(/\?$/, '') | |
p, m = :"#{name}?", :"#{name}" | |
send(:define_method, p) do |*args, &blk| | |
predicate.call(*args) | |
end | |
send(:define_method, m) do |*args, &blk| | |
if send(p, *args) | |
if blk | |
blk.call | |
else | |
@_predwrapper ||= Firewalls::Allow.new(self) | |
end | |
else | |
Firewalls::Deny.new | |
end | |
end | |
end | |
end | |
class T | |
extend PredicateMethods | |
define_predicate :yep, lambda { true } | |
define_predicate :nope, lambda { false } | |
define_predicate :maybe, lambda { |eh| eh } | |
end | |
x = T.new | |
x.yep.puts "x:Yep" | |
x.yep.yep.yep.yep { puts "x:Yeeeeep" } | |
x.nope.nope.nope.nope { puts "x:Nooooope" } | |
x.nope.puts "x:Nope" | |
x.yep.nope.puts "x:Nuh uh" | |
x.puts "x:I should raise NoMethodError" rescue nil | |
y = T.new | |
y.nope.puts "y:Nope" | |
y.puts "y:I should raise NoMethodError" rescue nil | |
y.maybe(true).puts "y:Maybe" | |
y.yep.maybe(true).yep.puts "y:Yep-Maybe-Yep" | |
y.maybe(false).puts "y:Maybe not" | |
First problem is that method_missing is permanently attached to the instance of an abused class, once it's abused. It would best if "x.puts" did not eval "puts" unless it came at the end of the predicate chain. So x.puts would raise no method error while x.yet.puts would work as designed. Time for some meta? Not sure if that'll actually work here.
By abused class I mean any class extended by PredicateMethods. I should minitest this instead of relying on my eyes to read puts. But I'm just not that unlazy.
Please check out rainmain. We rewrote it.3 times. It solves a lot of this.
Updated to accomplish my goals. Rainman is for far more complex mechanisms. This is really just about replacing code like:
if verbose?
puts "Hello"
end
with:
verbose.puts "Hello"
I have a few things that are pretty deeply nested that was using lambda's for. This cleans it up quite a bit.
Scott, don't end your sentences in prepositions. It clearly breaks reading comprehensibility then. Oops.
A little more goofiness with ruby. Define a predicate method. When the predicate evaluates to true then a modified 'self' is returned that will eval the next method in the chain. When the predicate evaluates to false then an imposter object is returned that absorbs the rest of the chain.
Nice for eliminating excessive if/then for simple things.