Rails 3 Render In Rescue Block - ruby-on-rails-3

I'm creating a series of objects on the fly using transaction and exception handling. Right now it handles the rollback and everything as expected but my rescue block doesn't attempt to render the action I tell it to.
Here's my code that handles the transaction
def post_validation
ActiveRecord::Base.transaction do
begin
params[:users].each do |user|
#process each user and save here
end
redirect_to root_path #success
rescue ActiveRecord::RecordInvalid
# something went wrong, roll back
raise ActiveRecord::Rollback
flash[:error] = "Please resolve any validation errors and re-submit"
render :action => "validation"
end
end
end
What's expected upon failure: Rolls back transaction and renders the action "validation".
What's happening upon failure: Rolls back transaction and attempts to render the view "post_validation" which doesn't exist.

Well it looks like there's a few things wrong with the code I provided. For starters you don't need to bother with the raise ActiveRecord::Rollback line, Rails does this behind the scenes when an exception is thrown inside of a transaction block. In addition the transaction block needed to be inside of the begin block. So the resulting code looked something like this:
def post_validation
begin
ActiveRecord::Base.transaction do
#process some new records here
redirect_to root_path
end
rescue ActiveRecord::RecordInvalid
# handle the exception here; the entire transaction gets rolled-back
flash[:error] = "Please resolve any validation errors and re-submit"
render :action => "validation"
end
end

Related

ActionMailer debugging

