variable assigned in conditional is used in same conditional, throws error - ruby-on-rails

I'm trying to do something like this:
if user = User.find_by(email: 'example#example.com') && !user.activated?
# do something
end
but I get an error thrown saying "no method 'activated' for nil:NilClass"
Is there a way for me to accomplish this functionality without using a nested conditional?

You can use the control flow operator and over the logical operator && like so:
if user = User.find_by(email: 'example#example.com') and !user.activated?
# do something
end
Example:
if a = 12 && a.even?
"Working"
end
#=> undefined method `even?' for nil:NilClass
if b = 12 and b.even?
"Working"
end
#=> "Working"
This works because and has a lower precedent than assignment so the assignment will occur prior to the second conditional check.
As long as you don't mind the found = in conditional, should be == warnings.
Second Option:
Your other option is explicit parentheses ie.
if a = 12 && a.even?
"Working"
end
#=> undefined method `even?' for nil:NilClass
if (b = 12) && b.even?
"Working"
end
#=> "Working"
This works because the parentheses () will be evaluated first before the conditional is evaluated so the assignment will occur inside the parens and then be evaluated as part of the conditional.
More on Ruby Operator Precedence

Nope, You can not assign and use the same var in same line, you will need to break it up.
user = User.find_by(email: 'example#example.com')
if user && !user.activated?
# do something
end
Let me know if this helps. You can check user.nil? but above will work as well.

Thats the case when a user is not found. You should add exception handling in condition:
if user = User.find_by(email: 'example#example.com') && !user.try(:activated)?
# do something
end

Related

Refactoring Rubocop Style/GuardClause in a more readable/maintainable way

