Опять рассмотрим tasks#create экшен.
В экшене 3 разных логики, которые выполняются последовательно:
- валидация данных - необходима
- сохраниение таска - необходима
- отправка нотификаций - мы не хотим, что бы наша транзакия не выполнялась, если отправка нотификации не выполнится
Поэтому напишем нашу транзакцию. Так же мы будем использовать Either монаду для возвращения статуса шага транзакции. Right для успешного, Left - не успешного:
require "dry/transaction"
class CreateTask
include Dry::Transaction
step :validate
try :persist
tee :notificate
def validate(params)
if paams.valid?
Right(paams.to_h)
else
Left(paams.to_h)
end
end
def persist(params)
Right(TaskRepository.new.create(params))
end
def notificate(task)
NewTaskNotificationWorker.perform_async(task.id)
end
endОбновим контроллер:
module Web::Controllers::Tasks
class Create
include Web::Action
expose :task
params do
# ...
end
def call(params)
return unless authenticated?
result = CreateTask.new.call(params)
if result.success?
flash[:info] = INFO_MESSAGE
redirect_to routes.tasks_path
else
@task = Task.new(result.value)
self.body = Web::Views::Tasks::New.render(format: format, task: @task,
current_user: current_user, params: params, updated_csrf_token: set_csrf_token)
end
end
end
endЭкшен опять стал чище и вся лишняя логика теперь в транзакции. Давайте воспользуемся матчером, вместо лишнего условия:
module Web::Controllers::Tasks
class Create
include Web::Action
expose :task
params do
# ...
end
def call(params)
return unless authenticated?
CreateTask.new.call(params) do |m|
m.success do
flash[:info] = INFO_MESSAGE
redirect_to routes.tasks_path
end
m.failure :validate do |error|
@task = Task.new(result.value)
self.body = Web::Views::Tasks::New.render(format: format, task: @task,
current_user: current_user, params: params, updated_csrf_token: set_csrf_token)
end
end
end
end
endВот и все. Что мы получили:
- больше не нужно переживать из-за сессии, что бы проверить создание таска в тестах;
- можно воспользоваться DI и протестировать экшен с
NullTransactionкоторый будет возвращать нужный результат без вызова бизнес логики и работы с BD; - убрав лишние методы в экшене, нужно думать, зачем нужен метод
task_paramsи почему было именно так; - каждый из шагов транзакции можно вынести в отдельный класс, что бы изолированно протестировать и легко контролировать логику в этом классе;