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
Related
The Rails 'flash' message system is very useful, but I've always found the details of its implementation make its use awkward.
Specifically, the flash messages are cleared at the end of a response. This means that you have to work out whether they're going to be used in a direct page render or a redirect, then inform the flash system by using 'now' if it's not a redirect.
This seems overly complex and error-prone.
Occasionally I find myself building things that need to exhibit flash-like behaviour, and the process I use is slightly different:
class FlashlikeStore
attr_accessor :session
def initialize(session)
session[:flashlike] ||= []
self.session = session
end
def add(name, data)
self.store << { name: name, data: data }
end
def read
session.delete(:flashlike).to_json
end
def any?
store && store.any?
end
protected
def store
session[:flashlike]
end
end
With a little syntactic sugar in the application helper I can easily add my name-value pairs, and the act of reading it deletes the data. (In this case I'm actually reading this in via AJAJ, but that isn't important here.)
The upshot is that messages are only ever read once, with no need up-front to determine or guess when they're going to appear. They're read when they're needed, then they go away.
One could argue, I suppose, that a call to an object shouldn't both read data and change state, so the 'read' method could be split into a 'read' and a 'wipe' if you wanted to be purist that way. (Although you probably wouldn't be using Rails if you were.)
So the question is this: am I missing something? Is there a compelling reason for the way Rails flash messaging works, with its need for the 'now' method?
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
In languages that have goto, I like to create an error block at the end of a function (after return) and then when I do error checking within the function, I can just be concise and goto the error handler within each check. As I understand it, this is the one valid use of goto that isn't considered bad practice.
Example in pseudocode:
def example
if (x) goto error
do something
if (y) goto error
do something
If (z) goto error
do something
return
label 'error'
log "error occurred"
begin
redirect_to :back
rescue
redirect_to root_url
end
return;
end
As you can see, in this case, my error block is as long as the function itself, and repeating it 3 times would double the size of my code, and not be very DRY. However, it seems that Ruby doesn't support goto, or at least if it does, as best as I can tell from looking on Google, it's some sort of possibly joke library labeled evil.
Therefore, what are people doing in Ruby in order to handle repeated error checking where the same result should occur in each error?
Callbacks
You should transfer many of these errors into your models, using Callbacks. These apply to errors that are relevant to actions that involve records in your database, i.e. checking whether a data input is appropriate.
Filters
Use before_filters and after_filters to check for errors, especially when you need to perform these checks on multiple controller actions. An example:
before_filter :check_errors
def example
regular code...
end
private
def check_errors
error checking...
end
Case statements
Use Case statements to improve your if statements, particularly when you have multiple checks involved.
Prioritizing the above
Use callbacks in your models whenever you can and definitely whenever data saving/updating/validation is involved.
Use before_filters whenever the code is to be reused across multiple actions (and in my opinion, always whenever you have involved error checking like this).
If you need these checks to occur only once, in this controller action alone, that do not involve records being changed, simply rewrite your code in a valid case statement (but my recommendation would still be to transfer to a before_filter).
Here's a little secret: Exceptions are basically glorified gotos. Also, ruby has a catch/throw syntax, see: http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_exceptions.html
In your case, ask, is it really an error or just an undesirable condition. An error to me is when a belongs_to references a record that doesn't exist, but having an empty belongs_to isn't. This changes from situation to situation.
Looking at your comment above, I think I would be more inclined to add some private methods that set the instance variables and return true of false, and chain them together:
if load_model1 && load_model2 && load_model3
... do regular page view
else
#render error page, use #load_error
end
private
def load_model1
#model1 = ....
if #model1.blank? # or nil? or whatever error condition
#load_error="model 1 failed
return false
else
return true
end
end
def load_model2
...
end
def load_model3
...
end
So there is
record.new_record?
To check if something is new
I need to check if something is on it's way out.
record = some_magic
record.destroy
record.is_destroyed? # => true
Something like that. I know destroying freezes the object, so frozen? sort of works, but is there something explicitly for this task?
Just do it:
record.destroyed?
Details are here ActiveRecord::Persistence
You can do this.
Record.exists?(record.id)
However that will do a hit on the database which isn't always necessary. The only other solution I know is to do a callback as theIV mentioned.
attr_accessor :destroyed
after_destroy :mark_as_destroyed
def mark_as_destroyed
self.destroyed = true
end
And then check record.destroyed.
This is coming very soon. In the latest Riding Rails post, it says this:
And finally, it's not necessarily
BugMash-related, but José Valim -
among dozens of other commits - added
model.destroyed?. This nifty method
will return true only if the instance
you're currently looking at has been
successfully destroyed.
So there you go. Coming soon!
destroying an object doesn't return anything other than a call to freeze (as far as I know) so I think frozen? is your best bet. Your other option is to rescue from ActiveRecord::RecordNotFound if you did something like record.reload.
I think Mike's tactic above could be best, or you could write a wrapper for these cases mentioned if you want to start 'making assumptions'.
Cheers.
While record.destroyed? works fine, and does return true or false, you can also DRY this up a little bit and create the if condition on the line you call destroy on in your controller.
record = Object.find(params[:id])
if record.destroy
... happy path
else
... sad path
end
Realize this post is a bit late in the game. But should anyone want to discuss this more, i'm game!
Side note: I also had an after_destroy validation on my model and while it worked, a separate method for something like this seems like overkill ;)
Without knowing more of the logic of your app, I think that frozen? is your best bet.
Failing that, you could certainly add a "destroyed" attribute to your models that you trigger in the callbacks and that could be checked against if you want a more precise solution.
The short answer is:
record.destroyed?
# or...
Record.exists?(record) # also very fast!
You'd think that record.destroyed? is better because it doesn't send an extra database request but actually Record.exists? is so extremely fast, that this typically isn't a reason to prefer one over the other.
Don't use the return value of record.destroy, which will always return a frozen instance of the record, whether it's deleted or not, from right before you tried to deleted it. See here: https://apidock.com/rails/v5.2.3/ActiveRecord/Persistence/destroy
# Assuming no issues when destroying the record...
x = record_one.destroy
x.destroyed? # Would return false! (even though the record no longer exists in the db)
Record.exists?(x) # Would correctly return false
# vs.
record_two.destroy
record_two.destroyed? # Would correctly return true
Record.exists?(record_two) # Would correctly return false
If you're in a controller, destroy! will throw a ActiveRecord::RecordNotDestroyed, which you can catch, based on before_destroy callbacks.
def destroy
render json: #record.destroy!
rescue ActiveRecord::RecordNotDestroyed
# #record will work fine down here, it still exists.
render json: { errors: ["Record not destroyed"] }
end
What's the best practices way to test that a model is valid in rails?
For example, if I have a User model that validates the uniqueness of an email_address property, how do I check that posting the form returned an error (or better yet, specifically returned an error for that field).
I feel like this should be something obvious, but as I'm quickly finding out, I still don't quite have the vocabulary required to effectively google ruby questions.
The easiest way would probably be:
class UserEmailAddressDuplicateTest < ActiveSupport::TestCase
def setup
#email = "test#example.org"
#user1, #user2 = User.create(:email => #email), User.new(:email => #email)
end
def test_user_should_not_be_valid_given_duplicate_email_addresses
assert !#user2.valid?
end
def test_user_should_produce_error_for_duplicate_email_address
# Test for the default error message.
assert_equal "has already been taken", #user2.errors.on(:email)
end
end
Of course it's possible that you don't want to create a separate test case for this behaviour, in which case you could duplicate the logic in the setup method and include it in both tests (or put it in a private method).
Alternatively you could store the first (reference) user in a fixture such as fixtures/users.yml, and simply instantiate a new user with a duplicate address in each test.
Refactor as you see fit!
http://thoughtbot.com/projects/shoulda/
Shoulda includes macros for testing things like validators along with many other things. Worth checking out for TDD.
errors.on is what you want
http://api.rubyonrails.org/classes/ActiveRecord/Errors.html#M002496
#obj.errors.on(:email) will return nil if field is valid, and the error messages either in a String or Array of Strings if there are one or more errors.
Testing the model via unit tests is, of course, step one. However, that doesn't necessarily guarantee that the user will get the feedback they need.
Section 4 of the Rails Guide on Testing has a lot of good information on functional testing (i.e. testing controllers and views). You have a couple of basic options here: check that the flash has a message in it about the error, or use assert_select to find the actual HTML elements that should have been generated in case of an error. The latter is really the only way to test that the user will actually get the message.