Adding tasks
In order to add tasks I need:
- Add a form that posts a new task
- Add a route that process the post
/tasks
and call a dry-transaction to create the task - The transaction should have a step for validating the input data and one step for persisting the data in the database
- The view and the template should show the validation errors
- The created task should be assigned to the logged user
Add a form to index tasks page
In apps/main/web/templates/tasks/index.html.slim
add form and show validation errors.
h1 Tasks
div
-if validation
- validation.messages.each do |key, all_messages|
b = key.capitalize
= ": #{all_messages.join(', ')}"
br
form action='/tasks' method='post'
== csrf_tag
input name='task[description]' placeholder='Add a new task' value=new_task.description
br
ul.tasks
- tasks.each do |task|
li.task
span = task.description
apps/main/lib/todo/main/views/tasks/index.rb
add expose of validation
and new_task
# frozen_string_literal: true
require 'todo/main/view/controller'
require 'todo/main/import'
module Todo
module Main
module Views
module Tasks
class Index < Main::View::Controller
include Main::Import['policies.tasks_scope']
configure do |config|
config.template = 'tasks/index'
end
private_expose :current_user
expose :tasks do |current_user|
tasks_scope.(current_user).to_a
end
expose :validation
expose :new_task do |validation|
description = validation.to_h[:description]
Struct.new(:description).new(description)
end
end
end
end
end
end
Add a route to process the post call
In apps/main/web/routes/tasks.rb
add the post route processing that calls the create_task
transaction with the form data (r[:task]
) and the current_user
.
Also, I added hash_with_user
to the get processing, in order to send current_user
to tasks.index
view controller.
# frozen_string_literal: true
module Todo
module Main
class Application
route 'tasks' do |r|
r.is do
r.get do
r.view 'tasks.index', **hash_with_user
end
r.post do
r.resolve 'transactions.create_task' do |create_task|
create_task.(params_with_user(r[:task])) do |m|
m.success do |_task|
flash[:notice] = 'Task created!'
r.redirect '/tasks'
end
m.failure do |validation|
r.view 'tasks.index', **hash_with_user(validation: validation)
end
end
end
end
end
end
end
end
end
In apps/main/system/todo/main/web.rb
add methods params_with_user
and hash_with_user
to be used in routes. And memorize current user with memoizable gem (that you need to add to Gemfile and install).
# frozen_string_literal: true
require 'dry/web/roda/application'
require_relative 'container'
require 'memoizable'
module Todo
module Main
class Web < Dry::Web::Roda::Application
include Memoizable
...
def current_user
return unless session[:user_id]
self.class['core.repositories.users_repo']
.users.by_pk(session[:user_id]).one
end
memoize :current_user
def params_with_user(params)
{ params: params, current_user: current_user }
end
def hash_with_user(hash = {})
hash.merge(current_user: current_user)
end
...
end
end
end
Add transaction to resolve create task action
The process of update data is quite standard: route / transaction / operation. And that's what I'm doing here. In apps/main/lib/todo/main/transactions/create_task.rb
we have the transaction that triggers its operations:
# frozen_string_literal: true
require 'todo/main/transaction'
module Todo
module Main
module Transactions
class CreateTask < Transaction
step :validate, with: 'operations.tasks.validate'
step :persist, with: 'operations.tasks.create'
end
end
end
end
In apps/main/lib/todo/main/operations/tasks/validate.rb
Task schema expect a task description that should have a length of at least 3, and a user_id
informed and numeric. The user_id
is formatted from the current_user
received as parameter.
# frozen_string_literal: true
require 'todo/operation'
require 'dry/validation'
module Todo
module Main
module Operations
module Tasks
class Validate < Todo::Operation
Schema = Dry::Validation.Form do
required(:description).filled(:str?, min_size?: 3)
required(:user_id).filled(:int?)
end.freeze
private_constant :Schema
def call(hash)
attrs = hash[:params].merge(user_id: hash[:current_user].id)
validation = Schema.(attrs)
if validation.success?
Right(validation.output)
else
Left(validation)
end
end
end
end
end
end
end
And in apps/main/lib/todo/main/operations/tasks/create.rb
the data is pushed to the DB.
# frozen_string_literal: true
require 'todo/operation'
module Todo
module Main
module Operations
module Tasks
class Create < Todo::Operation
include Import['core.repositories.tasks_repo']
def call(attrs)
task = tasks_repo.create(attrs)
Right(task)
end
end
end
end
end
end