Last active
August 29, 2015 14:00
-
-
Save v2e4lisp/11342428 to your computer and use it in GitHub Desktop.
before/after filter
This file contains hidden or 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
| # Include this module | |
| # to add :before and :after hooks for instance method | |
| # | |
| # Example | |
| # | |
| # class User | |
| # include Hookable | |
| # | |
| # def initialize | |
| # @name = "wenjun.yan" | |
| # end | |
| # | |
| # def name | |
| # @name | |
| # end | |
| # | |
| # def name=(new_name) | |
| # @name = new_name unless new_name == "Bob Marley" | |
| # end | |
| # | |
| # before :name= do |new_name| | |
| # puts "@name will be changed to #{new_name}" | |
| # end | |
| # | |
| # after :name= do |new_name| | |
| # if @name == new_name | |
| # puts "@name has been changed to #{new_name}" | |
| # else | |
| # puts "fail to changing name to #{new_name}" | |
| # end | |
| # end | |
| # end | |
| # | |
| # user = User.new | |
| # user.name = "Larry Wall" | |
| # user.name = "Yukihiro Matsumoto" | |
| # user.name = "Bob Marley" | |
| # | |
| # # => @name will be changed to Larry Wall | |
| # # => @name has been changed to Larry Wall | |
| # # => @name will be changed to Yukihiro Matsumoto | |
| # # => @name has been changed to Yukihiro Matsumoto | |
| # # => @name will be changed to Bob Marley | |
| # # => fail to changing name to Bob Marley | |
| # | |
| # # call method with hooks | |
| # User.new.name | |
| # | |
| # # call method without hooks | |
| # User.new.name_without_hooks | |
| # | |
| # # get all method hooks | |
| # User.hooks | |
| # | |
| # # get all hooked methods | |
| # User.hooks.keys | |
| # | |
| # 1. Hooks are executed in current instance context. | |
| # 2. Method args and optional block are passed, | |
| # so you can use them in the hook block | |
| # 3. :after hooks do not change the return value of the original method | |
| module Hookable | |
| def self.included(base) | |
| base.extend ClassMethods | |
| base.class_eval { | |
| undef_method :hooks if method_defined?(:hooks) | |
| } | |
| end | |
| def run_hooks(before_or_after, method, *args) | |
| self.class.hooks[method][before_or_after].each { |b| | |
| instance_exec(*args, &b) | |
| } | |
| end | |
| private :run_hooks | |
| module ClassMethods | |
| # Public: get all hooks | |
| # hooks is a hash structured: | |
| # | |
| # { | |
| # :method_name => { | |
| # :before => [proc1, proc2 ... ] | |
| # :after => [proc1, proc2 ... ] | |
| # } | |
| # ... | |
| # } | |
| def hooks | |
| {} | |
| end | |
| protected | |
| # Protected: add before hook | |
| # | |
| # method - {Symbol} method name | |
| # block - code called before the method, | |
| # block is evaluated in instance context. | |
| # | |
| # Example: | |
| # before :some_method do | |
| # puts "in before hook" | |
| # end | |
| def before(method, &block) | |
| hook(:before, method, &block) | |
| end | |
| # Protected: add after hook | |
| # | |
| # method - {Symbol} method name | |
| # block - code called after the method, | |
| # block is evaluated in instance context. | |
| # | |
| # Example: | |
| # after :some_method do | |
| # puts "in after hook" | |
| # end | |
| def after(method, &block) | |
| hook(:after, method, &block) | |
| end | |
| # method added trigger | |
| # if the newly added method has any hook | |
| # rename it and create a new method with the orignal method name, | |
| # which will trigger the hooks. | |
| def method_added(method) | |
| # this method has hooks and has not been redefined | |
| if hooks[method] and not method_defined? new_name_for(method) | |
| redefine_method_with_hooks method | |
| end | |
| super | |
| end | |
| # Protected: Get a new name for method which is hooked | |
| # | |
| # method - name of a method with hooks | |
| # | |
| # Example: | |
| # | |
| # new_name_for(:change) | |
| # # => change_without_hooks | |
| # | |
| # new_name_for(:changed?) | |
| # # => changed_without_hooks? | |
| # | |
| # new_name_for(:change!) | |
| # # => change_without_hooks! | |
| # | |
| # new_name_for(:name=) | |
| # # => name_without_hooks= | |
| # | |
| # method name with a suffix "!", "?" or "=" will be renamed to | |
| # method_without_hooks{suffix}. | |
| # Other special method names(<<, [] ...) will not be handled | |
| def new_name_for(method) | |
| name = method.to_s | |
| if ["!", "?", "="].include? name[-1] | |
| "#{name[0..-2]}_without_hooks#{name[-1]}" | |
| else | |
| "#{name}_without_hooks" | |
| end | |
| end | |
| def hook(before_or_after, method, &block) | |
| old_method = new_name_for(method) | |
| old_hooks = hooks.dup | |
| old_hooks[method] ||= {:before => [], :after => []} | |
| old_hooks[method][before_or_after] << block | |
| singleton_class.class_eval { | |
| define_method(:hooks) { old_hooks } | |
| } | |
| if method_defined?(method) and not method_defined?(old_method) | |
| redefine_method_with_hooks method | |
| end | |
| end | |
| def redefine_method_with_hooks(method) | |
| old_method = new_name_for(method) | |
| alias_method old_method, method | |
| define_method(method) {|*args, &block| | |
| args_with_optional_block = args + [block] | |
| run_hooks(:before, method, *args_with_optional_block) | |
| send(old_method, *args, &block).tap { | |
| run_hooks(:after, method, *args_with_optional_block) | |
| } | |
| } | |
| end | |
| end | |
| end |
Author
Author
継承しても問題なし!
The hooks works like an attribute defined by class_attribute in rails.
Author
E.G
require File.expand_path("../meta", __FILE__)
class User
include Hookable
def initialize
@nickname = "wenjun.yan"
end
def nickname
@nickname
end
def nickname=(new_nickname)
@nickname = new_nickname unless new_nickname == "Bob Marley"
end
before :nickname do
end
before :nickname= do |new_nickname|
puts "@nickname will be changed to #{new_nickname}"
end
after :nickname= do |new_nickname|
if @nickname == new_nickname
puts "@nickname has been changed to #{new_nickname}"
else
puts "fail to changing nickname to #{new_nickname}"
end
end
end
# p User.methods.map(&:to_s).grep /nickname/
class Me < User
after :hi do
puts "My nickname is #{nickname_without_hooks}"
end
def hi
puts "hello!"
end
end
p User.hooks
p Me.hooks
puts "-" * 50
user = User.new
user.nickname = "user"
user.nickname_without_hooks
user.nickname = "Larry Wall"
user.nickname = "Yukihiro Matsumoto"
user.nickname = "Bob Marley"
Me.new.hi
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
継承にしたらどうなるか?まだ確認してないです。