add party_shares table and model with associations
This commit is contained in:
parent
c3d9efa349
commit
329f86df20
7 changed files with 140 additions and 2 deletions
|
|
@ -10,6 +10,8 @@ class Crew < ApplicationRecord
|
||||||
has_many :crew_gw_participations, dependent: :destroy
|
has_many :crew_gw_participations, dependent: :destroy
|
||||||
has_many :gw_events, through: :crew_gw_participations
|
has_many :gw_events, through: :crew_gw_participations
|
||||||
has_many :phantom_players, dependent: :destroy
|
has_many :phantom_players, dependent: :destroy
|
||||||
|
has_many :party_shares, as: :shareable, dependent: :destroy
|
||||||
|
has_many :shared_parties, through: :party_shares, source: :party
|
||||||
|
|
||||||
validates :name, presence: true, length: { maximum: 100 }
|
validates :name, presence: true, length: { maximum: 100 }
|
||||||
validates :gamertag, length: { maximum: 50 }, allow_nil: true
|
validates :gamertag, length: { maximum: 50 }, allow_nil: true
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,8 @@ class Party < ApplicationRecord
|
||||||
inverse_of: :party
|
inverse_of: :party
|
||||||
|
|
||||||
has_many :favorites, dependent: :destroy
|
has_many :favorites, dependent: :destroy
|
||||||
|
has_many :party_shares, dependent: :destroy
|
||||||
|
has_many :shared_crews, through: :party_shares, source: :shareable, source_type: 'Crew'
|
||||||
|
|
||||||
accepts_nested_attributes_for :characters
|
accepts_nested_attributes_for :characters
|
||||||
accepts_nested_attributes_for :summons
|
accepts_nested_attributes_for :summons
|
||||||
|
|
@ -261,6 +263,38 @@ class Party < ApplicationRecord
|
||||||
visibility == 3
|
visibility == 3
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Checks if the party is shared with a specific crew.
|
||||||
|
#
|
||||||
|
# @param crew [Crew] the crew to check.
|
||||||
|
# @return [Boolean] true if shared with the crew; false otherwise.
|
||||||
|
def shared_with_crew?(crew)
|
||||||
|
return false unless crew
|
||||||
|
|
||||||
|
party_shares.exists?(shareable_type: 'Crew', shareable_id: crew.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Checks if a user can view this party based on visibility and sharing rules.
|
||||||
|
# A user can view if:
|
||||||
|
# - The party is public
|
||||||
|
# - The party is unlisted (accessible via direct link)
|
||||||
|
# - They own the party
|
||||||
|
# - They are an admin
|
||||||
|
# - The party is shared with a crew they belong to
|
||||||
|
#
|
||||||
|
# @param user [User, nil] the user to check.
|
||||||
|
# @return [Boolean] true if the user can view the party; false otherwise.
|
||||||
|
def viewable_by?(user)
|
||||||
|
return true if public?
|
||||||
|
return true if unlisted?
|
||||||
|
return true if user && user_id == user.id
|
||||||
|
return true if user&.admin?
|
||||||
|
return true if user&.crew && shared_with_crew?(user.crew)
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
# Checks if the party is favorited by a given user.
|
# Checks if the party is favorited by a given user.
|
||||||
#
|
#
|
||||||
|
|
|
||||||
63
app/models/party_share.rb
Normal file
63
app/models/party_share.rb
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
##
|
||||||
|
# PartyShare represents a sharing relationship between a party and a group (e.g., a crew).
|
||||||
|
# It allows party owners to share their parties with specific groups, granting view access
|
||||||
|
# to members of those groups without changing the party's base visibility.
|
||||||
|
#
|
||||||
|
# @!attribute [rw] party
|
||||||
|
# @return [Party] the party being shared.
|
||||||
|
# @!attribute [rw] shareable
|
||||||
|
# @return [Crew] the polymorphic group the party is shared with.
|
||||||
|
# @!attribute [rw] shared_by
|
||||||
|
# @return [User] the user who created this share.
|
||||||
|
class PartyShare < ApplicationRecord
|
||||||
|
# Associations
|
||||||
|
belongs_to :party
|
||||||
|
belongs_to :shareable, polymorphic: true
|
||||||
|
belongs_to :shared_by, class_name: 'User'
|
||||||
|
|
||||||
|
# Validations
|
||||||
|
validates :party_id, uniqueness: {
|
||||||
|
scope: [:shareable_type, :shareable_id],
|
||||||
|
message: 'has already been shared with this group'
|
||||||
|
}
|
||||||
|
validate :owner_can_share
|
||||||
|
validate :sharer_is_member_of_shareable
|
||||||
|
|
||||||
|
# Scopes
|
||||||
|
scope :for_crew, ->(crew) { where(shareable_type: 'Crew', shareable_id: crew.id) }
|
||||||
|
scope :for_crews, ->(crew_ids) { where(shareable_type: 'Crew', shareable_id: crew_ids) }
|
||||||
|
scope :for_party, ->(party) { where(party_id: party.id) }
|
||||||
|
|
||||||
|
##
|
||||||
|
# Returns the blueprint class for serialization.
|
||||||
|
#
|
||||||
|
# @return [Class] the PartyShareBlueprint class.
|
||||||
|
def blueprint
|
||||||
|
PartyShareBlueprint
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
##
|
||||||
|
# Validates that only the party owner can share the party.
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def owner_can_share
|
||||||
|
return if party&.user_id == shared_by_id
|
||||||
|
|
||||||
|
errors.add(:shared_by, 'must be the party owner')
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Validates that the sharer is a member of the group they're sharing to.
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def sharer_is_member_of_shareable
|
||||||
|
return unless shareable_type == 'Crew'
|
||||||
|
return if shareable&.active_memberships&.exists?(user_id: shared_by_id)
|
||||||
|
|
||||||
|
errors.add(:shareable, 'you must be a member of this crew')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -19,6 +19,7 @@ class User < ApplicationRecord
|
||||||
has_many :crew_invitations, dependent: :destroy
|
has_many :crew_invitations, dependent: :destroy
|
||||||
has_many :pending_crew_invitations, -> { where(status: :pending) }, class_name: 'CrewInvitation'
|
has_many :pending_crew_invitations, -> { where(status: :pending) }, class_name: 'CrewInvitation'
|
||||||
has_many :sent_crew_invitations, class_name: 'CrewInvitation', foreign_key: :invited_by_id, dependent: :nullify
|
has_many :sent_crew_invitations, class_name: 'CrewInvitation', foreign_key: :invited_by_id, dependent: :nullify
|
||||||
|
has_many :party_shares, foreign_key: :shared_by_id, dependent: :destroy
|
||||||
|
|
||||||
##### ActiveRecord Validations
|
##### ActiveRecord Validations
|
||||||
validates :username,
|
validates :username,
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
DataMigrate::Data.define(version: 20251230000002)
|
DataMigrate::Data.define(version: 20260104000002)
|
||||||
|
|
|
||||||
21
db/migrate/20260105053753_create_party_shares.rb
Normal file
21
db/migrate/20260105053753_create_party_shares.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreatePartyShares < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :party_shares, id: :uuid do |t|
|
||||||
|
t.references :party, type: :uuid, null: false, foreign_key: true
|
||||||
|
t.references :shareable, type: :uuid, null: false, polymorphic: true
|
||||||
|
t.references :shared_by, type: :uuid, null: false, foreign_key: { to_table: :users }
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prevent duplicate shares of the same party to the same group
|
||||||
|
add_index :party_shares, [:party_id, :shareable_type, :shareable_id],
|
||||||
|
unique: true,
|
||||||
|
name: 'index_party_shares_unique_per_shareable'
|
||||||
|
|
||||||
|
# Quick lookup of all parties shared with a specific group
|
||||||
|
add_index :party_shares, [:shareable_type, :shareable_id]
|
||||||
|
end
|
||||||
|
end
|
||||||
19
db/schema.rb
19
db/schema.rb
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_12_30_000004) do
|
ActiveRecord::Schema[8.0].define(version: 2026_01_05_053753) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "btree_gin"
|
enable_extension "btree_gin"
|
||||||
enable_extension "pg_catalog.plpgsql"
|
enable_extension "pg_catalog.plpgsql"
|
||||||
|
|
@ -677,6 +677,20 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_30_000004) do
|
||||||
t.index ["weapons_count", "characters_count", "summons_count"], name: "index_parties_on_counters"
|
t.index ["weapons_count", "characters_count", "summons_count"], name: "index_parties_on_counters"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "party_shares", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
t.uuid "party_id", null: false
|
||||||
|
t.string "shareable_type", null: false
|
||||||
|
t.uuid "shareable_id", null: false
|
||||||
|
t.uuid "shared_by_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["party_id", "shareable_type", "shareable_id"], name: "index_party_shares_unique_per_shareable", unique: true
|
||||||
|
t.index ["party_id"], name: "index_party_shares_on_party_id"
|
||||||
|
t.index ["shareable_type", "shareable_id"], name: "index_party_shares_on_shareable"
|
||||||
|
t.index ["shareable_type", "shareable_id"], name: "index_party_shares_on_shareable_type_and_shareable_id"
|
||||||
|
t.index ["shared_by_id"], name: "index_party_shares_on_shared_by_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "pg_search_documents", force: :cascade do |t|
|
create_table "pg_search_documents", force: :cascade do |t|
|
||||||
t.text "content"
|
t.text "content"
|
||||||
t.string "granblue_id"
|
t.string "granblue_id"
|
||||||
|
|
@ -1029,6 +1043,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_30_000004) do
|
||||||
t.string "forged_from"
|
t.string "forged_from"
|
||||||
t.uuid "forge_chain_id"
|
t.uuid "forge_chain_id"
|
||||||
t.integer "forge_order"
|
t.integer "forge_order"
|
||||||
|
t.integer "max_exorcism_level"
|
||||||
t.index ["forge_chain_id"], name: "index_weapons_on_forge_chain_id"
|
t.index ["forge_chain_id"], name: "index_weapons_on_forge_chain_id"
|
||||||
t.index ["forged_from"], name: "index_weapons_on_forged_from"
|
t.index ["forged_from"], name: "index_weapons_on_forged_from"
|
||||||
t.index ["gacha"], name: "index_weapons_on_gacha"
|
t.index ["gacha"], name: "index_weapons_on_gacha"
|
||||||
|
|
@ -1111,6 +1126,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_30_000004) do
|
||||||
add_foreign_key "parties", "parties", column: "source_party_id"
|
add_foreign_key "parties", "parties", column: "source_party_id"
|
||||||
add_foreign_key "parties", "raids"
|
add_foreign_key "parties", "raids"
|
||||||
add_foreign_key "parties", "users"
|
add_foreign_key "parties", "users"
|
||||||
|
add_foreign_key "party_shares", "parties"
|
||||||
|
add_foreign_key "party_shares", "users", column: "shared_by_id"
|
||||||
add_foreign_key "phantom_players", "crew_memberships", column: "claimed_from_membership_id"
|
add_foreign_key "phantom_players", "crew_memberships", column: "claimed_from_membership_id"
|
||||||
add_foreign_key "phantom_players", "crews"
|
add_foreign_key "phantom_players", "crews"
|
||||||
add_foreign_key "phantom_players", "users", column: "claimed_by_id"
|
add_foreign_key "phantom_players", "users", column: "claimed_by_id"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue