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.
Related
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.
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.
I'm serving a versioned web service from Rails.
I would very much like to be able to call render like normal:
render 'index'
And have it correctly serve the requested version from the following:
index.v1.json.jbuilder
index.v2.json.jbuilder
index.v3.json.jbuilder
Assuming that I already know the requested version within the context of the controller action execution, how do I get render() to leverage it?
I have used the versioncake gem
You should definitely check this out. File name will be very close to what you have:
index.v1.json.jbuilder
would be
index.json.v1.jbuilder
Sounds like a builder design pattern might work here. Have a view builder object that returns the desired behavior.
module ViewBuiler
def build(name, api_version)
View.new(name, api_version).build
end
class View < Struct(:name, :api_version)
def build
[name, api_version, 'json', 'jbuilder'].join('.')
end
end
end
and in your controller you could just do something like:
ApplicationController
include ViewBuilder
end
MyController < ApplicationController
def index
...
# you can pass either strings or symbols to build and it will
# return 'index.v1.json.jbuilder'
render build(:index, params[:api_version])
end
end
And disclaimer, this is just an idea and not something I've implemented. I like the approach for 2 reason. The Controller actions remain skinny and don't have logic in it. A builder like this seems pretty easy to test. And it keeps the thing that might change (views etc) isolated into something that can change frequently as long as it retains it's interface that the Controllers will work with.
This seems like a candidate for Variants. This is new to 4.1 though.
class MyController < ActionController::Base
before_action :set_variant
def my_action
.....
respond_to do |format|
format.json do |json|
json.v1 do
# render whatever you need here
end
end
end
end
protected
def set_variant
request.variant = :v1 if request.params[:version] == "v1"
....
end
end
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
I am using Ruby on Rails 3 and I am trying to understand the behavior of the before_filter method in a controller.
In my controller I have
class UsersController < ApplicationController
before_filter :authorize
def show
...
end
end
If I browse, for example, the page http://<my_web_site>/user/1 (that loads the users/show.html.erb view file populated of data from the User with ID 1) the before_filter works as well. That is, the authorize method does what it must do.
If I render the users/show.html.erb view file as a template for another controller (example: the PostsController) this way
# This code is in the `post/show.html.erb` file
<%= render :template => "/users/show", :locals => { :user => #user } %>
the before_filter doesn't work. That is, the authorize method seams do not run.
Why?! There is a reason for that behavior or I am wrong somewhere?
UPDATE (after the #brad comment)
Are you rendering that view as a
partial template from within the users
controller? If not the before_filter
won't apply
If it is as #brad say in his comment, how can I make the before_filter to work rendering that view for another controller than UsersController?
Move authorize method to ApplicationController
Add before_filter to each controller where you want to check user authorization.
When you render a template or a view file/partial, it is not really treated as a request on your controller, hence the filters dont apply.
Firstly you should understand the routing in the Rails. When you type in the browser http://<my_web_site>/user/1 then it goes to route file, after this to proper controller's action, after that controller initiate the render view. And controller has these callbacks, when some action is initiated then these callbacks should act before or after controller's action. So in your case you're calling partial template without any controller's involving
before_filter applies to controller actions, not rendering actions.
One solution, then, is to abstract your authorization logic out into a helper that can be used when you're rendering your partial:
if authorized?
render :partial => 'users/show'
end
Another solution is to implement authorization at the model level, using something like the declarative_authorization gem (https://github.com/stffn/declarative_authorization)