6

Is it possible to call a before_action before some specified method like in rails?

class Calculator
  before_action { raise Exception, "calculator is empty" if @numbers.nil? }, 
                 only: [:plus, :minus, :divide, :times]

  def push number
    @numbers ||= [] 
    @numbers << number
  end

  def plus
    # ... 
  end

  def minus
    # ... 
  end

  def divide
    # ... 
  end

  def times
    # ... 
  end

    # ... 
end
sawa
  • 165,429
  • 45
  • 277
  • 381
MrPizzaFace
  • 7,807
  • 15
  • 79
  • 123
  • 1
    of course you can, you just need to implement it :-). Consider reading this question http://stackoverflow.com/questions/5513558/executing-code-for-every-method-call-in-a-ruby-module – xlembouras May 03 '14 at 13:23
  • You could just `include ActiveSupport::Callbacks` if you don't mind installing the Rails gem. You can find examples of that at http://www.intridea.com/blog/2012/3/22/define-custom-callbacks-for-activerecord-and-more. – subvertallchris May 03 '14 at 13:25

4 Answers4

10

It can be done with pure Ruby! One way to go is to use method aliasing

class Foo
  def bar
    #true bar
  end

  alias_method :original_bar, :bar

  def bar
    before_stuff
    original_bar
    after_stuff
  end
end

but for a more general approach you could read this thread.

An example for your code can be:

class Calculator

    def plus
      # ...
    end

    def end
      # ...
    end

    def divide
      # ...
    end

    def times
      # ...
    end

    [:plus, :minus, :divide, :times].each do |m|
      alias_method "original_#{m.to_s}".to_sym, m

      define_method m do
        check_numbers
        send("original_#{m.to_s}".to_sym)
      end
    end

    private

    def check_numbers
      raise Exception, "calculator is empty" if @numbers.nil? 
    end
end
ggorlen
  • 44,755
  • 7
  • 76
  • 106
xlembouras
  • 8,215
  • 4
  • 33
  • 42
5

What you are looking for is Aspect oriented programming support for ruby. There are several gems implementing this, like aquarium.

I think though, that in your case, some lazy checks will be sufficient:

class Calculator
  def numbers
    raise Exception, "calculator is empty" if @numbers.nil?
    @numbers
  end

  def push number
    @numbers ||= [] 
    @numbers << number
  end

  def plus
    numbers.inject(:+) # <-- will throw the correct Exception if `@numbers` is nil
    # ... 
  end

  def minus
    # ... 
  end

  def divide
    # ... 
  end

  def times
    # ... 
  end

    # ... 
end
Uri Agassi
  • 36,848
  • 14
  • 76
  • 93
2

You can include the ActiveSupport::Callbacks from active_support and define whatever callbacks you need:

Example from the documentation:

class Record
  include ActiveSupport::Callbacks
  define_callbacks :save

  def save
    run_callbacks :save do
      puts "- save"
    end
  end
end

class PersonRecord < Record
  set_callback :save, :before, :saving_message
  def saving_message
    puts "saving..."
  end

  set_callback :save, :after do |object|
    puts "saved"
  end
end

person = PersonRecord.new
person.save

# Output: 
# saving...
# - save
# saved
spickermann
  • 100,941
  • 9
  • 101
  • 131
0

You could write your own logic, so it is possible. But I don't think you need to in this case. Ruby gives you a tool to deal with that already. You can use the class' initialize method, which is called when the class is first initialized: For example, Calculate.new

class Calculator
  def initialize(inputs)
    raise Exception, "calculator is empty" unless inputs
  end

  # ... rest of the class
end

You will need inputs for any math operation, so why not add an init method? If no numbers are passed in, it's pretty hard to do any math.

Beware of porting Rails controller logic to pure Ruby. Rails controllers in themselves are not your typical OOP, and they violate a lot of OOP rules. Ruby is far more flexible.

Mohamad
  • 34,731
  • 32
  • 140
  • 219