Commit 56b298f5 authored by Alex Kalderimis's avatar Alex Kalderimis

Avoid canonical slug conflict in the same project

This adds validations that ensure that wiki pages cannot have the same
canonical slug in the same project. We cannot easily move this check to
the DB, so this is handled in logic - we check in `.find_or_create` if
either of the known slugs matches a record, and we raise errors on
conflict.

This also introduces some optimisations that eliminate unnecessary
queries.

Minor updates from review comments

Includes:

- Use Class#name instead of constant
- Wrap mutable state in an outer transaction

  The one subtle thing here is that the first query (to find the record)
  must be within the transaction, even though it does not mutate the state
  of the DB. This is because we learn important facts from that query
  (namely whether there are any conflicting canonical slugs) that could be
  invalidated outside a transaction.

- Use standard uniqueness validator

  This is more declarative than our hand-rolled implementation

- Move page construction into factory

  This requires a new trait for the wiki page factory to create pages that
  are in the wiki repo.

- Changes to some test descriptions

- Reduce DB queries in specs

we don't strictly need multiple slugs here - one will do.

- Avoid running validation if there is no project_id

- Raise error if there are no slugs at all
parent 7d278de4
...@@ -81,7 +81,7 @@ class Event < ApplicationRecord ...@@ -81,7 +81,7 @@ class Event < ApplicationRecord
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
scope :code_push, -> { where(action: PUSHED) } scope :code_push, -> { where(action: PUSHED) }
scope :merged, -> { where(action: MERGED) } scope :merged, -> { where(action: MERGED) }
scope :for_wiki_page, -> { where(target_type: 'WikiPage::Meta') } scope :for_wiki_page, -> { where(target_type: WikiPage::Meta.name) }
scope :with_associations, -> do scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association # We're using preload for "push_event_payload" as otherwise the association
...@@ -229,7 +229,7 @@ class Event < ApplicationRecord ...@@ -229,7 +229,7 @@ class Event < ApplicationRecord
end end
def wiki_page? def wiki_page?
target_type == "WikiPage::Meta" target_type == WikiPage::Meta.name
end end
def milestone def milestone
......
...@@ -4,6 +4,8 @@ class WikiPage ...@@ -4,6 +4,8 @@ class WikiPage
class Meta < ApplicationRecord class Meta < ApplicationRecord
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
CanonicalSlugConflictError = Class.new(ActiveRecord::RecordInvalid)
self.table_name = 'wiki_page_meta' self.table_name = 'wiki_page_meta'
belongs_to :project belongs_to :project
...@@ -13,6 +15,11 @@ class WikiPage ...@@ -13,6 +15,11 @@ class WikiPage
validates :title, presence: true validates :title, presence: true
validates :project_id, presence: true validates :project_id, presence: true
validate :no_two_metarecords_in_same_project_can_have_same_canonical_slug
scope :with_canonical_slug, ->(slug) do
joins(:slugs).where(wiki_page_slugs: { canonical: true, slug: slug })
end
alias_method :resource_parent, :project alias_method :resource_parent, :project
...@@ -28,33 +35,32 @@ class WikiPage ...@@ -28,33 +35,32 @@ class WikiPage
# validation issues. # validation issues.
def self.find_or_create(last_known_slug, wiki_page) def self.find_or_create(last_known_slug, wiki_page)
project = wiki_page.wiki.project project = wiki_page.wiki.project
known_slugs = [last_known_slug, wiki_page.slug].compact.uniq
raise 'no slugs!' if known_slugs.empty?
meta = find_by_canonical_slug(last_known_slug, project) || create(title: wiki_page.title, project_id: project.id) transaction do
found = find_by_canonical_slug(known_slugs, project)
meta.update_wiki_page_attributes(wiki_page) meta = found || create(title: wiki_page.title, project_id: project.id)
meta.insert_slugs([last_known_slug, wiki_page.slug], wiki_page.slug)
meta.canonical_slug = wiki_page.slug
meta
end
def update_wiki_page_attributes(page) meta.update_state(found.nil?, known_slugs, wiki_page)
update_column(:title, page.title) unless page.title == title
end
def insert_slugs(strings, canonical) # We don't need to run validations here, since find_by_canonical_slug
slug_attrs = strings.uniq.map do |slug| # guarantees that there is no conflict in canonical_slug, and DB
{ wiki_page_meta_id: id, slug: slug } # constraints on title and project_id enforce our other invariants
# This saves us a query.
meta
end end
slugs.insert_all(slug_attrs)
end end
def self.find_by_canonical_slug(canonical_slug, project) def self.find_by_canonical_slug(canonical_slug, project)
meta = joins(:slugs).find_by(project_id: project.id, meta, conflict = with_canonical_slug(canonical_slug)
wiki_page_slugs: { canonical: true, slug: canonical_slug }) .where(project_id: project.id)
.limit(2)
# Prevent queries for canonical_slug if conflict.present?
meta.instance_variable_set(:@canonical_slug, canonical_slug) if meta meta.errors.add(:canonical_slug, 'Duplicate value found')
raise CanonicalSlugConflictError.new(meta)
end
meta meta
end end
...@@ -67,14 +73,48 @@ class WikiPage ...@@ -67,14 +73,48 @@ class WikiPage
return if @canonical_slug == slug return if @canonical_slug == slug
if persisted? if persisted?
slugs.update_all(canonical: false) transaction do
page_slug = slugs.create_with(canonical: true).find_or_create_by(slug: slug) slugs.update_all(canonical: false)
page_slug.update_column(:canonical, true) unless page_slug.canonical? page_slug = slugs.create_with(canonical: true).find_or_create_by(slug: slug)
page_slug.update_column(:canonical, true) unless page_slug.canonical?
end
else else
slugs.new(slug: slug, canonical: true) slugs.new(slug: slug, canonical: true)
end end
@canonical_slug = slug @canonical_slug = slug
end end
def update_state(created, known_slugs, wiki_page)
update_wiki_page_attributes(wiki_page)
insert_slugs(known_slugs, created, wiki_page.slug)
self.canonical_slug = wiki_page.slug
end
private
def update_wiki_page_attributes(page)
update_column(:title, page.title) unless page.title == title
end
def insert_slugs(strings, is_new, canonical_slug)
slug_attrs = strings.map do |slug|
{ wiki_page_meta_id: id, slug: slug, canonical: (is_new && slug == canonical_slug) }
end
slugs.insert_all(slug_attrs) unless !is_new && slug_attrs.size == 1
@canonical_slug = canonical_slug if is_new || strings.size == 1
end
def no_two_metarecords_in_same_project_can_have_same_canonical_slug
return unless project_id.present? && canonical_slug.present?
offending = self.class.with_canonical_slug(canonical_slug).where(project_id: project_id)
offending = offending.where.not(id: id) if persisted?
if offending.exists?
errors.add(:canonical_slug, 'each page in a wiki must have a distinct canonical slug')
end
end
end end
end end
...@@ -7,22 +7,12 @@ class WikiPage ...@@ -7,22 +7,12 @@ class WikiPage
belongs_to :wiki_page_meta, class_name: 'WikiPage::Meta', inverse_of: :slugs belongs_to :wiki_page_meta, class_name: 'WikiPage::Meta', inverse_of: :slugs
validates :slug, presence: true, uniqueness: { scope: :wiki_page_meta_id } validates :slug, presence: true, uniqueness: { scope: :wiki_page_meta_id }
validate :only_one_slug_can_be_canonical_per_meta_record validates :canonical, uniqueness: {
scope: :wiki_page_meta_id,
if: :canonical?,
message: 'Only one slug can be canonical per wiki metadata record'
}
scope :canonical, -> { where(canonical: true) } scope :canonical, -> { where(canonical: true) }
private
def only_one_slug_can_be_canonical_per_meta_record
return unless canonical?
if other_slugs.canonical.exists?
errors.add(:canonical, 'Only one slug can be canonical per wiki metadata record')
end
end
def other_slugs
self.class.unscoped.where(wiki_page_meta_id: wiki_page_meta_id)
end
end end
end end
...@@ -30,6 +30,16 @@ FactoryBot.define do ...@@ -30,6 +30,16 @@ FactoryBot.define do
to_create do |page| to_create do |page|
page.create page.create
end end
trait :with_real_page do
project { create(:project, :repository) }
page do
wiki.create_page(title, content)
page_title, page_dir = wiki.page_title_and_dir(title)
wiki.wiki.page(title: page_title, dir: page_dir, version: nil)
end
end
end end
factory :wiki_page_meta, class: 'WikiPage::Meta' do factory :wiki_page_meta, class: 'WikiPage::Meta' do
......
...@@ -112,17 +112,18 @@ describe Event do ...@@ -112,17 +112,18 @@ describe Event do
end end
context 'for an issue' do context 'for an issue' do
let(:issue) { create(:issue, project: project) } let(:title) { generate(:title) }
let(:issue) { create(:issue, title: title, project: project) }
let(:target) { issue } let(:target) { issue }
it 'delegates to issue title' do it 'delegates to issue title' do
expect(event.target_title).to eq(issue.title) expect(event.target_title).to eq(title)
end end
end end
context 'for a wiki page' do context 'for a wiki page' do
let(:wiki) { create(:project_wiki, project: project) } let(:title) { generate(:wiki_page_title) }
let(:wiki_page) { create(:wiki_page, wiki: wiki) } let(:wiki_page) { create(:wiki_page, title: title, project: project) }
let(:event) { create(:wiki_page_event, project: project, wiki_page: wiki_page) } let(:event) { create(:wiki_page_event, project: project, wiki_page: wiki_page) }
it 'delegates to issue title' do it 'delegates to issue title' do
...@@ -484,16 +485,10 @@ describe Event do ...@@ -484,16 +485,10 @@ describe Event do
describe '#wiki_page and #wiki_page?' do describe '#wiki_page and #wiki_page?' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:title) { FFaker::Lorem.sentence }
context 'for a wiki page event' do context 'for a wiki page event' do
let(:wiki) { create(:project_wiki, project: project) }
let(:wiki_page) do let(:wiki_page) do
wiki.create_page(title, FFaker::Lorem.sentence) create(:wiki_page, :with_real_page, project: project)
page_title, page_dir = wiki.page_title_and_dir(title)
page = wiki.wiki.page(title: page_title, dir: page_dir, version: nil)
WikiPage.new(wiki, page)
end end
subject(:event) { create(:wiki_page_event, project: project, wiki_page: wiki_page) } subject(:event) { create(:wiki_page_event, project: project, wiki_page: wiki_page) }
......
...@@ -4,6 +4,7 @@ require 'spec_helper' ...@@ -4,6 +4,7 @@ require 'spec_helper'
describe WikiPage::Meta do describe WikiPage::Meta do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:other_project) { create(:project) }
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
...@@ -25,6 +26,21 @@ describe WikiPage::Meta do ...@@ -25,6 +26,21 @@ describe WikiPage::Meta do
it { is_expected.to validate_presence_of(:project_id) } it { is_expected.to validate_presence_of(:project_id) }
it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_presence_of(:title) }
it 'is forbidden to add extremely long titles' do
expect do
create(:wiki_page_meta, project: project, title: FFaker::Lorem.characters(300))
end.to raise_error(ActiveRecord::ValueTooLong)
end
it 'is forbidden to have two records for the same project with the same canonical_slug' do
the_slug = generate(:sluggified_title)
create(:wiki_page_meta, canonical_slug: the_slug, project: project)
in_violation = build(:wiki_page_meta, canonical_slug: the_slug, project: project)
expect(in_violation).not_to be_valid
end
end end
describe '#canonical_slug' do describe '#canonical_slug' do
...@@ -74,9 +90,11 @@ describe WikiPage::Meta do ...@@ -74,9 +90,11 @@ describe WikiPage::Meta do
end end
end end
describe '= slug' do describe 'canonical_slug=' do
shared_examples 'canonical_slug setting examples' do shared_examples 'canonical_slug setting examples' do
let(:lower_query_limit) { [query_limit - 1, 0].max } # Constant overhead of two queries for the transaction
let(:upper_query_limit) { query_limit + 2 }
let(:lower_query_limit) { [upper_query_limit - 1, 0].max}
let(:other_slug) { generate(:sluggified_title) } let(:other_slug) { generate(:sluggified_title) }
it 'changes it to the correct value' do it 'changes it to the correct value' do
...@@ -92,7 +110,7 @@ describe WikiPage::Meta do ...@@ -92,7 +110,7 @@ describe WikiPage::Meta do
end end
it 'issues at most N queries' do it 'issues at most N queries' do
expect { subject.canonical_slug = slug }.not_to exceed_query_limit(query_limit) expect { subject.canonical_slug = slug }.not_to exceed_query_limit(upper_query_limit)
end end
it 'issues fewer queries if we already know the current slug' do it 'issues fewer queries if we already know the current slug' do
...@@ -127,7 +145,6 @@ describe WikiPage::Meta do ...@@ -127,7 +145,6 @@ describe WikiPage::Meta do
context 'the slug is up to date and in the DB' do context 'the slug is up to date and in the DB' do
let(:slug) { generate(:sluggified_title) } let(:slug) { generate(:sluggified_title) }
let(:query_limit) { 0 }
before do before do
subject.canonical_slug = slug subject.canonical_slug = slug
...@@ -135,14 +152,15 @@ describe WikiPage::Meta do ...@@ -135,14 +152,15 @@ describe WikiPage::Meta do
include_examples 'canonical_slug setting examples' do include_examples 'canonical_slug setting examples' do
let(:other_slug) { slug } let(:other_slug) { slug }
let(:upper_query_limit) { 0 }
end end
end end
end end
end end
describe '.find_or_create' do describe '.find_or_create' do
let(:old_title) { FactoryBot.generate(:wiki_page_title) } let(:old_title) { generate(:wiki_page_title) }
let(:last_known_slug) { FactoryBot.generate(:sluggified_title) } let(:last_known_slug) { generate(:sluggified_title) }
let(:current_slug) { wiki_page.slug } let(:current_slug) { wiki_page.slug }
let(:title) { wiki_page.title } let(:title) { wiki_page.title }
let(:wiki_page) { create(:wiki_page, project: project) } let(:wiki_page) { create(:wiki_page, project: project) }
...@@ -152,14 +170,30 @@ describe WikiPage::Meta do ...@@ -152,14 +170,30 @@ describe WikiPage::Meta do
end end
def create_previous_version(title = old_title, slug = last_known_slug) def create_previous_version(title = old_title, slug = last_known_slug)
described_class.create!(title: title, project: project, canonical_slug: slug) create(:wiki_page_meta, title: title, project: project, canonical_slug: slug)
end
def create_context
# Ensure that we behave nicely with respect to other projects
# We have:
# - page in other project with same canonical_slug
create(:wiki_page_meta, project: other_project, canonical_slug: wiki_page.slug)
# - page in same project with different canonical_slug, but with
# an old slug that = canonical_slug
different_slug = generate(:sluggified_title)
create(:wiki_page_meta, project: project, canonical_slug: different_slug)
.slugs.create(slug: wiki_page.slug)
end end
shared_examples 'metadata examples' do shared_examples 'metadata examples' do
it 'establishes the correct state', :aggregate_failures do it 'establishes the correct state', :aggregate_failures do
create_context
meta = find_record meta = find_record
expect(meta).to have_attributes( expect(meta).to have_attributes(
valid?: true,
canonical_slug: wiki_page.slug, canonical_slug: wiki_page.slug,
title: wiki_page.title, title: wiki_page.title,
project: wiki_page.wiki.project project: wiki_page.wiki.project
...@@ -183,34 +217,44 @@ describe WikiPage::Meta do ...@@ -183,34 +217,44 @@ describe WikiPage::Meta do
end end
end end
context 'a conflicting record exists' do
before do
create(:wiki_page_meta, project: project, canonical_slug: last_known_slug)
create(:wiki_page_meta, project: project, canonical_slug: current_slug)
end
it 'raises an error' do
expect { find_record }.to raise_error(ActiveRecord::RecordInvalid)
end
end
context 'no existing record exists' do context 'no existing record exists' do
include_examples 'metadata examples' do include_examples 'metadata examples' do
# The base case is 7 queries: # The base case is 5 queries:
# - 2 for the outer transaction
# - 1 to find the metadata object if it exists # - 1 to find the metadata object if it exists
# - 1 to create it if it does not # - 1 to create it if it does not
# - 2 for 1 savepoint
# - 1 to insert last_known_slug and current_slug # - 1 to insert last_known_slug and current_slug
# - 1 to find the current slug
# - 2 to set canonical status correctly
# #
# (Log has been edited for clarity) # (Log has been edited for clarity)
# SAVEPOINT active_record_2
#
# SELECT * FROM wiki_page_meta # SELECT * FROM wiki_page_meta
# INNER JOIN wiki_page_slugs # INNER JOIN wiki_page_slugs
# ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id # ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id
# WHERE wiki_page_meta.project_id = ? # WHERE wiki_page_meta.project_id = ?
# AND wiki_page_slugs.canonical = TRUE # AND wiki_page_slugs.canonical = TRUE
# AND wiki_page_slugs.slug = ? # AND wiki_page_slugs.slug IN (?,?)
# LIMIT 1 # LIMIT 2
# SAVEPOINT active_record_2 #
# INSERT INTO wiki_page_meta (project_id, title) VALUES (?, ?) RETURNING id # INSERT INTO wiki_page_meta (project_id, title) VALUES (?, ?) RETURNING id
# RELEASE SAVEPOINT active_record_2 #
# INSERT INTO wiki_page_slugs (wiki_page_meta_id,slug) # INSERT INTO wiki_page_slugs (wiki_page_meta_id,slug,canonical)
# VALUES (?, ?) (?, ?) # VALUES (?, ?, ?) (?, ?, ?)
# ON CONFLICT DO NOTHING RETURNING id # ON CONFLICT DO NOTHING RETURNING id
# UPDATE wiki_page_slugs SET canonical = FALSE WHERE wiki_page_meta_id = ? #
# SELECT * FROM wiki_page_slugs WHERE wiki_page_meta_id = ? AND slug = ? LIMIT 1 # RELEASE SAVEPOINT active_record_2
# UPDATE wiki_page_slugs SET canonical = TRUE WHERE id = ? let(:query_limit) { 5 }
let(:query_limit) { 8 }
end end
end end
...@@ -219,7 +263,7 @@ describe WikiPage::Meta do ...@@ -219,7 +263,7 @@ describe WikiPage::Meta do
include_examples 'metadata examples' do include_examples 'metadata examples' do
# Identical to the base case. # Identical to the base case.
let(:query_limit) { 8 } let(:query_limit) { 5 }
end end
end end
...@@ -232,17 +276,19 @@ describe WikiPage::Meta do ...@@ -232,17 +276,19 @@ describe WikiPage::Meta do
end end
include_examples 'metadata examples' do include_examples 'metadata examples' do
# We just need to do the initial query, and the outer transaction
# SAVEPOINT active_record_2
#
# SELECT * FROM wiki_page_meta # SELECT * FROM wiki_page_meta
# INNER JOIN wiki_page_slugs # INNER JOIN wiki_page_slugs
# ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id # ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id
# WHERE wiki_page_meta.project_id = ? # WHERE wiki_page_meta.project_id = ?
# AND wiki_page_slugs.canonical = TRUE # AND wiki_page_slugs.canonical = TRUE
# AND wiki_page_slugs.slug = ? # AND wiki_page_slugs.slug = ?
# LIMIT 1 # LIMIT 2
# #
# INSERT INTO wiki_page_slugs (wiki_page_meta_id,slug,canonical) # RELEASE SAVEPOINT active_record_2
# VALUES (?, ?, ?) ON CONFLICT DO NOTHING RETURNING id let(:query_limit) { 3 }
let(:query_limit) { 2 }
end end
end end
...@@ -254,8 +300,13 @@ describe WikiPage::Meta do ...@@ -254,8 +300,13 @@ describe WikiPage::Meta do
end end
include_examples 'metadata examples' do include_examples 'metadata examples' do
# Same as minimal case, plus the additional queries needed to update the # Here we need:
# slug. # - 2 for the outer transaction
# - 1 to find the record
# - 1 to insert the new slug
# - 3 to set canonical state correctly
#
# SAVEPOINT active_record_2
# #
# SELECT * FROM wiki_page_meta # SELECT * FROM wiki_page_meta
# INNER JOIN wiki_page_slugs # INNER JOIN wiki_page_slugs
...@@ -272,10 +323,11 @@ describe WikiPage::Meta do ...@@ -272,10 +323,11 @@ describe WikiPage::Meta do
# WHERE wiki_page_slugs.wiki_page_meta_id = ? # WHERE wiki_page_slugs.wiki_page_meta_id = ?
# AND wiki_page_slugs.slug = ? # AND wiki_page_slugs.slug = ?
# LIMIT 1 # LIMIT 1
#
# UPDATE wiki_page_slugs SET canonical = FALSE WHERE wiki_page_meta_id = ? # UPDATE wiki_page_slugs SET canonical = FALSE WHERE wiki_page_meta_id = ?
# UPDATE wiki_page_slugs SET canonical = TRUE WHERE id = ? # UPDATE wiki_page_slugs SET canonical = TRUE WHERE id = ?
let(:query_limit) { 5 } #
# RELEASE SAVEPOINT active_record_2
let(:query_limit) { 7 }
end end
end end
...@@ -289,6 +341,8 @@ describe WikiPage::Meta do ...@@ -289,6 +341,8 @@ describe WikiPage::Meta do
include_examples 'metadata examples' do include_examples 'metadata examples' do
# Same as minimal case, plus one query to update the title. # Same as minimal case, plus one query to update the title.
# #
# SAVEPOINT active_record_2
#
# SELECT * FROM wiki_page_meta # SELECT * FROM wiki_page_meta
# INNER JOIN wiki_page_slugs # INNER JOIN wiki_page_slugs
# ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id # ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id
...@@ -299,9 +353,8 @@ describe WikiPage::Meta do ...@@ -299,9 +353,8 @@ describe WikiPage::Meta do
# #
# UPDATE wiki_page_meta SET title = ? WHERE id = ? # UPDATE wiki_page_meta SET title = ? WHERE id = ?
# #
# INSERT INTO wiki_page_slugs (wiki_page_meta_id,slug,canonical) # RELEASE SAVEPOINT active_record_2
# VALUES (?, ?, ?) ON CONFLICT DO NOTHING RETURNING id let(:query_limit) { 4 }
let(:query_limit) { 3 }
end end
end end
...@@ -318,12 +371,12 @@ describe WikiPage::Meta do ...@@ -318,12 +371,12 @@ describe WikiPage::Meta do
end end
include_examples 'metadata examples' do include_examples 'metadata examples' do
let(:query_limit) { 5 } let(:query_limit) { 7 }
end end
end end
context 'we want to change the slug a bunch of times' do context 'we want to change the slug a bunch of times' do
let(:slugs) { FactoryBot.generate_list(:sluggified_title, 3) } let(:slugs) { generate_list(:sluggified_title, 3) }
before do before do
meta = create_previous_version meta = create_previous_version
...@@ -331,7 +384,7 @@ describe WikiPage::Meta do ...@@ -331,7 +384,7 @@ describe WikiPage::Meta do
end end
include_examples 'metadata examples' do include_examples 'metadata examples' do
let(:query_limit) { 8 } let(:query_limit) { 7 }
end end
end end
...@@ -341,18 +394,22 @@ describe WikiPage::Meta do ...@@ -341,18 +394,22 @@ describe WikiPage::Meta do
end end
include_examples 'metadata examples' do include_examples 'metadata examples' do
# Same as minimal case, plus one for the title, and two for the slug # -- outer transaction
# SAVEPOINT active_record_2
# #
# -- to find the record
# SELECT * FROM wiki_page_meta # SELECT * FROM wiki_page_meta
# INNER JOIN wiki_page_slugs # INNER JOIN wiki_page_slugs
# ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id # ON wiki_page_slugs.wiki_page_meta_id = wiki_page_meta.id
# WHERE wiki_page_meta.project_id = ? # WHERE wiki_page_meta.project_id = ?
# AND wiki_page_slugs.canonical = TRUE # AND wiki_page_slugs.canonical = TRUE
# AND wiki_page_slugs.slug = ? # AND wiki_page_slugs.slug IN (?,?)
# LIMIT 1 # LIMIT 2
# #
# -- to update the title
# UPDATE wiki_page_meta SET title = ? WHERE id = ? # UPDATE wiki_page_meta SET title = ? WHERE id = ?
# #
# -- to update slug
# INSERT INTO wiki_page_slugs (wiki_page_meta_id,slug,canonical) # INSERT INTO wiki_page_slugs (wiki_page_meta_id,slug,canonical)
# VALUES (?, ?, ?) ON CONFLICT DO NOTHING RETURNING id # VALUES (?, ?, ?) ON CONFLICT DO NOTHING RETURNING id
# #
...@@ -364,7 +421,9 @@ describe WikiPage::Meta do ...@@ -364,7 +421,9 @@ describe WikiPage::Meta do
# LIMIT 1 # LIMIT 1
# #
# UPDATE wiki_page_slugs SET canonical = TRUE WHERE id = ? # UPDATE wiki_page_slugs SET canonical = TRUE WHERE id = ?
let(:query_limit) { 6 } #
# RELEASE SAVEPOINT active_record_2
let(:query_limit) { 8 }
end end
end end
end end
......
...@@ -25,7 +25,7 @@ describe WikiPage::Slug do ...@@ -25,7 +25,7 @@ describe WikiPage::Slug do
context 'there are some non-canonical slugs' do context 'there are some non-canonical slugs' do
before do before do
create_list(:wiki_page_slug, 3) create(:wiki_page_slug)
end end
it { is_expected.to be_empty } it { is_expected.to be_empty }
...@@ -33,7 +33,7 @@ describe WikiPage::Slug do ...@@ -33,7 +33,7 @@ describe WikiPage::Slug do
context 'there is at least one canonical slugs' do context 'there is at least one canonical slugs' do
before do before do
create(:wiki_page_slug, canonical: true) create(:wiki_page_slug, :canonical)
end end
it { is_expected.not_to be_empty } it { is_expected.not_to be_empty }
...@@ -64,7 +64,7 @@ describe WikiPage::Slug do ...@@ -64,7 +64,7 @@ describe WikiPage::Slug do
context 'there are other slugs, but they are not canonical' do context 'there are other slugs, but they are not canonical' do
before do before do
create_list(:wiki_page_slug, 3, wiki_page_meta: meta) create(:wiki_page_slug, wiki_page_meta: meta)
end end
it { is_expected.to be_valid } it { is_expected.to be_valid }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment