diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index ba24bdab3..000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(git merge:*)", - "Bash(git add:*)" - ], - "deny": [], - "ask": [] - } -} \ No newline at end of file diff --git a/app/controllers/cards/drops_controller.rb b/app/controllers/cards/drops_controller.rb index 007580a3f..af4f985f5 100644 --- a/app/controllers/cards/drops_controller.rb +++ b/app/controllers/cards/drops_controller.rb @@ -41,7 +41,7 @@ class Cards::DropsController < ApplicationController end def render_column_replacement - columns = Cards::Columns.new(user_filtering: @user_filtering, page_size: CardsController::PAGE_SIZE) + columns = Cards::OldColumns.new(user_filtering: @user_filtering, page_size: CardsController::PAGE_SIZE) column = columns.public_send(@drop_target) if @drop_target == :doing && params[:stage_id].present? diff --git a/app/controllers/cards_controller.rb b/app/controllers/cards_controller.rb index e30867991..d84329747 100644 --- a/app/controllers/cards_controller.rb +++ b/app/controllers/cards_controller.rb @@ -11,7 +11,7 @@ class CardsController < ApplicationController PAGE_SIZE = 25 def index - @columns = Cards::Columns.new(user_filtering: @user_filtering, page_size: PAGE_SIZE) + @columns = Cards::OldColumns.new(user_filtering: @user_filtering, page_size: PAGE_SIZE) fresh_when etag: @columns end diff --git a/app/models/card.rb b/app/models/card.rb index 02394131c..65c05b4f1 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -5,6 +5,7 @@ class Card < ApplicationRecord belongs_to :collection, touch: true belongs_to :creator, class_name: "User", default: -> { Current.user } + belongs_to :column, optional: true has_many :comments, dependent: :destroy has_one_attached :image, dependent: :purge_later diff --git a/app/models/cards/columns/column.rb b/app/models/cards/columns/column.rb index c48c29fb8..ac95ee26d 100644 --- a/app/models/cards/columns/column.rb +++ b/app/models/cards/columns/column.rb @@ -1,4 +1,4 @@ -class Cards::Columns::Column +class Cards::OldColumns::Column attr_reader :page, :filter, :user_filtering def initialize(page:, filter:, user_filtering:) diff --git a/app/models/cards/columns.rb b/app/models/cards/old_columns.rb similarity index 97% rename from app/models/cards/columns.rb rename to app/models/cards/old_columns.rb index 2ef9b9957..745ebfe86 100644 --- a/app/models/cards/columns.rb +++ b/app/models/cards/old_columns.rb @@ -1,4 +1,4 @@ -class Cards::Columns +class Cards::OldColumns attr_reader :user_filtering, :page_size delegate :filter, to: :user_filtering diff --git a/app/models/collection.rb b/app/models/collection.rb index 0d8fdeb37..6f50d2d4a 100644 --- a/app/models/collection.rb +++ b/app/models/collection.rb @@ -5,6 +5,7 @@ class Collection < ApplicationRecord has_rich_text :public_description + has_many :columns, dependent: :destroy has_many :cards, dependent: :destroy has_many :tags, -> { distinct }, through: :cards has_many :events diff --git a/app/models/column.rb b/app/models/column.rb new file mode 100644 index 000000000..7191d333b --- /dev/null +++ b/app/models/column.rb @@ -0,0 +1,7 @@ +class Column < ApplicationRecord + belongs_to :collection + has_many :cards, dependent: :nullify + + validates :name, presence: true + validates :color, presence: true +end \ No newline at end of file diff --git a/db/migrate/20250924100149_create_columns_and_add_column_id_to_cards.rb b/db/migrate/20250924100149_create_columns_and_add_column_id_to_cards.rb new file mode 100644 index 000000000..edced4a9f --- /dev/null +++ b/db/migrate/20250924100149_create_columns_and_add_column_id_to_cards.rb @@ -0,0 +1,12 @@ +class CreateColumnsAndAddColumnIdToCards < ActiveRecord::Migration[8.1] + def change + create_table :columns do |t| + t.string :name, null: false + t.string :color, null: false + t.references :collection, null: false, foreign_key: true + t.timestamps + end + + add_reference :cards, :column, foreign_key: true, index: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 7a7ff1900..b5d76fbaa 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: 2025_09_17_064006) do +ActiveRecord::Schema[8.1].define(version: 2025_09_24_100149) do create_table "accesses", force: :cascade do |t| t.datetime "accessed_at" t.integer "collection_id", null: false @@ -133,6 +133,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_17_064006) do create_table "cards", force: :cascade do |t| t.integer "collection_id", null: false + t.integer "column_id" t.datetime "created_at", null: false t.integer "creator_id", null: false t.date "due_on" @@ -142,6 +143,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_17_064006) do t.string "title" t.datetime "updated_at", null: false t.index ["collection_id"], name: "index_cards_on_collection_id" + t.index ["column_id"], name: "index_cards_on_column_id" t.index ["last_active_at", "status"], name: "index_cards_on_last_active_at_and_status" t.index ["stage_id"], name: "index_cards_on_stage_id" end @@ -197,6 +199,15 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_17_064006) do t.index ["filter_id"], name: "index_collections_filters_on_filter_id" end + create_table "columns", force: :cascade do |t| + t.integer "collection_id", null: false + t.string "color", null: false + t.datetime "created_at", null: false + t.string "name", null: false + t.datetime "updated_at", null: false + t.index ["collection_id"], name: "index_columns_on_collection_id" + end + create_table "comments", force: :cascade do |t| t.integer "card_id", null: false t.datetime "created_at", null: false @@ -508,11 +519,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_17_064006) do add_foreign_key "ai_quotas", "users" add_foreign_key "card_activity_spikes", "cards" add_foreign_key "card_goldnesses", "cards" + add_foreign_key "cards", "columns" add_foreign_key "cards", "workflow_stages", column: "stage_id" add_foreign_key "closures", "cards" add_foreign_key "closures", "users" add_foreign_key "collection_publications", "collections" add_foreign_key "collections", "workflows" + add_foreign_key "columns", "collections" add_foreign_key "comments", "cards" add_foreign_key "conversation_messages", "conversations" add_foreign_key "conversations", "users" diff --git a/db/schema_cache.yml b/db/schema_cache.yml index 8c9bc6772..33b7d5d87 100644 --- a/db/schema_cache.yml +++ b/db/schema_cache.yml @@ -435,6 +435,16 @@ columns: - *9 cards: - *24 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: column_id + cast_type: *3 + sql_type_metadata: *4 + 'null': true + default: + default_function: + collation: + comment: - *5 - &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: @@ -610,6 +620,22 @@ columns: collections_filters: - *24 - *20 + columns: + - *24 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: color + cast_type: *7 + sql_type_metadata: *8 + 'null': false + default: + default_function: + collation: + comment: + - *5 + - *6 + - *10 + - *9 comments: - *23 - *5 @@ -1495,6 +1521,7 @@ primary_keys: collection_publications: id collections: id collections_filters: + columns: id comments: id conversation_messages: id conversations: id @@ -1549,6 +1576,7 @@ data_sources: collection_publications: true collections: true collections_filters: true + columns: true comments: true conversation_messages: true conversations: true @@ -1989,6 +2017,22 @@ indexes: nulls_not_distinct: comment: valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: cards + name: index_cards_on_column_id + unique: false + columns: + - column_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: cards name: index_cards_on_last_active_at_and_status @@ -2205,6 +2249,23 @@ indexes: nulls_not_distinct: comment: valid: true + columns: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: columns + name: index_columns_on_collection_id + unique: false + columns: + - collection_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true comments: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: comments @@ -3191,4 +3252,4 @@ indexes: comment: valid: true workflows: [] -version: 20250917064006 +version: 20250924100149 diff --git a/script/migrations/populate_columns_from_workflow_stages.rb b/script/migrations/populate_columns_from_workflow_stages.rb new file mode 100755 index 000000000..e17ee791b --- /dev/null +++ b/script/migrations/populate_columns_from_workflow_stages.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby + +require_relative "../../config/environment" + +ApplicationRecord.with_each_tenant do |tenant| + puts "Processing tenant: #{tenant}" + + Collection.find_each do |collection| + next unless collection.workflow.present? + + # Map to track stage_id -> column + columns_by_stage = {} + + # Create columns from workflow stages + collection.workflow.stages.find_each do |stage| + column = collection.columns.create!( + name: stage.name, + color: stage.color || Card::Colored::COLORS.first + ) + columns_by_stage[stage] = column + puts "Created column '#{column.name}' for collection '#{collection.name}'" + end + + # Associate cards with their corresponding columns based on stages + collection.cards.includes(:stage).find_each do |card| + next unless card.stage.present? + + unless card.stage.workflow.collections.include?(collection) + puts "Corrupt data: the card with id #{card.id} has the stage #{card.stage.name} with id #{card.stage.id} that belongs to a workflow not asociated ot its collection" + next + end + + stage = columns_by_stage[card.stage] + card.update!(column: stage) + puts "Associated card ##{card.id} with column '#{stage.name}'" + end + end +end + +puts "Migration completed!"