Stop this, use this instead : Top 5 best practices in Ruby to reduce "if" in your code
Tags: ruby, rails, programming, tutorial
Published: 2023-12-06 11:19:48

## Table of Contents
&nbsp;1. [Inject dependency instead of sending a boolean](#1-inject-dependency-instead-of-sending-a-boolean)

&nbsp;2. [Use a NilObject as fallback value](#2-use-a-nilobject-as-fallback-value)

&nbsp;3. [Use validator object](#3-use-validator-object)

&nbsp;4. [Use dry-monads to reduce if else statements](#4-use-drymonads-to-reduce-if-else-statements)

&nbsp;5. [Inject a Strategy instead of a symbol](#5-inject-a-strategy-instead-of-a-symbol)

---

## 1- Inject dependency instead of sending a boolean

### **❌ Stop this :**

```ruby
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
```

### **✅ Use instead :**

```ruby
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
```

---

## 2- Use a NilObject as fallback value

### **❌ Stop this :**

```ruby
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
```

### **✅ Use instead :**

```ruby
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
```

---

## 3- Use validator object

### **❌ Stop this :**

```ruby
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
```

### **✅ Use instead :**

```ruby
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
```

---

## 4- Use dry-monads to reduce if else statements

### **❌ Stop this :**

```ruby
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
```

### **✅ Use instead :**

```ruby
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
```

---

## 5- Inject a Strategy instead of a symbol

### **❌ Stop this :**

```ruby
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
```

### **✅ Use instead :**

```ruby
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
```