Skip to content

Instantly share code, notes, and snippets.

@davydovanton
Last active February 21, 2022 08:51
Show Gist options
  • Select an option

  • Save davydovanton/0a9e9dcaef75582e3c2fe9b4392b61d9 to your computer and use it in GitHub Desktop.

Select an option

Save davydovanton/0a9e9dcaef75582e3c2fe9b4392b61d9 to your computer and use it in GitHub Desktop.

Пример рефакторинга с использованием транзакций

Опять рассмотрим tasks#create экшен.

В экшене 3 разных логики, которые выполняются последовательно:

  1. валидация данных - необходима
  2. сохраниение таска - необходима
  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

Вот и все. Что мы получили:

  1. больше не нужно переживать из-за сессии, что бы проверить создание таска в тестах;
  2. можно воспользоваться DI и протестировать экшен с NullTransaction который будет возвращать нужный результат без вызова бизнес логики и работы с BD;
  3. убрав лишние методы в экшене, нужно думать, зачем нужен метод task_params и почему было именно так;
  4. каждый из шагов транзакции можно вынести в отдельный класс, что бы изолированно протестировать и легко контролировать логику в этом классе;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment