From b63a6b3e9719f49e408a63e16201d6d9b3e37f1b Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Wed, 24 Sep 2025 14:37:14 +0200 Subject: [PATCH] Add postponable concern --- app/models/card.rb | 2 +- app/models/card/engageable.rb | 75 ------------------- app/models/card/not_now.rb | 3 + app/models/card/postponable.rb | 30 ++++++++ .../20250924123001_create_card_not_nows.rb | 8 ++ db/schema.rb | 10 ++- db/schema_cache.yml | 26 ++++++- test/models/card/engageable_test.rb | 60 --------------- test/models/card/postponable_test.rb | 43 +++++++++++ 9 files changed, 119 insertions(+), 138 deletions(-) delete mode 100644 app/models/card/engageable.rb create mode 100644 app/models/card/not_now.rb create mode 100644 app/models/card/postponable.rb create mode 100644 db/migrate/20250924123001_create_card_not_nows.rb delete mode 100644 test/models/card/engageable_test.rb create mode 100644 test/models/card/postponable_test.rb diff --git a/app/models/card.rb b/app/models/card.rb index 65c05b4f1..bb8f68000 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -1,6 +1,6 @@ class Card < ApplicationRecord include Assignable, Attachments, Cacheable, Closeable, Colored, Engageable, Entropic, Eventable, - Golden, Mentions, Multistep, Pinnable, Promptable, Readable, Searchable, + Golden, Mentions, Multistep, Pinnable, Postponable, Promptable, Readable, Searchable, Staged, Stallable, Statuses, Taggable, Watchable belongs_to :collection, touch: true diff --git a/app/models/card/engageable.rb b/app/models/card/engageable.rb deleted file mode 100644 index ae0f795d7..000000000 --- a/app/models/card/engageable.rb +++ /dev/null @@ -1,75 +0,0 @@ -module Card::Engageable - extend ActiveSupport::Concern - - included do - has_one :engagement, dependent: :destroy, class_name: "Card::Engagement" - - scope :considering, -> { published_or_drafted_by(Current.user).open.where.missing(:engagement) } - scope :on_deck, -> { published.open.joins(:engagement).where(engagement: { status: "on_deck" }) } - scope :doing, -> { published.open.joins(:engagement).where(engagement: { status: "doing" }) } - - scope :by_engagement_status, ->(status) do - case status.to_s - when "considering" then considering.with_golden_first - when "on_deck" then on_deck.with_golden_first - when "doing" then doing.with_golden_first - end - end - end - - def doing? - open? && published? && engagement&.status == "doing" - end - - def on_deck? - open? && published? && engagement&.status == "on_deck" - end - - def considering? - open? && published? && engagement.blank? - end - - def engagement_status - if doing? - "doing" - elsif on_deck? - "on_deck" - elsif considering? - "considering" - end - end - - def engage - unless doing? - reengage(status: "doing") - end - end - - def move_to_on_deck - unless on_deck? - reengage(status: "on_deck") - end - end - - def reconsider - transaction do - reopen - engagement&.destroy - activity_spike&.destroy - update!(stage: nil) - touch_last_active_at - end - end - - private - def reengage(status:) - transaction do - reopen - engagement&.destroy - create_engagement!(status:) - if status == "doing" && stage.blank? - update!(stage: collection.initial_workflow_stage) - end - end - end -end diff --git a/app/models/card/not_now.rb b/app/models/card/not_now.rb new file mode 100644 index 000000000..3efd46517 --- /dev/null +++ b/app/models/card/not_now.rb @@ -0,0 +1,3 @@ +class Card::NotNow < ApplicationRecord + belongs_to :card, class_name: "::Card", touch: true +end \ No newline at end of file diff --git a/app/models/card/postponable.rb b/app/models/card/postponable.rb new file mode 100644 index 000000000..91b49ae75 --- /dev/null +++ b/app/models/card/postponable.rb @@ -0,0 +1,30 @@ +module Card::Postponable + extend ActiveSupport::Concern + + included do + has_one :not_now, dependent: :destroy, class_name: "Card::NotNow" + + scope :not_now, -> { joins(:not_now) } + scope :active, -> { where.missing(:not_now) } + end + + def postponed? + not_now.present? + end + + def active? + !postponed? + end + + def postpone + unless postponed? + create_not_now! + end + end + + def resume + if postponed? + not_now.destroy + end + end +end diff --git a/db/migrate/20250924123001_create_card_not_nows.rb b/db/migrate/20250924123001_create_card_not_nows.rb new file mode 100644 index 000000000..0de844dff --- /dev/null +++ b/db/migrate/20250924123001_create_card_not_nows.rb @@ -0,0 +1,8 @@ +class CreateCardNotNows < ActiveRecord::Migration[8.1] + def change + create_table :card_not_nows do |t| + t.references :card, null: false, foreign_key: true, index: { unique: true } + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index b5d76fbaa..dde47c0d3 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_24_100149) do +ActiveRecord::Schema[8.1].define(version: 2025_09_24_123001) do create_table "accesses", force: :cascade do |t| t.datetime "accessed_at" t.integer "collection_id", null: false @@ -131,6 +131,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_24_100149) do t.index ["card_id"], name: "index_card_goldnesses_on_card_id", unique: true end + create_table "card_not_nows", force: :cascade do |t| + t.integer "card_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["card_id"], name: "index_card_not_nows_on_card_id", unique: true + end + create_table "cards", force: :cascade do |t| t.integer "collection_id", null: false t.integer "column_id" @@ -519,6 +526,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_09_24_100149) do add_foreign_key "ai_quotas", "users" add_foreign_key "card_activity_spikes", "cards" add_foreign_key "card_goldnesses", "cards" + add_foreign_key "card_not_nows", "cards" add_foreign_key "cards", "columns" add_foreign_key "cards", "workflow_stages", column: "stage_id" add_foreign_key "closures", "cards" diff --git a/db/schema_cache.yml b/db/schema_cache.yml index 33b7d5d87..a17c2b601 100644 --- a/db/schema_cache.yml +++ b/db/schema_cache.yml @@ -433,6 +433,11 @@ columns: - *5 - *6 - *9 + card_not_nows: + - *23 + - *5 + - *6 + - *9 cards: - *24 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column @@ -1514,6 +1519,7 @@ primary_keys: card_activity_spikes: id card_engagements: id card_goldnesses: id + card_not_nows: id cards: id closers_filters: closure_reasons: id @@ -1569,6 +1575,7 @@ data_sources: card_activity_spikes: true card_engagements: true card_goldnesses: true + card_not_nows: true cards: true closers_filters: true closure_reasons: true @@ -2000,6 +2007,23 @@ indexes: nulls_not_distinct: comment: valid: true + card_not_nows: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: card_not_nows + name: index_card_not_nows_on_card_id + unique: true + columns: + - card_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true cards: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: cards @@ -3252,4 +3276,4 @@ indexes: comment: valid: true workflows: [] -version: 20250924100149 +version: 20250924123001 diff --git a/test/models/card/engageable_test.rb b/test/models/card/engageable_test.rb deleted file mode 100644 index 25ac9def3..000000000 --- a/test/models/card/engageable_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -require "test_helper" - -class Card::EngageableTest < ActiveSupport::TestCase - setup do - Current.session = sessions(:david) - end - - test "check the engagement status of a card" do - assert cards(:logo).doing? - assert_not cards(:text).doing? - - assert_not cards(:logo).considering? - assert cards(:text).considering? - - assert_not cards(:logo).on_deck? - assert_not cards(:text).on_deck? - - assert_equal "doing", cards(:logo).engagement_status - assert_equal "considering", cards(:text).engagement_status - end - - test "change the engagement" do - assert_changes -> { cards(:text).reload.doing? }, to: true do - cards(:text).engage - end - - assert_changes -> { cards(:logo).reload.doing? }, to: false do - cards(:logo).reconsider - end - end - - test "engaging with closed cards" do - cards(:text).close - - assert_not cards(:text).considering? - assert_not cards(:text).doing? - assert_not cards(:text).on_deck? - - cards(:text).engage - assert_not cards(:text).reload.closed? - assert cards(:text).doing? - - cards(:text).close - cards(:text).reconsider - assert_not cards(:text).reload.closed? - assert cards(:text).considering? - end - - test "scopes" do - assert_includes Card.doing, cards(:logo) - assert_not_includes Card.doing, cards(:text) - - assert_includes Card.considering, cards(:text) - assert_not_includes Card.considering, cards(:logo) - - cards(:text).move_to_on_deck - assert_includes Card.on_deck, cards(:text) - assert_not_includes Card.on_deck, cards(:logo) - end -end diff --git a/test/models/card/postponable_test.rb b/test/models/card/postponable_test.rb new file mode 100644 index 000000000..0a1d261f2 --- /dev/null +++ b/test/models/card/postponable_test.rb @@ -0,0 +1,43 @@ +require "test_helper" + +class Card::PostponableTest < ActiveSupport::TestCase + setup do + Current.session = sessions(:david) + end + + test "check the postponed status of a card" do + card = cards(:logo) + + assert_not card.postponed? + assert card.active? + + card.postpone + assert card.postponed? + assert_not card.active? + end + + test "postpone and resume a card" do + card = cards(:text) + + assert_changes -> { card.reload.postponed? }, to: true do + card.postpone + end + + assert_changes -> { card.reload.postponed? }, to: false do + card.resume + end + end + + test "scopes" do + logo = cards(:logo) + text = cards(:text) + + logo.postpone + + assert_includes Card.not_now, logo + assert_not_includes Card.not_now, text + + assert_includes Card.active, text + assert_not_includes Card.active, logo + end +end