Сервис возвращает т...
 
Уведомления
Очистить всё

Сервис возвращает то true, то строку с ошибкой. Как сделать по-людски?

5 Сообщения
4 Пользователи
1 Реакции
662 Просмотры
slava52
Сообщения: 9
Автор темы
Разобрался с блоками middle-разработчик
Присоединился: 2 года назад
[#66]

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. Хочу переделать, пока таких сервисов три, а не тридцать. Как принято?


4 ответа
kirill_spb
Сообщения: 27
Собрал свой gem разработчик инструментов
Присоединился: 4 года назад

Если хочется как в 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 поддерживают.


Ответ
kostya.sokurov
Сообщения: 73
Держит прод бэкенд-разработчик, senior
Присоединился: 4 года назад

Для трёх сервисов тащить 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.


Ответ
1 ответ
Валерий40
Присоединился: 4 года назад

Читает чужие PR фулстек-разработчик
Сообщения: 33

А валидацию я бы не заворачивал: save! и rescue ActiveRecord::RecordInvalid в контроллере, как с исключениями в Laravel


Ответ
slava52
Сообщения: 9
Автор темы
Разобрался с блоками middle-разработчик
Присоединился: 2 года назад

Сделал на Data, как у kostya.sokurov, письмо перевёл в deliver_later, ловить SMTP больше не нужно. Все три сервиса возвращают Result, проверок == true в контроллерах не осталось. Валерий40, про исключения тоже думал, но в регистрации ещё проверяется промокод, и неверный промокод там ситуация ожидаемая, исключение туда не просится.


Ответ