Как я могу перехватить вызов метода в ruby?

В настоящее время у меня есть суперкласс, у которого есть функция, которую я хочу, чтобы весь подкласс вызывал внутри каждой из своих функций. Предполагается, что функция должна вести себя как функция before_filter в рельсах, но я не уверен, как начать реализацию before_filter. Вот пример

class Superclass
  def before_each_method
    puts "Before Method" #this is supposed to be invoked by each extending class' method
  end
end

class Subclass < Superclass
  def my_method
    #when this method is called, before_each_method method is supposed to get invoked
  end
end

Ответ 1

Это один из способов сделать это:

class Superclass
  def before_each_method name
    p [:before_method, name]
  end

  def self.method_added name
    return if @__last_methods_added && @__last_methods_added.include?(name)
    with = :"#{name}_with_before_each_method"
    without = :"#{name}_without_before_each_method"
    @__last_methods_added = [name, with, without]
    define_method with do |*args, &block|
      before_each_method name
      send without, *args, &block
    end
    alias_method without, name
    alias_method name, with
    @__last_methods_added = nil
  end
end

class SubclassA < Superclass
  def my_new_method
    p :my_new_method
  end

  def my_new_other_method
    p :my_new_other_method
  end
end

SubclassA.new.my_new_method
SubclassA.new.my_new_other_method

Это создаст метод обертки, используя метод alias_method_chaining, как только метод, который вы хотите обернуть, определяется в подклассе.

Ответ 2

Это мое решение:

require 'active_support/all'

module BeforeEach
  extend ActiveSupport::Concern

  module InstanceMethods
    def before_each
      raise NotImplementedError('Please define before_each method')
    end
  end

  module ClassMethods
    def method_added(method)
      method = method.to_s.gsub(/_with(out)?_before$/, '')
      with_method, without_method = "#{method}_with_before", "#{method}_without_before"

      return if method == 'before_each' or method_defined?(with_method)

      define_method(with_method) do |*args, &block|
        before_each
        send(without_method, *args, &block)
      end
      alias_method_chain(method, :before)
    end
  end
end

Чтобы использовать его, просто включите BeforeEach в свой класс:

class Superclass
  include BeforeEach

  def before_each
    puts "Before Method" #this is supposed to be invoked by each extending class' method
  end
end

class Subclass < Superclass
  def my_method
    #when this method is called, before_each_method method is supposed to get invoked
  end
end

Subclass.new.my_method

# => Before Method

Надеюсь, это сработает для вас!

Ответ 3

class BalanceChart < BalanceFind
  include ExecutionHooks

  attr_reader :options

  def initialize(options = {})
    @options = options
    @begin_at = @options[:begin_at]
  end

  def months_used
   range.map{|date| I18n.l date, format: :month_year}.uniq!
  end

  before_hook :months_data, :months_used, :debits_amount
end

module ExecutionHooks

  def self.included(base)
   base.send :extend, ClassMethods
  end

  module ClassMethods

    def before
      @hooks.each do |name|
        m = instance_method(name)
        define_method(name) do |*args, &block|  

          return if @begin_at.blank? ## the code you can execute before methods

          m.bind(self).(*args, &block) ## your old code in the method of the class
        end
      end
    end

    def before_hook(*method_name)
      @hooks = method_name
      before
    end

    def hooks
      @hooks ||= []
    end
  end
end