在普通 Ruby class 中使用方法回调
Using method callbacks in plain Ruby class
我有一个普通的rubyclassEspresso::MyExampleClass
.
module Espresso
class MyExampleClass
def my_first_function(value)
puts "my_first_function"
end
def my_function_to_run_before
puts "Running before"
end
end
end
使用 class 中的一些方法,我想执行类似于 ActiveSupport 回调 before_action
或 before_filter
的 before
或 after
回调.我想在我的 class 中添加这样的内容,它将 运行 my_function_to_run_before
在 my_first_function
:
之前
before_method :my_function_to_run_before, only: :my_first_function
结果应该是这样的:
klass = Espresso::MyExampleClass.new
klass.my_first_function("yes")
> "Running before"
> "my_first_function"
如何在 ruby class 中使用回调,例如 Rails 到 运行 每个指定方法之前的方法?
编辑 2:
感谢@tadman 推荐 XY problem。我们遇到的真正问题是 API 客户端的令牌过期。在每次调用 API 之前,我们需要检查令牌是否过期。如果我们有很多 API 的函数,那么每次检查令牌是否过期会很麻烦。
这里是例子class:
require "rubygems"
require "bundler/setup"
require 'active_support/all'
require 'httparty'
require 'json'
module Espresso
class Client
include HTTParty
include ActiveSupport::Callbacks
def initialize
login("admin@example.com", "password")
end
def login(username, password)
puts "logging in"
uri = URI.parse("localhost:3000" + '/login')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(username: username, password: password)
response = http.request(request)
body = JSON.parse(response.body)
@access_token = body['access_token']
@expires_in = body['expires_in']
@expires = @expires_in.seconds.from_now
@options = {
headers: {
Authorization: "Bearer #{@access_token}"
}
}
end
def is_token_expired?
#if Time.now > @expires.
if 1.hour.ago > @expires
puts "Going to expire"
else
puts "not going to expire"
end
1.hour.ago > @expires ? false : true
end
# Gets posts
def get_posts
#Check if the token is expired, if is login again and get a new token
if is_token_expired?
login("admin@example.com", "password")
end
self.class.get('/posts', @options)
end
# Gets comments
def get_comments
#Check if the token is expired, if is login again and get a new token
if is_token_expired?
login("admin@example.com", "password")
end
self.class.get('/comments', @options)
end
end
end
klass = Espresso::Client.new
klass.get_posts
klass.get_comments
一个简单的实现是;
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
@overridden_methods ||= []
end
def callbacks
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
run_callbacks_for(method_name)
send(original_method_name, *args)
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_name, callback)
callbacks[method_name] << callback
end
module InstanceMethods
def run_callbacks_for(method_name)
self.class.callbacks[method_name].to_a.each do |callback|
send(callback)
end
end
end
end
class Foo
extend Callbacks
before_run :bar, :zoo
def bar
puts 'bar'
end
def zoo
puts 'This runs everytime you call `bar`'
end
end
Foo.new.bar #=> This runs everytime you call `bar`
#=> bar
这个实现中的棘手点是,method_added
。每当一个方法被绑定时,method_added
方法就会被 ruby 使用该方法的名称调用。在此方法内部,我所做的只是名称修改并用首先运行回调然后调用原始方法的新方法覆盖原始方法。
请注意,此实现既不支持块回调也不支持超级 class 方法的回调。不过,它们都可以很容易地实现。
我有一个普通的rubyclassEspresso::MyExampleClass
.
module Espresso
class MyExampleClass
def my_first_function(value)
puts "my_first_function"
end
def my_function_to_run_before
puts "Running before"
end
end
end
使用 class 中的一些方法,我想执行类似于 ActiveSupport 回调 before_action
或 before_filter
的 before
或 after
回调.我想在我的 class 中添加这样的内容,它将 运行 my_function_to_run_before
在 my_first_function
:
before_method :my_function_to_run_before, only: :my_first_function
结果应该是这样的:
klass = Espresso::MyExampleClass.new
klass.my_first_function("yes")
> "Running before"
> "my_first_function"
如何在 ruby class 中使用回调,例如 Rails 到 运行 每个指定方法之前的方法?
编辑 2:
感谢@tadman 推荐 XY problem。我们遇到的真正问题是 API 客户端的令牌过期。在每次调用 API 之前,我们需要检查令牌是否过期。如果我们有很多 API 的函数,那么每次检查令牌是否过期会很麻烦。
这里是例子class:
require "rubygems"
require "bundler/setup"
require 'active_support/all'
require 'httparty'
require 'json'
module Espresso
class Client
include HTTParty
include ActiveSupport::Callbacks
def initialize
login("admin@example.com", "password")
end
def login(username, password)
puts "logging in"
uri = URI.parse("localhost:3000" + '/login')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data(username: username, password: password)
response = http.request(request)
body = JSON.parse(response.body)
@access_token = body['access_token']
@expires_in = body['expires_in']
@expires = @expires_in.seconds.from_now
@options = {
headers: {
Authorization: "Bearer #{@access_token}"
}
}
end
def is_token_expired?
#if Time.now > @expires.
if 1.hour.ago > @expires
puts "Going to expire"
else
puts "not going to expire"
end
1.hour.ago > @expires ? false : true
end
# Gets posts
def get_posts
#Check if the token is expired, if is login again and get a new token
if is_token_expired?
login("admin@example.com", "password")
end
self.class.get('/posts', @options)
end
# Gets comments
def get_comments
#Check if the token is expired, if is login again and get a new token
if is_token_expired?
login("admin@example.com", "password")
end
self.class.get('/comments', @options)
end
end
end
klass = Espresso::Client.new
klass.get_posts
klass.get_comments
一个简单的实现是;
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
@overridden_methods ||= []
end
def callbacks
@callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
run_callbacks_for(method_name)
send(original_method_name, *args)
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_name, callback)
callbacks[method_name] << callback
end
module InstanceMethods
def run_callbacks_for(method_name)
self.class.callbacks[method_name].to_a.each do |callback|
send(callback)
end
end
end
end
class Foo
extend Callbacks
before_run :bar, :zoo
def bar
puts 'bar'
end
def zoo
puts 'This runs everytime you call `bar`'
end
end
Foo.new.bar #=> This runs everytime you call `bar`
#=> bar
这个实现中的棘手点是,method_added
。每当一个方法被绑定时,method_added
方法就会被 ruby 使用该方法的名称调用。在此方法内部,我所做的只是名称修改并用首先运行回调然后调用原始方法的新方法覆盖原始方法。
请注意,此实现既不支持块回调也不支持超级 class 方法的回调。不过,它们都可以很容易地实现。