Add have_implicit_order_column matcher
Add a matcher that can test the new [implicit_order_column][1] class property that is available on ActiveRecord classes in Rails 6. [1]: https://github.com/rails/rails/pull/34480 Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
This commit is contained in:
parent
c956f6a44d
commit
f82329a679
|
@ -14,6 +14,7 @@ require "shoulda/matchers/active_record/association_matchers/model_reflection"
|
|||
require "shoulda/matchers/active_record/association_matchers/option_verifier"
|
||||
require "shoulda/matchers/active_record/have_db_column_matcher"
|
||||
require "shoulda/matchers/active_record/have_db_index_matcher"
|
||||
require "shoulda/matchers/active_record/have_implicit_order_column"
|
||||
require "shoulda/matchers/active_record/have_readonly_attribute_matcher"
|
||||
require "shoulda/matchers/active_record/have_rich_text_matcher"
|
||||
require "shoulda/matchers/active_record/have_secure_token_matcher"
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveRecord
|
||||
# The `have_implicit_order_column` matcher tests that the model has `implicit_order_column`
|
||||
# assigned to one of the table columns. (Rails 6+ only)
|
||||
#
|
||||
# class Product < ApplicationRecord
|
||||
# self.implicit_order_column = :created_at
|
||||
# end
|
||||
#
|
||||
# # RSpec
|
||||
# RSpec.describe Product, type: :model do
|
||||
# it { should have_implicit_order_column(:created_at) }
|
||||
# end
|
||||
#
|
||||
# # Minitest (Shoulda)
|
||||
# class ProductTest < ActiveSupport::TestCase
|
||||
# should have_implicit_order_column(:created_at)
|
||||
# end
|
||||
#
|
||||
# @return [HaveImplicitOrderColumnMatcher]
|
||||
#
|
||||
if RailsShim.active_record_gte_6?
|
||||
def have_implicit_order_column(column_name)
|
||||
HaveImplicitOrderColumnMatcher.new(column_name)
|
||||
end
|
||||
end
|
||||
|
||||
# @private
|
||||
class HaveImplicitOrderColumnMatcher
|
||||
attr_reader :failure_message
|
||||
|
||||
def initialize(column_name)
|
||||
@column_name = column_name
|
||||
end
|
||||
|
||||
def matches?(subject)
|
||||
@subject = subject
|
||||
check_column_exists!
|
||||
check_implicit_order_column_matches!
|
||||
true
|
||||
rescue SecondaryCheckFailedError => error
|
||||
@failure_message = Shoulda::Matchers.word_wrap(
|
||||
"Expected #{model.name} to #{expectation}, " +
|
||||
"but that could not be proved: #{error.message}."
|
||||
)
|
||||
false
|
||||
rescue PrimaryCheckFailedError => error
|
||||
@failure_message = Shoulda::Matchers.word_wrap(
|
||||
"Expected #{model.name} to #{expectation}, but #{error.message}."
|
||||
)
|
||||
false
|
||||
end
|
||||
|
||||
def failure_message_when_negated
|
||||
Shoulda::Matchers.word_wrap(
|
||||
"Expected #{model.name} not to #{expectation}, but it did."
|
||||
)
|
||||
end
|
||||
|
||||
def description
|
||||
expectation
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :column_name, :subject
|
||||
|
||||
def check_column_exists!
|
||||
matcher = HaveDbColumnMatcher.new(column_name)
|
||||
|
||||
if !matcher.matches?(@subject)
|
||||
raise SecondaryCheckFailedError.new(
|
||||
"The :#{model.table_name} table does not have a " +
|
||||
":#{column_name} column"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def check_implicit_order_column_matches!
|
||||
if model.implicit_order_column.to_s != column_name.to_s
|
||||
message =
|
||||
if model.implicit_order_column.nil?
|
||||
"implicit_order_column is not set"
|
||||
else
|
||||
"it is :#{model.implicit_order_column}"
|
||||
end
|
||||
|
||||
raise PrimaryCheckFailedError.new(message)
|
||||
end
|
||||
end
|
||||
|
||||
def model
|
||||
subject.class
|
||||
end
|
||||
|
||||
def expectation
|
||||
"have an implicit_order_column of :#{column_name}"
|
||||
end
|
||||
|
||||
class SecondaryCheckFailedError < StandardError; end
|
||||
class PrimaryCheckFailedError < StandardError; end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -21,6 +21,10 @@ module Shoulda
|
|||
Gem::Requirement.new('>= 5').satisfied_by?(active_record_version)
|
||||
end
|
||||
|
||||
def active_record_gte_6?
|
||||
Gem::Requirement.new('>= 6').satisfied_by?(active_record_version)
|
||||
end
|
||||
|
||||
def active_record_version
|
||||
Gem::Version.new(::ActiveRecord::VERSION::STRING)
|
||||
rescue NameError
|
||||
|
|
|
@ -25,7 +25,7 @@ module UnitTests
|
|||
&block
|
||||
)
|
||||
@table_name = table_name
|
||||
@columns = columns
|
||||
@columns = normalize_columns(columns)
|
||||
@connection = connection
|
||||
@customizer = block || proc {}
|
||||
end
|
||||
|
@ -66,6 +66,26 @@ module UnitTests
|
|||
to: UnitTests::DatabaseHelpers,
|
||||
)
|
||||
|
||||
def normalize_columns(columns)
|
||||
if columns.is_a?(Hash)
|
||||
if columns.values.first.is_a?(Hash)
|
||||
columns
|
||||
else
|
||||
columns.transform_values do |value|
|
||||
if value == false
|
||||
value
|
||||
else
|
||||
{ type: value }
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
columns.inject({}) do |hash, column_name|
|
||||
hash.merge(column_name => { type: :string })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_columns_to_table(table)
|
||||
columns.each do |column_name, column_specification|
|
||||
add_column_to_table(table, column_name, column_specification)
|
||||
|
@ -75,7 +95,6 @@ module UnitTests
|
|||
end
|
||||
|
||||
def add_column_to_table(table, column_name, column_specification)
|
||||
if column_specification.is_a?(Hash)
|
||||
column_specification = column_specification.dup
|
||||
column_type = column_specification.delete(:type)
|
||||
column_options = column_specification.delete(:options) { {} }
|
||||
|
@ -105,10 +124,6 @@ module UnitTests
|
|||
'inside an :options key!',
|
||||
)
|
||||
end
|
||||
else
|
||||
column_type = column_specification
|
||||
column_options = {}
|
||||
end
|
||||
|
||||
table.column(column_name, column_type, column_options)
|
||||
end
|
||||
|
|
|
@ -50,5 +50,9 @@ module UnitTests
|
|||
def active_record_supports_validate_presence_on_active_storage?
|
||||
active_record_version >= '6.0.0.beta1'
|
||||
end
|
||||
|
||||
def active_record_supports_implicit_order_column?
|
||||
active_record_version >= '6.0.0.beta1'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,10 @@ module UnitTests
|
|||
ModelBuilder.define_model(*args, &block)
|
||||
end
|
||||
|
||||
def define_model_instance(*args, &block)
|
||||
define_model(*args, &block).new
|
||||
end
|
||||
|
||||
def define_model_class(*args, &block)
|
||||
ModelBuilder.define_model_class(*args, &block)
|
||||
end
|
||||
|
|
|
@ -204,8 +204,7 @@ end
|
|||
bundle.remove_gem 'byebug'
|
||||
bundle.remove_gem 'web-console'
|
||||
bundle.add_gem 'pg'
|
||||
bundle.remove_gem 'sqlite3'
|
||||
bundle.add_gem 'sqlite3', '~> 1.3.6'
|
||||
bundle.add_gem 'sqlite', '~> 1.3.6'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
require 'unit_spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::ActiveRecord::HaveImplicitOrderColumnMatcher, type: :model do
|
||||
if active_record_supports_implicit_order_column?
|
||||
context 'when the given column exists' do
|
||||
context 'when an implicit_order_column is set on the model' do
|
||||
context 'and it matches the given column name' do
|
||||
context 'and the column name is a symbol' do
|
||||
it 'matches' do
|
||||
record = record_with_implicit_order_column_on(
|
||||
:created_at,
|
||||
class_name: 'Employee',
|
||||
columns: [:created_at]
|
||||
)
|
||||
|
||||
expect { have_implicit_order_column(:created_at) }
|
||||
.to match_against(record)
|
||||
.or_fail_with(<<~MESSAGE, wrap: true)
|
||||
Expected Employee not to have an implicit_order_column of
|
||||
:created_at, but it did.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the column name is a string' do
|
||||
it 'matches' do
|
||||
record = record_with_implicit_order_column_on(
|
||||
:created_at,
|
||||
class_name: 'Employee',
|
||||
columns: [:created_at]
|
||||
)
|
||||
|
||||
expect { have_implicit_order_column('created_at') }
|
||||
.to match_against(record)
|
||||
.or_fail_with(<<~MESSAGE, wrap: true)
|
||||
Expected Employee not to have an implicit_order_column of
|
||||
:created_at, but it did.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and it does not match the given column name' do
|
||||
context 'and the column name is a symbol' do
|
||||
it 'does not match, producing an appropriate message' do
|
||||
record = record_with_implicit_order_column_on(
|
||||
:created_at,
|
||||
class_name: 'Employee',
|
||||
columns: [:created_at, :email]
|
||||
)
|
||||
|
||||
expect { have_implicit_order_column(:email) }
|
||||
.not_to match_against(record)
|
||||
.and_fail_with(<<-MESSAGE, wrap: true)
|
||||
Expected Employee to have an implicit_order_column of :email,
|
||||
but it is :created_at.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the column name is a string' do
|
||||
it 'does not match, producing an appropriate message' do
|
||||
record = record_with_implicit_order_column_on(
|
||||
:created_at,
|
||||
class_name: 'Employee',
|
||||
columns: [:created_at, :email]
|
||||
)
|
||||
|
||||
expect { have_implicit_order_column('email') }
|
||||
.not_to match_against(record)
|
||||
.and_fail_with(<<-MESSAGE, wrap: true)
|
||||
Expected Employee to have an implicit_order_column of :email,
|
||||
but it is :created_at.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no implicit_order_column is set on the model' do
|
||||
context 'and the given column name is a symbol' do
|
||||
it 'does not match, producing an appropriate message' do
|
||||
record = record_without_implicit_order_column(
|
||||
class_name: 'Employee',
|
||||
columns: [:created_at]
|
||||
)
|
||||
|
||||
expect { have_implicit_order_column(:created_at) }
|
||||
.not_to match_against(record)
|
||||
.and_fail_with(<<-MESSAGE, wrap: true)
|
||||
Expected Employee to have an implicit_order_column of
|
||||
:created_at, but implicit_order_column is not set.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the given column name is a string' do
|
||||
it 'does not match, producing an appropriate message' do
|
||||
record = record_without_implicit_order_column(
|
||||
class_name: 'Employee',
|
||||
columns: [:created_at]
|
||||
)
|
||||
|
||||
expect { have_implicit_order_column('created_at') }
|
||||
.not_to match_against(record)
|
||||
.and_fail_with(<<-MESSAGE, wrap: true)
|
||||
Expected Employee to have an implicit_order_column of
|
||||
:created_at, but implicit_order_column is not set.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given column does not exist' do
|
||||
context 'and it is a symbol' do
|
||||
it 'does not match, producing an appropriate message' do
|
||||
record = record_without_any_columns(class_name: 'Employee')
|
||||
|
||||
expect { have_implicit_order_column(:whatever) }
|
||||
.not_to match_against(record)
|
||||
.and_fail_with(<<-MESSAGE, wrap: true)
|
||||
Expected Employee to have an implicit_order_column of :whatever,
|
||||
but that could not be proved: The :employees table does not have a
|
||||
:whatever column.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
|
||||
context 'and it is a string' do
|
||||
it 'does not match, producing an appropriate message' do
|
||||
record = record_without_any_columns(class_name: 'Employee')
|
||||
|
||||
expect { have_implicit_order_column('whatever') }
|
||||
.not_to match_against(record)
|
||||
.and_fail_with(<<-MESSAGE, wrap: true)
|
||||
Expected Employee to have an implicit_order_column of :whatever,
|
||||
but that could not be proved: The :employees table does not have a
|
||||
:whatever column.
|
||||
MESSAGE
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the correct description' do
|
||||
matcher = have_implicit_order_column(:created_at)
|
||||
|
||||
expect(matcher.description).to eq(
|
||||
'have an implicit_order_column of :created_at'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def record_with_implicit_order_column_on(
|
||||
column_name,
|
||||
class_name:,
|
||||
columns: { column_name => :string }
|
||||
)
|
||||
define_model_instance(class_name, columns) do |model|
|
||||
model.implicit_order_column = column_name
|
||||
end
|
||||
end
|
||||
|
||||
def record_without_implicit_order_column(class_name:, columns:)
|
||||
define_model_instance(class_name, columns)
|
||||
end
|
||||
|
||||
def record_without_any_columns(class_name:)
|
||||
define_model_instance(class_name)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue