RideStatus::class, 'category' => RideCategory::class, 'opening_date' => 'date', 'closing_date' => 'date', 'status_since' => 'date', 'min_height_in' => 'integer', 'max_height_in' => 'integer', 'capacity_per_hour' => 'integer', 'ride_duration_seconds' => 'integer', ]; // Core Relationships (Django Parity) public function park(): BelongsTo { return $this->belongsTo(Park::class); } public function parkArea(): BelongsTo { return $this->belongsTo(ParkArea::class); } public function manufacturer(): BelongsTo { return $this->belongsTo(Manufacturer::class, 'manufacturer_id'); } public function designer(): BelongsTo { return $this->belongsTo(Designer::class); } public function rideModel(): BelongsTo { return $this->belongsTo(RideModel::class); } // Extended Relationships public function coasterStats(): HasOne { return $this->hasOne(RollerCoasterStats::class); } // Photo Relationships (Polymorphic) public function photos(): MorphMany { return $this->morphMany(Photo::class, 'photosable'); } // Review Relationships public function reviews(): MorphMany { return $this->morphMany(Review::class, 'reviewable'); } public function approvedReviews(): MorphMany { return $this->reviews()->where('status', 'approved'); } // Query Scopes public function scopeActive($query) { return $query->where('status', 'operating'); } public function scopeByCategory($query, $category) { return $query->where('category', $category); } public function scopeInPark($query, $parkId) { return $query->where('park_id', $parkId); } public function scopeByManufacturer($query, $manufacturerId) { return $query->where('manufacturer_id', $manufacturerId); } // Attributes & Helper Methods public function getAverageRatingAttribute(): ?float { return $this->approvedReviews()->avg('rating'); } public function getReviewCountAttribute(): int { return $this->approvedReviews()->count(); } public function getDisplayNameAttribute(): string { return $this->name . ' at ' . $this->park->name; } public function getIsOperatingAttribute(): bool { return $this->status === RideStatus::OPERATING; } public function getHeightRequirementTextAttribute(): ?string { if (!$this->min_height_in) { return null; } $text = "Must be at least {$this->min_height_in}\" tall"; if ($this->max_height_in) { $text .= " and no taller than {$this->max_height_in}\" tall"; } return $text; } // Review Management Methods public function canBeReviewedBy(?int $userId): bool { if (!$userId) { return false; } return !$this->reviews() ->where('user_id', $userId) ->exists(); } public function addReview(array $data): Review { return $this->reviews()->create([ 'user_id' => Auth::id(), 'rating' => $data['rating'], 'title' => $data['title'] ?? null, 'content' => $data['content'], 'status' => 'pending', ]); } // Cache Management (Future: will use HasCaching trait) public function getCacheKey(string $suffix = ''): string { return "ride:{$this->id}" . ($suffix ? ":{$suffix}" : ''); } public function clearRelatedCache(): void { cache()->forget($this->getCacheKey('reviews')); cache()->forget($this->getCacheKey('statistics')); cache()->forget($this->getCacheKey('photos')); } // Statistics Management (Future: will use HasStatistics trait) public function updateStatistics(): void { // Placeholder for future HasStatistics trait integration // For now, manually manage statistics $totalReviews = $this->reviews()->count(); $averageRating = $this->reviews()->avg('rating'); // Update any related statistics tracking $this->clearRelatedCache(); } }