Refactoring Rubocop Style/GuardClause in a more readable/maintainable way - ruby-on-rails

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

Related

Testing after_save callback evaluating saved_change_to_attribute?

I have recently upgraded my application to rails 5.1 causing me to start using saved_change_to_attribute? in my after_save callbacks. This has caused a test to break.
The code that I am trying to test is the following which gets called through another function in an after_save:
MODEL.RB:
def items_update_needed?
return (online_only? || saved_change_to_online_only?) &&
(saved_change_to_published? && !complete? ||
saved_change_to_online_only? ||
saved_change_to_listing? ||
saved_change_to_third_party_bidding_url? ||
saved_change_to_starts_at? ||
saved_change_to_scheduled_end_time? ||
saved_change_to_closing_speed_lots? ||
saved_change_to_closing_speed_minutes?)
end
TEST.RB:
test "items_update_needed? NOT for a live auction UNLESS it was previous timed" do
# Timed auction NEEDS items update if starts_at changes
#online_only.starts_at = 5.minutes.from_now
assert #online_only.items_update_needed?
#online_only.reload
end
The assert line fails with an error of: Expected false to be truthy.
If I drop a debugger in items_update_needed?, saved_changes_to_starts_at? returns false.
If I put a save before the assert it still returns false which makes sense because the function would have been called already through the after_save callback.
So I am trying to determine how can you actually test this method and get a return of true?

Simplifying or improving a method

The following code is executed before the model is saved. It checks a has_many association for change before looping through and setting a value on each. It checks through each answer option whether the correct_answer is changed on any of the answers. If so, it looks for which was changed and was true.
if self.answer_options.select{|a| a.correct_answer_changed?}.any?
self.answer_options.each do |answer_option|
if answer_option.correct_answer_changed? && !answer_option.correct_answer_was
answer_option.correct_answer = true
else
answer_option.correct_answer = false
end
end
end
How could I simplify or improve this method?
Assuming the code you have shared is working, I would dry it up a bit as..
self.answer_options.map{|a| a if a.correct_answer_changed?}.compact.each do |answer_option|
answer_option.toggle(:correct_answer)
end
answer_options.each do |answer_option|
answer_option.correct_answer =
answer_option.correct_answer_changed? && !answer_option.correct_answer_was
end
if answer_options.all?{|answer_option| answer_option.correct_answer == false}
answer_options.each{|answer_option| answer_option.correct_answer == nil}
end

variable assigned in conditional is used in same conditional, throws error

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

facing issues about array in rails

