Rails 8.0, Ruby 3.4. До Ruby я писал на C#, там бы вернул Result или кинул исключение. Здесь сервисы писали до меня, и они отвечают то true, то строкой:
class RegisterUser
def call(params)
return "Email занят" if User.exists?(email: params[:email])
user = User.new(params)
return user.errors.full_messages.join(", ") unless user.save
WelcomeMailer.with(user: user).welcome.deliver_now
true
rescue Net::SMTPAuthenticationError
"Не удалось отправить письмо"
end
end
В контроллере проверка if result == true, и уже дважды ловили баг, когда кто-то возвращал nil. Хочу переделать, пока таких сервисов три, а не тридцать. Как принято?
Если хочется как в C#, есть dry-monads, там Success и Failure из коробки:
require "dry/monads"
class RegisterUser
include Dry::Monads[:result]
def call(params)
return Failure(:email_taken) if User.exists?(email: params[:email])
user = User.new(params)
return Failure(user.errors) unless user.save
Success(user)
end
end
В контроллере результат удобно разбирать через case ... in Success(user), pattern matching dry-monads поддерживают.
Для трёх сервисов тащить dry-monads мне было бы жалко. С Ruby 3.2 есть Data, и на нём результат пишется в несколько строк:
Result = Data.define(:value, :error) do
def self.success(value) = new(value: value, error: nil)
def self.failure(error) = new(value: nil, error: error)
def ok? = error.nil?
end
# в контроллере
result = RegisterUser.new.call(user_params)
if result.ok?
redirect_to result.value
else
flash.now[:alert] = result.error
render :new, status: :unprocessable_entity
end
И посмотри, что сейчас письмо уходит после save: если почта упадёт, пользователь уже создан, а сервис отвечает ошибкой. Письмо лучше отправлять через deliver_later, тогда сервису вообще не придётся ловить SMTP.
Сделал на Data, как у kostya.sokurov, письмо перевёл в deliver_later, ловить SMTP больше не нужно. Все три сервиса возвращают Result, проверок == true в контроллерах не осталось. Валерий40, про исключения тоже думал, но в регистрации ещё проверяется промокод, и неверный промокод там ситуация ожидаемая, исключение туда не просится.