4

I'm trying to decorate a controller from another rails engine. I have one controller method that I want to extend with just one more line. I rather not duplicate the whole original controller method.

This is what I tried:

  Backend::BaseContentsController.class_eval do
    def booking_update
      # do some stuff
      update
    end
    alias_method :update, :booking_update
  end

Unfortunately this throws the exception stack level too deep. Normally with inheritance I could just call super. What would be ideal to do in my case?

dan-klasson
  • 13,734
  • 14
  • 63
  • 101
  • 1
    See this question for a discussion of some of the options: [*When monkey patching a method, can you call the overridden method from the new implementation?*](http://stackoverflow.com/a/4471202/2988) – Jörg W Mittag Dec 16 '15 at 08:52
  • I think it would make sense to close your question as a duplicate of that question, but I wanted to get your input first: do you believe the answers on that other question answer your question? – Jörg W Mittag Dec 16 '15 at 08:55
  • 1
    @JörgWMittag: Maybe, I haven't tried it yet. I am not sure if I can just implement the delegation example and call `WrappedFoo.new(Backend::BaseContentsController)`. IMHO the linked answer is rather generic. Perhaps better creating a new answer that references that one, explaining why `alias_method_chain` should not be used. – dan-klasson Dec 16 '15 at 09:00

4 Answers4

6

You should try alias_method_chain:

def update_with_booking
  # do some stuff
  update_without_booking # that's your old update
end

alias_method_chain :update, :booking
Marek Lipka
  • 50,622
  • 7
  • 87
  • 91
3
module Decorator
  def update
    # do some stuff
    super
  end
end
Backend::BaseContentsController.prepend(Decorator)
sawa
  • 165,429
  • 45
  • 277
  • 381
  • This is probably the better implementation, it's more explicit. But I want to follow conventions so I went with the accepted answer. – dan-klasson Dec 16 '15 at 08:31
  • 1
    @dan-klasson this convention was invented when no `prepend` existed. FYI: `alias_method_chain` will create an alias, what in general might lead to weird problems in the future, when other piece of code would try to do the same. The solution above is bullet-proof. Rails is often too silly to follow it’s conventions. – Aleksei Matiushkin Dec 16 '15 at 08:45
  • @mudasobwa: I meant the conventions on this legacy app I am working on. Thanks for the heads up though. – dan-klasson Dec 16 '15 at 08:48
2

You have defined an infinite recursion. The result is the following code snippet.

def update
  # do some stuff
  update
end

Ensure that your alias doesn't override a method you still using.

Backend::BaseContentsController.class_eval do
  alias_method :update_original, :update

  def booking_update    
    # do some stuff
    update_original
  end

  alias_method :update, :booking_update
end
sschmeck
  • 7,233
  • 4
  • 40
  • 67
0

Explanation:

When both alias_method and Module#prepend are used on the same method stack level too deep error occurs.

When both are used simultaneously both tries to find the actual definitions but gets others definition, now both of them tries to put their definition on the top of the stack which causes too many stack entries and gives stack level too deep error.

Solution:

Avoid use of alias_method and alias_method_chain as these are already deprecated. Instead of these use Module#prepend for the purpose. Module#prepend is added in Ruby 2.0

Ref:

https://blog.newrelic.com/engineering/ruby-agent-module-prepend-alias-method-chains/ https://docs.newrelic.com/docs/agents/ruby-agent/troubleshooting/systemstackerror-stack-level-too-deep https://ethigeek.com/blogs/mutually-exclusive-alias-method-and-prepend

Prateek Sen
  • 392
  • 2
  • 9