Complete a task

The process is always the same: add a form that triggers the action, add a route that captures the request and process it, a transaction with its steps or operations, and render the response through a view controller.

Add the form

In order to allow mark tasks as completed. In tasks index page we will add a button to set it up. Here I added a form for each uncompleted task or show the Completed status.

apps/main/web/templates/tasks/index.html.slim

...
ul.tasks
  - tasks.each do |task|
    li.task
      span = task.description
      |  
      - if task.completed
          | [Completed]
      - else
        form action="/tasks/#{task.id}" method='post'
          == csrf_tag
          input type='hidden' name='_method' value='patch'
          input type='submit' value='Complete'

Add a route to process the action

The patch method is not accepted by default by Roda, but it can be activated by adding the plugin all_verbs and using the Rack::MethodOverride middleware. So in Main::Application (apps/main/system/todo/main/web.rb) add these lines:

...
module Todo
  module Main
    class Web < Dry::Web::Roda::Application
      ...
      use Rack::MethodOverride

      plugin :all_verbs
      ...
    end
  end
end

An update to the tasks routes to process patch '/tasks/:id calling to the complete_task transaction and rendering the response.

apps/main/web/routes/tasks.rb

# frozen_string_literal: true

module Todo
  module Main
    class Application
      route 'tasks' do |r|
        r.is do
          ...
        end

        r.is :id do |id|
          r.patch do
            r.resolve 'transactions.complete_task' do |complete_task|
              complete_task.(params_with_user(id: id)) do |m|
                m.success do |_task|
                  flash[:notice] = "task id: #{id}, completed"
                  r.redirect '/tasks'
                end
                m.failure do
                  flash[:alert] = 'Unexpected error'
                  r.view 'tasks.index', **hash_with_user
                end
              end
            end
          end
        end
      end
    end
  end
end

Add the transaction and its operations

The actions to do are: first find the task in the current_user scope and return its id. And mark the task as completed with the provided id.

apps/main/lib/todo/main/transactions/complete_task.rb

# frozen_string_literal: true

require 'todo/main/transaction'

module Todo
  module Main
    module Transactions
      class CompleteTask < Transaction
        step :find_task, with: 'operations.tasks.find_task'
        step :complete,  with: 'operations.tasks.complete'
      end
    end
  end
end

In apps/main/lib/todo/main/operations/tasks/find_task.rb search the task id in the current_user scope of tasks.

# frozen_string_literal: true

require 'todo/operation'

module Todo
  module Main
    module Operations
      module Tasks
        class FindTask < Todo::Operation
          include Import['policies.tasks_scope']

          def call(hash)
            id = tasks_scope.(hash[:current_user])
              .where(id: hash[:params][:id]).pluck(:id).first

            if id
              Right(id)
            else
              Left(:error)
            end
          end
        end
      end
    end
  end
end

And in apps/main/lib/todo/main/operations/tasks/complete.rb update its complete status.

# frozen_string_literal: true

require 'todo/operation'

module Todo
  module Main
    module Operations
      module Tasks
        class Complete < Todo::Operation
          include Import['core.repositories.tasks_repo']

          def call(id)
            result = tasks_repo.update(id, completed: true)
            Right(result)
          end
        end
      end
    end
  end
end

And that's all.