Statuses in task - db field and forms
This commit is contained in:
@@ -28,6 +28,10 @@ Bundler/OrderedGems:
|
|||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Style/FrozenStringLiteralComment:
|
||||||
|
Exclude:
|
||||||
|
- db/migrate/**/*.rb
|
||||||
|
|
||||||
Rails/ActionOrder:
|
Rails/ActionOrder:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ class TasksController < ApplicationController
|
|||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@form = Tasks::Create.new(project_id: fetch_project&.id)
|
@project = fetch_project || Project.order(:name).first
|
||||||
|
@form = Tasks::Create.new(project_id: @project.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@form = Tasks::Create.new(params.expect(task: %i[project_id title description]))
|
@form = Tasks::Create.new(params.expect(task: %i[project_id title description status_id]))
|
||||||
if @form.perform
|
if @form.perform
|
||||||
redirect_to tasks_path(project: @form.project)
|
redirect_to tasks_path(project: @form.project)
|
||||||
else
|
else
|
||||||
@@ -30,11 +31,11 @@ class TasksController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@form = Tasks::Update.new(id: @task.id, title: @task.title, description: @task.description)
|
@form = Tasks::Update.new(@task.attributes.slice(*Tasks::Update.attribute_names))
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@form = Tasks::Update.new(params.expect(task: %i[title description]))
|
@form = Tasks::Update.new(params.expect(task: %i[title description status_id]))
|
||||||
|
|
||||||
if @form.perform(@task)
|
if @form.perform(@task)
|
||||||
redirect_to task_path(@task)
|
redirect_to task_path(@task)
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus'
|
||||||
|
|
||||||
|
// TODO: unite with ProjectsSelectorController?
|
||||||
|
class FormProjectsSelectorController extends Controller {
|
||||||
|
static values = {
|
||||||
|
frame: String
|
||||||
|
}
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
console.log("Connected", this.element)
|
||||||
|
}
|
||||||
|
|
||||||
|
changeProject(event) {
|
||||||
|
const loc = new URL(location)
|
||||||
|
const selected = event.target.selectedOptions[0]
|
||||||
|
const code = selected.dataset.code
|
||||||
|
loc.searchParams.set('project', code)
|
||||||
|
Turbo.visit(loc.toString(), {frame: this.frameValue})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormProjectsSelectorController
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
class Task < ApplicationRecord
|
class Task < ApplicationRecord
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
|
belongs_to :status, class_name: 'TaskStatus'
|
||||||
|
|
||||||
validates :number, :title, presence: true
|
validates :number, :title, presence: true
|
||||||
validates :number, numericality: { greater_than: 0 }
|
validates :number, numericality: { greater_than: 0 }
|
||||||
|
validate :associations_should_have_same_project
|
||||||
|
|
||||||
has_rich_text :description
|
has_rich_text :description
|
||||||
|
|
||||||
@@ -25,4 +27,12 @@ class Task < ApplicationRecord
|
|||||||
project = Project.find_by!(code: project_code.downcase)
|
project = Project.find_by!(code: project_code.downcase)
|
||||||
find_by!(project:, number:)
|
find_by!(project:, number:)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def associations_should_have_same_project
|
||||||
|
return if status&.project == project
|
||||||
|
|
||||||
|
errors.add(:status, "Doesn't belong in the same project")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ class TaskStatus < ApplicationRecord
|
|||||||
|
|
||||||
validates :name, presence: true, uniqueness: { scope: :project }
|
validates :name, presence: true, uniqueness: { scope: :project }
|
||||||
validates :category, presence: true
|
validates :category, presence: true
|
||||||
|
|
||||||
|
scope :default_order, -> { order(:category, :name) }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,18 +5,21 @@ module Tasks
|
|||||||
attribute :project_id, :integer
|
attribute :project_id, :integer
|
||||||
attribute :title, :string
|
attribute :title, :string
|
||||||
attribute :description, :string
|
attribute :description, :string
|
||||||
|
attribute :status_id, :integer
|
||||||
|
|
||||||
validates :project_id, :title, presence: true
|
validates :project_id, :title, presence: true
|
||||||
|
|
||||||
delegate :model_name, to: Task
|
delegate :model_name, to: Task
|
||||||
|
|
||||||
attr_reader :project, :task
|
attr_reader :task
|
||||||
|
|
||||||
|
def project
|
||||||
|
@project ||= Project.find(project_id)
|
||||||
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
@project = Project.find(project_id)
|
@task = project.tasks.build(title:, description:, status_id:, number: @project.next_task_number)
|
||||||
|
save @task
|
||||||
@task = @project.tasks.build(title:, description:, number: @project.next_task_number)
|
|
||||||
@task.save.tap { @errors = @task.errors }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module Tasks
|
|||||||
attribute :id, :integer
|
attribute :id, :integer
|
||||||
attribute :title, :string
|
attribute :title, :string
|
||||||
attribute :description, :string
|
attribute :description, :string
|
||||||
|
attribute :status_id, :integer
|
||||||
|
|
||||||
validates :title, presence: true
|
validates :title, presence: true
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ module Tasks
|
|||||||
def perform(task)
|
def perform(task)
|
||||||
@task = task
|
@task = task
|
||||||
@id = task.id
|
@id = task.id
|
||||||
@task.assign_attributes(title:, description:)
|
@task.assign_attributes(title:, description:, status_id:)
|
||||||
save @task
|
save @task
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ h1
|
|||||||
=< @task.full_number
|
=< @task.full_number
|
||||||
|
|
||||||
= form_with model: @form do |form|
|
= form_with model: @form do |form|
|
||||||
|
.field
|
||||||
|
= form.label :status_id
|
||||||
|
= form.select :status_id, current_project.task_statuses.default_order.map { |ts| [ts.name, ts.id] }
|
||||||
.field
|
.field
|
||||||
= form.label :title
|
= form.label :title
|
||||||
= form.text_field :title
|
= form.text_field :title
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
h1 New task
|
h1 New task
|
||||||
|
|
||||||
= form_with model: @form do |form|
|
= form_with model: @form, data: {controller: 'form-projects-selector', 'form-projects-selector-frame-value' => 'status_select'} do |form|
|
||||||
.field
|
.field
|
||||||
= form.label :project_id
|
= form.label :project_id
|
||||||
= form.select :project_id, Project.order(:name).map { |p| [p.name, p.id] }
|
= form.select :project_id, Project.order(:name).map { |p| [p.name, p.id, {'data-code': p.code}] }, {}, data: {action: 'form-projects-selector#changeProject'}
|
||||||
|
= turbo_frame_tag :status_select do
|
||||||
|
.field
|
||||||
|
= form.label :status_id
|
||||||
|
= form.select :status_id, TaskStatus.where(project: @form.project).default_order.map { |ts| [ts.name, ts.id] }
|
||||||
.field
|
.field
|
||||||
= form.label :title
|
= form.label :title
|
||||||
= form.text_field :title
|
= form.text_field :title
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddStatusToTasks < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
add_reference :tasks, :status, foreign_key: { to_table: :task_statuses }
|
||||||
|
end
|
||||||
|
end
|
||||||
Generated
+4
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_10_25_131322) do
|
ActiveRecord::Schema[8.0].define(version: 2025_10_25_132011) do
|
||||||
create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
|
create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.text "body", size: :long
|
t.text "body", size: :long
|
||||||
@@ -86,7 +86,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_25_131322) do
|
|||||||
t.string "title", null: false
|
t.string "title", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.bigint "status_id"
|
||||||
t.index ["project_id"], name: "index_tasks_on_project_id"
|
t.index ["project_id"], name: "index_tasks_on_project_id"
|
||||||
|
t.index ["status_id"], name: "index_tasks_on_status_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
|
create_table "users", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t|
|
||||||
@@ -102,4 +104,5 @@ ActiveRecord::Schema[8.0].define(version: 2025_10_25_131322) do
|
|||||||
add_foreign_key "sessions", "users"
|
add_foreign_key "sessions", "users"
|
||||||
add_foreign_key "task_statuses", "projects"
|
add_foreign_key "task_statuses", "projects"
|
||||||
add_foreign_key "tasks", "projects"
|
add_foreign_key "tasks", "projects"
|
||||||
|
add_foreign_key "tasks", "task_statuses", column: "status_id"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
namespace :data_migrations do
|
||||||
|
desc 'Set initial status for tasks'
|
||||||
|
task set_initial_status_for_tasks: :environment do
|
||||||
|
Task.includes(project: :task_statuses).in_batches do |tasks|
|
||||||
|
projects = Project.where(id: tasks.pluck(:project_id))
|
||||||
|
statuses = TaskStatus.backlog.where(project: projects).group_by(&:project)
|
||||||
|
|
||||||
|
tasks.each do |task|
|
||||||
|
task.update!(status: statuses[task.project].first)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user