Table of Contents
1. Inject dependency instead of sending a boolean
2. Use a NilObject as fallback value
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
✅ 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
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
✅ 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
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
✅ 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
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
✅ 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
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
✅ 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
Top comments (2)
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 😂
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 🧞♂️