#-- # Author:: Tyler Rick # Copyright:: Copyright (c) 2007 QualitySmith, Inc. # License:: Ruby License # Submit to Facets?:: Yes! # Developer notes:: # * Is it thread-safe?? Probably not, as it stands... # But the whole thing that prompted me to create a guard method in the first place was to try to avoid a deadlock that was # caused by recursively calling a method with a synchronize in it (in other words, someone else's attempt at thread-safety # resulted in me getting into a deadlock, which is why I wrote this method to begin with). So I'm not even sure if it's # possible to make it thread-safe? #++ require 'rubygems' require 'qualitysmith_extensions/module/attribute_accessors' require 'qualitysmith_extensions/module/mattr_tester' require 'qualitysmith_extensions/module/attr_tester' require 'qualitysmith_extensions/symbol/match' class Module # A guard method (by this definition anyway) is a method that sets a flag, executes a block, and then returns the flag to its # previous value. It ensures that the flag is set during the execution of the block. # # In the simplest case, you'd use it like this: # class A # guard_method :disable_stupid_stuff!, :@stupid_stuff_disabled # end # a = A.new # a.disable_stupid_stuff! do # Causes @stupid_stuff_disabled to be set to true # # Section of code during which you don't want any stupid stuff to happen # end # Causes @stupid_stuff_disabled to be set back to false # # Okay, a, you can resume doing stupid stuff again... # # If you also want a guard method that *disables* the flag rather than *enabling* it, simply pass in the desired name of this # "unguard" method as the value of the +unguard_method_name+ argument. # class A # guard_method :guard_the_fruit!, :@guarding_the_fruit, :stop_guarding_the_fruit! # end # # Examples of unguard methods: # mguard_method :color_on!, :@@color_enabled, :color_off! # mguard_method :without_benchmarking, :@@benchmarking_disabled, :with_benchmarking # # These calls can be nested however you wish: # a.guard_the_fruit! do # a.stop_guarding_the_fruit! do # assert_equal false, a.guarding_the_fruit? # end # assert_equal true, a.guarding_the_fruit? # end # # You can also use the guard methods as normal flag setter/clearer methods by simply not passing a block to it. Hence # a.guard_the_fruit! # will simply set @guarding_the_fruit to true, and # a.stop_guarding_the_fruit! # will set @guarding_the_fruit to false. # def guard_method(guard_method_name, guard_variable, unguard_method_name = nil) raise ArgumentError.new("Expected an instance variable name but got #{guard_variable}") if guard_variable !~ /^@([\w_]+)$/ guard_variable.to_s =~ /^@([\w_]+)$/ # Why didn't the regexp above set $1 ?? class_eval do attr_tester $1.to_sym end module_eval <<-End, __FILE__, __LINE__+1 def #{guard_method_name}(new_value = nil, &block) old_guard_state, #{guard_variable} = #{guard_variable}, true if block_given? returning = yield #{guard_variable} = old_guard_state returning end end End module_eval <<-End, __FILE__, __LINE__+1 unless unguard_method_name.nil? def #{unguard_method_name}(new_value = nil, &block) old_guard_state, #{guard_variable} = #{guard_variable}, false if block_given? returning = yield #{guard_variable} = old_guard_state returning end end End end # See the documentation for guard_method. mguard_method does the same thing, only it creates a _class_ (or _module_) method # rather than an instance method and it uses a _class_ (or _module_) variable rather than an instance variable to store the # guard state. # # Example: # mguard_method :guard_the_fruit!, :@@guarding_the_fruit def mguard_method(guard_method_name, guard_variable, unguard_method_name = nil) raise ArgumentError.new("Expected a class variable name but got #{guard_variable}") if guard_variable !~ /^@@[\w_]+$/ guard_variable.to_s =~ /^@@([\w_]+)$/ class_eval do mattr_tester $1.to_sym end module_eval <<-End, __FILE__, __LINE__+1 class << self def #{guard_method_name}(new_value = nil, &block) old_guard_state, #{guard_variable} = #{guard_variable}, true if block_given? returning = yield #{guard_variable} = old_guard_state returning end end end End module_eval <<-End, __FILE__, __LINE__+1 unless unguard_method_name.nil? class << self def #{unguard_method_name}(new_value = nil, &block) old_guard_state, #{guard_variable} = #{guard_variable}, false if block_given? returning = yield #{guard_variable} = old_guard_state returning end end end End end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin test require 'test/unit' class GuardMethodTest_Simple < Test::Unit::TestCase class A guard_method :guard_the_fruit!, :@guarding_the_fruit end def test_guard_method a = A.new assert_equal nil, a.guarding_the_fruit? a.guard_the_fruit! do # Call it recursively! a.guard_the_fruit! do assert_equal true, a.guarding_the_fruit? end assert_equal true, a.guarding_the_fruit? # This is the reason why we have to save the 'old_guard_state'. So that we don't stupidly set it back to false if we still haven't exited from the outermost call to the guard black. end assert_equal nil, a.guarding_the_fruit? end def test_guard_method_error assert_raise(ArgumentError) do self.class.class_eval do guard_method :guard_the_fruit!, :@@guarding_the_fruit end end end def test_return_value assert_equal 'special return value', A.new.guard_the_fruit! { 'special return value' } end end class GuardMethodTest_WithUnguard < Test::Unit::TestCase class A guard_method :guard_the_fruit!, :@guarding_the_fruit, :stop_guarding_the_fruit! end def test_guard_method a = A.new assert_equal nil, a.guarding_the_fruit? a.guard_the_fruit! do a.guard_the_fruit! do assert_equal true, a.guarding_the_fruit? a.stop_guarding_the_fruit! do assert_equal false, a.guarding_the_fruit? a.guard_the_fruit! do assert_equal true, a.guarding_the_fruit? end assert_equal false, a.guarding_the_fruit? end assert_equal true, a.guarding_the_fruit? end assert_equal true, a.guarding_the_fruit? end assert_equal nil, a.guarding_the_fruit? end def test_guard_method_with_simple_blockless_toggles a = A.new assert_equal nil, a.guarding_the_fruit? a.guard_the_fruit! assert_equal true, a.guarding_the_fruit? a.stop_guarding_the_fruit! do assert_equal false, a.guarding_the_fruit? a.guard_the_fruit! do assert_equal true, a.guarding_the_fruit? a.stop_guarding_the_fruit! assert_equal false, a.guarding_the_fruit? a.guard_the_fruit! assert_equal true, a.guarding_the_fruit? end assert_equal false, a.guarding_the_fruit? end assert_equal true, a.guarding_the_fruit? end def test_return_value assert_equal nil, A.new.guard_the_fruit! assert_equal nil, A.new.stop_guarding_the_fruit! assert_equal 'special return value', A.new.guard_the_fruit! { 'special return value' } assert_equal 'special return value', A.new.stop_guarding_the_fruit! { 'special return value' } end end #--------------------------------------------------------------------------------------------------------------------------------- # Begin duplication # The following TestCases are simply duplicates of the previous except that they test mguard_method rather than guard_method # The main differenes/substitutions: # * @@guarding_the_fruit, rather than @guarding_the_fruit # * :'<,'>s/A.new/B/g # * :'<,'>s/\/B/g class MGuardMethodTest_Simple < Test::Unit::TestCase class B mguard_method :guard_the_fruit!, :@@guarding_the_fruit end def test_mguard_method assert_equal nil, B.guarding_the_fruit? B.guard_the_fruit! do # Call it recursively! B.guard_the_fruit! do assert_equal true, B.guarding_the_fruit? end assert_equal true, B.guarding_the_fruit? # This is the reason why we have to save the 'old_guard_state'. So that we don't stupidly set it back to false if we still haven't exited from the outermost call to the guard black. end assert_equal nil, B.guarding_the_fruit? end def test_mguard_method_error assert_raise(ArgumentError) do self.class.class_eval do mguard_method :guard_the_fruit!, :@guarding_the_fruit end end end def test_return_value assert_equal 'special return value', B.guard_the_fruit! { 'special return value' } end end class MGuardMethodTest_WithUnguard < Test::Unit::TestCase class B mguard_method :guard_the_fruit!, :@@guarding_the_fruit, :stop_guarding_the_fruit! end def test_guard_method assert_equal nil, B.guarding_the_fruit? B.guard_the_fruit! do B.guard_the_fruit! do assert_equal true, B.guarding_the_fruit? B.stop_guarding_the_fruit! do assert_equal false, B.guarding_the_fruit? B.guard_the_fruit! do assert_equal true, B.guarding_the_fruit? end assert_equal false, B.guarding_the_fruit? end assert_equal true, B.guarding_the_fruit? end assert_equal true, B.guarding_the_fruit? end assert_equal nil, B.guarding_the_fruit? end def test_guard_method_with_simple_blockless_toggles assert_equal nil, B.guarding_the_fruit? B.guard_the_fruit! assert_equal true, B.guarding_the_fruit? B.stop_guarding_the_fruit! do assert_equal false, B.guarding_the_fruit? B.guard_the_fruit! do assert_equal true, B.guarding_the_fruit? B.stop_guarding_the_fruit! assert_equal false, B.guarding_the_fruit? B.guard_the_fruit! assert_equal true, B.guarding_the_fruit? end assert_equal false, B.guarding_the_fruit? end assert_equal true, B.guarding_the_fruit? end def test_return_value assert_equal nil, B.guard_the_fruit! assert_equal nil, B.stop_guarding_the_fruit! assert_equal 'special return value', B.guard_the_fruit! { 'special return value' } assert_equal 'special return value', B.stop_guarding_the_fruit! { 'special return value' } end end # End duplication #--------------------------------------------------------------------------------------------------------------------------------- =end