2018-07-25 05:30:33 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-03-28 09:17:42 -04:00
|
|
|
class Route < ApplicationRecord
|
2018-01-04 17:16:13 -05:00
|
|
|
include CaseSensitivity
|
2020-04-15 14:09:36 -04:00
|
|
|
include Gitlab::SQL::Pattern
|
2018-01-04 17:16:13 -05:00
|
|
|
|
2020-12-15 10:09:59 -05:00
|
|
|
belongs_to :source, polymorphic: true, inverse_of: :route # rubocop:disable Cop/PolymorphicAssociations
|
2022-01-07 16:17:37 -05:00
|
|
|
belongs_to :namespace, inverse_of: :namespace_route
|
2016-10-31 07:00:53 -04:00
|
|
|
validates :source, presence: true
|
|
|
|
|
|
|
|
validates :path,
|
|
|
|
length: { within: 1..255 },
|
|
|
|
presence: true,
|
|
|
|
uniqueness: { case_sensitive: false }
|
|
|
|
|
2017-05-05 17:31:33 -04:00
|
|
|
after_create :delete_conflicting_redirects
|
2019-01-15 16:05:36 -05:00
|
|
|
after_update :delete_conflicting_redirects, if: :saved_change_to_path?
|
2017-05-03 13:14:30 -04:00
|
|
|
after_update :create_redirect_for_old_path
|
2017-05-05 17:31:33 -04:00
|
|
|
after_update :rename_descendants
|
2016-10-31 07:00:53 -04:00
|
|
|
|
2017-03-21 12:04:12 -04:00
|
|
|
scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
|
2020-04-15 14:09:36 -04:00
|
|
|
scope :for_routable, -> (routable) { where(source: routable) }
|
2020-11-02 10:08:52 -05:00
|
|
|
scope :for_routable_type, -> (routable_type) { where(source_type: routable_type) }
|
2020-04-15 14:09:36 -04:00
|
|
|
scope :sort_by_path_length, -> { order('LENGTH(routes.path)', :path) }
|
2017-03-21 12:04:12 -04:00
|
|
|
|
2017-05-05 17:31:33 -04:00
|
|
|
def rename_descendants
|
2019-01-15 16:05:36 -05:00
|
|
|
return unless saved_change_to_path? || saved_change_to_name?
|
2017-02-04 13:26:11 -05:00
|
|
|
|
2019-01-15 16:05:36 -05:00
|
|
|
descendant_routes = self.class.inside_path(path_before_last_save)
|
2017-02-08 15:54:33 -05:00
|
|
|
|
2017-05-05 17:31:33 -04:00
|
|
|
descendant_routes.each do |route|
|
2017-05-03 18:26:44 -04:00
|
|
|
attributes = {}
|
2017-02-08 15:54:33 -05:00
|
|
|
|
2019-01-15 16:05:36 -05:00
|
|
|
if saved_change_to_path? && route.path.present?
|
|
|
|
attributes[:path] = route.path.sub(path_before_last_save, path)
|
2017-05-03 18:26:44 -04:00
|
|
|
end
|
2017-02-04 13:26:11 -05:00
|
|
|
|
2019-01-15 16:05:36 -05:00
|
|
|
if saved_change_to_name? && name_before_last_save.present? && route.name.present?
|
|
|
|
attributes[:name] = route.name.sub(name_before_last_save, name)
|
2017-02-04 13:26:11 -05:00
|
|
|
end
|
2017-05-03 18:26:44 -04:00
|
|
|
|
2022-08-17 17:09:50 -04:00
|
|
|
next if attributes.empty?
|
2017-05-05 17:31:33 -04:00
|
|
|
|
2022-08-17 17:09:50 -04:00
|
|
|
old_path = route.path
|
2017-05-05 17:31:33 -04:00
|
|
|
|
2022-08-17 17:09:50 -04:00
|
|
|
# Callbacks must be run manually
|
|
|
|
route.update_columns(attributes.merge(updated_at: Time.current))
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
route.create_redirect(old_path) if attributes[:path]
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|
|
|
|
end
|
2017-05-01 19:48:05 -04:00
|
|
|
|
2017-05-03 13:14:30 -04:00
|
|
|
def delete_conflicting_redirects
|
|
|
|
conflicting_redirects.delete_all
|
|
|
|
end
|
|
|
|
|
|
|
|
def conflicting_redirects
|
2018-03-05 07:02:36 -05:00
|
|
|
RedirectRoute.matching_path_and_descendants(path)
|
2017-05-03 13:14:30 -04:00
|
|
|
end
|
|
|
|
|
2018-03-05 07:02:36 -05:00
|
|
|
def create_redirect(path)
|
|
|
|
RedirectRoute.create(source: source, path: path)
|
2017-05-01 19:48:05 -04:00
|
|
|
end
|
2017-05-03 13:33:01 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def create_redirect_for_old_path
|
2019-01-15 16:05:36 -05:00
|
|
|
create_redirect(path_before_last_save) if saved_change_to_path?
|
2017-05-03 13:33:01 -04:00
|
|
|
end
|
2016-10-31 07:00:53 -04:00
|
|
|
end
|