How to display descriptive error message? - ruby-on-rails

I have a problem in displaying the error message in Ruby on Rails. I am using:
rescue => Exception ex
#display ex.message
The output I get when I tried to display it in an alert messagebox is this:
"DBI::DatabaseError: 37000 (50000)
[Microsoft][ODBC SQL Server
Driver][SQL Server]Cannot approve
records for the specified date..: Exec
uspTestProc 279, 167, 2."
It displays some words that are not friendly to users. What I want is to display only these words: "Cannot approve records for the specified date"

Common practice in Rails is to use the "flash" session variable within the Controller:
# error catching logic goes here
flash[:error] = "There was an error!"
# notice logic goes here
flash[:notice] = "I am sending you a notice."
Then display it (possibly within a catchall layout):
<% if flash[:error] %>
<div id="error"><%= flash[:error] %></div>
<% end %>
<% if flash[:notice] %>
<div id="notice"><%= flash[:notice] %></div>
<% end %>
Was that what you are looking for?

I think an error like that can be catch by rescue_from
class ApplicationController
rescue_from MyException do
render :text => 'We have some issue in our database'
end
end

In any language, I would usually always handle the exceptions and show the user a dumbed down version.
Users shouldn't get to see the inner workings of something and exceptions are a great way to show them a big mess of nonsense.
I:
Log the actual exception because I, or the system maintainer needs to know exactly what happened, with a tracelog if possible.
Show the user either a tailored exception for specific to the problem - "You've entered the wrong data!"
Or a generic error - "Oh noes! something went hideously wrong!!1" - if it wasn't caused by the user or I haven't got a case for handling it (yet).

Related

Responders gem: add a model's base errors to the flash message?

Is there an easy way to add a model's base errors to the flash message in the responders gem?
When I try to delete a record with depending children that has dependent: :restrict_with_error set, then I see an error like "X could not be destroyed", but nothing more.
Inspecting the record, I see that there is an additional error added to base:
#messages={:base=>["Cannot delete record because dependent children exist"]}, #details={:base=>[{:error=>:"restrict_dependent_destroy.has_many", :record=>"children"}]
Is there an easy way to append base errors to the flash message?
You use following code in order to display flash errors message
if object.destroy
flash[:success] = "Success Message"
elsif object.errors.messages[:base].present?
flash[:error] = object.errors.messages[:base]
else
flash[:error] = 'Object Not Destroyed'
end
and write following code on view to display flash message
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>"><%= value %></div>
<% end %>

correct way to pass errors

