【问题标题】:Using method callbacks in plain Ruby class在普通 Ruby 类中使​​用方法回调
【发布时间】:2018-08-20 18:30:15
【问题描述】:

我有一个普通的 ruby​​ 类 Espresso::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

使用类中的某些方法,我想执行类似于 ActiveSupport 回调 before_actionbefore_filterbeforeafter 回调。我想在我的课堂上放这样的东西,它将在my_first_function之前运行my_function_to_run_before

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"

如何在 Rails 等普通 ruby​​ 类中使​​用回调在每个指定方法之前运行方法?

编辑2:

感谢@tadman 推荐XY problem。我们遇到的真正问题是使用令牌过期的 API 客户端。在每次调用 API 之前,我们需要检查令牌是否过期。如果我们有大量的 API 函数,那么每次检查令牌是否过期会很麻烦。

这是示例类:

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

【问题讨论】:

  • 这种方法链接在实现方面变得非常丑陋,因为您必须重新定义 x 以环绕 x。 ActiveRecord 不包装方法,因为它有更好的内部调度系统。
  • 为了避免XY Problems,创建一个比返回玩具字符串更能代表您的意图的示例可能更有意义。
  • @DogEatDog 您正在使用ActiveSupport::Callbacks,但据我所知似乎没有使用它。您是否尝试过实现类似于their example 的东西?
  • @DogEatDog 值得一试。也许你可以展示你之前的尝试

标签: ruby activesupport


【解决方案1】:

一个简单的实现会是;

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​​ 以该方法的名称调用。在这个方法内部,我所做的只是命名修改并用新方法覆盖原始方法,新方法首先运行回调,然后调用原始方法。

请注意,此实现既不支持块回调也不支持超类方法的回调。不过,它们都可以轻松实现。

【讨论】:

    猜你喜欢
    • 2013-04-21
    • 1970-01-01
    • 2020-10-29
    • 2013-10-11
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多