views:

298

answers:

2

Can anyone explain where render() comes from in ActionController::Base?

I managed to trace it down only that far:

ActionController::Base includes ActionController::Rendering module where render() method is defined. This definition however calls to render() of the superclass. Superclass is ActionController::Metal. Which in its turn inherits from AbstractController::Base. None of those have render () either defined or included.

Now, presumably it comes from AbstractController::Rendering, but I'm really missing how it gets included.

A: 

render is defined in AbstractController::Rendering. It calls render_to_body, which in turn dispatches to other methods. It's a bit of a rabbit hole, isn't it?

Jordan
I don't see how this answers the original question.
artemave
+4  A: 

The render method you call in your action is defined in ActionController::Base.

def render(action = nil, options = {}, &blk)
  options = _normalize_options(action, options, &blk)
  super(options)
end

This method passes the call to super which calls the render method defined in ActionController::Rendering.

def render(options)
  super
  self.content_type ||= options[:_template].mime_type.to_s
  response_body
end

ActionController::Rendering is effectively a Module, mixed into the ActionController::Base class at the beginning of the base.rb file.

include ActionController::Redirecting
include ActionController::Rendering # <--
include ActionController::Renderers::All

In turns, ActionController::Rendering includes AbstractController::Rendering as you can see in the ActionController::Rendering module definition.

module ActionController
  module Rendering
    extend ActiveSupport::Concern

    included do
      include AbstractController::Rendering
      include AbstractController::LocalizedCache
    end

AbstractController::Rendering provides a render method which is the final method invoked in the render chain.

# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
# Delegates render_to_body and sticks the result in self.response_body.
def render(*args)
  if response_body
    raise AbstractController::DoubleRenderError, "Can only render or redirect once per action"
  end

  self.response_body = render_to_body(*args)
end

The full chain is

AbstractController::Base#render 
--> super() 
--> ActionController::Rendering#render
--> super()
--> AbstractController::Rendering#render
--> render_to_body
Simone Carletti
"super invokes a method with the same name as the current one, in the superclass of the current class"ActionController::Rendering is NOT A PARENT CLASS for ActionController::Base. It is INCLUDED MODULE. So how come super can work the way you described?
artemave
When the class A includes the module B, B becomes an ancestor of A. See `Object#ancestors`. When you call `super` in A, it searches for a method with the same name in all ancestors of class A, including B. In Ruby you can extend a Class by Inheritance and/or using Mixins.
Simone Carletti
This is certainly a gotcha of the day.
artemave
So if a class includes two modules with method a() defined, only one (from last included module) gets imported. Or, better, first module you include defines a() which you rely on and the second module includes some other module which includes some other module which includes some other module with method a(). One tough debugging session guarantied. This smells so bad. Probably deserves its own question - it is interesting how people get around this.
artemave
If both module `A` and `B` defines `foo()` and you include `B` and `A` into `C` which defines its on `foo()`, there's probably something wrong at architectural level in your code. Ruby silently allows you to do so but it doesn't mean you should. ;)
Simone Carletti
No no no, David Blaine! You include some library module with only foo() defined. This module includes other module which has got bar() defined. You don't know about it. And now, all of a sudden, super inside bar() defined in your class goes mental.
artemave