feat: Implement SEO-friendly slug generation and improve geocoding UX

- Add Rails parameterize for server-side slug generation (name-venue-city format)
- Configure client-side slug library with RFC3986 mode for consistency
- Remove slug field from edit forms to prevent URL changes after publication
- Enable image_processing gem for Active Storage variants
- Make geocoding notifications visible indefinitely on promoter event forms
- Add server-side slug generation fallback with uniqueness validation
- Update promoter controller to allow slug only for new events

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
kbe
2025-10-01 08:33:06 +02:00
parent 20dcee0a5b
commit da3522d118
7 changed files with 174 additions and 39 deletions

View File

@@ -26,6 +26,7 @@ class Event < ApplicationRecord
# === Callbacks ===
before_validation :generate_slug, if: :should_generate_slug?
before_validation :geocode_address, if: :should_geocode_address?
before_update :handle_image_replacement, if: :image_attached?
@@ -62,6 +63,71 @@ class Event < ApplicationRecord
# === Instance Methods ===
# Generate SEO-friendly slug from name, venue name, and city
def generate_slug
return if name.blank? && venue_name.blank? && venue_address.blank?
# Extract city from venue address
city = extract_city_from_address(venue_address)
# Build slug parts
slug_parts = []
slug_parts << name if name.present?
slug_parts << venue_name if venue_name.present?
slug_parts << city if city.present?
# Generate slug using Rails' parameterize
slug_value = slug_parts.join('-').parameterize
# Ensure minimum length
if slug_value.length < 3
slug_value = "event-#{Time.current.to_i}".parameterize
end
# Make sure slug is unique
base_slug = slug_value
counter = 1
while Event.where.not(id: id).where(slug: slug_value).exists?
slug_value = "#{base_slug}-#{counter}".parameterize
counter += 1
end
self.slug = slug_value
end
# Check if slug should be generated
def should_generate_slug?
# Generate slug if it's blank or if it's a new record
slug.blank? || new_record?
end
# Extract city from address
def extract_city_from_address(address)
return "" if address.blank?
# Look for French postal code pattern (5 digits) + city
match = address.match(/(\d{5})\s+([^,]+)/)
if match
return match[2].strip
end
# Fallback: extract last part after comma (assume it's city)
parts = address.split(',')
if parts.length > 1
return parts[parts.length - 1].strip
end
# Another fallback: look for common French city indicators
city_indicators = ["Paris", "Lyon", "Marseille", "Toulouse", "Nice", "Nantes", "Strasbourg", "Montpellier", "Bordeaux", "Lille"]
for city in city_indicators
if address.downcase.include?(city.downcase)
return city
end
end
""
end
# Get image URL prioritizing old image field if it exists
def display_image_url
# First check if old image field exists and has a value