Skip to content

Commit

Permalink
Add have_implicit_order_column matcher
Browse files Browse the repository at this point in the history
Add a matcher that can test the new [implicit_order_column][1] class
property that is available on ActiveRecord classes in Rails 6.

[1]: rails/rails#34480

Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
  • Loading branch information
yaorlov and mcmire committed Jul 12, 2020
1 parent c956f6a commit f82329a
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 30 deletions.
1 change: 1 addition & 0 deletions lib/shoulda/matchers/active_record.rb
Expand Up @@ -14,6 +14,7 @@
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"
Expand Down
106 changes: 106 additions & 0 deletions lib/shoulda/matchers/active_record/have_implicit_order_column.rb
@@ -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
4 changes: 4 additions & 0 deletions lib/shoulda/matchers/rails_shim.rb
Expand Up @@ -21,6 +21,10 @@ def active_record_gte_5?
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
Expand Down
71 changes: 43 additions & 28 deletions spec/support/unit/active_record/create_table.rb
Expand Up @@ -25,7 +25,7 @@ def initialize(
&block
)
@table_name = table_name
@columns = columns
@columns = normalize_columns(columns)
@connection = connection
@customizer = block || proc {}
end
Expand Down Expand Up @@ -66,6 +66,26 @@ def call
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)
Expand All @@ -75,39 +95,34 @@ def add_columns_to_table(table)
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) { {} }
column_specification = column_specification.dup
column_type = column_specification.delete(:type)
column_options = column_specification.delete(:options) { {} }

if column_options[:array]
if !active_record_supports_array_columns?
raise ArgumentError.new(
'An array column is being added to a table, but this version ' +
"of ActiveRecord (#{active_record_version}) " +
'does not support array columns.',
)
end

if !database_supports_array_columns?
raise ArgumentError.new(
'An array column is being added to a table, but this ' +
"database adapter (#{database_adapter}) " +
'does not support array columns.',
)
end
if column_options[:array]
if !active_record_supports_array_columns?
raise ArgumentError.new(
'An array column is being added to a table, but this version ' +
"of ActiveRecord (#{active_record_version}) " +
'does not support array columns.',
)
end

if column_specification.any?
if !database_supports_array_columns?
raise ArgumentError.new(
"Invalid column specification.\nYou need to put " +
"#{column_specification.keys.map(&:inspect).to_sentence} " +
'inside an :options key!',
'An array column is being added to a table, but this ' +
"database adapter (#{database_adapter}) " +
'does not support array columns.',
)
end
else
column_type = column_specification
column_options = {}
end

if column_specification.any?
raise ArgumentError.new(
"Invalid column specification.\nYou need to put " +
"#{column_specification.keys.map(&:inspect).to_sentence} " +
'inside an :options key!',
)
end

table.column(column_name, column_type, column_options)
Expand Down
4 changes: 4 additions & 0 deletions spec/support/unit/helpers/active_record_versions.rb
Expand Up @@ -50,5 +50,9 @@ def active_record_supports_expression_indexes?
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
4 changes: 4 additions & 0 deletions spec/support/unit/helpers/model_builder.rb
Expand Up @@ -10,6 +10,10 @@ def define_model(*args, &block)
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
Expand Down
3 changes: 1 addition & 2 deletions spec/support/unit/rails_application.rb
Expand Up @@ -204,8 +204,7 @@ def update_gems
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

Expand Down

0 comments on commit f82329a

Please sign in to comment.