Rubocop complains: Style/GuardClause: Use a guard clause instead of wrapping the code inside a conditional expression.
if issue_flag == true && issue_notification_sent && !issue_notification_follow_up_sent && send_follow_up
^^
My original code is
if issue_flag == true && issue_notification_sent && !issue_notification_follow_up_sent && send_follow_up
email_address = "sales#test.com"
puts "Emailing Follow Up #{email_address} - #{sales_order}"
UserMailer.issue_notification(self, email_address).deliver_now
update(issue_notification_follow_up_sent: true)
end
and from reading the docs, it seems that I can solve this by implementing the following code instead:
return unless issue_flag == true && issue_notification_sent && !issue_notification_follow_up_sent && send_follow_up
email_address = "sales#test.com"
puts "Emailing Follow Up #{email_address} - #{sales_order}"
UserMailer.issue_notification(self, email_address).deliver_now
update(issue_notification_follow_up_sent: true)
I can see that this essentially breaks early from the method unless the condition is met, but to me, this seems less readable. It also seems less maintainable as further conditionals cannot be added after this code unless they pass the condition on the first line, for instance, to execute something else if issue_flag == true && !issue_notification_sent (anything matching this condition would have already returned on line 1 of the refactored code above).
Is there a better way to refactor this so that more conditions could be added after the code below, without the code returning prematurely?
Thanks.
I think we can do something like below
# issue_flag is boolean so we can directly put it
# create a new method with all the condition and give a proper name
return unless issue_flag && send_follow_up? # change name accourdingly
email_address = "sales#test.com"
puts "Emailing Follow Up #{email_address} - #{sales_order}"
UserMailer.issue_notification(self, email_address).deliver_now
update(issue_notification_follow_up_sent: true)
end
# document its behaviour
def send_follow_up?
issue_notification_sent && !issue_notification_follow_up_sent && send_follow_up
end
The guard structure is used to send the control out of block so if you need change something or do something after the condition then you will not be able to use the guard clause. Have a look at the below code in such scenarios we will not use guard clause
def test
if something_here?
do_something
end
do_something_else
do_something_else1
do_something_else2
end
I would probably extract most part of the method as privates with clear names that tells the intention. Pseudocode implementation would look like this:
def method_name
return unless flag? && issue_notification_sent_with_follow_up
log_follow_up
UserMailer.issue_notification(self, #email_address).deliver_now
update(issue_notification_follow_up_sent: true)
end
private
def flag?
issue_flag == true
end
def issue_notification_sent_with_follow_up
issue_notification_sent && !issue_notification_follow_up_sent && send_follow_up
end
def log_follow_up
#email_address = "sales#test.com"
puts "Emailing Follow Up #{#email_address} - #{sales_order}"
end

Rails 5 ActiveRecord - check if any results before using methods

In my Rails 5 + Postgres app I make a query like this:
user = User.where("name = ?", name).first.email
So this gives me the email of the first user with the name.
But if no user with this names exists I get an error:
NoMethodError (undefined method `email' for nil:NilClass)
How can I check if I have any results before using the method?
I can think if various ways to do this using if-clauses:
user = User.where("name = ?", name).first
if user
user_email = user.email
end
But this does not seem to be the most elegant way and I am sure Rails has a better way.
You can use find_by, returns the object or nil if nothing is found.
user = User.find_by(name: name)
if user
...
end
That being said you could have still used the where clause if you're expecting more than one element.
users = User.where(name: name)
if users.any?
user = users.first
...
end
Then there is yet another way as of Ruby 2.3 where you can do
User.where(name: name).first&.name
The & can be used if you're not sure if the object is nil or not, in this instance the whole statement would return nil if no user is found.
I use try a lot to handle just this situation.
user = User.where("name = ?", name).first.try(:email)
It will return the email, or if the collection is empty (and first is nil) it will return nil without raising an error.
The catch is it'll also not fail if the record was found but no method or attribute exists, so you're less likely to catch a typo, but hopefully your tests would cover that.
user = User.where("name = ?", name).first.try(:emial)
This is not a problem if you use the Ruby 2.3 &. feature because it only works with nil object...
user = User.where("name = ?", name).first&.emial
# this will raise an error if the record is found but there's no emial attrib.
You can always use User.where("name = ?", name).first&.email, but I disagree that
user = User.where("name = ?", name).first
if user
user_email = user.email
end
is particularly inelegant. You can clean it up with something like
def my_method
if user
# do something with user.email
end
end
private
def user
#user ||= User.where("name = ?", name).first
# #user ||= User.find_by("name = ?", name) # can also be used here, and it preferred.
end
Unless you really think you're only going to use the user record once, you should prefer being explicit with whatever logic you're using.

Add to Integer Variable from Rails Controller

This should be a fairly simple error to fix (I hope). When a joke on my app is approved, I want that user to be awarded 5 manpoints (don't ask). I currently have this in my 'jokes_controller`:
def approve
#joke = Joke.find(params[:id])
#joke.update_attributes(approved: true)
if #joke.user.manpoints = nil
#joke.user.manpoints = 5
else
#joke.user.manpoints += 5
end
#joke.save
redirect_to jokes_path
end
I'm getting this error when I try to approve a joke:
undefined method `+' for nil:NilClass
I thought += was the "Ruby way" to do this? Can anyone set me straight?
Change = in if condition to ==. I assume you need to compare manpoints with nil, not to assign nil to manpoints.
It should be like:
if #joke.user.manpoints == nil
...
You can omit == operator here:
unless #joke.user.manpoints
...
PS. Why are you excepting that manpoints will be nil?

NoMethodError when method is returning nil

I have two methods which are used to determine whether to apply a class to the page to show that something is overdue and needs attention.
I'm getting an error when a brand new user registers:
undefined method `last_contact_done_date=' for #<User:0x6183708>
The line that it's referencing the error to is this:
2: <div class="span3 <%= "overdue" if (signed_in? && contact_overdue?(current_user.id)) %>">
The contact_overdue? method is this (in a home page helper)
def contact_overdue?(user_id)
#user = User.find_by_id(user_id)
return true if (Date.today - (#user.last_contact_done_date ||= Date.tomorrow)) > 6
end
and the last_contact_done_date method is this in the User model
def last_contact_done_date
self.contacts.order('date_done DESC').first.try(:date_done)
end
I thought that if I was using the ||= operator in the contact_overdue? method, then I would return -1 if the last_contact_done_date is nil. But it appears that's not working. What operator should I be using in last_contact_done_date or how should I change the contact_overdue? method so that if there are no contacts, then false is returned from the contact_overdue? method?
To return the default value of -1 when there is no last contact done date, use
#user.last_contact_done_date || -1
(it is unreasonable to expect Date.tomorrow to return -1 ;) )
||= is an assignment operator; a ||= b is equivalent to a = a || b; and if a is an attribute (i.e. is prefixed with a dot and an instance, c.a), assignment to it will call the method a=. Thus, your code necessitates that you have a method named last_contact_done_date= to handle the assignment, which you don't.

Ruby on Rails: how do I set a variable where the variable being changed can change?

i want to do
current_user.allow_????? = true
where ????? could be whatever I wanted it to be
I've seen it done before.. just don't remember where, or what the thing is called.
foo = "bar"
current_user.send("allow_#{foo}=", true)
EDIT:
what you're asking for in the comment is another thing. If you want to grab a constant, you should use for instance
role = "admin"
User.const_get(role)
That's a "magic method" and you implement the method_missing on your current_user object. Example from Design Patterns
#example method passed into computer builder class
builder.add_dvd_and_harddisk
#or
builder.add_turbo_and_dvd_dvd_and_harddisk
def method_missing(name, *args)
words = name.to_s.split("_")
return super(name, *args) unless words.shift == 'add'
words.each do |word|
#next is same as continue in for loop in C#
next if word == 'and'
#each of the following method calls are a part of the builder class
add_cd if word == 'cd'
add_dvd if word == 'dvd'
add_hard_disk(100000) if word == 'harddisk'
turbo if word == 'turbo'
end
end

Resources