Teach have_db_index about expression indexes
In Rails 5, the schema layer was updated so that indexes could be
created on expressions rather that simply columns. Update
`have_db_index` so that you can test for this.
More reading: <edc2b77187
>
This commit is contained in:
parent
646469e5c2
commit
4e2448d775
|
@ -101,6 +101,7 @@ Style/CharacterLiteral:
|
||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/CollectionMethods:
|
Style/CollectionMethods:
|
||||||
|
Enabled: true
|
||||||
PreferredMethods:
|
PreferredMethods:
|
||||||
find: detect
|
find: detect
|
||||||
reduce: inject
|
reduce: inject
|
||||||
|
|
|
@ -49,6 +49,30 @@ module Shoulda
|
||||||
# should have_db_index([:user_id, :name])
|
# should have_db_index([:user_id, :name])
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
|
# Finally, if you're using Rails 5 and PostgreSQL, you can also specify an
|
||||||
|
# expression:
|
||||||
|
#
|
||||||
|
# class CreateLoggedErrors < ActiveRecord::Migration
|
||||||
|
# def change
|
||||||
|
# create_table :logged_errors do |t|
|
||||||
|
# t.string :code
|
||||||
|
# t.jsonb :content
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# add_index :logged_errors, 'lower(code)::text'
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # RSpec
|
||||||
|
# RSpec.describe LoggedError, type: :model do
|
||||||
|
# it { should have_db_index('lower(code)::text') }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# # Minitest (Shoulda)
|
||||||
|
# class LoggedErrorTest < ActiveSupport::TestCase
|
||||||
|
# should have_db_index('lower(code)::text')
|
||||||
|
# end
|
||||||
|
#
|
||||||
# #### Qualifiers
|
# #### Qualifiers
|
||||||
#
|
#
|
||||||
# ##### unique
|
# ##### unique
|
||||||
|
@ -171,9 +195,16 @@ module Shoulda
|
||||||
end
|
end
|
||||||
|
|
||||||
def matched_index
|
def matched_index
|
||||||
@_matched_index ||= actual_indexes.find do |index|
|
@_matched_index ||=
|
||||||
index.columns == expected_columns
|
if expected_columns.one?
|
||||||
end
|
actual_indexes.detect do |index|
|
||||||
|
Array.wrap(index.columns) == expected_columns
|
||||||
|
end
|
||||||
|
else
|
||||||
|
actual_indexes.detect do |index|
|
||||||
|
index.columns == expected_columns
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def actual_indexes
|
def actual_indexes
|
||||||
|
|
|
@ -40,5 +40,9 @@ module UnitTests
|
||||||
def active_record_supports_optional_for_associations?
|
def active_record_supports_optional_for_associations?
|
||||||
active_record_version >= 5
|
active_record_version >= 5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def active_record_supports_expression_indexes?
|
||||||
|
active_record_version >= 5
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,5 +16,6 @@ module UnitTests
|
||||||
alias_method :database_supports_array_columns?, :postgresql?
|
alias_method :database_supports_array_columns?, :postgresql?
|
||||||
alias_method :database_supports_uuid_columns?, :postgresql?
|
alias_method :database_supports_uuid_columns?, :postgresql?
|
||||||
alias_method :database_supports_money_columns?, :postgresql?
|
alias_method :database_supports_money_columns?, :postgresql?
|
||||||
|
alias_method :database_supports_expression_indexes?, :postgresql?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
require 'unit_spec_helper'
|
require 'unit_spec_helper'
|
||||||
|
|
||||||
describe Shoulda::Matchers::ActiveRecord::HaveDbIndexMatcher, type: :model do
|
describe Shoulda::Matchers::ActiveRecord::HaveDbIndexMatcher, type: :model do
|
||||||
|
def self.can_test_expression_indexes?
|
||||||
|
active_record_supports_expression_indexes? &&
|
||||||
|
database_supports_expression_indexes?
|
||||||
|
end
|
||||||
|
|
||||||
describe 'the matcher' do
|
describe 'the matcher' do
|
||||||
# rubocop:disable Layout/MultilineBlockLayout
|
# rubocop:disable Layout/MultilineBlockLayout
|
||||||
# rubocop:disable Layout/SpaceAroundBlockParameters
|
# rubocop:disable Layout/SpaceAroundBlockParameters
|
||||||
|
@ -237,6 +242,99 @@ Expected the examples table to have an index on [:geocodable_id,
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if can_test_expression_indexes?
|
||||||
|
context 'when given an expression' do
|
||||||
|
context 'qualified with nothing' do
|
||||||
|
context 'when the table has the given index' do
|
||||||
|
it 'matches when used in the positive' do
|
||||||
|
record = record_with_index_on(
|
||||||
|
'lower((code)::text)',
|
||||||
|
columns: { code: :string },
|
||||||
|
)
|
||||||
|
expect(record).to have_db_index('lower((code)::text)')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not match when used in the negative' do
|
||||||
|
record = record_with_index_on(
|
||||||
|
'lower((code)::text)',
|
||||||
|
model_name: 'Example',
|
||||||
|
columns: { code: :string },
|
||||||
|
)
|
||||||
|
|
||||||
|
assertion = lambda do
|
||||||
|
expect(record).not_to have_db_index('lower((code)::text)')
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(&assertion).to fail_with_message(<<-MESSAGE, wrap: true)
|
||||||
|
Expected the examples table not to have an index on "lower((code)::text)", but
|
||||||
|
it does.
|
||||||
|
MESSAGE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the table does not have the given index' do
|
||||||
|
it 'matches when used in the negative' do
|
||||||
|
record = record_with_index_on(
|
||||||
|
'code',
|
||||||
|
columns: { code: :string },
|
||||||
|
)
|
||||||
|
expect(record).not_to have_db_index('lower((code)::text)')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not match when used in the positive' do
|
||||||
|
record = record_with_index_on(
|
||||||
|
'code',
|
||||||
|
model_name: 'Example',
|
||||||
|
columns: { code: :string },
|
||||||
|
)
|
||||||
|
|
||||||
|
assertion = lambda do
|
||||||
|
expect(record).to have_db_index('lower((code)::text)')
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(&assertion).to fail_with_message(<<-MESSAGE, wrap: true)
|
||||||
|
Expected the examples table to have an index on "lower((code)::text)", but it
|
||||||
|
does not.
|
||||||
|
MESSAGE
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when qualified with unique' do
|
||||||
|
include_examples(
|
||||||
|
'for when the matcher is qualified',
|
||||||
|
index: 'lower((code)::text)',
|
||||||
|
other_index: 'code',
|
||||||
|
columns: { code: :string },
|
||||||
|
unique: true,
|
||||||
|
qualifier_args: [],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when qualified with unique: true' do
|
||||||
|
include_examples(
|
||||||
|
'for when the matcher is qualified',
|
||||||
|
index: 'lower((code)::text)',
|
||||||
|
other_index: 'code',
|
||||||
|
columns: { code: :string },
|
||||||
|
unique: true,
|
||||||
|
qualifier_args: [true],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when qualified with unique: false' do
|
||||||
|
include_examples(
|
||||||
|
'for when the matcher is qualified',
|
||||||
|
index: 'lower((code)::text)',
|
||||||
|
other_index: 'code',
|
||||||
|
columns: { code: :string },
|
||||||
|
unique: false,
|
||||||
|
qualifier_args: [false],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when not all models are connected to the same database' do
|
context 'when not all models are connected to the same database' do
|
||||||
|
@ -352,6 +450,44 @@ Expected the examples table to have an index on [:geocodable_id,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if can_test_expression_indexes?
|
||||||
|
context 'when given an expression' do
|
||||||
|
context 'when not qualified with anything' do
|
||||||
|
it 'returns the correct description' do
|
||||||
|
matcher = have_db_index('lower(code)')
|
||||||
|
expect(matcher.description).to eq('have an index on "lower(code)"')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when qualified with unique' do
|
||||||
|
include_examples(
|
||||||
|
'for when the matcher is qualified',
|
||||||
|
index: 'lower(code)',
|
||||||
|
index_type: 'unique',
|
||||||
|
qualifier_args: [],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when qualified with unique: true' do
|
||||||
|
include_examples(
|
||||||
|
'for when the matcher is qualified',
|
||||||
|
index: 'lower(code)',
|
||||||
|
index_type: 'unique',
|
||||||
|
qualifier_args: [true],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when qualified with unique: false' do
|
||||||
|
include_examples(
|
||||||
|
'for when the matcher is qualified',
|
||||||
|
index: 'lower(code)',
|
||||||
|
index_type: 'non-unique',
|
||||||
|
qualifier_args: [false],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_with_index_on(
|
def record_with_index_on(
|
||||||
|
|
Loading…
Reference in New Issue