From 633bf8927c1fe666e3bef6798edd35883c1a16ea Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 24 Dec 2025 22:45:07 +0900 Subject: [PATCH 1/8] AR Enum auto-defines this the auto-defined one might be very slightly less eficient, but that's not a big deal here. --- app/models/invitation.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/invitation.rb b/app/models/invitation.rb index 995616faf..e322c5ec6 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -4,8 +4,6 @@ class Invitation < ApplicationRecord belongs_to :proposal belongs_to :user, optional: true - scope :not_accepted, -> { where(state: [:pending, :declined]) } - before_create :set_default_state before_create :set_slug From e294f996d086265849fc94a03339de12396d9961 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 24 Dec 2025 22:48:01 +0900 Subject: [PATCH 2/8] Enum can set the default via :default option --- app/models/invitation.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/models/invitation.rb b/app/models/invitation.rb index e322c5ec6..b2e0459d2 100644 --- a/app/models/invitation.rb +++ b/app/models/invitation.rb @@ -1,10 +1,9 @@ class Invitation < ApplicationRecord - enum :state, {pending: 'pending', accepted: 'accepted', declined: 'declined'} + enum :state, {pending: 'pending', accepted: 'accepted', declined: 'declined'}, default: :pending belongs_to :proposal belongs_to :user, optional: true - before_create :set_default_state before_create :set_slug validates :email, presence: true @@ -28,10 +27,6 @@ def decline def set_slug self.slug = Digest::SHA1.hexdigest([email, rand(1000)].map(&:to_s).join('-'))[0, 10] end - - def set_default_state - self.state = :pending if state.nil? - end end # == Schema Information From e5716363ca90d085ea2fb768e3655cbe1e998cc3 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 24 Dec 2025 23:01:43 +0900 Subject: [PATCH 3/8] bundle stateful_enum --- Gemfile | 2 ++ Gemfile.lock | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 2b0f4d2d7..dab801514 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,8 @@ gem 'omniauth-github' gem 'omniauth-twitter' gem "omniauth-rails_csrf_protection", "~> 2.0" +gem 'stateful_enum' + gem 'actionview-encoded_mail_to' gem 'active_model_serializers', '~> 0.10.16' gem 'bootsnap', '~> 1.20', require: false diff --git a/Gemfile.lock b/Gemfile.lock index f36ac69ba..8643d0ba1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -428,6 +428,7 @@ GEM snaky_hash (2.0.1) hashie version_gem (~> 1.1, >= 1.1.1) + stateful_enum (0.7.0) stringio (3.2.0) temple (0.10.4) thor (1.4.0) @@ -508,6 +509,7 @@ DEPENDENCIES sendgrid-ruby sidekiq simple_form + stateful_enum turbo-rails RUBY VERSION From c2f54cde1b84a849ebfe83e7c2c9073bd525486c Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 25 Dec 2025 02:25:29 +0900 Subject: [PATCH 4/8] Define ProgramSession#confirm event on enum --- app/models/program_session.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/models/program_session.rb b/app/models/program_session.rb index 044c02d4f..61132498c 100644 --- a/app/models/program_session.rb +++ b/app/models/program_session.rb @@ -6,7 +6,12 @@ class ProgramSession < ApplicationRecord unconfirmed_waitlisted: 'unconfirmed waitlisted', confirmed_waitlisted: 'confirmed waitlisted', declined: 'declined' - }, default: :draft + }, default: :draft do + event :confirm do + transition :unconfirmed_waitlisted => :confirmed_waitlisted + transition :unconfirmed_accepted => :live + end + end STATE_GROUPS = { live: 'program', @@ -86,10 +91,6 @@ def can_confirm? CONFIRMATIONS.key?(state) end - def confirm - update(state: CONFIRMATIONS[state]) if can_confirm? - end - def can_promote? PROMOTIONS.key?(state) end From eb831b0a96bcbabadfe52f1d5cd558eb756ba242 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 25 Dec 2025 02:26:01 +0900 Subject: [PATCH 5/8] stateful_enum auto-defines this exact method --- app/models/program_session.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/models/program_session.rb b/app/models/program_session.rb index 61132498c..88b533f66 100644 --- a/app/models/program_session.rb +++ b/app/models/program_session.rb @@ -28,11 +28,6 @@ class ProgramSession < ApplicationRecord confirmed_waitlisted: :live }.with_indifferent_access - CONFIRMATIONS = { - unconfirmed_waitlisted: :confirmed_waitlisted, - unconfirmed_accepted: :live - }.with_indifferent_access - belongs_to :event belongs_to :proposal, optional: true belongs_to :track, optional: true @@ -87,10 +82,6 @@ def self.create_from_proposal(proposal) end end - def can_confirm? - CONFIRMATIONS.key?(state) - end - def can_promote? PROMOTIONS.key?(state) end From 1c65802b4ec75df1bc7b18bc9d59338dbd7e5e13 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 25 Dec 2025 03:01:26 +0900 Subject: [PATCH 6/8] Define ProgramSession#promote event on enum --- app/models/program_session.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/models/program_session.rb b/app/models/program_session.rb index 88b533f66..e6922609f 100644 --- a/app/models/program_session.rb +++ b/app/models/program_session.rb @@ -11,6 +11,16 @@ class ProgramSession < ApplicationRecord transition :unconfirmed_waitlisted => :confirmed_waitlisted transition :unconfirmed_accepted => :live end + + event :promote do + before do + proposal.promote if proposal + end + + transition :draft => :live + transition :unconfirmed_waitlisted => :unconfirmed_accepted + transition :confirmed_waitlisted => :live + end end STATE_GROUPS = { @@ -86,13 +96,6 @@ def can_promote? PROMOTIONS.key?(state) end - def promote - if proposal.present? - proposal.promote - end - update(state: PROMOTIONS[state]) - end - def multiple_speakers? speakers.count > 1 end From 9cb1ebe3b6c0517ed2b1e8ccc835d7b2c5bf4088 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 25 Dec 2025 03:02:35 +0900 Subject: [PATCH 7/8] Fix buggy test data ProgramSession factory creates :live instance that cannot be promoted --- spec/models/program_session_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/program_session_spec.rb b/spec/models/program_session_spec.rb index 5efb0a5b4..8e75f47ba 100644 --- a/spec/models/program_session_spec.rb +++ b/spec/models/program_session_spec.rb @@ -189,8 +189,7 @@ end it "promotes it's proposal" do - ps = create(:program_session, proposal: proposal, track: proposal.track) - proposal = create(:proposal_with_track, program_session: ps) + ps = create(:program_session, state: :draft, proposal: proposal, track: proposal.track) expect(proposal).to receive(:promote) ps.promote From 9df4c7f1d4f0e64eed7faf3099e6a66aade11b8b Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 25 Dec 2025 03:05:32 +0900 Subject: [PATCH 8/8] stateful_enum auto-defines this exact method --- app/models/program_session.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/models/program_session.rb b/app/models/program_session.rb index e6922609f..77f3f108f 100644 --- a/app/models/program_session.rb +++ b/app/models/program_session.rb @@ -32,11 +32,6 @@ class ProgramSession < ApplicationRecord declined: 'declined' }.with_indifferent_access - PROMOTIONS = { - draft: :live, - unconfirmed_waitlisted: :unconfirmed_accepted, - confirmed_waitlisted: :live - }.with_indifferent_access belongs_to :event belongs_to :proposal, optional: true @@ -92,10 +87,6 @@ def self.create_from_proposal(proposal) end end - def can_promote? - PROMOTIONS.key?(state) - end - def multiple_speakers? speakers.count > 1 end