DEV Community

Cover image for Stop this, use this instead : Top 5 best practices in Ruby to reduce "if" in your code
Pimp My Ruby
Pimp My Ruby

Posted on • Updated on

Stop this, use this instead : Top 5 best practices in Ruby to reduce "if" in your code

Table of Contents

 1. Inject dependency instead of sending a boolean

 2. Use a NilObject as fallback value

 3. Use validator object

 4. Use dry-monads to reduce if else statements

 5. Inject a Strategy instead of a symbol


1- Inject dependency instead of sending a boolean

❌ Stop this :

class UpdateCart
  def call(cart:, attributes: {}, edited_by_admin: false)
    if edited_by_admin
      cart.update(**attributes, updated_by_admin_at: Time.zone.now)
    else
      cart.update(**attributes, updated_by_user_at: Time.zone.now)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

✅ Use instead :

class UpdateCart
  def initialize(cart_updater: UserUpdater)
    @cart_updater = cart_updater
  end

  def call(cart:, attributes: {})
    @cart_updater.call(cart: cart, attributes: attributes)
  end
end

class UpdateCart
  class AdminUpdater
    def self.call(cart:, attributes: {})
      cart.update(**attributes, updated_by_admin_at: Time.zone.now)
    end
  end
end

class UpdateCart
  class UserUpdater
    def self.call(cart:, attributes: {})
      cart.update(**attributes, updated_by_user_at: Time.zone.now)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

2- Use a NilObject as fallback value

❌ Stop this :

class Mediator
  STATUS_SERVICES_MAPPING = {
    'new' => NewStatusService,
    'active' => ActiveStatusService,
    'closed' => ClosedStatusService
  }

  def call(user:, **)
    service = STATUS_SERVICES_MAPPING.fetch(user.status, nil)
    return false if service.nil?

    service.call(user: user, **)
  end
end
Enter fullscreen mode Exit fullscreen mode

✅ Use instead :

class Mediator
  STATUS_SERVICES_MAPPING = {
    'new' => NewStatusService,
    'active' => ActiveStatusService,
    'closed' => ClosedStatusService
  }

  def call(user:, **)
    STATUS_SERVICES_MAPPING.fetch(user.status, NilStatusService)
                           .call(user: user, **)
  end
end

class NilStatusService
  def self.call(**)
    false
  end
end
Enter fullscreen mode Exit fullscreen mode

3- Use validator object

❌ Stop this :

class CreateUserPromotionService
  def call(user:)
    return if user.orders.size < 3
    return if user.promotions.any?
    return if user.created_at < 1.month.ago
    return if user.email.include?('+test')

    create_promotion(user)
  end
end
Enter fullscreen mode Exit fullscreen mode

✅ Use instead :

class CreateUserPromotionService
  def initialize(validator: UserPromotionValidator)
    @validator = validator
  end

  def call(user:)
    return unless @validator.valid?(user)

    create_promotion(user)
  end
end

class UserPromotionValidator
  def self.valid?(user)
    user.orders.size > 3 &&
      user.promotions.empty? &&
      user.created_at > 1.month.ago &&
      user.email.exclude?('+test')
  end
end
Enter fullscreen mode Exit fullscreen mode

4- Use dry-monads to reduce if else statements

❌ Stop this :

class ProcessCalendlyEvent
  def call(email:, event_data:)
    account = find_account(email)
    return if account.nil?

    account.update(event_data: event_data)
  end

  def find_account(email)
    Account.find_by(email: email)
  end
end
Enter fullscreen mode Exit fullscreen mode

✅ Use instead :

class ProcessCalendlyEvent
  def call(email:, event_data:)
    account = yield find_account(email)

    Success(account.update(event_data: event_data))
  end

  def find_account(email)
    Maybe(
      Account.find_by(email: email)
    ).to_result(:account_not_found)
  end
end
Enter fullscreen mode Exit fullscreen mode

5- Inject a Strategy instead of a symbol

❌ Stop this :

class PaymentProcessor
  def process(payment, method)
    case method
    when :credit_card
      process_credit_card(payment)
    when :paypal
      process_paypal(payment)
    else
      raise ArgumentError, "Invalid payment method: #{method}"
    end
  end

  def process_credit_card(payment); end

  def process_paypal(payment); end
end
Enter fullscreen mode Exit fullscreen mode

✅ Use instead :

class PaymentProcessor
  def initialize(strategy: CreditCardPaymentProcessor.new)
    @strategy = strategy
  end

  def process(payment)
    @strategy.process(payment)
  end
end

class CreditCardPaymentProcessor
  def process(payment); end
end

class PaypalPaymentProcessor
  def process(payment); end
end
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
a_chris profile image
Christian

Great examples! Some of them gave me Java vibes but in a positive way.
The one I liked most is the example n.2, that could have saved us a lot of bug at work 😂

Collapse
 
pimp_my_ruby profile image
Pimp My Ruby

Ahaha I completely understand the Java vibe.

I’m a bigger project, having a NilObject can help by introducing some default behavior. In my exemple I just have 1 method. But when you have several attributes and stuff it shine a lot 🧞‍♂️