I want provide as much feedback as possible to users trying to import data from csv. The problem is on the cases I need to rescue CSV::MalformedCSVError because the exception message is in english(I can't use english) and there is no other way to distinguish one error from another.
What can I do to show the exact problem to the user?
I noticed you tagged this as Rails so that means you have I18n. Why don't you do something like the following:
begin
# csv parsing code
rescue CSV::MalformedCSVError => ex
raise(CSV::MalformedCSVError.new(I18n.t("csv_parser.malformed_csv_error"))
end
Note: this does assume CSV::MalformedCSVError inherits from StandardError which might not be the case but you get the idea, raise an exception and set the message to some translated I18n string.
UPDATE:
If you wanted even more detail you could match against the exception message and have translations for each message type, while capturing the data you want from the error message string - for instance line number etc. I have no idea right now what the error messages look like but say you have something like "error in column 45" then you could do the following
begin
# csv import code
rescue CSV::MalformedCSVError => ex
err_message = case ex.message
when /column (\d+)/
I18n.t("csv_error.column_error_message", column: Regexp.last_match[1])
else
I18n.t("csv_error.generic_message")
end
raise(CSV::MalformedCSVError.new(err_message))
end
UPDATE: The GEM
I've taken this monkey patch and turned it into a gem. Check it out, along with a test suite:
https://github.com/jhubert/csv-i18n
Original Answer
I bumped into this problem and decided that unless I was ok with showing unhelpful error messages to my users, I could go with two options:
Introduce a new class like FriendlyCSV that wrapped the CSV parser, examined the exception message and returned a relevant error message in the right language.
Monkey-patch the CSV library to return translated error messages.
The first way is probably the proper way to go, since I control the source code and can use whatever class I want to process the incoming CSV files.
However, I'm far too reckless for that and I liked the idea that ANY CSV parsing done anywhere in the system returned translated messages, without other people needing to be aware of the FriendlyCSV class.
So, I went with #2.
The Monkey Patch
You can find the patch here:
https://gist.github.com/jhubert/7d75586857d41fb4c45c4491363636e9
The core behavior is that we're overwriting the shift method, which is where the bulk of the string parsing is. We're attempting to translate the error message and then returning the original exception.
def shift
super
rescue ::CSV::MalformedCSVError => exception
raise $!, translated_exception_message(exception.message), $!.backtrace
end
Hope that helps!
Related
I am getting this type of error in the logs :
Parameters: {"id"=>"4", "step"=>{"documents_attributes"=>{"0"=>
{"file"=>"\x89PNG\r\n\u001A\n\u0000\u0000\u0000\rIHDR\u0000\..."}}}}
def update
#step = Step.find_by(id: params[:id])
if #step.update(steps_params)
render :json => #step
else
render :json => { :responseStatus => 402,
:responseMessage => #step.errors.full_messages.first}
end
end
During update, it rollbacks without giving any error (not execute else condition)
ArgumentError (invalid byte sequence in UTF-8):
(0.2ms) ROLLBACK
How can I fix or handle this type of request?
Your question is how to handle this type of request or error. So here is my suggestion of a general strategy.
First, do your homework. You could easily find this past question, for example. If you have tried the way already but found it did not work, you should have described what you did and what did not work in your question.
Now, I am assuming you can reproduce the case or at least you can expect you will encounter the same problem in near future (or you can wait till then) so you will have a more chance to pin down the problem next time. If you know what parameters caused the error, I guess you can reproduce the case in your development environment. However, if not, it is more tricky to pin down — it heavily depends how much information about the error and input you have and what development environment you can use, and my answer does not cover the case.
The first objective should be to pin down which command (method) exactly in your code caused an error. Did it happen just inside Rails or did your DB raise an error?
In your specific case, did it occur at Step.find_by or #step.update or else? What is steps_params? It seems like a method you have defined. Are you sure steps_params is working as expected? (You may be sure, but we don't know…)
A convenient way to find it out is simply to insert logger.debug (or logger.error) etc before and after each sentence. In doing it, it is recommended to split a sentence into smaller units in some cases. For example, steps_params and update() should be separated, such as (in the simplest case),
logger.debug 'Before steps_params'
res_steps_params = steps_params
logger.debug 'Before update'
res_update = #step.update(res_steps_params)
logger.debug 'Before if'
if res_update
# ……
Obviously you can (and perhaps should) log more detailed information, such as, res_steps_params.inspect, and you may also enclose a part with a begin-rescue clause so that you can get the detailed infromation about the exception and log it. Also, I can recommend to split update into 2 parts – substitutions and save – to find out exactly what action and parameter cause a problem.
Once you have worked out which of DB or Rails or something before (like HTTP-server or Client-browser) is to blame and which parameter causes a problem, then you can proceed to the next stage. The error message suggests it is a character-encoding issue. Is the character encoding of a string invalid (as a UTF-8), or wrongly recognised by Rails (which might be not a fault of Rails but of the client), or not recognised correctly by the DB?
Wherever the problem lies, it is usually (though not always!) possible to fix or circumvent character-encoding problems with Ruby (Rails). The Ruby methods of String#encode, String#encoding, and String#force_encoding would be useful to diagnose and perhaps fix the problem.
As an added note, it can be useful, if possible in your environment, to browse the logfile of your DB (PostgreSQL?) to find out which query passed from Rails to the DB caused a problem (if a query was indeed passed to them!). Alternatively, Rails Gem SQL Query Tracker might be handy to know what queries your Rails app create (though I have never used it and so can't tell much.)
At the end of the day, when a code misbehaves mysteriously, I am afraid only the sure way to solve is to narrow down the problematic clause or parameter step by step. Good luck!
I'm writing a script for a rails app that manages (in a loose sense of the term) files in Dropbox through the dropbox-api gem. Anyway, I'm attempting to delete a file (if it exists) before taking a set of actions.
To delete the file I use the following:
Dropbox::API::Client#destroy
Removes the file specified by path
Returns a Dropbox::API::File object of the deleted file
client.destroy 'file.txt' # => #<Dropbox::API::File>
Where client is
client = Dropbox::API::Client.new(:token => 'xxxxxxxxxxx', :secret => 'yyyyyyyyyyy')
Simply deleting the file is not a problem; it's that I'd like the script not to throw an error if the file doesn't exist. I thought that this might be a job for try() but I haven't been able to get it to work. Is try() appropriate for this application? If not, might there be a good alternative?
You need to write the code in begin and rescue block. Like this:
begin
#code here which may throw error
rescue
#code here to rescue if it throws error
puts "File not found"
end
This is an example. You can do like this. Hope this helps.
Edit:
The try method is used when you need to rescue any exception while calling any object's attributes. May be I am not clear with this sentence, I will explain with an example. Suppose there are users you are not sure about their attributes. So what you can do is:
#user.try(:name)
So if the name exists then it would return the value or else if it doesn't exists it will not throw and error instead it will return nil.
The begin rescue block is useful when you want to handle any exception. Like the above your code might throw an error but you need to handle the error, it should not break the functionality, so you use it. Hope I am clear with this.
I'm writing tests which need to test the rescues in my code.
Model code:
rescue Coinbase::Error => e
#debugger
if e == "You don't have that many bitcoins in your account to sell."
...
end
Rspec code:
allow_any_instance_of(Order).to receive(:sell).and_raise(Coinbase::Error, "You don't have that many bitcoins in your account to sell.")
Adding the debugger where I did and looking at the value of e in console, I see
#<Coinbase::UnauthorizedError: Coinbase::UnauthorizedError>
So the message isn't being passed in.
I've been googling for this for the last 40 minutes and everything I've found only covers sending the error class in, not the message. Presumably there are situations where there are the same error class but different messages.
Any suggestions would be great. Thanks!
I think you want to do: Coinbase::Error.new("You don't have that many bitcoins in your account to sell.") inside the raise call.
Update, I think you also want e.message == "" not e == "" because you are comparing an error to a string not an error message.
In situations where you are rescuing a custom error class, it is possible that the package does not conform to the standard Ruby interface for errors.
Normally, the first argument passed to an error is the message but, in errors that are not coming from the standard library, this might not be the case.
The Ferrum gem does this a lot. For example, when it raises a Ferrum::BrowserError, the first argument is a custom response hash that includes a "message" parameter so stubbing this is something like:
allow(ferrum_node).to receive(:focus).and_raise(
Ferrum::BrowserError.new({ "message" => "Element is not focusable" })
)
I am using Ruby on Rails 3.0.9 and I am trying to validate a nested model. Supposing that I run validation for the "main" model and that generates some errors for the nested model I get the following:
#user.valid?
#user.errors.inspect
# => {:"account.firstname"=>["is too short", "can not be blank"], :"account.lastname"=>["is too short", "can not be blank"], :account=>["is invalid"]}
How you can see the RoR framework creates an errors hash having following keys: account.firstname, account.lastname, account. Since I would like to display error messages on the front-end content by handling those error key\value pairs with JavaScript (BTW: I use jQuery) that involves CSS properties I thought to "prepare" that data and to change those keys to account_firstname, account_lastname, account (note: I substitute the . with the _ character).
How can I change key values from, for example, account.firstname to account_firstname?
And, mostly important, how I should handle this situation? Is what I am trying to do a "good" way to handle nested model errors? If no, what is the common\best approach to do that?
I've made a quick Concern which shows full error messages for nested models:
https://gist.github.com/4710856
#1.9.3-p362 :008 > s.all_full_error_messages
# => ["Purchaser can't be blank", "Consumer email can't be blank", "Consumer email is invalid", "Consumer full name can't be blank"]
Some creative patching of the Rails errors hash will let you achieve your aim. Create an initializer in config/initalizers, let call it errors_hash_patch.rb and put the following in it:
ActiveModel::Errors.class_eval do
def [](attribute)
attribute = attribute.to_sym
dotted_attribute = attribute.to_s.gsub("_", ".").to_sym
attribute_result = get(attribute)
dotted_attribute_result = get(dotted_attribute)
if attribute_result
attribute_result
elsif dotted_attribute_result
dotted_attribute_result
else
set(attribute, [])
end
end
end
All you're doing in here is simply overriding the accessor method [] to try a little harder. More specifically, if the key you're looking for has underscores, it will try to look it up as is, but if it can't find anything it will also replace all the underscores with dots and try to look that up as well. Other than that the behaviour is the same as the regular [] method. For example, let's say you have an errors hash like the one from your example:
errors = {:"account.firstname"=>["is too short", "can not be blank"], :"account.lastname"=>["is too short", "can not be blank"], :account=>["is invalid"]}
Here are some of the ways you can access it and the results that come back:
errors[:account] => ["is invalid"]
errors[:"account.lastname"] => ["is too short", "can not be blank"]
errors[:account_lastname] => ["is too short", "can not be blank"]
errors[:blah] => []
We don't change the way the keys are stored in the errors hash, so we won't accidentally break libraries and behaviours that may rely on the format of the the hash. All we're doing is being a little smarter regarding how we access the data in the hash. Of course, if you DO want to change the data in the hash, the pattern is the same you will just need to override the []= method, and every time rails tries to store keys with dots in them, just change the dots to underscores.
As to your second question, even though I have shown you how to do what you're asking, in general it is best to try and comply with the way rails tries to do things, rather than trying to bend rails to your will. In your case, if you want to display the error messages via javascript, presumably your javascript will have access to a hash of error data, so why not tweak this data with javascript to be in the format that you need it to be. Alternatively you may clone the error data inside a controller and tweak it there (before your javascript ever has access to it). It is difficult to give advice without knowing more about your situation (how are you writing your forms, what exactly is your validation JS trying to do etc.), but those are some general guidelines.
I had the same problem with AngularJs, so I decided to overwrite the as_json method for the ActiveModel::Errors class in an initializer called active_model_errors.rb so that it can replace . for _
Here is the initializer code:
module ActiveModel
class Errors
def as_json(options=nil)
hash = {}
to_hash(options && options[:full_messages]).each{ |k,v| hash[k.to_s.sub('.', '_')] = messages[k] }
hash
end
end
end
I hope it can be helpful for someone
I'm not sure but I think you can't change that behavior without pain. But you could give a try to solutions like http://bcardarella.com/post/4211204716/client-side-validations-3-0
I'm building a method that ingests incoming email, and processes the email. Along the way there are a lot of things that could prevent the email from being processes successfully. The wrong reply-to address, the wrong from address, an empty message body etc..
The code is full of Switch Statements (case/when/end) and If statements. I'd like to learn a smarter, cleaner way of doing this. Additionally, a way to can track an error and at the end have one location where it emails back the user with an error. Is something like this possible with rails?
#error = []
Case XXX
when xxxx
if XXXXX
else
#error = 'You don't have permission to reply to the xxxxx'
end
else
#error = 'Unfamilar XXXX'
end
Then something at the end like...
If #errors.count > 0
Send the user an email letting them know what went wrong
else
do nothing
end
Thanks for the help here. If you know of any other tutorials that would teach me how to write logic like the above smarter, that'd be great. Right now I have case/if statements going 3 levels deeps, it's hard to keep it straight.
Thanks
First, I would just assign a symbol to each error message as a simple hash:
ErrorsDescription = {
:first => "First error",
:second => "Second error",
...
}
And use symbols instead of strings.
Then, your if and switch statements. Basicaly I can't really help you, because I don't see what kind of condition statements you have. What are you checking? Why do you have 3 level deep conditions? Probably you can write it simpler using if and switch - so this is my first answer to this issue. Another solution may be writing simple methods to improve readability, so you can write like this:
if #email.has_wrong_reply_to_address?
#errors << :wrong_reply_to_address
else
...
end
Also, as #mpapis suggested, you can use Rails build in validation system, but not as ActiveRecord but as ActiveModel. Here you have some examples how to do it and how it works (also take a look here). Of course you may need to write custom validations, but they are just simple methods. Once you do all above job, you can just use:
#email.valid?
And if it is not, you have all errors in hash:
#email.errors
Just as in ordinary ActiveRecord object.
Then you may extend your Emial class with send_error_email method which sends an email if there was an error.
EDIT:
This is about new information you attached in comment.
You don't have to use nested ifs and switch here. You can have it looking like this:
def is_this_email_valid?
if !email_from_user_in_system?
#errors << :user_not_in_system
return false
end
if comment_not_exists?
#errors << :comment_not_exists
return false
end
if user_cannot_comment_here?
#errors << :permision_error
return false
end
...
true
end
Then you can use it:
if !#email.is_this_email_valid?
#email.send_error_mail
end
I suggest using Exceptions. Start with this tutorial, then use Google, trial and error to go from there.
Edit: In more complex cases, exceptions may not be the right tool. You might want to use validator functions instead, for example (see other answers), or you could just return early instead of nesting ifs, e.g.:
unless sender_valid?
#error = "Sender invalid"
return
end
unless subject_valid?
#error = "Invalid command"
return
end
# normal no-errors flow continues here...
You could throw an error when something is not right. Then catch it at the end of your method.
http://phrogz.net/programmingruby/tut_exceptions.html
To make your code more readable and not have a lot of switch and if/then statements, you could create separate methods that validate certain aspects and call them from your main error-checking method.
Is it possible to map your message to a model ? then all the if/switch logic would be validations and automatically handled by rails. Good starting point is active record validations guide
Also worth reading is action mailer guide