Запуск метода ruby вокруг существующего указанного метода - PullRequest
3 голосов
/ 06 февраля 2020

У меня есть существующая библиотека, состоящая из множества сервисов, которые все отвечают на метод execute каждый метод выполняет свою логику c

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

Я хотел бы сделать что-то для достижения sh что-то вроде:

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

, который затем оборачивает каждый метод execute, включая дочерние классы, так что logi c может выполняться до и после.

Я сделал что-то вроде этого:

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

Есть ли способ, которым я могу это сделать sh? Спасибо!

Ответы [ 2 ]

1 голос
/ 06 февраля 2020

То, что я в итоге сделал, я не уверен, в порядке ли я.

class BaseService
  include Hooks

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

        block.call

      end
    end
  end
end

Это позволило мне применить ко всем остальным классам функциональность вокруг, чтобы избежать массивный рефакторинг. Не уверен, что это лучшее решение, но хотел опубликовать его, чтобы другие могли его увидеть.

1 голос
/ 06 февраля 2020

Вы не изменяете дочерние классы и не вызываете super из их execute методов.

Насколько я могу судить, нет причины, по которой CreateUserService#execute должен вызывать обернутый BaseService#execute.

Один из способов достижения желаемого: уточнения :

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

Вы все равно должны были бы include Hooks в каждом Service классе, хотя.

...