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 :gw_events, through: :crew_gw_participations
|
||||
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 :gamertag, length: { maximum: 50 }, allow_nil: true
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ class Party < ApplicationRecord
|
|||
inverse_of: :party
|
||||
|
||||
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 :summons
|
||||
|
|
@ -261,6 +263,38 @@ class Party < ApplicationRecord
|
|||
visibility == 3
|
||||
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.
|
||||
#
|
||||
|
|
|
|||
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 :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 :party_shares, foreign_key: :shared_by_id, dependent: :destroy
|
||||
|
||||
##### ActiveRecord Validations
|
||||
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.
|
||||
|
||||
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
|
||||
enable_extension "btree_gin"
|
||||
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"
|
||||
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|
|
||||
t.text "content"
|
||||
t.string "granblue_id"
|
||||
|
|
@ -1029,6 +1043,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_30_000004) do
|
|||
t.string "forged_from"
|
||||
t.uuid "forge_chain_id"
|
||||
t.integer "forge_order"
|
||||
t.integer "max_exorcism_level"
|
||||
t.index ["forge_chain_id"], name: "index_weapons_on_forge_chain_id"
|
||||
t.index ["forged_from"], name: "index_weapons_on_forged_from"
|
||||
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", "raids"
|
||||
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", "crews"
|
||||
add_foreign_key "phantom_players", "users", column: "claimed_by_id"
|
||||
|
|
|
|||
Loading…
Reference in a new issue