Override rails helper route methods to set layout from a GET parameter - ruby-on-rails

I'm trying to change the layout of my application according to a GET parameter passed in the URL of each request : &layout=name_of_the_layout.
And in my application_controller :
class ApplicationController < ActionController::Base
layout :get_layout_from_params
private
def get_layout_from_params
if params['layout']
params['layout']
else
'application'
end
end
end
It works fine, but to "persist" the layout when the the user navigates in the application, I need to add this parameter on each rails route helper in my views (even for POST requests in forms…):
ressource_path(#ressource, :layout => get_layout_from_url())
where get_layout_from_url() is a helper that checks if the params['layout'] is set in the URL, validates then returns it.
This is definitely not DRY... How can i override every route helper to include this behavior without writing any additional code in my views? I would like to call the standard rails methods in my views : ressource_path(#ressource), ...
Or is there a smarter way to achieve this?
PS : Im using rails 3.2.3
Thanks !

There was the old and deprecated default_url_options now replaced by url_options :
class ApplicationController < ActionController::Base
def url_options
{ :layout => validate_layout }.merge(super)
end
def validate_layout
# some stuff reusable
params[:layout]
end
end
If it doesn't fit, it is close.

I think you would better store it in session instead of appending it to the url for every request.
In rails, you could use session in a very simple way:
def get_layout_from_params
if params['layout']
session['layout'] = params['layout']
else
session['layout'] || 'application'
end
end
If there is a params['layout'], it means the user is going to change the layout, so you assign it to the session.
If there is no params['layout'] given, then it checks if there is session['layout'], return it or return 'application' if session['layout'] is false or nil.
--- edit ---
the following version even shorter, see if you like it or not:
def get_layout_from_params
session['layout'] = params['layout'] || session['layout'] || 'application'
end

Related

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

Disable auto rendering in 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

Custom global exception & error handler in Rails 4

What is the correct way to assign a controller action or middleware to handle all exceptions (globally) and strict parameter errors in Rails 4?
Keep in mind, I don't want to do it just for one controller.
Well, the obvious solution is the application controller (/app/controllers/application_controller.rb), should work fine if your controllers have the 7 INCSEUD actions.
Use exceptions_app.
application.rb
class Application < Rails::Application
...
config.exceptions_app = lambda do |env|
ExceptionController.action(:render_error).call(env)
end
...
end
exception_controller.rb
class ExceptionController < ActionController::Base
layout 'application'
def render_error
#exception = env["action_dispatch.exception"]
#status_code = ActionDispatch::ExceptionWrapper.new(env, #exception).status_code
render :error_page, status: #status_code, layout: true
end
end
Taken from Rails 3.2 error handling with exceptions_app (Example)

Rails for ActionMailer - How to disable a Layout for a certain mailer

I have a user_mailer with a layout.
For one of my actionmailer methods I want the mailer to NOT use the default layout. But I can't see to find a setting for No Layout.
Any ideas?
Simply specify in your mailer:
layout false
You can also append :only => my_action (or :except) to limit the methods it applies to, like so:
layout false, :only => 'email_method_no_layout'
(applicable API documentation)
I did it by using a small function, looking at the action name and return the correct mailer layout to be used:
class TransactionMailer < ApplicationMailer
layout :select_layout
def confirmation_email contact
#code
end
private
def select_layout
if action_name == 'confirmation_email'
false
else
'mailer'
end
end
end
The layout method can accept the name of a method; use the method to determine whether to show a layout and return that name or false.
layout :choose_layout
...
private
def choose_layout
if something
return false
else
return 'application'
end
end
You could also be very sketchy and do this before the mail( ) call at the end of the specific mailer action instead:
#_action_has_layout = false

Rails - Flow control question, is there a better way?

I am trying to lock-down a few controllers based on role and the 'posts' controller by whether or not they ANY permissions assigned. This appears to be working, but I'm wondering if there is a clean way to handle this. This is what I have in the application controller, which I'm calling as a before filter...
if controller_name == 'users' || 'accounts'
unless #current_user.master? || #current_user.power?
render :template => "layouts/no_content"
end
elsif controller_name == 'posts'
unless #current_user.permissions.count > 0
render :template => "layouts/no_content"
end
end
Thanks in advance.
You shouldn't make a code snippet that checks for a controller name to take a specific action in application.rb. You should define that before filters only in the controllers that need them
Make 2 methods in ApplicationController:
private
def require_master_or_power_user
unless #current_user.master? || #current_user.power?
render :template => "layouts/no_content"
end
end
def require_some_permisions
unless #current_user.permissions.count > 0
render :template => "layouts/no_content"
end
end
Now add this as a before filter where you need it:
class UsersController < ApplicationController
before_filter :require_master_or_power_user
...
end
class AccountsController < ApplicationController
before_filter :require_master_or_power_user
...
end
class PostsController < ApplicationController
before_filter :require_some_permisions
...
end
So the ApplicationController defines the filters, but its up to your other controllers whether or not to actually use those filters. A superclass like the ApplicationController should never conditionally branch its execution based on its subclasses. Choosing when to use the provided behaviours are one of the reasons why you want to subclass in the first place.
It's also much clearer from a code readability standpoint. When looking at the UsersController, its immediately obvious there is some permission stuff happening when you see a before filter with the name like "require_something". With your approach, you can't tell that from looking at the users controller code itself at all.
I would strongly suggest you adhere to MVC and OOP and move as much of the user related logic back into the User model like this:
class User < ActiveRecord::Base
def has_permission?
true if self.master? || self.power? || (self.permissions.count > 1)
end
then you could just use one filter in application.rb:
protected
def check_template
render :template => "layouts/no_content" if current_user.has_permission? == true
end
and call that with a before_filter as suggested by Squeegy, either in the respective controllers, or site wide in application_controller.rb
before_filter :check_template
This approach is obviously a little cleaner and a lot less brittle if you ever decide to change the scope of what gives people permission, you only have to make one change application wide.
I would advise that you use an ACL system for this: http://github.com/ezmobius/acl_system2
A short little handwritten DSL. Haven't even checked the code for syntax errors, but you'll get the picture. In your application controller:
before_filter :handle_requirements
def self.requirement(*controllers, &block)
#_requirements ||= {}
#_requirements[controllers] = block
end
def handle_requirements
return unless #_requirements
#_requirements.each do |controllers, proc|
if controllers.include?(controller.controller_name)
restrict_access unless instance_eval(&block)
end
end
end
def restrict_access
render :template => "layouts/no_content"
end
Usage (also in your application controller)
requirement('users', 'accounts') do
#current_user.master? || #current_user.power?
end
Or, just use the ACL system Radar mentions.
Another plugin worth a look is role requirement, which I've been using. I think they can both do roughly the same things.
Here is a plug for RESTful_ACL; an ACL plugin/gem I've developed, and is being pretty widely used. It give you freedom to design your roles as you see fit, and it very transparent.

Resources