add joined_at to memberships and phantoms for historical data

- editable field separate from created_at
- active_during scope uses joined_at for filtering
- backfills from created_at in migration
This commit is contained in:
Justin Edmund 2025-12-04 03:02:13 -08:00
parent 50e2318f59
commit 5968ed74d5
5 changed files with 58 additions and 4 deletions

View file

@ -3,10 +3,10 @@
module Api
module V1
class CrewMembershipBlueprint < ApiBlueprint
fields :role, :retired, :retired_at, :created_at
fields :role, :retired, :retired_at, :joined_at, :created_at
view :with_user do
fields :role, :retired, :retired_at, :created_at
fields :role, :retired, :retired_at, :joined_at, :created_at
field :user do |membership|
UserBlueprint.render_as_hash(membership.user, view: :minimal)
@ -14,7 +14,7 @@ module Api
end
view :with_crew do
fields :role, :retired, :retired_at, :created_at
fields :role, :retired, :retired_at, :joined_at, :created_at
field :crew do |membership|
CrewBlueprint.render_as_hash(membership.crew, view: :minimal)

View file

@ -3,7 +3,7 @@
module Api
module V1
class PhantomPlayerBlueprint < ApiBlueprint
fields :name, :granblue_id, :notes, :claim_confirmed
fields :name, :granblue_id, :notes, :claim_confirmed, :retired, :retired_at, :joined_at
field :claimed do |phantom|
phantom.claimed_by_id.present?

View file

@ -6,6 +6,8 @@ class CrewMembership < ApplicationRecord
enum :role, { member: 0, vice_captain: 1, captain: 2 }
before_validation :set_joined_at, on: :create
validates :user_id, uniqueness: { scope: :crew_id }
validate :one_active_crew_per_user, on: :create
validate :captain_limit
@ -14,6 +16,13 @@ class CrewMembership < ApplicationRecord
scope :active, -> { where(retired: false) }
scope :retired, -> { where(retired: true) }
# Members who were active during a date range (either still active, or retired after the end date)
# Uses joined_at (editable) instead of created_at (system timestamp) for historical accuracy
scope :active_during, ->(start_date, end_date) {
where('retired = false OR retired_at >= ?', start_date)
.where('joined_at <= ?', end_date)
}
def retire!
update!(retired: true, retired_at: Time.current, role: :member)
end
@ -24,6 +33,10 @@ class CrewMembership < ApplicationRecord
private
def set_joined_at
self.joined_at ||= Time.current
end
def one_active_crew_per_user
return if retired

View file

@ -7,6 +7,8 @@ class PhantomPlayer < ApplicationRecord
has_many :gw_individual_scores, dependent: :nullify
before_validation :set_joined_at, on: :create
validates :name, presence: true, length: { maximum: 100 }
validates :granblue_id, length: { maximum: 20 }, allow_blank: true
validates :granblue_id, uniqueness: { scope: :crew_id }, if: -> { granblue_id.present? }
@ -17,6 +19,15 @@ class PhantomPlayer < ApplicationRecord
scope :unclaimed, -> { where(claimed_by_id: nil) }
scope :claimed, -> { where.not(claimed_by_id: nil) }
scope :pending_confirmation, -> { claimed.where(claim_confirmed: false) }
scope :active, -> { where(retired: false) }
scope :retired, -> { where(retired: true) }
# Phantoms who were active during a date range (either still active, or retired after the end date)
# Uses joined_at (editable) instead of created_at for historical accuracy
scope :active_during, ->(start_date, end_date) {
where('retired = false OR retired_at >= ?', start_date)
.where('joined_at <= ?', end_date)
}
# Assign this phantom to a user (officer action)
def assign_to(user)
@ -43,8 +54,17 @@ class PhantomPlayer < ApplicationRecord
save!
end
# Retire the phantom player (keeps scores)
def retire!
update!(retired: true, retired_at: Time.current)
end
private
def set_joined_at
self.joined_at ||= Time.current
end
def claimed_by_must_be_crew_member
return unless claimed_by.present?
return if claimed_by.crew == crew

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddJoinedAtToCrewMembershipsAndPhantomPlayers < ActiveRecord::Migration[8.0]
def up
add_column :crew_memberships, :joined_at, :datetime
add_column :phantom_players, :joined_at, :datetime
# Backfill joined_at from created_at for existing records
execute <<-SQL
UPDATE crew_memberships SET joined_at = created_at WHERE joined_at IS NULL
SQL
execute <<-SQL
UPDATE phantom_players SET joined_at = created_at WHERE joined_at IS NULL
SQL
end
def down
remove_column :crew_memberships, :joined_at
remove_column :phantom_players, :joined_at
end
end