You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails应用订阅状态变迁记录与历史状态查询方案咨询

Tracking Subscription Status History & Querying Past States in Rails

Great question! Your plan to add a subscription_status_transitions table is exactly the right approach for tracking state changes and answering historical queries like "which subscriptions were active 3 months ago." Let's break down how to implement this end-to-end:

1. Set Up the Status Transitions Table

First, generate the model and migration for your transition records. We'll include the necessary fields plus indexes to keep queries fast:

rails generate model SubscriptionStatusTransition subscription:references status:string transitioned_at:datetime
rails db:migrate

Then, add a composite index to optimize queries that look up the latest transition for a subscription. Edit the generated migration file (before running db:migrate if you haven't already) to include:

add_index :subscription_status_transitions, [:subscription_id, :transitioned_at], order: { transitioned_at: :desc }, name: "index_subs_transitions_on_sub_id_and_transitioned_at"

This index ensures we can quickly find the most recent status change for any subscription, which is critical for your historical query.

2. Configure Model Associations & Auto-Record Transitions

Next, link the models and set up callbacks to automatically create transition records whenever a subscription's status changes.

In app/models/subscription.rb:

class Subscription < ApplicationRecord
  has_many :status_transitions, class_name: "SubscriptionStatusTransition", dependent: :destroy

  # Record initial status when a subscription is created
  after_create :record_initial_status

  # Record status changes whenever the status is updated
  before_update :record_status_transition, if: :status_changed?

  private

  def record_initial_status
    status_transitions.create(status: status, transitioned_at: created_at)
  end

  def record_status_transition
    # Record the NEW status and the time of the change
    status_transitions.create(status: status, transitioned_at: Time.current)
  end
end

In app/models/subscription_status_transition.rb:

class SubscriptionStatusTransition < ApplicationRecord
  belongs_to :subscription
end

3. Query Subscriptions Active 3 Months Ago

The core challenge is finding, for each subscription, what its status was exactly 3 months ago. The most efficient way to do this (especially with large datasets) is using a window function to get the latest status transition that occurred before or at your target date.

Here's how to write this query in Active Record:

target_date = 3.months.ago

# Use a subquery with ROW_NUMBER() to get the latest transition per subscription before the target date
active_subs_3_months_ago = Subscription.joins(<<-SQL.squish)
  INNER JOIN (
    SELECT
      subscription_id,
      status,
      ROW_NUMBER() OVER (
        PARTITION BY subscription_id
        ORDER BY transitioned_at DESC
      ) AS transition_rank
    FROM subscription_status_transitions
    WHERE transitioned_at <= '#{target_date.utc}'
  ) AS latest_past_transitions
  ON subscriptions.id = latest_past_transitions.subscription_id
  AND latest_past_transitions.transition_rank = 1
SQL
.where("latest_past_transitions.status = 'active'")

How This Works:

  • The subquery uses ROW_NUMBER() to assign a rank to each transition for a subscription, ordered by transitioned_at (newest first).
  • We only keep transitions that happened on or before 3 months ago.
  • Joining back to subscriptions where the rank is 1 gives us the most recent status each subscription had at that point in time.
  • Finally, we filter for subscriptions where that status was active.

4. Optional: Handle Edge Cases

  • Subscriptions created after 3 months ago: These won't appear in the results, since they didn't exist at the target date. If you want to include them (assuming they were active when created), you can adjust the query to union with subscriptions created after the target date that are active.
  • Soft-deleted subscriptions: If you use soft deletion, add a WHERE subscriptions.deleted_at IS NULL clause (or your soft delete column) to exclude them.

内容的提问来源于stack exchange,提问作者brandonhilkert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:58:48