Rails controller methods - ruby-on-rails

I have a controller action that is calling a model method to render some data. The controller looks something like this
class WeekController < ApplicationController
def index
#week = Week.first
end
def render
Week.render
redirect_to root_url
end
end
Currently my Week.render method has a bug in it and is causing errors. Which is fine, however when I am calling my index action the page fails because of the error in my render action.
Is it normal for rails to call actions other than the one being called or is there something weird going on?

It is ok to call other actions as long as you don't call redirect_to or render multiple times in a response.
By the way render is a reserved word, and you should not use it for a custom method.

Related

Why Rails allows double rendering?

class SomeController < ApplicationController
before_action :api_limit
def new
if user.can_access_foo?
render 'foo'
end
if user.can_access_bar?
render 'bar'
end
if user.can_access_hello?
render 'hello'
end
end
def api_limit
render 'exceed_limit_error'
# code in action will not be executed.
end
end
When render or redirect_to exists in before_filter, action will not be executed. In short, there is no risk of double rendering.
See the Rails Guide on controllers :
If a "before" filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter, they are also cancelled.
However, why Rails allow double rendering in an action?
Take following code as example. a Double Render Exception will raised by Rails when user can access foo, bar, or hello.
class SomeController < ApplicationController
before_action :callback
def new
if user.can_access_foo?
render 'foo'
# From my understanding, following render should
# be ignored if this step is successfully performed.
end
if user.can_access_bar?
render 'bar'
end
if user.can_access_hello?
render 'hello'
end
end
end
Why not respond immediately and halt the request cycle when render 'foo' completes? That sounds more reasonable.
The reason why a render statement in an action doesn't return the code execution is that it is a reasonable use case that there is further (non-rendering) code to be executed after the render in the action.
The reason that a render in the before_action callback doesn't allow code execution to go into the action is because this would assume that you have actions that have code paths that neither render nor redirect (otherwise you would get a double render error). This code path in the action is a much less reasonable use case because it would rely on the "before" filter to have triggered and performed a render already.
The intention of the structure of Rails' actions and filters in the controllers is that they are not so tightly coupled. Generally a filter will not be aware of what action will run after it, and an action is not aware of what filters triggered before it ran. So to make them coordinate as to which is doing the rendering would break that loose coupling. Each action must assume that rendering is an essential part of its role, that is why it doesn't make sense for an action to run if the filter already rendered.

Render page more flexible

in my current project, have a assumption that a lot of different UI will be required.
ex> there will be two clients, boogle and yumhoo, these two client want totally different view.
I made a prototype like this,
class HomeController < ApplicationController
after_action :render_ui
def index
end
def render_ui
render "#{self.controller_name}/#{ENV['CLIENT_UI']}/#{self.action_name}"
end
end
my plan was to generate the path to the view file dynamically,
but there is a big problem here,
as you all know rails automatically runs,
render 'there own contoller and action name combination'
and throws an error,
Render and/or redirect were called multiple times in this action
Is there a way to skip the default automatic render feature in rails?
after_action is called when the view has already been rendered. If you want to render a specific file, simply call render in the action itself.
class HomeController < ApplicationController
def index
render "#{self.controller_name}/#{ENV['CLIENT_UI']}/#{self.action_name}"
end
end
You may also want to consider to use the Rails 4.1 template variants feature.

Rails render and return in before_action, DoubleRenderError

In a controller action with a before_action, using render something and return does not actually cause the controller to stop executing the rest of the action. In my testing, only when called in the controller action does and return work as I would expect.
# DoubleRenderError
class SomeController < ApplicationController
before_filter :double_render, only: [:index]
def index
render file: "public/500.html", status: :internal_server_error
end
def double_render
render file: "public/404.html", status: :not_found and return
end
end
# Renders 404 only, no error
class SomeController < ApplicationController
def index
render file: "public/404.html", status: :not_found and return
render file: "public/500.html", status: :internal_server_error
end
end
What's going on here? Can before_actions stop the rest of a controller's execution?
If your intention is to render the 404 page, you shouldn't render it manually in the before_filter. The right way of doing that is raising a routing error, like this:
raise ActionController::RoutingError.new('Not Found')
You could implement a method called "render_404", raise this exception in it and let the routing system do the rest.
EDIT: Actually, your code should work. I really don't know what is going on. I wrote the same thing here and it worked - the action method is never called if the before_filter renders something. What version of Rails are you using?
This is a pretty simple case of flow control.
Yes, when you and return from within the method, it obviously returns and doesn't reach the second render.
Nothing about rendering inside a before_filter is meant to stop the actual invocation of the action itself, and calling and return from a before_filter doesn't affect the execution of a completely separate method invocation.
This is pure Ruby flow-control, no Rails magic involved.

Conditionally render a view if it exists? How to call render from a second method in a controller?

I am trying to render a custom partial if it exists, because I am running multiple websites with one codebase. The views are generally the same but sometimes they are different enough that it's easier to render a custom view.
Does anyone know how to call render from a method like below? The problem I am running into is that you have to call return after calling render, but I have to call return in the "index" method rather than calling the return in the "render_custom_view_if_exists" method. I can't seem to find a clever way to automatically check if the view exists and render it, since I would have to manually go into every method to call a return to cancel rendering the default view.
class ApplicationController
after_action :render_custom_view_if_exists
def render_custom_view_if_exists
render "#{path}" if lookup_context.template_exists?("#{path}")
return true
end
end
class UsersController < ApplicationController
def index
#users = User.all
end
end
ERROR: Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most once per action.
Also note that neither redirect nor render terminate execution of the action,
so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
You could use append_view_path

Theoretical: method_missing

I just started to learn Rails and I cannot understand that:
In my Post controller I do not have method show (not described), but I put in my controller that:
def method_missing(name, *args)
redirect_to posts_path
end
I think that if controller couldn't find action show - it would call method_missing and after that redirect to index method, but Rails tries to render view show.html.erb.
Why is method missing not catching? How can I use method_missing?
Rails does not require action to be present in controller if corresponding template exists. It just assumes empty action and renders template, that is why your method_missing isn't invoked.
If you don't need show action anyway - just remove show.html.erb and method_missing will work as expected.

Resources