I have a form in a view of my project and
Im doing
redirect_to root_path(#locale), alert: #client.errors
and getting the errors in my view iterating with
flash[:alert].full_messages.each
but when there are a lot of errors rails launch the error
ActionDispatch::Cookies::CookieOverflow
what is the correct way to pass a lot of error descriptions in RoR?
Normally, the alert is a set length string - not something that can grow this long.
render :new, alert: "There has been an error"
When you render you can look for the errors in an instance object - in this case #client.errors.
The rails way is something like this in the view...
<% if #client.errors %>
<ul>
<% #client.errors.full_messages.each do |m| %>
<li><%= m %></li>
<% end %>
</ul>
<% end %>
Forgive my erb - I've been writing haml too long.
The main thing to take away from this is to render instead of redirect. Usually the page that's rendered is the same one that had the form. So, in the create method, you render new on error. In the update method, you render edit.
Personally, I think that the most correct way to pass error is to use a redirect_to only if you pass the validation, and a render if you don't (a simple test on the value of #object.update_attributes(hash)).
This way, you can directly access your object errors from inside your view (through #object.errors) without loading anything in the user cookies.

Holding off on create action

I want to have a form that anyone can fill out, but must be logged in to submit it - if not, put the process on hold until they do.
My thought process is if someone comes across the form, it checks if they are logged in, if not the user can still fill it out, but instead prompted to log in or sign up as opposed to "Submit". If they click on either link (log in/sign up) I want it to save the form data, most likely to the session, have them log in/sign up, and then have the computer check to see if there is a saved form, and if so display a confirmation like screen, where the newly registered user can accept it.
I've posted come code below that I thought would work, but it isn't - and I was thinking that knowing rails, theres probably some convention out the to do this much faster and easier!
And I don't believe the way that I am passing the form data around is correct, so if anyone can correct me there that would be great!
View
<%= form_for :comment, :url => {:action => 'create'} %>
form fields here
<% if current_user %>
<%= f.submit "Submit" %>
<% else %>
<%= link_to "Log In", save_to_session_and_log_in_path(:comment => :comment) %> or
<%= link_to "Sign Up", save_to_session_and_sign_up_path(:comment => :comment) %>
<% end %>
Controller
def save_to_session_and_log_in_path
session[:temp_comment] = Comment.new(params[:comment])
redirect_to log_in_path
end
def save_to_session_and_sign_up_path
session[:temp_comment] = Comment.new(params[:comment])
redirect_to sign_up_path
end
User* Controller
def create
#Normal create action, under the redirect:
if session[:temp_comment]
redirect_to confirm_comment
else
redirect_to users_home_page
end
end
The error I am receiving is:
ActiveRecord::RecordNotFound in CommentsController#show
Couldn't find Comment with id=save_to_session_and_log_in
Rails.root: scrubbed
Application Trace | Framework Trace | Full Trace
app/controllers/comments_controller.rb:87:in `show'
Request
Parameters:
{"comment"=>"comment"
"id"=>"save_to_session_and_log_in"}
I've tried tweaking it every which way but it still keeps getting me here so I am unable to test if any of my code is working
What are your CommentsController#create and #show action? About your error, could you paste the rake routes result for the save_to_session_and_log_in_path and the save_to_session_and_sign_up_path methods? Since you don't have an id at that moment, you should set them as collection routes.
--
The way I'd do it, though, is submitting the form to different controllers that will handle each scenario. That way, your CommentsController will be for logged in users and you can have a TemporaryCommentsController that will take care of comments made by guests.
--
Also, depending on the comment's field, I don't know if it's a great idea to store them in the session. You could probably store a tmp_comment_id in the session and recover that from the DB. Also, delete all records with a cron-job. (This is a problem only when "the comment is big and 'hard' to serialize" though).

How to bubble up exception in rails

New to rails and running into some issues around best practice error handling.
I have the following code in helper in the /lib folder which is used to apply a coupon to some pricing.
As you can see from code below i'm trying a few different ways to return any errors. Specifically, model.errors.add and flash[:error].
The code below is supposed to return a new price once a valid coupon has been applied. What i'm not sure about is what to return in the case of an error? I can't return false as I am below because this method should really be returning a numeric value, not a boolean. What is the best method to bubble up an error to the view from the lib/helper and stop execution?
def calc_coupon transaction_price, discount_code, current_tenant
coupon = Coupon.where(code: discount_code, tenant_id: current_tenant.id).first
if coupon.nil?
current_tenant.errors.add :base, "No such coupon exists"
return false
else
if coupon.maxed? || coupon.expired?
flash[:error] = "This coupon is no longer valid."
return false
else
transaction_price = coupon.calculate(transaction_price)
end
end
return transaction_price
end
I think you are on the right track but you are struggling because you are mixing view logic with business logic.
Forget the helper class and add the functionality to your model.
By adding the validations to the model rather than a helper class and adding the errors to base your controller action will be able to handle the errors when it tries to save/create the record in the normal way, thus "bubbling up the errors"
e.g.
class MyModel < ActiveRecord ...
validate :validate_coupon
protected
def validate_coupon
# add your validations here - call your boolean check which adds errors to the model
# if validations are ok then calculate your transaction price here
end
end
If you have your checks as public methods on your model then they can be called from anywhere, i.e. from within a view
so a public transaction_price method on your model that first checks for errors and sets the price could be used in a view like so
<%= #some_object.transaction_price %>
As I stated earlier because the validations are added in the model then your controller action just needs to check the value of the call to save and flash the errors as normal if it returns false so no need to do anything custom in your controller actions at all. Just the default scaffolded behaviour.
There are a mountain of validation options available to you. Have a look at
http://guides.rubyonrails.org/active_record_validations_callbacks.html
Hope that makes sense.
UPDATE IN RESPONSE TO COMMENT
1) You have that pretty much spot on. It is merely a suggestion that you could calculate the transaction price as part of the validation. If that doesn't suit your requirement then that's fine. I was just trying to give you the best fit for your question. The thing is that you should have two seperate instance methods then you can call them from wherever you wish which makes it a lot simpler for experimentation and moving stuff around. Go with your suggested solution It's good :)
2) Have a close look at a scaffolded controller and views. You will see how the saves/updates fail (They will fail automatically if you add errors as part of your validation process) using an if statement and you will see how the _form partial displays the errors
This is an example of an form that deals with engines in an app I am working on.
<%= form_for(#engine) do |f| %>
<% if #engine.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#engine.errors.count, "error") %> prohibited this engine from being saved:</h2>
<ul>
<% #engine.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Basically it is checking for errors and looping through them all if any exist to display them.
To handle flash messages you should utilise the applications main layout
The following code will display flash messages inside a div called "info" which you can style however you want
<% unless flash.empty? %>
<div class="info">
<%- flash.each do |name, msg| -%>
<%= content_tag :div, msg, :id => "flash_#{name}" %>
<%- end -%>
</div>
<% end %>
By placing that before the main yield in your application.html.erb in your views/layouts folder you can be sure that whatever page is rendered, flash messages will always be shown to the user
Of course if you are displaying flash messages then maybe you don;t need to display the errors. Sometimes both is the right thing to do and sometimes just one or the other is fine. That's up to you to decide on a case by case basis.
The main thing is that the receiving action for the HTTP POST or HTTP PUT (create or update action) handles the failure to save with or without a flash as you see fit
e.g.
A create action
def create
#engine = Engine.new(params[:engine])
respond_to do |format|
if #engine.save
format.html { redirect_to #engine, notice: 'Engine was successfully created.' }
format.json { render json: #engine, status: :created, location: #engine }
else
flash.now[:notice] = "Failed to save!" # flash.now because we are rendering. flash[:notice] if using a redirect
format.html { render action: "new" }
format.json { render json: #engine.errors, status: :unprocessable_entity }
end
end
end
Hope that clears things up
UPDATE 2
Forgot to mention that flash is just a hash and uses the session cookie so you can set any key you like
e.g.
flash.now[:fred]
This would enable you to style and handle a specific fred notification in a specific view if you wanted a different styling from that supplied by your application layout by adding the code to handle the :fred key (or name of your choice) in a specific view

Rails: Changing Error Messages

My error messages are not showing automatically, so I decided to use flash as a workaround. This is what I'm doing
Controller:
flash[:notice] = #post.errors.full_messages
View:
<%= flash[:notice] %>
Then, I get this ugly error message on my view.
["Content can't be blank", "Content is too short (minimum is 10 characters)"]
But at least, the user successfully gets the error message. Now I need to customize the error message and make it look a little bit more pretty. I guess I could parse each error sentence into some local variables and show them (is there a more sophisticated way?). However, I don't know how to customize the error message. For example, "Content can't be blank" should be changed to "You left the content blank". Where can I fix this?
What happens is that when #post contains some validation errors doing #post.errors.full_messages returns an array of errors that happened during validation.
To display them nicely you might want to do something like
<%- flash[:notice].each do |error| %>
<%= error %>
<% end %>
EDIT
Whoops I misread the question.
These errors are validation errors in your model where you have the validations like
validates you can pass custom messages like so
validates :content, :presence => { :message => "You left the content blank" }
Update: check this link out for the options you have
http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates

Resources