I'm trying to use Action Mailer in Rails to send email to users.
def create
...
if #post.save
UserMailer.archive_confirmation(#site).deliver
end
end
But when I try it, I don't get any email, and I have no idea how to debug (since the create method runs successfully, and everything else goes as expected, there's no error message) Where could ActionMailer go wrong?
Check your RAILS log, APP_ROOT/log/development.log or APP_ROOT/log/production.log.
If I am writing the code to sent the email in controller i can write like these to handle the rescue in my application and i can display the error message that is come in rescue we can display.
I'm using something like this in the controller:
if #user.save
begin
UserMailer.welcome_email(#user).deliver
flash[:success] = "#{#user.name} created"
rescue Net::SMTPAuthenticationError, Net::SMTPServerBusy, Net::SMTPSyntaxError, Net::SMTPFatalError, Net::SMTPUnknownError => e
flash[:notice]=e.message
end
redirect_to home_index_path
end
In your respective environment file:
## config/environments/development.rb
config.action_mailer.raise_delivery_errors = true

Rspec: Test rescue block

I have a block like this:
begin
response = Facebook.make_profile_request(params[:token])
rescue => e
Airbrake.notify(
:error_class => "Facebook Processing",
:error_message => "Error: #{e.message}"
)
flash[:notice] = "Uh oh...something went wrong. Please try again."
redirect_to root_path
end
This is what I have so far:
it "should notify Airbrake if call to FB fails" do
Facebook.stub(:make_profile_request).with(fb_token).and_raise(Exception)
Airbrake.should_receive(:notify)
get :facebook_process, token: fb_token
end
I get error:
1) UsersController GET facebook_process should notify Airbrake if call to FB fails
Failure/Error: get :facebook_process, token: fb_token
Exception:
Exception
# ./app/controllers/users_controller.rb:9:in `facebook_process'
# ./spec/controllers/users_controller_spec.rb:41:in `block (3 levels) in <top (required)>'
How should I properly test rescue?
You have to specify a specific exception class, otherwise rspec will bail as soon as it detects the exception; but, here is how you can do it without rescuing from Exception (as pointed out in Nick's comment).
class MyCustomError < StandardError; end
begin
response = Facebook.make_profile_request(params[:token])
rescue MyCustomError => e
...
end
And in your spec, you should make the stub return the custom error class. Something like this:
Facebook.stub(:make_profile_request).with(fb_token).and_raise(MyCustomError)
I have faced the similar issue recently.
if you change your code
rescue => e
to
rescue Exception => e
your test case will pass.

Forcing 404 error in the right way in Rails [duplicate]

I'd like to 'fake' a 404 page in Rails. In PHP, I would just send a header with the error code as such:
header("HTTP/1.0 404 Not Found");
How is that done with Rails?
Don't render 404 yourself, there's no reason to; Rails has this functionality built in already. If you want to show a 404 page, create a render_404 method (or not_found as I called it) in ApplicationController like this:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rails also handles AbstractController::ActionNotFound, and ActiveRecord::RecordNotFound the same way.
This does two things better:
1) It uses Rails' built in rescue_from handler to render the 404 page, and
2) it interrupts the execution of your code, letting you do nice things like:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
without having to write ugly conditional statements.
As a bonus, it's also super easy to handle in tests. For example, in an rspec integration test:
# RSpec 1
lambda {
visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)
# RSpec 2+
expect {
get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)
And minitest:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
OR refer more info from Rails render 404 not found from a controller action
HTTP 404 Status
To return a 404 header, just use the :status option for the render method.
def action
# here the code
render :status => 404
end
If you want to render the standard 404 page you can extract the feature in a method.
def render_404
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
format.xml { head :not_found }
format.any { head :not_found }
end
end
and call it in your action
def action
# here the code
render_404
end
If you want the action to render the error page and stop, simply use a return statement.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
ActiveRecord and HTTP 404
Also remember that Rails rescues some ActiveRecord errors, such as the ActiveRecord::RecordNotFound displaying the 404 error page.
It means you don't need to rescue this action yourself
def show
user = User.find(params[:id])
end
User.find raises an ActiveRecord::RecordNotFound when the user doesn't exist. This is a very powerful feature. Look at the following code
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
You can simplify it by delegating to Rails the check. Simply use the bang version.
def show
user = User.find_by_email!(params[:email])
# ...
end
The newly Selected answer submitted by Steven Soroka is close, but not complete. The test itself hides the fact that this is not returning a true 404 - it's returning a status of 200 - "success". The original answer was closer, but attempted to render the layout as if no failure had occurred. This fixes everything:
render :text => 'Not Found', :status => '404'
Here's a typical test set of mine for something I expect to return 404, using RSpec and Shoulda matchers:
describe "user view" do
before do
get :show, :id => 'nonsense'
end
it { should_not assign_to :user }
it { should respond_with :not_found }
it { should respond_with_content_type :html }
it { should_not render_template :show }
it { should_not render_with_layout }
it { should_not set_the_flash }
end
This healthy paranoia allowed me to spot the content-type mismatch when everything else looked peachy :) I check for all these elements: assigned variables, response code, response content type, template rendered, layout rendered, flash messages.
I'll skip the content type check on applications that are strictly html...sometimes. After all, "a skeptic checks ALL the drawers" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: I don't recommend testing for things that are happening in the controller, ie "should_raise". What you care about is the output. My tests above allowed me to try various solutions, and the tests remain the same whether the solution is raising an exception, special rendering, etc.
You could also use the render file:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Where you can choose to use the layout or not.
Another option is to use the Exceptions to control it:
raise ActiveRecord::RecordNotFound, "Record not found."
The selected answer doesn't work in Rails 3.1+ as the error handler was moved to a middleware (see github issue).
Here's the solution I found which I'm pretty happy with.
In ApplicationController:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :handle_exception
end
def not_found
raise ActionController::RoutingError.new('Not Found')
end
def handle_exception(exception=nil)
if exception
logger = Logger.new(STDOUT)
logger.debug "Exception Message: #{exception.message} \n"
logger.debug "Exception Class: #{exception.class} \n"
logger.debug "Exception Backtrace: \n"
logger.debug exception.backtrace.join("\n")
if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
return render_404
else
return render_500
end
end
end
def render_404
respond_to do |format|
format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500
respond_to do |format|
format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
and in application.rb:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
And in my resources (show, edit, update, delete):
#resource = Resource.find(params[:id]) or not_found
This could certainly be improved, but at least, I have different views for not_found and internal_error without overriding core Rails functions.
these will help you...
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
unless Rails.application.config.consider_all_requests_local
rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
end
private
def render_error(status, exception)
Rails.logger.error status.to_s + " " + exception.message.to_s
Rails.logger.error exception.backtrace.join("\n")
respond_to do |format|
format.html { render template: "errors/error_#{status}",status: status }
format.all { render nothing: true, status: status }
end
end
end
Errors controller
class ErrorsController < ApplicationController
def error_404
#not_found_path = params[:not_found]
end
end
views/errors/error_404.html.haml
.site
.services-page
.error-template
%h1
Oops!
%h2
404 Not Found
.error-details
Sorry, an error has occured, Requested page not found!
You tried to access '#{#not_found_path}', which is not a valid page.
.error-actions
%a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
%span.glyphicon.glyphicon-home
Take Me Home
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
<%= render file: 'public/404', status: 404, formats: [:html] %>
just add this to the page you want to render to the 404 error page and you are done.
I wanted to throw a 'normal' 404 for any logged in user that isn't an admin, so I ended up writing something like this in Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, 'Not Found'
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
Raising ActionController::RoutingError('not found') has always felt a little bit strange to me - in the case of an unauthenticated user, this error does not reflect reality - the route was found, the user is just not authenticated.
I happened across config.action_dispatch.rescue_responses and I think in some cases this is a more elegant solution to the stated problem:
# application.rb
config.action_dispatch.rescue_responses = {
'UnauthenticatedError' => :not_found
}
# my_controller.rb
before_action :verify_user_authentication
def verify_user_authentication
raise UnauthenticatedError if !user_authenticated?
end
What's nice about this approach is:
It hooks into the existing error handling middleware like a normal ActionController::RoutingError, but you get a more meaningful error message in dev environments
It will correctly set the status to whatever you specify in the rescue_responses hash (in this case 404 - not_found)
You don't have to write a not_found method that needs to be available everywhere.
To test the error handling, you can do something like this:
feature ErrorHandling do
before do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
end
scenario 'renders not_found template' do
visit '/blah'
expect(page).to have_content "The page you were looking for doesn't exist."
end
end
If you want to handle different 404s in different ways, consider catching them in your controllers. This will allow you to do things like tracking the number of 404s generated by different user groups, have support interact with users to find out what went wrong / what part of the user experience might need tweaking, do A/B testing, etc.
I have here placed the base logic in ApplicationController, but it can also be placed in more specific controllers, to have special logic only for one controller.
The reason I am using an if with ENV['RESCUE_404'], is so I can test the raising of AR::RecordNotFound in isolation. In tests, I can set this ENV var to false, and my rescue_from would not fire. This way I can test the raising separate from the conditional 404 logic.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']
private
def conditional_404_redirect
track_404(#current_user)
if #current_user.present?
redirect_to_user_home
else
redirect_to_front
end
end
end

respond_with is redirecting to specified location even on validation errors, in rails3

When using location in respond with, it is ignoring validation errors and redirecting to the specified location. Is this expected behavior?
I checked in the responder module that it checking if there are any errors on the model. I inspected the model and it contains validation errors in the #solution object. What am I missing here?
controller:
def create
#problem = Problem.find(params[:problem_id])
#solution = #problem.solutions.build params[:solution]
#solution.save
respond_with(#solution, :location => detail_problem_solution_path(#problem, #solution)
end
model:
validates :body, :presence => true, :unless => :reference
reference is true or false false.
I encountered this problem today, and come upon this Rails issue over at github. The exception seems to be thrown since the route url helper can't generate a valid for unsaved (invalid) records.
There's discussion on the github issue about allowing procs as an argument to the location parameter, but it doesn't look like it'll be added anytime soon.
For now I'll stick with using the following solution:
def create
#post = Post.new(params[:post])
if #post.save
respond_with(#post, location: edit_post_path(#post))
else
respond_with #post
end
end
The only way I was able to solve is this:
def create
#problem = Problem.find(params[:problem_id])
#solution = #problem.solutions.build solution_params
success = #solution.save
respond_with(#solution) do |format|
format.html {redirect_to detail_problem_solution_path(#problem, #solution) } if success
end
end

how to display error information onto another page?

how to display error onto another page?
I have a form under user folder
And in another place, my_account folder, i render the form from user folder inside the index page of my_account. so that i can reuse the form,to allow user to update their user information.
So when user click update, update method in UserController will be invoked.
My question is, if faild to update user object, how can i display error message on to my_account index page, and still have the field highlight, and error message??
e.g invalid format of email address
User
-- new.html.erb
-- _form
my_account
-- index.html.erb
I tried to do following, but not sure how can i print the "error" in my_account page:
// try to update user information
// if failed, redirect to my account page
format.html { redirect_to my_account_path, :error => #user.errors }
I'm not sure about it but might work for you.
In update method of user_controller when you get any error just redirect to the index page of my_account and pass the error details with it to be displayed on index page. ie.:
def update
#user = User.find(params[:id])
## update process and if fails
redirect_to :action=> 'index', :status=>:unprocessable_entity, :error=>#user.errors
end
You need the errors to be set on the user object. This is used by form helper to display error messages when calling <%= f.error_messages %> inside your <% form_for %> block.
The Rails best-practice is to call render here, not redirect, so that we ensure we pass the #user object that has errors properly assigned.
To render the action, you can simply call:
if !#user.save
render :action => '../my_account/index'
end
This should easily solve your problem; just make sure to have all of your #member-variables set that the index.html.erb view expects.