2018-11-18 11:00:15 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-07-07 11:18:12 +05:30
|
|
|
class Route < ApplicationRecord
|
2018-03-17 18:26:18 +05:30
|
|
|
include CaseSensitivity
|
2020-04-22 19:07:51 +05:30
|
|
|
include Gitlab::SQL::Pattern
|
2018-03-17 18:26:18 +05:30
|
|
|
|
2021-02-22 17:27:13 +05:30
|
|
|
belongs_to :source, polymorphic: true, inverse_of: :route # rubocop:disable Cop/PolymorphicAssociations
|
2017-08-17 22:00:37 +05:30
|
|
|
validates :source, presence: true
|
|
|
|
|
|
|
|
validates :path,
|
|
|
|
length: { within: 1..255 },
|
|
|
|
presence: true,
|
|
|
|
uniqueness: { case_sensitive: false }
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
before_validation :delete_conflicting_orphaned_routes
|
2017-08-17 22:00:37 +05:30
|
|
|
after_create :delete_conflicting_redirects
|
2019-07-31 22:56:46 +05:30
|
|
|
after_update :delete_conflicting_redirects, if: :saved_change_to_path?
|
2017-08-17 22:00:37 +05:30
|
|
|
after_update :create_redirect_for_old_path
|
|
|
|
after_update :rename_descendants
|
|
|
|
|
|
|
|
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
|
2020-04-22 19:07:51 +05:30
|
|
|
scope :for_routable, -> (routable) { where(source: routable) }
|
2021-01-29 00:20:46 +05:30
|
|
|
scope :for_routable_type, -> (routable_type) { where(source_type: routable_type) }
|
2020-04-22 19:07:51 +05:30
|
|
|
scope :sort_by_path_length, -> { order('LENGTH(routes.path)', :path) }
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
def rename_descendants
|
2019-07-31 22:56:46 +05:30
|
|
|
return unless saved_change_to_path? || saved_change_to_name?
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
descendant_routes = self.class.inside_path(path_before_last_save)
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
descendant_routes.each do |route|
|
|
|
|
attributes = {}
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
if saved_change_to_path? && route.path.present?
|
|
|
|
attributes[:path] = route.path.sub(path_before_last_save, path)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2019-07-31 22:56:46 +05:30
|
|
|
if saved_change_to_name? && name_before_last_save.present? && route.name.present?
|
|
|
|
attributes[:name] = route.name.sub(name_before_last_save, name)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
if attributes.present?
|
|
|
|
old_path = route.path
|
|
|
|
|
|
|
|
# Callbacks must be run manually
|
2020-06-23 00:09:42 +05:30
|
|
|
route.update_columns(attributes.merge(updated_at: Time.current))
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
# We are not calling route.delete_conflicting_redirects here, in hopes
|
|
|
|
# of avoiding deadlocks. The parent (self, in this method) already
|
|
|
|
# called it, which deletes conflicts for all descendants.
|
2018-05-09 12:01:36 +05:30
|
|
|
route.create_redirect(old_path) if attributes[:path]
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_conflicting_redirects
|
|
|
|
conflicting_redirects.delete_all
|
|
|
|
end
|
|
|
|
|
|
|
|
def conflicting_redirects
|
2018-05-09 12:01:36 +05:30
|
|
|
RedirectRoute.matching_path_and_descendants(path)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2018-05-09 12:01:36 +05:30
|
|
|
def create_redirect(path)
|
|
|
|
RedirectRoute.create(source: source, path: path)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def create_redirect_for_old_path
|
2019-07-31 22:56:46 +05:30
|
|
|
create_redirect(path_before_last_save) if saved_change_to_path?
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def delete_conflicting_orphaned_routes
|
|
|
|
conflicting = self.class.iwhere(path: path)
|
|
|
|
conflicting_orphaned_routes = conflicting.select do |route|
|
|
|
|
route.source.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
conflicting_orphaned_routes.each(&:destroy)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|