What is so bad with throw :async - ruby-on-rails

I'm trying to find out how to create Rails API which could perform asynchronous responses. Many comments below the articles and the tutorials say something like:
That throw :async really scares me
throw :async makes me sad
So apparently using throw :async is considered as the bad practice but I haven't figured out why.
So I'll appreciate if anyone explain me why is it so bad, what are alternative approaches and so on.

People are making comments like:
That throw :async really scares me
because using throw is akin to writing a GOTO.
This is a big topic for discussion/debate, but the short answer is that GOTOs are generally frowned upon as "badly structured" code.
It is always possible to solve a programming problem without GOTOs, and seeing them in code typically indicates a hack that will cause issues down the line.
Here is an example of throw being used in ruby code:
def my_method
catch(:escape) do
outer_list.each do |item1|
inner_list.each do |item2|
throw :escape if item2 == "something"
end
end
puts "something was not found"
return
end
puts "something was found!"
end
The above throw is needed to break from a nested loop... But as you can hopefully see, there is a much simpler/cleaner way of writing the above code.
A GOTO is a weak way out when you have weak design.
Perhaps the two most commonly used arguments against GOTOs are that code is more difficult to understand, and that there are more ways to arrive at a particular point in code.
This does not mean you should never use throw statements - but simply that they are generally discouraged, and you should think carefully about your design if you find yourself needing them.

Related

How do I fix: ArgumentError: invalid byte sequence in UTF-8?

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!

A spelling, expected to be equivalent, causes error. Why?

I'm reading this question, where it says, that the calls
something {|i| i.foo }
something(&:foo)
are equivalent.
Now I was trying to refactor my model named AdminUser according to this pattern and replaced
after_create { |admin| admin.send_reset_password_instructions }
with
after_create(&:send_reset_password_instructions)
, but when I'm running my migration, which contains the lines
def migrate(direction)
super
# Create a default user
AdminUser.create!(email: 'a#b.de', password: 'very_clever', password_confirmation: 'very_clever') if direction == :up
end
it gives me the error
ArgumentError: no receiver given
pointing to the line AdminUser.create!....
Can anyone tell me what goes wrong here?
I know this is an older question, but it interested me quite a bit causing me to do some research of my own. I don't have a corrected code answer for you, but in looking around I believe this post what happened when pass a method to iterator method is very closely related and will most likely answer your question of "what's going wrong here?"
Basically because you're now passing it a proc with the refactored code it's expecting a specific argument that your AdminUser is no longer passing it and causing the error of it not having a receiver.
That being said, I'm sure you have your reasons of setting up your code the way you do, but based on the implied idea of what you're doing and the context I would agree with #photoionized with using
after_create :send_reset_password_instructions as it's clear, concise and (most likely) has your desired outcome.

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

Am I abusing "rescue" for nil checks?

I use rescue for everything, not just for "rescuing" exceptions. I mean, I just love the way it spares me verification and double checking data.
By example, lets say I have a model Item what may or may not have a User. Then, when I want to get the owner's name of the item I write:
item.user.name rescue ""
instead of something like
item.user.nil? ? "" : item.user.name
It makes the same think, since nil.name trigger an exception that I rescue with "", but I'm not pretty sure this is good practice. It makes what I want to, and it makes it with less code, but... I don't know, all that rescue words here and there makes me feel insecure.
Is it a bad practice or is it valid abusing of the rescue keyword?
I think you are abusing rescue a bit, though in Rails, there is a specific method for these issues: try. Documentation
In your case, item.user.try(:name) might be a nicer approach.
I would say this is not really a good habit to get into. I've never really used this feature in Ruby since it feels like I'm just hiding error cases. It's also worth noting that you're rescuing any and all exceptions without specifying any type of expected error. It's just always looked like something which is going to make debugging down the road harder than it needs to be, although, like I've said, I've never bothered to use it myself.
Like in most other languages, making the check yourself will run faster than using rescue.
As an alternative to your rescue abuse, check out the andand gem. It's similiar to the try another posted suggested, but nicer. andand lets you say:
item.user.andand.name
The expression will be nil if item.user is nil.

NilClass definitions

I am tired of trapping for nil when looking for a dependent record when most of the time a return of 0 will do nicely. What sort of trouble am I creating for myself by adding "id" to the NilClass thus
class NilClass
def id
0
end
end
What might the unintended consequences be? I know about exists?(), but somehow thought this might be cleaner.
Your thoughts?
If you really have a problem with this, you should use referential integrity inside your database.
If you must call methods on nil which may or may not exist or throw some kind of error, you should use either a check a la
> nil.id if nil
=> nil
or Object#try (which is part of ActiveSupport nowadays I believe?), be warned - I reckon it's kind of a code smell.
> nil.try(:id)
=> nil
That being said, it is less of a smell than modifying NilClass to do something unexpected, think of what a new developer who had to work on your project would expect.
Won't this mean you will need to check for "id == 0" to confirm existence?
Not to mention unintended consequences of overiding base Ruby functionality - it becomes really hard to predict behaviour of other libraries and core Rails APIs when you mess with language internals. Not saying it won't just work, but it's hard to be sure.
I would leave the default - it works quite nicely as Ruby allows "if object.association" expressions.

Resources