围绕现有的指定方法运行 ruby​​ 方法

Posted

技术标签:

【中文标题】围绕现有的指定方法运行 ruby​​ 方法【英文标题】:Running ruby method around existing specified method 【发布时间】:2020-02-05 23:13:56 【问题描述】:

我有一个由许多服务组成的现有库,它们都响应方法execute 每个方法都符合逻辑

class BaseService
   def execute
     raise NotImplementedError
   end
end

class CreateUserService < BaseService
  def execute
    # Some code that does Other stuff
  end
end

class NotifyService < BaseService
  def execute
    # Some code that does other other stuff
  end
end

我想做一些事情来完成类似的事情:

class BaseService
  around :execute do |&block|
    puts 'Before'
    block.call
    puts 'After'
  end
end

然后包装每个执行方法,包括子类,以便可以在之前和之后执行逻辑。

我做过这样的事情:

module Hooks

  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def around(*symbols, &block)
      to_prepend = build_module(symbols) do |*args, &mblock|
        result = nil

        block.call do
          result = super(*args, &mblock)
        end

        result
      end

      prepend to_prepend
    end

    private

    def build_module(symbols, &block)
      Module.new do
        symbols.each do |symbol|
          define_method(symbol, &block)
        end
      end
    end
  end
end

class BaseService
  include Hooks

  around :execute do |&block|
    puts 'before'
    block.call
    puts 'after'
  end

  # ..
end

但是,当around 方法仅在基类上执行时。我猜这是由于prepend 的性质。祖序如下:

[<Module>, BaseService, Hooks, ...]
[NotifyService, <Module>, BaseService, Hooks, ...]
etc

有没有办法可以做到这一点? 谢谢!

【问题讨论】:

别猜祖序是什么,问! MyClass.ancestors. 谢谢@tadman,我已经更新以传达确切的顺序 【参考方案1】:

您不修改子类,也不从它们的 execute 方法调用 super

据我所知,CreateUserService#execute 没有理由调用已包装的 BaseService#execute

实现您想要的一种方法是refinements:

class BaseService
   def execute
     p "BaseService#execute"
   end
end

class CreateUserService < BaseService
  def execute
    p "CreateUserService#execute"
  end
end

class NotifyService < BaseService
  def execute
    p "NotifyService#execute"
  end
end

module WrappedExecute
  [NotifyService, CreateUserService, BaseService].each do |service_class|
    refine service_class do
      def execute
        puts "Before"
        super
        puts "After"
      end
    end
  end
end

CreateUserService.new.execute
#=> "CreateUserService#execute"

using WrappedExecute

CreateUserService.new.execute

# Before
# "CreateUserService#execute"
# After

注意:

  to_prepend = build_module(symbols) do |*args, &mblock|
    result = nil

    block.call do
      result = super(*args, &mblock)
    end

    result
  end

可以替换为

  to_prepend = build_module(symbols) do |*args, &mblock|
    block.call do
      super(*args, &mblock)
    end
  end

不过,您仍然需要在每个 Service 类中使用 include Hooks

【讨论】:

谢谢,我以前从未听说过 refine 方法,所以我一定会读一读【参考方案2】:

我最终做的是我不确定自己是否可以接受的事情。

class BaseService
  include Hooks

  def self.inherited(subclass)
    subclass.around(:execute) do |&block|
      Rails.logger.tagged(tags) do

        block.call

      end
    end
  end
end

这使我能够将周围的功能应用到所有其他类,以避免大规模重构。不确定这是否是最好的解决方案,但想发布给其他人看。

【讨论】:

好主意。 inherited 是缺少的部分,以便您的子类也可以由 Hooks 更新。

以上是关于围绕现有的指定方法运行 ruby​​ 方法的主要内容,如果未能解决你的问题,请参考以下文章

我可以为现有的Ruby类添加自定义方法吗?

ruby excel - 将数据写入现有的xls

将 Ruby on Rails 连接到现有的 MySQL 数据库(以前安装的 XAMPP)

运行生成脚手架命令时出现未定义的方法“组”错误

将聊天模块集成到现有的 ruby​​ 应用程序

LogicException 错误:传递的数组未指定现有的静态方法