Search code examples
ruby-on-railsrubyrubocop

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 = "[email protected]"
  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 = "[email protected]"
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.


Solution

  • 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 = "[email protected]"
      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