app/models/communication/website/permalink.rb
# == Schema Information
#
# Table name: communication_website_permalinks
#
# id :uuid not null, primary key
# about_type :string not null, indexed => [about_id]
# is_current :boolean default(TRUE)
# path :string
# created_at :datetime not null
# updated_at :datetime not null
# about_id :uuid not null, indexed => [about_type]
# university_id :uuid not null, indexed
# website_id :uuid not null, indexed
#
# Indexes
#
# index_communication_website_permalinks_on_about (about_type,about_id)
# index_communication_website_permalinks_on_university_id (university_id)
# index_communication_website_permalinks_on_website_id (website_id)
#
# Foreign Keys
#
# fk_rails_e9646cce64 (university_id => universities.id)
# fk_rails_f389ba7d45 (website_id => communication_websites.id)
#
class Communication::Website::Permalink < ApplicationRecord
attr_accessor :force_sync_about
include WithMapping
# We don't include Sanitizable as this model is never handled by users directly.
include WithUniversity
belongs_to :university
belongs_to :website, class_name: "Communication::Website"
belongs_to :about, polymorphic: true
validates :about_id, :about_type, :path, presence: true
before_validation :set_university, on: :create
# We should not sync the about object whenever we do something with the permalink, as they can be changed during a sync.
# so we have an attribute accessor to force-sync the about, for example in the Permalinkable concern
after_commit :sync_about, on: [:create, :destroy], if: :force_sync_about
scope :for_website, -> (website) { where(website_id: website.id) }
scope :current, -> { where(is_current: true) }
scope :not_current, -> { where(is_current: false) }
scope :not_root, -> { where.not(path: '/') }
def self.config_in_website(website, language)
required_kinds_in_website(website).map { |permalink_class|
[
permalink_class.static_config_key,
permalink_class.pattern_in_website(website, language)
]
}.to_h
end
# Can be overwritten
def self.required_in_config?(website)
false
end
# Should be defined in subclasses
# Not protected because it is used in the website config "DefaultLanguages"
def self.pattern_in_website(website, language)
raise NoMethodError
end
def self.clean_path(path)
clean_path = path.dup
# Remove eventual host
clean_path = URI(clean_path).path
# Leading slash for absolute path
clean_path = "/#{clean_path}" unless clean_path.start_with?('/')
# Trailing slash for coherence
clean_path = "#{clean_path}/" unless clean_path.end_with?('/')
clean_path
rescue URI::InvalidURIError
nil
end
# Méthode pour accéder facilement à la page spéciale,
# qui s'appuie sur le `special_page_type` de chaque Permalink
def self.special_page(website)
website.special_page(self.special_page_type)
end
# Méthode d'utilité pour récupérer le slug d'une page spéciale avec ses ancêtres
def self.special_page_path(website, language)
page_l10n = self.special_page(website).localization_for(language)
return '' if page_l10n.nil?
'/' + page_l10n.slug_with_ancestors_slugs
end
# Doit être surchargé dans les classes par type, comme `Communication::Website::Permalink::Post`
def self.special_page_type
raise NoMethodError
end
def pattern
language = about.respond_to?(:language) ? about.language : website.default_language
self.class.pattern_in_website(website, language)
end
def computed_path
return @computed_path if defined?(@computed_path)
@computed_path ||= published? ? Static.clean_path(published_path) : nil
end
def save_if_needed
current_permalink = about.current_permalink_in_website(website)
return unless computed_path.present? && (current_permalink.nil? || current_permalink.path != computed_path)
# If the object had no permalink or if its path changed, we create a new permalink and delete old with same path
existing_permalinks_for_path = self.class.unscoped.where(website_id: website_id, about_id: about_id, about_type: about_type, path: computed_path, is_current: false)
self.path = computed_path
if save
existing_permalinks_for_path.find_each(&:destroy)
current_permalink&.update(is_current: false)
end
end
def special_page(website)
self.class.special_page(website)
end
def to_s
"#{path}"
end
protected
# Can be overwritten (Page for example)
def published_path
language = about.respond_to?(:language) ? about.language : website.default_language
p = ""
p += "/#{language.iso_code}" if website.active_languages.many?
p += pattern
substitutions.each do |key, value|
p.gsub! ":#{key}", "#{value}"
end
p
end
# Can be overwritten
def published?
# TODO probleme si pas for_website?, par exemple pour les objets directs
about.for_website?(website)
end
# Can be overwritten
def substitutions
{
slug: about.slug
}
end
def set_university
self.university_id = website.university_id
end
def sync_about
return unless about.persisted?
about.is_direct_object? ? about.sync_with_git : about.touch
end
end