Checking a condition before running multiple methods. Ruby on Rails

484 Views Asked by At

Lets say I have a class with hundreds of instance methods in it. Now I have the requirement to run each method only if a certain thing is detected. Also I want to run my detection algorithm once for whole class instance no matter how many methods got called. If not detected first time no methods get called. I cannot afford if else around that many methods so I have to get a workaround. I have the following said class:

class CrawlerModule
    extend Callbacks
    before_run [:method_names, :of, :my, :class], :check_if_detected
    @detected = nil

    def check_if_detected
        if @detected.nil?
            detect
        end
        @detected
    end


    #hundreds of methods

    private

    def detect
        detected_now = #my_detection_algorithm
        @detected = detected_now
    end
end

What I have done so far is to include following Callbacks module to call my check_if_detected method before every method but it doesn't work because method_added called at the very start of program and my detect function need some things to get initialized before detection. So the result array is always nil. Here's that complete module:

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|
            result = run_callbacks_for(method_name)

            if result[0] || (self.class.callbacks.values.flatten.include? method_name)
                send(original_method_name, *args)
            end
        end
    end

    def should_override?(method_name)
        overridden_methods.include?(method_name) || method_name =~ /original_/
    end

    def before_run(method_names, callback)
        method_names.each do |method_name|
            callbacks[method_name] << callback unless method_name.eql? callback
        end
    end

    module InstanceMethods
        def run_callbacks_for(method_name)
            result = []
            self.class.callbacks[method_name].to_a.each do |callback|
                result << send(callback)
            end
            result
        end
    end
end
1

There are 1 best solutions below

2
On

This solution came to me while trying to get to sleep, so pardon the brevity and untested code.

Forget all of the callback stuff. Instead...

You could rename every method to include a prefix like prefix_method_name (or suffix if you prefer). Then implement a method_missing method which implements your check, and then calls the appropriate method afterward.

Something like this:

def method_missing(method_name, *args, &block)
  if detected_now
    send("prefix_#{method_name}")
  end
end

And then to run the detection once for the whole class instance do it in the constructor:

  def initialize
    detected_now
    super
  end

Cache the detected_now results if you wish in an instance variable as normal and work with it that way if that is something you want to do.