Skip to content

Instantly share code, notes, and snippets.

@davydovanton
Last active August 30, 2018 12:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davydovanton/5fc0f213b56da3baa3a1e2c7b378a6e9 to your computer and use it in GitHub Desktop.
Save davydovanton/5fc0f213b56da3baa3a1e2c7b378a6e9 to your computer and use it in GitHub Desktop.
Пример рефакторинга с interactor

Пример рефакторинга с interactor

Рассмотрим tasks#create экшен. Из названия понятно, что он сохраняет новые таски. Если посмотреть на код, возникает подсознательное недоверие к коду из-за его количества.

Во первых, видно, что мешается логика экшена (authenticated?) и логика сохранения, валидирования, вызова тасков в сайдкик. А так же, видно лишний метод #task_params который приводит данные с экшена в данные, которые будут скормлены нашей модели.

Для начала, давайте сделаем прсотой интерактор CreateTask:

require 'hanami/interactor'

module Interactors
  class CreateTask
    include Hanami::Interactor

    def call
    end
  end
end

А так же вынесем логику создания, приведение данных к нужному виду и вызов воркера:

require 'hanami/interactor'

module Interactors
  class CreateTask
    include Hanami::Interactor

    def initialize(params)
      @params = params
    end

    def call
      task = TaskRepository.new.create(task_params)
      NewTaskNotificationWorker.perform_async(task.id)
    end

    private

    def task_params
      hash = @params[:task]
      hash[:body] = Markdown.parse(hash[:md_body])
      hash[:status] = Task::VALID_STATUSES[:in_progress]
      hash[:approved] = nil
      hash
    end
  end
end

И обновим контроллер:

module Web::Controllers::Tasks
  class Create
    include Web::Action

    expose :task

    params do
      # ...
    end

    def call(params)
      return unless authenticated?

      if params.valid?
        Interactors::CreateTask.new(params).call
        flash[:info] = INFO_MESSAGE

        redirect_to routes.tasks_path
      else
        @task = Task.new(params[:task])
        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

    private

    INFO_MESSAGE = 'Task had been added to moderation. You can check your task status on profile page'.freeze
  end
end

Экшен стал чище, но валидация данных и создание таска в разных местах. Давайте исправим это:

require 'hanami/interactor'

module Interactors
  class CreateTask
    include Hanami::Interactor
    expose :task # геттер, которым мы будем пользоваться в экшене

    def initialize(params)
      @params = params
    end

    def call
      if @params.valid?
        task = TaskRepository.new.create(task_params)
        NewTaskNotificationWorker.perform_async(task.id)
      else
        @task = Task.new(@params[:task])
        error('invalid task attributes') # кидаем ошибку, что бы изменить статус интерактора
      end
    end

    private

    def task_params
      # ...
    end
  end
end

И опять обновляем наш экшен:

module Web::Controllers::Tasks
  class Create
    include Web::Action
    expose :task

    params do
      # ...
    end

    def call(params)
      return unless authenticated?
      result = Interactors::CreateTask.new(params).call

      if result.successful?
        flash[:info] = INFO_MESSAGE
        redirect_to routes.tasks_path
      else
        @task = result.task
        self.body = Web::Views::Tasks::New.render(format: format, task: result.task,
          current_user: current_user, params: params, updated_csrf_token: set_csrf_token)
      end
    end

    private

    INFO_MESSAGE = 'Task had been added to moderation. You can check your task status on profile page'.freeze
  end
end

Что мы получили:

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