Disable auto rendering in rails - ruby-on-rails

What I need is to disable the automatic page (HTML) rendering in rails and override it with a after_action method. What I'm trying to achieve is an equivalent of CakePHP $this->autoRender = false;
application_controller.rb
class ApplicationController < ActionController::Base
after_action :custom_render
layout nil # Tried this but didn't worked
def custom_render
render #[...]
end
end
some_controller.rb
class SomeController < ApplicationController
def index
# No rendering here
end
end
As shown in the code I tried to add a layout nil to prevent all actions from rendering, but that doesn't seem to affect the behaviour of the action.

Haven't checked whether it works with Rails 4, but this patch works for Rails 5.
According to the code of BasicImplicitRender and ImplicitRender, send_action of is BasicImplicitRender responsible for calling default_render
Documentation says:
For API controllers, the implicit response is always 204 No Content.
For all other controllers, we use ... heuristics to decide whether to
render a template, raise an error for a missing template, or respond with
204 No Content ...
So I suppose redefining default_render method will serve you purpose.
In your controller:
def a
# uses `default_render` unless you call `render` method explicitly
end
def b
render plain: 'Custom text for b' # `default_render` won't be called
end
private
# This does the trick
#
def default_render
render plain: 'Text'
end
You may also hack send_action just like it is done in Rails so as to even skip default_render call at all:
module ActionController
module BasicImplicitRender # :nodoc:
def send_action(method, *args)
# super.tap { default_render unless performed? }
super
end
end
end

To disable rendering (well return nothing) issue.
def index
render :nothing
end
But it's too late to do anything, as it will return response with empty body.
To disable layout:
def index
render layout: false
end
This will render you view without a layout, issue (render layout: 'my_custom_layout') to render default view but with different layout.
We don't know what you want, but the simplest solution is just to render a specific view, f.i.:
def index
render 'my_custom_file.'
end
There are really many options: http://guides.rubyonrails.org/layouts_and_rendering.html#using-render
EDIT - as requested in a comment
class ApplicationController < ActionController::Base
before_action :set_user_template
# ...
def set_user_template
template_name = current_user.template_name
self.class.layout "#{template_name}/application"
end
end

Related

Ruby/Rails: Why is render json: {hello: 'world'} hitting my database?

I have an action:
def test
_process_action_callbacks.map { |c| pp c.filter }
render json: {hello: 'world'}
end
That for some reason is calling my current_user function defined in my application controller.
At first I thought it was a before action that was calling my current_user function (hence _process_action_callbacks). But after stripping away all of my before actions the call remained. The only two before actions are part of rails:
:clean_temp_files
:set_turbolinks_location_header_from_session
I used caller to see where my method was getting called from. Here's the stacktrace (and method declaration):
def current_user
pp caller
# get the current user from the db.
end
As you can see, the current_user function is being called by the serialization_scope method in the serialization class. How do I prevent it from calling my current_user function?
Your tag indicates you are using active-model-serializers. By default current_user is the scope. To customize the scope, defined in the application-controller, you can do something like
class ApplicationController < ActionController::Base
serialization_scope :current_admin
end
The above example will change the scope from current_user (the default) to current_admin.
In your case, you probably just want to set the scope in your controller (I assume it is called SomeController ;) ) you can write
class SomeController < ApplicationController
serialization_scope nil
def test
render json: {hello: 'world'}
end
end
See for complete documentation: https://github.com/rails-api/active_model_serializers/tree/0-9-stable#customizing-scope

How to handle multiple layout in Rails

we have lots of panel in out Application like admin , teacher principal , student , parent etc .
Each panel have its own layout
So upon login we handle this using WelcomeController
class WelcomeController < ApplicationController
def index
respond_to do |format|
format.html do
return render :home if current_user.nil?
return render :admin if current_user.super?
return redirect_to("/student/lesson") if current_user.student?
return redirect_to("/teacher/lesson") if current_user.teacher?
return render "layouts/principal" if current_user.principal?
return render "layouts/coordinator" if current_user.coordinator?
return render "layouts/viceprincipal" if current_user.viceprincipal?
return render "layouts/parent" if current_user.parent?
end
end
end
end
So right now for getting data from controller we redirect to his route Like for Student
return redirect_to("/student/lesson") if current_user.student?
but we wants that on URL / we get data from controller .
So my problem is how to get data ? So we can use in views
I am new to Rails , if I am using something wrong Please let me know . Will I get data from Model ?
In routes we use
get '/student/lesson', to: 'student_lesson_plan#index', as: 'student_lesson'
And from index Action we have variables which we use . So I want
instead of
return redirect_to("/student/lesson") if current_user.student?
something like this
return render "layouts/student" if current_user.student?
And I can use those variables which I initialize in student_lesson_plan#index or from another place
In my application I would set something like this to get different layout depending on conditions:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
layout :layout_by_user_types # works like a before action
def layout_by_user_types
if current_user.student?
"students/application"
elsif current_user.other_condition?
"other_name_space/application"
else
"application"
end
end
end
In my views folder I would separate the different layouts so I can call different css/js if needed ...
-views
-layouts
-students
-_my_partials.html.erb
-application.html.erb
-other_users
-_my_partials.html.erb
-application.html.erb
....
The basic understanding of how we get info from models to views thanks to the controllers:
In a controller
class StudentController < ApplicationController
def_index
#my_var_i_want_in_my_view = Student.my_query_to_database
#my_var_i_want_in_my_view_too = Student.my_super_action_that_will_give_some_data_and_that_is_a_method_in_my_model
end
end
Then in the view you can grab and use #my_var_i_want_in_my_view

Filter to execute before render but after controller?

Suppose I have some logic in a base controller to pass information to the view to build something like a breadcrumb:
class ContextAwareController < ApplicationController
after_filter :build_breadcrumb
def build_breadcumb
#...
end
end
I want this build_breadcrumb method to run after the main controller logic, but before the view is rendered.
The above code runs too late, but a before_filter would be too early.
Can anybody suggest a way to accomplish this without explicitly calling build_breadcumb at the end of each of the actions in the child controllers?
Thanks
I had the same problem and solved it like this:
class ApplicationController < ActionController::Base
def render *args
add_breadcrumbs
super
end
end
There are also some gems to achieve this. One of them is rails3_before_render.
It works similarly to filters, for example:
class PostsController < ApplicationController
before_render :ping, :except => [:destroy]
def index; end
def new; end
def show; end
def destroy; end
private
def ping
Rails.logger.info "Ping-Pong actions"
end
end
(code snipped copied from gem documentation)
I believe rendering starts when render is called, and there's no default way to defer it. Here's one thing you could do:
filters are applied in the same order declared. So make a second after-filter that calls render with an array args stored in a class variable. Then anywhere you would normally call render, set the variable.
If we're overriding render, we're not really using the filter chain at all, so it might be simpler to determine which action we're in using the #_action_name.
StuffController < ApplicationController
def my_filter
# Do the stuff
end
def render(*args)
my_filter if #_action_name == "show"
super
end
end
You can use alias_method_chain like this
alias_method_chain :render, :before_render_action
this will create 2 methods :render_with_before_render_action and :render_without_before_render_action. If you call render, then :render_with_before_render_action will be called. You can override this method
def render_with_before_render_action(*options, &block)
<your code>
render_without_before_render_action(*options, &block)
end
If you don't want your code to be executed and you want to have default render then you should directly call the :render_without_before_render_action
You can do like this to fake a before_render:
class StuffController < ApplicationController
before_filter :my_filter, only: [:index, :show]
def my_filter
#my_filter = true
end
def _my_filter
# Do the actual stuff here
end
def render(*args)
_my_filter if #my_filter
super
end
end
Thanks to #joshua-muheim for the tip about using render

Generics Example Question