I am implementing a program in rails where there is a form and after submitting the form it will check if there is any record with duplicate value for a specific field in database. My database table is students. So my corresponding model name is Student. I am writing this code (what I have just discussed) in my controller.
But I am facing the following error. I am using some arrays for internal operations. When I wrote that particular function in ruby only(not rails) then it was working fine. Moreover I am also facing error due to the use of "length".
My error is:
NoMethodError in StudentsController#create
undefined method `[]' for nil:NilClass
My controller code is:
class StudentsController < ApplicationController
def new
#student=Student.new
#students=Student.all
end
def create
#student=Student.new(u_params)
ret_val=string_check
if ret_val==1
#student.save
redirect_to new_student_path , :notice => "Inserted!!!"
else
redirect_to new_student_path , :notice => "Match,Not inserted!!!"
end
end
def u_params
params.require(:student).permit(:id ,:firstname, :lastname)
end
def u_params_second
params.require(:student).permit(:firstname)
end
def string_check
count =0;
#temp1=:firstname
temp1=params[:firstname]
supplied_val=temp1
puts "Entered in string_check method"
for i in 46..100
temp2=Student.find_by(id:i)
table_val=temp2.firstname
size1=supplied_val.to_s.length
size2=table_val.to_s.length
arr1=Array.new
arr2=Array.new
# arr1[i] ||= []
# arr2[i] ||= []
for i in 0..size1
arr1.push(supplied_val[i])
end
for i in 0..size2
arr2.push(table_val[i])
end
for i in 0..size1
if arr1[i]=="#" || arr1[i]=="#" || arr1[i]=="{" || arr1[i]=="}" || arr1[i]=="(" || arr1[i]==")" || arr1[i]=="[" || arr1[i]=="]" || arr1[i]=="." || arr1[i]==";" || arr1[i]=="," || arr1[i]=="%" || arr1[i]=="&" || arr1[i]=="*" || arr1[i]=="!" || arr1[i]=="?" || arr1[i]=="$" || arr1[i]=="^" || arr1[i]==":" || arr1[i]=="-" || arr1[i]=="/"
count=count+1
# puts count
arr1[i]=""
end
end
# puts arr1
puts arr1.join
final1=arr1.join
for i in 0..size2
if arr2[i]=="#" || arr2[i]=="#" || arr2[i]=="{" || arr2[i]=="}" || arr2[i]=="(" || arr2[i]==")" || arr2[i]=="[" || arr2[i]=="]" || arr2[i]=="." || arr2[i]==";" || arr2[i]=="," || arr2[i]=="%" || arr2[i]=="&" || arr2[i]=="*" || arr2[i]=="!" || arr2[i]=="?" || arr2[i]=="$" || arr2[i]=="^" || arr2[i]==":" || arr2[i]=="-" || arr2[i]=="/"
count=count+1
# puts count
arr2[i]=""
end
end
# puts arr2
puts arr2.join
final2=arr2.join
if final1==final2
flag=0
else
flag=1
end
return flag
end
end
end
The routes.rb file is:
Rails.application.routes.draw do
resources :students
end
My error is: NoMethodError in StudentsController#create
undefined method `[]' for nil:NilClass
It simply means that you are trying to access something as an array that is actually a nil object, and not an array.
To get rid of this error, you can a technique called short-circuit in Ruby.
Let's say your following piece of code is producing the said error:
arr1[i]
You can use an if condition like this:
if arr1
arr1[i]
end
Or use short-circuit technique like this:
arr1 && arr1[i]
If you sure that the relevant code snippet was working for ruby and it's not for rails, the problem is most likely due to variable i used at inner and outer loops both. In any case, this needs to be fixed first or else it will result in unexpected behaviour only.
Outer Loops:
for i in 46..100
Inner Loops:
for i in 0..size1
for i in 0..size2
...
Keep i for outer loop and change the inner loop iterator to j
Hope it Helps : )
Adding to the answers of #harish and #arslan, there may be a case where, temp2=Student.find_by(id:i) may fail because there may not be a student with that id.
So, temp2 may return nil at that time.
for i in 0..size2
arr2.push(table_val[i]) // this may get failed
end
Then arr2.push will not work because table_val[i] is nil, so there are chances of undefined method [] for nil class.

Silence ActionView::Template::Errors, like "isn't precompiled"

my question is about the standard behavior of the action-view gem when using the rails asset-pipeline.
It throws an Exception and the app-execution stops whenever there's an image which isn't precompiled, so the user just gets to see the standard blank page saying: "... something went wrong".
Something as trivial as a missing image (could be an icon, maybe with just a misspelled name...) shouldn't be a showstopper. Should it be?!
We would like to change this radical behavior to a more mild version: Having the app continue working, but, of course, notifying us about the missing image.
Question:
Is there any other way then monkeypatching the relevant part of the helper method contained in the action-view gem?
Is there any config we could modify so there would be no need for this patch?
Having this kind of monkeypatch is considered a maintenance nightmare in case of gem-updates, isn't it?
This is our actual patch: called: "assetpipe_easy_errors.rb" residing in config/initializers, the relevant method is "digest_for"
Sprockets::Helpers::RailsHelper::AssetPaths.class_eval do
attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets
class AssetNotPrecompiledError < StandardError; end
def asset_for(source, ext)
source = source.to_s
return nil if is_uri?(source)
source = rewrite_extension(source, nil, ext)
asset_environment[source]
rescue Sprockets::FileOutsidePaths
nil
end
def digest_for(logical_path)
if digest_assets && asset_digests && (digest = asset_digests[logical_path])
return digest
end
if compile_assets
if digest_assets && asset = asset_environment[logical_path]
return asset.digest_path
end
return logical_path
else
#original code: raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
### own Patch: these next four lines:
Rails.logger.info(" arrg!! an image is missing ")
### example: FeedbackMailer.generic_system_message(subject,bodytext).deliver
FeedbackMailer.generic_system_message("asset error",logical_path).deliver
return logical_path
end
end
def rewrite_asset_path(source, dir, options = {})
if source[0] == ?/
source
else
if digest_assets && options[:digest] != false
source = digest_for(source)
end
source = File.join(dir, source)
source = "/#{source}" unless source =~ /^\//
source
end
end
def rewrite_extension(source, dir, ext)
source_ext = File.extname(source)
if ext && source_ext != ".#{ext}"
if !source_ext.empty? && (asset = asset_environment[source]) &&
asset.pathname.to_s =~ /#{source}\Z/
source
else
"#{source}.#{ext}"
end
else
source
end
end
end
Any ideas are highly appreciated

Resources