11

Just curious if anybody knows what Ruby technique is used to accomplish the following in the Rails framework.

If I don't write, say, an index method on a Rails controller, Rails will still render the index view file if the URL matches that route. That makes sense, because my controller inherits from a parent class, which must have its own index method.

However, if I do define an index method, and only tell it to set an instance variable, it still renders the appropriate view. For example:

def index
  @weasels = Weasel.all

  # If I omit this line, Rails renders the index anyway.
  # If this behavior is defined in the parent class's index method,
  # it seems that by overriding the method, my index wouldn't do it. 
  # I'm not explicitly calling super to get the method from 
  # ActionController::Base, but maybe Rails is doing something like that?
  render :index
end

In pure Ruby, I would expect to have to call super to get that behavior.

I assume that Rails uses some kind of metaprogramming technique to guarantee that my controller methods will call super. If so, can anyone explain it? Can you point to the source code that does this?

Update

As Mladen Jablanović has pointed out, my initial mental model was wrong; the controller method doesn't normally render the view; instead, both the controller method and the view rendering are called by some framework code. This is apparent because I can make a controller method with any name - for example, search - and the search view will get rendered. So clearly I'm not overriding a parent method in that case, and some framework code is parsing the controller method name and looking for the matching view.

Still, the framework must be able to detect whether or not the controller method has already called render. So that's another small mystery to me.

Community
  • 1
  • 1
Nathan Long
  • 122,748
  • 97
  • 336
  • 451
  • 1
    I don't see what `super` got to do with anything? I can define any method I want in the controller, I am not overriding any existing one. And yet, the right view will be implicitly rendered. – Mladen Jablanović Dec 20 '10 at 15:53
  • @Mladen Jablanović - good point. So the controller method must be called by some other code which takes note of the name and looks for a matching view. – Nathan Long Dec 20 '10 at 16:01
  • Must be. Not familiar with Rails architecture, but I'd expect some dispatcher to take user's request, calculates which controller/action to call, invoke appropriate method (if defined), and use appropriate view to render the output (if no explicit `render` called). – Mladen Jablanović Dec 20 '10 at 16:03
  • @skaffman - I intended the 'how-does-it-work' tag to differentiate between questions about how to accomplish something and questions about what goes on behind the scenes. I think this is appropriate for a question that attempts to demystify the workings of a framework, and I think the tag would be useful for browsing questions of this nature. I would personally love to see more questions of this type, especially about Rails. I am going to re-add the tag. If you think this is a bad idea, will you please comment explaining why? – Nathan Long Dec 20 '10 at 17:39
  • Perhaps `internals` tag would be suitable. – Mladen Jablanović Dec 20 '10 at 17:53
  • @Mladen - I didn't know that existed! Thanks - updated. – Nathan Long Dec 20 '10 at 19:10

3 Answers3

6

On the controller, there is a method called render_for_text. This takes a string and sets the result as the response body. Even if you don't render as text, rendering a view file simply reads the file's contents, evaluates it, and passes it to the render_for_text method. The method then stores sets an instance variable called @performed_render to true, which tells rails that the controller has already rendered a view.

There is then a method called performed? that indicates whether or not the render action has been called. It does this by checking if @performed_render or @performed_redirect is true.

With the above information, the very first line of the render method should make sense now, and hopefully answer your question:

raise DoubleRenderError, "Can only render or redirect once per action" if performed?

ryeguy
  • 65,519
  • 58
  • 198
  • 260
  • Excellent! Do you know where `render_for_text` normally gets called? – Nathan Long Dec 20 '10 at 17:09
  • @Nathan: Take a look at the render function in that same linked file. It's called in multiple places. Basically, the pattern is that the renderable item is converted to plain text and passed to that method. A view is evaluated to text, a json render is converted to json text, a partial is evaluated to text, etc. – ryeguy Dec 20 '10 at 19:23
1

When rendering a view, Rails initializes an instance of the appropriate view template with copies of the instance variables in the controller. So the instance variables aren't actually inherited by the view, but rather copied from the controller into the view before the view is rendered.

I haven't looked up the exact details in the source, but the above explanation should at least outline the general idea of how it works.

Pär Wieslander
  • 28,374
  • 7
  • 55
  • 54
  • I understand (vaguely) that the instance variables are copied to the view. I'm trying to understand how, if my `index` action doesn't say to do so, Rails know that it should render the `index` template at all. – Nathan Long Dec 20 '10 at 15:30
1

Jamis Buck wrote quite a bit about implicit routes a few years ago here.

I believe the code you're looking for is in Resources. Rails looks at the inbound request and (for RESTful routes) goes here to determine the controller and action if they aren't specified.

Update: Controllers are a really complex part of the Rails framework. It isn't quite as simple as a single Ruby class with a single method (which is why super isn't really needed, but a series of calls both before and after your individual method is called.

Rack handles the request and passes it to Rails which does the routing, delegates to the instance of ActionController for the action (which may or may not have been coded by you), then passes the result of that to the rendering process.

cdmwebs
  • 933
  • 7
  • 13
  • I believe that Nathan's question isn't really about routing, but rather how Rails can render the appropriate view even when there's no explicit code in the action method to make sure that the view is rendered. – Pär Wieslander Dec 20 '10 at 15:02
  • Agreed, but that happens in the Resource class linked above. Based on the route (GET /people, PUT /people/5), Rails determines which controller, action and template to render. – cdmwebs Dec 20 '10 at 15:07
  • I understand that Rails automatically uses the route to decide to run my controller's index method, and that, if I don't define index, a default index method will render the index template. What I'm trying to understand is if I override the parent class's index method, why does my index method have behavior in addition to what I defined? Where does it come from? – Nathan Long Dec 20 '10 at 15:26
  • @Nathan you know, I never thought about that part. I'd say it happens somewhere in [here](https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/rendering.rb) and I'd like to dig in to it when I have some time. Maybe tonight... – cdmwebs Dec 20 '10 at 15:51