add crew and crew_membership models with migrations
- crews table with name, gamertag, granblue_crew_id, description - crew_memberships with role enum (member/vice_captain/captain) - partial unique index ensures one active crew per user - updated User model with crew associations and helper methods
This commit is contained in:
parent
35b8a674ab
commit
9b01aa0ff3
7 changed files with 175 additions and 11 deletions
32
app/models/crew.rb
Normal file
32
app/models/crew.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Crew < ApplicationRecord
|
||||
has_many :crew_memberships, dependent: :destroy
|
||||
has_many :users, through: :crew_memberships
|
||||
has_many :active_memberships, -> { where(retired: false) }, class_name: 'CrewMembership'
|
||||
has_many :active_members, through: :active_memberships, source: :user
|
||||
|
||||
validates :name, presence: true, length: { maximum: 100 }
|
||||
validates :gamertag, length: { maximum: 50 }, allow_nil: true
|
||||
validates :granblue_crew_id, uniqueness: true, allow_nil: true
|
||||
|
||||
def captain
|
||||
crew_memberships.find_by(role: :captain, retired: false)&.user
|
||||
end
|
||||
|
||||
def vice_captains
|
||||
crew_memberships.where(role: :vice_captain, retired: false).includes(:user).map(&:user)
|
||||
end
|
||||
|
||||
def officers
|
||||
crew_memberships.where(role: [:captain, :vice_captain], retired: false).includes(:user).map(&:user)
|
||||
end
|
||||
|
||||
def member_count
|
||||
active_memberships.count
|
||||
end
|
||||
|
||||
def blueprint
|
||||
CrewBlueprint
|
||||
end
|
||||
end
|
||||
48
app/models/crew_membership.rb
Normal file
48
app/models/crew_membership.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CrewMembership < ApplicationRecord
|
||||
belongs_to :crew
|
||||
belongs_to :user
|
||||
|
||||
enum :role, { member: 0, vice_captain: 1, captain: 2 }
|
||||
|
||||
validates :user_id, uniqueness: { scope: :crew_id }
|
||||
validate :one_active_crew_per_user, on: :create
|
||||
validate :captain_limit
|
||||
validate :vice_captain_limit
|
||||
|
||||
scope :active, -> { where(retired: false) }
|
||||
scope :retired, -> { where(retired: true) }
|
||||
|
||||
def retire!
|
||||
update!(retired: true, retired_at: Time.current, role: :member)
|
||||
end
|
||||
|
||||
def blueprint
|
||||
CrewMembershipBlueprint
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def one_active_crew_per_user
|
||||
return if retired
|
||||
|
||||
if CrewMembership.where(user_id: user_id, retired: false).where.not(id: id).exists?
|
||||
errors.add(:user, 'can only be in one active crew')
|
||||
end
|
||||
end
|
||||
|
||||
def captain_limit
|
||||
return unless captain? && !retired
|
||||
|
||||
existing = crew.crew_memberships.where(role: :captain, retired: false).where.not(id: id)
|
||||
errors.add(:role, 'crew can only have one captain') if existing.exists?
|
||||
end
|
||||
|
||||
def vice_captain_limit
|
||||
return unless vice_captain? && !retired
|
||||
|
||||
existing = crew.crew_memberships.where(role: :vice_captain, retired: false).where.not(id: id)
|
||||
errors.add(:role, 'crew can only have up to 3 vice captains') if existing.count >= 3
|
||||
end
|
||||
end
|
||||
|
|
@ -12,8 +12,10 @@ class User < ApplicationRecord
|
|||
has_many :collection_job_accessories, dependent: :destroy
|
||||
has_many :collection_artifacts, dependent: :destroy
|
||||
|
||||
# Note: The crew association will be added when crews feature is implemented
|
||||
# belongs_to :crew, optional: true
|
||||
# Crew associations
|
||||
has_many :crew_memberships, dependent: :destroy
|
||||
has_one :active_crew_membership, -> { where(retired: false) }, class_name: 'CrewMembership'
|
||||
has_one :crew, through: :active_crew_membership
|
||||
|
||||
##### ActiveRecord Validations
|
||||
validates :username,
|
||||
|
|
@ -76,9 +78,7 @@ class User < ApplicationRecord
|
|||
when 'everyone'
|
||||
true
|
||||
when 'crew_only'
|
||||
# Will be implemented when crew feature is added:
|
||||
# viewer.present? && crew.present? && viewer.crew_id == crew_id
|
||||
false # For now, crew_only acts like private until crews are implemented
|
||||
viewer.present? && in_same_crew_as?(viewer)
|
||||
when 'private_collection'
|
||||
false
|
||||
else
|
||||
|
|
@ -86,11 +86,26 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# Helper method to check if user is in same crew (placeholder for future)
|
||||
# Check if user is in same crew as another user
|
||||
def in_same_crew_as?(other_user)
|
||||
# Will be implemented when crew feature is added:
|
||||
# return false unless other_user.present?
|
||||
# crew.present? && other_user.crew_id == crew_id
|
||||
false
|
||||
return false unless other_user.present?
|
||||
return false unless crew.present? && other_user.crew.present?
|
||||
|
||||
crew.id == other_user.crew.id
|
||||
end
|
||||
|
||||
# Get the user's crew role
|
||||
def crew_role
|
||||
active_crew_membership&.role
|
||||
end
|
||||
|
||||
# Check if user is a crew officer (captain or vice captain)
|
||||
def crew_officer?
|
||||
crew_role.in?(%w[captain vice_captain])
|
||||
end
|
||||
|
||||
# Check if user is a crew captain
|
||||
def crew_captain?
|
||||
crew_role == 'captain'
|
||||
end
|
||||
end
|
||||
15
db/migrate/20251204063628_create_crews.rb
Normal file
15
db/migrate/20251204063628_create_crews.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
class CreateCrews < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :crews, id: :uuid do |t|
|
||||
t.string :name, null: false
|
||||
t.string :gamertag
|
||||
t.string :granblue_crew_id
|
||||
t.text :description
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :crews, :name
|
||||
add_index :crews, :granblue_crew_id, unique: true, where: "granblue_crew_id IS NOT NULL"
|
||||
end
|
||||
end
|
||||
20
db/migrate/20251204063649_create_crew_memberships.rb
Normal file
20
db/migrate/20251204063649_create_crew_memberships.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
class CreateCrewMemberships < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :crew_memberships, id: :uuid do |t|
|
||||
t.references :crew, type: :uuid, null: false, foreign_key: true
|
||||
t.references :user, type: :uuid, null: false, foreign_key: true
|
||||
t.integer :role, default: 0, null: false
|
||||
t.boolean :retired, default: false, null: false
|
||||
t.datetime :retired_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :crew_memberships, [:crew_id, :user_id], unique: true
|
||||
add_index :crew_memberships, [:crew_id, :role]
|
||||
add_index :crew_memberships, [:user_id],
|
||||
unique: true,
|
||||
where: "retired = false",
|
||||
name: "index_crew_memberships_on_active_user"
|
||||
end
|
||||
end
|
||||
5
db/migrate/20251204063711_add_show_gamertag_to_users.rb
Normal file
5
db/migrate/20251204063711_add_show_gamertag_to_users.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class AddShowGamertagToUsers < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :users, :show_gamertag, :boolean, default: true, null: false
|
||||
end
|
||||
end
|
||||
31
db/schema.rb
31
db/schema.rb
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_12_03_221115) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_12_04_063711) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gin"
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
|
@ -234,6 +234,32 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_03_221115) do
|
|||
t.index ["weapon_key4_id"], name: "index_collection_weapons_on_weapon_key4_id"
|
||||
end
|
||||
|
||||
create_table "crew_memberships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "crew_id", null: false
|
||||
t.uuid "user_id", null: false
|
||||
t.integer "role", default: 0, null: false
|
||||
t.boolean "retired", default: false, null: false
|
||||
t.datetime "retired_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["crew_id", "role"], name: "index_crew_memberships_on_crew_id_and_role"
|
||||
t.index ["crew_id", "user_id"], name: "index_crew_memberships_on_crew_id_and_user_id", unique: true
|
||||
t.index ["crew_id"], name: "index_crew_memberships_on_crew_id"
|
||||
t.index ["user_id"], name: "index_crew_memberships_on_active_user", unique: true, where: "(retired = false)"
|
||||
t.index ["user_id"], name: "index_crew_memberships_on_user_id"
|
||||
end
|
||||
|
||||
create_table "crews", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "gamertag"
|
||||
t.string "granblue_crew_id"
|
||||
t.text "description"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["granblue_crew_id"], name: "index_crews_on_granblue_crew_id", unique: true, where: "(granblue_crew_id IS NOT NULL)"
|
||||
t.index ["name"], name: "index_crews_on_name"
|
||||
end
|
||||
|
||||
create_table "data_migrations", primary_key: "version", id: :string, force: :cascade do |t|
|
||||
end
|
||||
|
||||
|
|
@ -718,6 +744,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_03_221115) do
|
|||
t.string "theme", default: "system", null: false
|
||||
t.integer "role", default: 1, null: false
|
||||
t.integer "collection_privacy", default: 0, null: false
|
||||
t.boolean "show_gamertag", default: true, null: false
|
||||
t.index ["collection_privacy"], name: "index_users_on_collection_privacy"
|
||||
end
|
||||
|
||||
|
|
@ -847,6 +874,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_03_221115) do
|
|||
add_foreign_key "collection_weapons", "weapon_keys", column: "weapon_key3_id"
|
||||
add_foreign_key "collection_weapons", "weapon_keys", column: "weapon_key4_id"
|
||||
add_foreign_key "collection_weapons", "weapons"
|
||||
add_foreign_key "crew_memberships", "crews"
|
||||
add_foreign_key "crew_memberships", "users"
|
||||
add_foreign_key "effects", "effects", column: "effect_family_id"
|
||||
add_foreign_key "favorites", "parties"
|
||||
add_foreign_key "favorites", "users"
|
||||
|
|
|
|||
Loading…
Reference in a new issue