Stubbing rspec and_raise and adding a message - ruby-on-rails

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" })
)

Related

Friendly way to show MalformedCSVError to non-english speakers

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!

Rails Active Record - How to do validation which calls method rather than throws error

Active Record validations throw an error when they fail. What I have in a model is
validate_format_of :field_which_cannot_have_spaces, :with => /^[^\s]+$/, :message => "Some error message"
What I want instead, is for a string replacement to substitute spaces for underscores (snake_case).
The advantages of using validation for me, are that it runs every time the field is changed unless save(validate: false), and that I don't need to repeat the replacement in the create and update controller methods.
Front end javascript solutions won't help if the user hacks the form... a rails solution is needed!
It sounds like you want a callback rather than a validation. This can run each time your object is modified.
So, to remove spaces from your field before the object is saved you can do:
before_save :remove_spaces_from_x
def remove_spaces_from_x
self.field_which_cannot_have_spaces.gsub!("\s","_")
end
Note also that validation do not always raise an error when they fail. If you use save! or create! then an error is raised but if you use the equivalent save or create then no error is raised, false is returned and the object's errors are populated with details of the validation failure.
Co-worker just told me to do the following in the model:
def field_which_cannot_have_spaces=(input_from_form)
super(input_from_form.gsub("\s","_"))
end
This will change the value as it is set.
"Validations are for informing the client there is a problem, and shouldn't be doing something other than throwing an error."
Hope this helps someone else...

How to debug silent database errors (such as "save") in Rails?

How can I get further information on errors that occur silently in Rails, such as #object.save?
Add bang so that an error is raised when validation fails.
#article.save!
# ActiveRecord::RecordInvalid: Validation failed: Title can't be blank...
Always use this method in preference to save if you don't expect validation to fail.
As per this post, something like the following works:
logger.debug #item.errors.full_messages
Sometimes AR silently fails for reasons other than validation errors. A couple of other things to check are:
AR callbacks that do not return true (eg before_save)
Invalid parent/ child records
#item.valid?, #item.errors.full_messages
#item.changes, #item.changed?
I have also included user456584's comment on checking for validation errors. And as Semyon said, #item.save! will at least raise an Exception even if it isn't particularly helpful.

Nested model error messages

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

Rails 3 - How to deal with complicated Switch Statements / If Statements

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

Resources