Custom global exception & error handler in Rails 4 - ruby-on-rails

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)

Related

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

Rails: Undefined method `flash' for ActionDispatch::Request

I'm trying to write an Ember application in Rails 4, and have decided to go with rails-api for the api controllers, while keeping the application controller intact for a few pages that aren't part of the single-page app. To put it in more concrete terms, here are my controllers:
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
end
app/controllers/sample_controller.rb:
class SampleController < ApplicationController
# my methods
end
app/controllers/api/v1/api_controller.rb:
class Api::V1::ApiController < ActionController::Api
include ActionController::MimeResponds
end
app/controllers/api/v1/sample_controller.rb:
module Api::V1
class SampleController < ApiController
respond_to :json
# my methods
end
end
My application.html.slim contains the following line:
== render partial: "flash_msgs" unless flash.blank?
The inclusion of which results in the following error:
undefined method 'flash' for #< ActionDispatch::Request:0x007f99f41d8720 >
Per discussion on this thread, it seems that the culprit could be rails-api, but I'm not entirely convinced given the inheritance I've set up. Any suggestions?
Not sure but maybe you need to include the ActionDispatch::Flash middleware to support the flash. Using:
config.middleware.use ActionDispatch::Flash
The docs says:
ActionDispatch::Flash: Supports the flash mechanism in
ActionController.
I hope it helps
See: https://github.com/plataformatec/devise/issues/2775
Inside devise.rb change
config.navigational_formats = ['*/*', :html]
to:
config.navigational_formats = [:json]
or just [ ]
If you're like me and creating an API on top of an existing application, you can add this to your config/application.rb file:
config.api_only = false
well, in my case my API mode rails app I had this code in one of my controller:
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
due to which handle_unverified_request was getting called that has this small piece of code request.flash = nil which was raising Undefined method 'flash' for ActionDispatch::Request for me.
Dealt with it by replacing protect_from_forgery with skip_before_action :verify_authenticity_token

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

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

How can I log Rails errors into a separate log file?

Our production logs are long and contain a lot more than just errors. I'd like a second log file with just the errors/exceptions in.
Is this possible?
We're using rails 2.x
Thanks.
For example, to log all ActiveRecord::Base errors in a file called log/exceptions.log
new_logger = Logger.new('log/exceptions.log')
new_logger.level = Logger::ERROR
new_logger.error('THIS IS A NEW EXCEPTION!')
ActiveRecord::Base.logger = new_logger
For controllers and view(because ActionView logger doesn't have it's own logger, so it depends on the ActionController logger):
ActionController::Base.logger = new_logger
Try the following. Put the rescue_from method in your controller.
I haven't tested this. But maybe it puts you in the right direction
class ApplicationController < ActionController::Base
rescue_from StandardError do |exception|
new_logger = Logger.new('log/exceptions.log')
new_logger.info('THIS IS A NEW EXCEPTION!')
new_logger.info(exception.message)
new_logger.info(exception.backtrace)
# Raise it anyway because you just want to put it in the log
raise exception
end
end
If you use Rails 2.1 (also not tested)
class ApplicationController < ActionController::Base
def rescue_action_in_public(exception)
new_logger = Logger.new('log/exceptions.log')
new_logger.info('THIS IS A NEW EXCEPTION!')
new_logger.info(exception.message)
new_logger.info(exception.backtrace)
# Raise it anyway because you just want to put it in the log
raise exception
end
end

RoR Catch Exception on Application Level

For a specific role (group of users) I added the :readonly to every find on the active record
def self.find(*args)
if User.current_user.has_role? 'i_can_only_read'
with_scope({:find => {:readonly => true}}) do
result = super *args
end
end
end
Of course it raises now ActiveRecord::ReadOnlyRecord Exceptions in Controller passed on to the user; not very nice.
Can I catch this type of error in one place? Like in production.rb or in the application.rb? Or can I configure a specific error page for this error?
Yes, simply override rescue_action_in_public like this:
class ApplicationController < ActionController::Base
...
def rescue_action_in_public(exception)
case exception
when ActiveRecord::ReadOnlyRecord
# DO SOME LOGIC HERE
end
end
end
end
This will execute your action when in "production", but leave you with an informative stack trace when you are in "development".
Rails has a number of other rescue_action_* methods that might be more suitable to your problem...take a look at http://api.rubyonrails.org/classes/ActionController/Rescue.html

Resources