I am fairly new to Ruby on Rails and as a C# developer, when I want to re-use code (for a repository class), I could put it into a base class of type <T> to be able to do something like this:
public virtual IEnumerable<T> GetAll()
{
return Context<T>.GetAll();
}
If I need to do any custom logic, I could, of course, override the method in my 'User' repository.
In Ruby, I am familiar that you can do this:
class UsersController < ApplicationController
This will allow access to all methods in ApplicationController and it's parent classes. When using scaffolding, it generates the following method in each of my child classes:
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #users }
end
end
What I end up with is 10 classes that have the same method, but the only difference is 'User.all', 'Post.all', etc.
How would I make this method generic so I can put it in my ApplicationController class?
Thanks for any assistance you can provide to a Ruby on Rails newbie.
The first thing to realize about the scaffolding code is that it can be abreviated, as such:
def index
#users = User.all
end
unless you intend to deliver the view in another format, like json, html, pdf, the respond_to block is unnecessary. If you still feel the need to dry up this method, you could do something like
# app/controllers/concerns/autoload_records.rb
module AutoloadRecords
included do
before_action :load_records, only: :index
before_action :load_record, only: [:create, :show, :edit, :update, :destroy]
end
private
def load_records
#records = model_class.all
end
def load_record
#record = model_class.find(params[:id])
end
def model_class
klass = self.class.to_s[/\A(\w+)sController\Z/,1] #=> get the name of the class from the controller Constant
Object.const_get(klass)
end
end
and write your controller like
class UsersController < ApplicationController
include AutoloadRecords
def index
#records # => #<ActiveRecord::Relation[...]>
end
def show
#record # => #<User ...>
end
def non_rest_action
#record # => nil
#records # => nil
end
end
Rather than doing an eval where you really don't want to be doing one. Check out Jose Valim's Inherited Resources gem. It provides the standard CRUD methods for all of your controllers and is quite sophisticated. It is also thoroughly tested so you don't have to worry about making sure your generic code operates as expected in all cases.
For details on how to use it see the GitHub page linked.
Maybe a simple solution could be to rely on mixins.
You define a module,
module MyModule
def my_index(klass)
#elements = klass.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #elements }
end
end
end
Then, you have in your controller,
include MyModule
def index
my_index(User)
end
Of course, you need to use #elements in your views. If you want a different variable name in each view you can do
def my_index(klass, var_name)
self.instance_variable_set(var_name, klass.all)
...
end
There are several rails plugins that help to reduce this kind of duplication. This one was covered in railscast episode 230.
https://github.com/josevalim/inherited_resources
Based on my experience, you rarely end up with 10 index action looking like #user = User.all. If you know in advance that some actions between different models will be identical - well then may be it makes sense to extract common logic. But then again may be these models are somehow connected? I wouldn't say in advance that Post and User will have identical index actions.
For a short method like this I wouldn't try to eleminate repetition because you may end up losing readability.

Set layout from responder?

I am trying to figure out how to set the layout from a custom made responder. I want to use the request.xhr? to set the layout for render to 'ajax'. Does anybody know how to do that?
I am using Rails 3 and I have a responder like this:
module AjaxLayoutResponder
def to_html
if request.xhr?
# do something here to change layout...
end
super
end
end
It seem to me like a responder is the best way to accomplish this 'ajax' layout switching.
I disagree that a responder is the way to go. Here's an easy solution that I use in most of my projects (however I just set the ajax layout to nil):
In application_controller.rb
layout :set_layout
def set_layout
request.xhr? 'ajax' : 'application'
end
you could simply do this:
module AjaxLayoutResponder
def to_html
if request.xhr?
options[:layout] = 'ajax'
end
super
end
end
because what is called at the end of responder execution is:
# from https://github.com/plataformatec/responders/blob/master/lib/action_controller/responder.rb
def default_render
if #default_response
#default_response.call(options)
else
controller.render(options)
end
end

Resources