diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 39597ff..d766283 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -11,7 +11,7 @@ class TasksController < ApplicationController Task.all end - @tasks = @tasks.includes(:status, :workflow, project: :task_statuses) + @tasks = @tasks.includes(:status, :project, workflow: :task_statuses) end def show; end diff --git a/app/models/project.rb b/app/models/project.rb index 50799f3..386bb4b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -7,8 +7,8 @@ class Project < ApplicationRecord validates :code, exclusion: { in: %w[new] }, uniqueness: true, format: { with: /\A[a-z][a-z0-9]{1,}\z/ } has_many :tasks, dependent: :restrict_with_exception - has_many :task_statuses, dependent: :destroy has_many :workflows, dependent: :destroy + has_many :task_statuses, through: :workflows has_rich_text :description diff --git a/app/models/task_status.rb b/app/models/task_status.rb index e2f43ea..6aeae6f 100644 --- a/app/models/task_status.rb +++ b/app/models/task_status.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true class TaskStatus < ApplicationRecord - belongs_to :project belongs_to :workflow + has_one :project, through: :workflow enum :category, { backlog: 100, analysis: 1000, development: 20_000, fulfillment: 60_000 } - validates :name, presence: true, uniqueness: { scope: :project } + validates :name, presence: true, uniqueness: { scope: :workflow } validates :category, presence: true validate :associations_should_have_same_project diff --git a/app/models/workflow.rb b/app/models/workflow.rb index 28d75ff..102856d 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -4,7 +4,7 @@ class Workflow < ApplicationRecord belongs_to :project has_many :tasks, dependent: :restrict_with_exception - has_many :statuses, class_name: 'TaskStatus', dependent: :restrict_with_error + has_many :task_statuses, dependent: :restrict_with_error enum :icon, { task: 'task', warning: 'warning' }, default: 'task', scopes: false enum :color, { blue: 'blue', gray: 'gray', yellow: 'yellow', red: 'red' }, default: 'gray', scopes: false diff --git a/app/view_models/tasks/statuses/selector_view_model.rb b/app/view_models/tasks/statuses/selector_view_model.rb index dc5b401..b9ad4e4 100644 --- a/app/view_models/tasks/statuses/selector_view_model.rb +++ b/app/view_models/tasks/statuses/selector_view_model.rb @@ -17,16 +17,15 @@ module Tasks view_context.render( partial: 'tasks/status_selector', locals: { task: @task, id: dom_id, with_form: @with_form, - project_task_statuses:, + workflow_task_statuses:, task_status_badge: ->(status) { task_status_badge(status, view_context) }} ) end private - def project_task_statuses - # TODO: refactor because it causes N+1 (task statuses loaded separately) - @task.project.task_statuses.default_order + def workflow_task_statuses + @task.workflow.task_statuses.sort_by { |e| [e.category, e.name] } end def task_status_badge(status, view_context) diff --git a/app/views/tasks/_status_selector.html.slim b/app/views/tasks/_status_selector.html.slim index c91c34b..957c2d8 100644 --- a/app/views/tasks/_status_selector.html.slim +++ b/app/views/tasks/_status_selector.html.slim @@ -1,7 +1,7 @@ details.dropdown.task-status-selector id=id data-controller="tasks--status-selector" summary= task_status_badge[task.status] ul - - project_task_statuses.each do |status| + - workflow_task_statuses.each do |status| li a href="#" data-status-id="#{status.id}" data-action="tasks--status-selector#changeStatus:prevent" = task_status_badge[status] - if with_form diff --git a/db/migrate/20260321132125_change_task_statuses_uniqueness.rb b/db/migrate/20260321132125_change_task_statuses_uniqueness.rb new file mode 100644 index 0000000..04d8a99 --- /dev/null +++ b/db/migrate/20260321132125_change_task_statuses_uniqueness.rb @@ -0,0 +1,9 @@ +class ChangeTaskStatusesUniqueness < ActiveRecord::Migration[8.1] + def change + change_table :task_statuses, bulk: true do |t| + t.remove_index %i[project_id name], unique: true + t.remove_index %i[project_id category name] + t.index %i[workflow_id name], unique: true + end + end +end diff --git a/db/migrate/20260321132550_add_null_false_to_task_statuses_workflow.rb b/db/migrate/20260321132550_add_null_false_to_task_statuses_workflow.rb new file mode 100644 index 0000000..bb6b62d --- /dev/null +++ b/db/migrate/20260321132550_add_null_false_to_task_statuses_workflow.rb @@ -0,0 +1,5 @@ +class AddNullFalseToTaskStatusesWorkflow < ActiveRecord::Migration[8.1] + def change + change_column_null :task_statuses, :workflow_id, false + end +end diff --git a/db/migrate/20260321132740_drop_project_id_from_task_statuses.rb b/db/migrate/20260321132740_drop_project_id_from_task_statuses.rb new file mode 100644 index 0000000..407b78a --- /dev/null +++ b/db/migrate/20260321132740_drop_project_id_from_task_statuses.rb @@ -0,0 +1,5 @@ +class DropProjectIdFromTaskStatuses < ActiveRecord::Migration[8.1] + def change + remove_reference :task_statuses, :project, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index a124fed..6cfd8d2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_03_21_115521) do +ActiveRecord::Schema[8.1].define(version: 2026_03_21_132740) do create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_uca1400_ai_ci", force: :cascade do |t| t.text "body", size: :long t.datetime "created_at", null: false @@ -73,12 +73,9 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_21_115521) do t.integer "category", limit: 2, null: false, unsigned: true t.datetime "created_at", null: false t.string "name", null: false - t.bigint "project_id", null: false t.datetime "updated_at", null: false - t.bigint "workflow_id" - t.index ["project_id", "category", "name"], name: "index_task_statuses_on_project_id_and_category_and_name" - t.index ["project_id", "name"], name: "index_task_statuses_on_project_id_and_name", unique: true - t.index ["project_id"], name: "index_task_statuses_on_project_id" + t.bigint "workflow_id", null: false + t.index ["workflow_id", "name"], name: "index_task_statuses_on_workflow_id_and_name", unique: true t.index ["workflow_id"], name: "index_task_statuses_on_workflow_id" end @@ -117,7 +114,6 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_21_115521) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "sessions", "users" - add_foreign_key "task_statuses", "projects" add_foreign_key "tasks", "projects" add_foreign_key "tasks", "task_statuses", column: "status_id" add_foreign_key "workflows", "projects"