From 6373da1ebdd14f33675cb5b6e89ad6a4ef05627d Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 6 Oct 2023 11:54:32 -0300
Subject: [PATCH 1/9] Add Rails 7.1 to appraisals

---
 Appraisals                      |  28 +++
 gemfiles/rails_7_1.gemfile      |  38 ++++
 gemfiles/rails_7_1.gemfile.lock | 362 ++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+)
 create mode 100644 gemfiles/rails_7_1.gemfile
 create mode 100644 gemfiles/rails_7_1.gemfile.lock

diff --git a/Appraisals b/Appraisals
index bab3a9ed9..f24fbf8e5 100644
--- a/Appraisals
+++ b/Appraisals
@@ -68,3 +68,31 @@ appraise 'rails_7_0' do
   gem 'sqlite3', '~> 1.4'
   gem 'pg', '~> 1.1'
 end
+
+appraise 'rails_7_1' do
+  instance_eval(&shared_spring_dependencies)
+  instance_eval(&controller_test_dependency)
+
+  gem 'rails', '7.1.0'
+  gem 'sprockets-rails'
+  gem 'puma', '~> 6.0'
+  gem 'importmap-rails'
+  gem 'turbo-rails'
+  gem 'stimulus-rails'
+  gem 'jbuilder'
+  gem 'bootsnap', require: false
+  gem 'capybara'
+  gem 'selenium-webdriver'
+  gem 'webdrivers'
+
+  # test dependencies
+  gem 'rspec-rails', '~> 6.0'
+  gem 'shoulda-context', '~> 2.0.0'
+
+  # other dependencies
+  gem 'bcrypt', '~> 3.1.7'
+
+  # Database adapters
+  gem 'sqlite3', '~> 1.4'
+  gem 'pg', '~> 1.1'
+end
diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile
new file mode 100644
index 000000000..0a21a31f9
--- /dev/null
+++ b/gemfiles/rails_7_1.gemfile
@@ -0,0 +1,38 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+gem "appraisal", "2.4.0"
+gem "bundler", "~> 2.0"
+gem "pry"
+gem "pry-byebug"
+gem "rake", "13.0.1"
+gem "rspec", "~> 3.9"
+gem "rubocop", require: false
+gem "rubocop-packaging", require: false
+gem "rubocop-rails", require: false
+gem "warnings_logger"
+gem "zeus", require: false
+gem "fssm"
+gem "redcarpet"
+gem "rouge"
+gem "yard"
+gem "spring"
+gem "spring-watcher-listen", "~> 2.0.0"
+gem "rails-controller-testing", ">= 1.0.1"
+gem "rails", "7.1.0"
+gem "sprockets-rails"
+gem "puma", "~> 6.0"
+gem "importmap-rails"
+gem "turbo-rails"
+gem "stimulus-rails"
+gem "jbuilder"
+gem "bootsnap", require: false
+gem "capybara"
+gem "selenium-webdriver"
+gem "webdrivers"
+gem "rspec-rails", "~> 6.0"
+gem "shoulda-context", "~> 2.0.0"
+gem "bcrypt", "~> 3.1.7"
+gem "sqlite3", "~> 1.4"
+gem "pg", "~> 1.1"
diff --git a/gemfiles/rails_7_1.gemfile.lock b/gemfiles/rails_7_1.gemfile.lock
new file mode 100644
index 000000000..7cbc7e9f7
--- /dev/null
+++ b/gemfiles/rails_7_1.gemfile.lock
@@ -0,0 +1,362 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    actioncable (7.1.0)
+      actionpack (= 7.1.0)
+      activesupport (= 7.1.0)
+      nio4r (~> 2.0)
+      websocket-driver (>= 0.6.1)
+      zeitwerk (~> 2.6)
+    actionmailbox (7.1.0)
+      actionpack (= 7.1.0)
+      activejob (= 7.1.0)
+      activerecord (= 7.1.0)
+      activestorage (= 7.1.0)
+      activesupport (= 7.1.0)
+      mail (>= 2.7.1)
+      net-imap
+      net-pop
+      net-smtp
+    actionmailer (7.1.0)
+      actionpack (= 7.1.0)
+      actionview (= 7.1.0)
+      activejob (= 7.1.0)
+      activesupport (= 7.1.0)
+      mail (~> 2.5, >= 2.5.4)
+      net-imap
+      net-pop
+      net-smtp
+      rails-dom-testing (~> 2.2)
+    actionpack (7.1.0)
+      actionview (= 7.1.0)
+      activesupport (= 7.1.0)
+      nokogiri (>= 1.8.5)
+      rack (>= 2.2.4)
+      rack-session (>= 1.0.1)
+      rack-test (>= 0.6.3)
+      rails-dom-testing (~> 2.2)
+      rails-html-sanitizer (~> 1.6)
+    actiontext (7.1.0)
+      actionpack (= 7.1.0)
+      activerecord (= 7.1.0)
+      activestorage (= 7.1.0)
+      activesupport (= 7.1.0)
+      globalid (>= 0.6.0)
+      nokogiri (>= 1.8.5)
+    actionview (7.1.0)
+      activesupport (= 7.1.0)
+      builder (~> 3.1)
+      erubi (~> 1.11)
+      rails-dom-testing (~> 2.2)
+      rails-html-sanitizer (~> 1.6)
+    activejob (7.1.0)
+      activesupport (= 7.1.0)
+      globalid (>= 0.3.6)
+    activemodel (7.1.0)
+      activesupport (= 7.1.0)
+    activerecord (7.1.0)
+      activemodel (= 7.1.0)
+      activesupport (= 7.1.0)
+      timeout (>= 0.4.0)
+    activestorage (7.1.0)
+      actionpack (= 7.1.0)
+      activejob (= 7.1.0)
+      activerecord (= 7.1.0)
+      activesupport (= 7.1.0)
+      marcel (~> 1.0)
+    activesupport (7.1.0)
+      base64
+      bigdecimal
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      connection_pool (>= 2.2.5)
+      drb
+      i18n (>= 1.6, < 2)
+      minitest (>= 5.1)
+      mutex_m
+      tzinfo (~> 2.0)
+    addressable (2.8.5)
+      public_suffix (>= 2.0.2, < 6.0)
+    appraisal (2.4.0)
+      bundler
+      rake
+      thor (>= 0.14.0)
+    ast (2.4.2)
+    base64 (0.1.1)
+    bcrypt (3.1.19)
+    bigdecimal (3.1.4)
+    bootsnap (1.16.0)
+      msgpack (~> 1.2)
+    builder (3.2.4)
+    byebug (11.1.3)
+    capybara (3.39.2)
+      addressable
+      matrix
+      mini_mime (>= 0.1.3)
+      nokogiri (~> 1.8)
+      rack (>= 1.6.0)
+      rack-test (>= 0.6.3)
+      regexp_parser (>= 1.5, < 3.0)
+      xpath (~> 3.2)
+    coderay (1.1.3)
+    concurrent-ruby (1.2.2)
+    connection_pool (2.4.1)
+    crass (1.0.6)
+    date (3.3.3)
+    diff-lcs (1.5.0)
+    drb (2.1.1)
+      ruby2_keywords
+    erubi (1.12.0)
+    ffi (1.16.3)
+    fssm (0.2.10)
+    globalid (1.2.1)
+      activesupport (>= 6.1)
+    i18n (1.14.1)
+      concurrent-ruby (~> 1.0)
+    importmap-rails (1.2.1)
+      actionpack (>= 6.0.0)
+      railties (>= 6.0.0)
+    io-console (0.6.0)
+    irb (1.8.1)
+      rdoc
+      reline (>= 0.3.8)
+    jbuilder (2.11.5)
+      actionview (>= 5.0.0)
+      activesupport (>= 5.0.0)
+    json (2.6.3)
+    language_server-protocol (3.17.0.3)
+    listen (3.8.0)
+      rb-fsevent (~> 0.10, >= 0.10.3)
+      rb-inotify (~> 0.9, >= 0.9.10)
+    loofah (2.21.3)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.12.0)
+    mail (2.8.1)
+      mini_mime (>= 0.1.1)
+      net-imap
+      net-pop
+      net-smtp
+    marcel (1.0.2)
+    matrix (0.4.2)
+    method_source (1.0.0)
+    mini_mime (1.1.5)
+    minitest (5.20.0)
+    msgpack (1.7.2)
+    mutex_m (0.1.2)
+    net-imap (0.4.0)
+      date
+      net-protocol
+    net-pop (0.1.2)
+      net-protocol
+    net-protocol (0.2.1)
+      timeout
+    net-smtp (0.4.0)
+      net-protocol
+    nio4r (2.5.9)
+    nokogiri (1.15.4-arm64-darwin)
+      racc (~> 1.4)
+    nokogiri (1.15.4-x86_64-linux)
+      racc (~> 1.4)
+    parallel (1.23.0)
+    parser (3.2.2.4)
+      ast (~> 2.4.1)
+      racc
+    pg (1.5.4)
+    pry (0.14.2)
+      coderay (~> 1.1)
+      method_source (~> 1.0)
+    pry-byebug (3.10.1)
+      byebug (~> 11.0)
+      pry (>= 0.13, < 0.15)
+    psych (5.1.0)
+      stringio
+    public_suffix (5.0.3)
+    puma (6.4.0)
+      nio4r (~> 2.0)
+    racc (1.7.1)
+    rack (3.0.8)
+    rack-session (2.0.0)
+      rack (>= 3.0.0)
+    rack-test (2.1.0)
+      rack (>= 1.3)
+    rackup (2.1.0)
+      rack (>= 3)
+      webrick (~> 1.8)
+    rails (7.1.0)
+      actioncable (= 7.1.0)
+      actionmailbox (= 7.1.0)
+      actionmailer (= 7.1.0)
+      actionpack (= 7.1.0)
+      actiontext (= 7.1.0)
+      actionview (= 7.1.0)
+      activejob (= 7.1.0)
+      activemodel (= 7.1.0)
+      activerecord (= 7.1.0)
+      activestorage (= 7.1.0)
+      activesupport (= 7.1.0)
+      bundler (>= 1.15.0)
+      railties (= 7.1.0)
+    rails-controller-testing (1.0.5)
+      actionpack (>= 5.0.1.rc1)
+      actionview (>= 5.0.1.rc1)
+      activesupport (>= 5.0.1.rc1)
+    rails-dom-testing (2.2.0)
+      activesupport (>= 5.0.0)
+      minitest
+      nokogiri (>= 1.6)
+    rails-html-sanitizer (1.6.0)
+      loofah (~> 2.21)
+      nokogiri (~> 1.14)
+    railties (7.1.0)
+      actionpack (= 7.1.0)
+      activesupport (= 7.1.0)
+      irb
+      rackup (>= 1.0.0)
+      rake (>= 12.2)
+      thor (~> 1.0, >= 1.2.2)
+      zeitwerk (~> 2.6)
+    rainbow (3.1.1)
+    rake (13.0.1)
+    rb-fsevent (0.11.2)
+    rb-inotify (0.10.1)
+      ffi (~> 1.0)
+    rdoc (6.5.0)
+      psych (>= 4.0.0)
+    redcarpet (3.6.0)
+    regexp_parser (2.8.1)
+    reline (0.3.9)
+      io-console (~> 0.5)
+    rexml (3.2.6)
+    rouge (4.1.3)
+    rspec (3.12.0)
+      rspec-core (~> 3.12.0)
+      rspec-expectations (~> 3.12.0)
+      rspec-mocks (~> 3.12.0)
+    rspec-core (3.12.2)
+      rspec-support (~> 3.12.0)
+    rspec-expectations (3.12.3)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-mocks (3.12.6)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-rails (6.0.3)
+      actionpack (>= 6.1)
+      activesupport (>= 6.1)
+      railties (>= 6.1)
+      rspec-core (~> 3.12)
+      rspec-expectations (~> 3.12)
+      rspec-mocks (~> 3.12)
+      rspec-support (~> 3.12)
+    rspec-support (3.12.1)
+    rubocop (1.56.4)
+      base64 (~> 0.1.1)
+      json (~> 2.3)
+      language_server-protocol (>= 3.17.0)
+      parallel (~> 1.10)
+      parser (>= 3.2.2.3)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml (>= 3.2.5, < 4.0)
+      rubocop-ast (>= 1.28.1, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 2.4.0, < 3.0)
+    rubocop-ast (1.29.0)
+      parser (>= 3.2.1.0)
+    rubocop-packaging (0.5.2)
+      rubocop (>= 1.33, < 2.0)
+    rubocop-rails (2.21.2)
+      activesupport (>= 4.2.0)
+      rack (>= 1.1)
+      rubocop (>= 1.33.0, < 2.0)
+    ruby-progressbar (1.13.0)
+    ruby2_keywords (0.0.5)
+    rubyzip (2.3.2)
+    selenium-webdriver (4.10.0)
+      rexml (~> 3.2, >= 3.2.5)
+      rubyzip (>= 1.2.2, < 3.0)
+      websocket (~> 1.0)
+    shoulda-context (2.0.0)
+    spring (2.1.1)
+    spring-watcher-listen (2.0.1)
+      listen (>= 2.7, < 4.0)
+      spring (>= 1.2, < 3.0)
+    sprockets (4.2.1)
+      concurrent-ruby (~> 1.0)
+      rack (>= 2.2.4, < 4)
+    sprockets-rails (3.4.2)
+      actionpack (>= 5.2)
+      activesupport (>= 5.2)
+      sprockets (>= 3.0.0)
+    sqlite3 (1.6.6-arm64-darwin)
+    sqlite3 (1.6.6-x86_64-linux)
+    stimulus-rails (1.2.2)
+      railties (>= 6.0.0)
+    stringio (3.0.8)
+    thor (1.2.2)
+    timeout (0.4.0)
+    turbo-rails (1.4.0)
+      actionpack (>= 6.0.0)
+      activejob (>= 6.0.0)
+      railties (>= 6.0.0)
+    tzinfo (2.0.6)
+      concurrent-ruby (~> 1.0)
+    unicode-display_width (2.5.0)
+    warnings_logger (0.1.1)
+    webdrivers (5.3.1)
+      nokogiri (~> 1.6)
+      rubyzip (>= 1.3.0)
+      selenium-webdriver (~> 4.0, < 4.11)
+    webrick (1.8.1)
+    websocket (1.2.10)
+    websocket-driver (0.7.6)
+      websocket-extensions (>= 0.1.0)
+    websocket-extensions (0.1.5)
+    xpath (3.2.0)
+      nokogiri (~> 1.8)
+    yard (0.9.34)
+    zeitwerk (2.6.12)
+    zeus (0.15.14)
+      method_source (>= 0.6.7)
+
+PLATFORMS
+  arm64-darwin-22
+  x86_64-linux
+
+DEPENDENCIES
+  appraisal (= 2.4.0)
+  bcrypt (~> 3.1.7)
+  bootsnap
+  bundler (~> 2.0)
+  capybara
+  fssm
+  importmap-rails
+  jbuilder
+  pg (~> 1.1)
+  pry
+  pry-byebug
+  puma (~> 6.0)
+  rails (= 7.1.0)
+  rails-controller-testing (>= 1.0.1)
+  rake (= 13.0.1)
+  redcarpet
+  rouge
+  rspec (~> 3.9)
+  rspec-rails (~> 6.0)
+  rubocop
+  rubocop-packaging
+  rubocop-rails
+  selenium-webdriver
+  shoulda-context (~> 2.0.0)
+  spring
+  spring-watcher-listen (~> 2.0.0)
+  sprockets-rails
+  sqlite3 (~> 1.4)
+  stimulus-rails
+  turbo-rails
+  warnings_logger
+  webdrivers
+  yard
+  zeus
+
+BUNDLED WITH
+   2.4.13

From 2be4162eb0622ac88d5e882444e199e1844efcd3 Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 6 Oct 2023 12:18:58 -0300
Subject: [PATCH 2/9] fix: Adjust validate options when managing columns and
 tables in migration

This commit adjusts the way we manage columns and tables in migrations
to account for the changes in Rails 7.1.0, it was introduced in this
Rails version a validation around the options passed to tables and
columns in migrations, so we need to adjust our code to skip this
validation.

This PR introduced this new validation on the migrations:
https://github.com/rails/rails/pull/46178
---
 lib/shoulda/matchers/rails_shim.rb              | 4 ++++
 spec/support/unit/active_record/create_table.rb | 1 +
 spec/support/unit/attribute.rb                  | 2 +-
 3 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/lib/shoulda/matchers/rails_shim.rb b/lib/shoulda/matchers/rails_shim.rb
index 5c903d4e5..a69867cb2 100644
--- a/lib/shoulda/matchers/rails_shim.rb
+++ b/lib/shoulda/matchers/rails_shim.rb
@@ -146,6 +146,10 @@ def supports_full_attributes_api?(model)
             model.respond_to?(:attribute_types)
         end
 
+        def validates_column_options?
+          active_record_version >= '7.1.0'
+        end
+
         private
 
         def simply_generate_validation_message(
diff --git a/spec/support/unit/active_record/create_table.rb b/spec/support/unit/active_record/create_table.rb
index 208207675..ed93268f0 100644
--- a/spec/support/unit/active_record/create_table.rb
+++ b/spec/support/unit/active_record/create_table.rb
@@ -92,6 +92,7 @@ def add_column_to_table(table, column_name, column_specification)
         column_specification = column_specification.dup
         column_type = column_specification.delete(:type)
         column_options = column_specification.delete(:options) { {} }
+        column_options.merge!({ _skip_validate_options: true }) if Shoulda::Matchers::RailsShim.validates_column_options?
 
         if column_options[:array] && !database_supports_array_columns?
           raise ArgumentError.new(
diff --git a/spec/support/unit/attribute.rb b/spec/support/unit/attribute.rb
index f3e8ad49f..68e9e3810 100644
--- a/spec/support/unit/attribute.rb
+++ b/spec/support/unit/attribute.rb
@@ -3,7 +3,7 @@ class Attribute
     DEFAULT_COLUMN_TYPE = :string
     DEFAULT_COLUMN_OPTIONS = {
       null: false,
-      array: false,
+      array: false
     }.freeze
 
     def initialize(args)

From e836d246c4e77a0c5ef54b022006209cd106fdfa Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 6 Oct 2023 15:16:06 -0300
Subject: [PATCH 3/9] Refine `ActiveRecord::SerializeMatcher` for Rails 7.1
 compatibility

This commit refines the `ActiveRecord::SerializeMatcher` to
accommodate changes in the public API introduced in Rails 7.1.
The `serialize` method's API has been updated, necessitating
adjustments to our matcher specs to align with the new API.

While this PR addresses the immediate need for compatibility,
there is potential for further enhancements and refinements in
the matcher's implementation to bring it in line with the revised
public API. However, such improvements will be explored in a future PR.

For reference, the changes in the public API can be found in the following PR: https://github.com/rails/rails/pull/47463

This commit adjusts the `ActiveRecord::SerializeMatcher` the public
API for the `serialize` method has changed on Rails 7.1, so we
had to
---
 lib/shoulda/matchers/rails_shim.rb            |  2 +-
 spec/support/unit/attribute.rb                |  2 +-
 .../validate_presence_of_matcher_spec.rb      | 97 ++++++++++++++-----
 .../active_record/serialize_matcher_spec.rb   | 18 ++--
 4 files changed, 86 insertions(+), 33 deletions(-)

diff --git a/lib/shoulda/matchers/rails_shim.rb b/lib/shoulda/matchers/rails_shim.rb
index a69867cb2..079b8d2de 100644
--- a/lib/shoulda/matchers/rails_shim.rb
+++ b/lib/shoulda/matchers/rails_shim.rb
@@ -147,7 +147,7 @@ def supports_full_attributes_api?(model)
         end
 
         def validates_column_options?
-          active_record_version >= '7.1.0'
+          Gem::Requirement.new('>= 7.1.0').satisfied_by?(active_record_version)
         end
 
         private
diff --git a/spec/support/unit/attribute.rb b/spec/support/unit/attribute.rb
index 68e9e3810..f3e8ad49f 100644
--- a/spec/support/unit/attribute.rb
+++ b/spec/support/unit/attribute.rb
@@ -3,7 +3,7 @@ class Attribute
     DEFAULT_COLUMN_TYPE = :string
     DEFAULT_COLUMN_OPTIONS = {
       null: false,
-      array: false
+      array: false,
     }.freeze
 
     def initialize(args)
diff --git a/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb
index afa52e816..a54c029b2 100644
--- a/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb
+++ b/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb
@@ -56,51 +56,102 @@
       expect(&assertion).to fail_with_message(message)
     end
 
-    context 'when the attribute is decorated with serialize' do
-      context 'and the serializer is a built-in Ruby type' do
-        context 'and the type is a string' do
+    if rails_version >= 7.1
+      context 'when the attribute is decorated with serialize' do
+        context 'and the serializer is a built-in Ruby type' do
+          context 'and the type is a string' do
+            it 'still works' do
+              record = record_validating_presence_of(:traits) do
+                serialize :traits, type: String, coder: YAML
+              end
+
+              expect(record).to validate_presence_of(:traits)
+            end
+          end
+
+          context 'and the type is not a string' do
+            it 'still works' do
+              record = record_validating_presence_of(:traits) do
+                serialize :traits, type: Array, coder: YAML
+              end
+
+              expect(record).to validate_presence_of(:traits)
+            end
+          end
+        end
+
+        context 'and the serializer is JSON' do
           it 'still works' do
             record = record_validating_presence_of(:traits) do
-              serialize :traits, String
+              serialize :traits, coder: JSON
             end
 
             expect(record).to validate_presence_of(:traits)
           end
         end
 
-        context 'and the type is not a string' do
+        context 'and the serializer is something custom' do
           it 'still works' do
-            record = record_validating_presence_of(:traits) do
-              serialize :traits, Array
+            serializer = Class.new do
+              define_singleton_method(:dump) { |value| value }
+              define_singleton_method(:load) { |value| value }
             end
 
-            expect(record).to validate_presence_of(:traits)
+            record = record_validating_presence_of(:data) do
+              serialize :data, coder: serializer
+            end
+
+            expect(record).to validate_presence_of(:data)
           end
         end
       end
+    else
+      context 'when the attribute is decorated with serialize' do
+        context 'and the serializer is a built-in Ruby type' do
+          context 'and the type is a string' do
+            it 'still works' do
+              record = record_validating_presence_of(:traits) do
+                serialize :traits, String
+              end
 
-      context 'and the serializer is JSON' do
-        it 'still works' do
-          record = record_validating_presence_of(:traits) do
-            serialize :traits, JSON
+              expect(record).to validate_presence_of(:traits)
+            end
           end
 
-          expect(record).to validate_presence_of(:traits)
-        end
-      end
+          context 'and the type is not a string' do
+            it 'still works' do
+              record = record_validating_presence_of(:traits) do
+                serialize :traits, Array
+              end
 
-      context 'and the serializer is something custom' do
-        it 'still works' do
-          serializer = Class.new do
-            define_singleton_method(:dump) { |value| value }
-            define_singleton_method(:load) { |value| value }
+              expect(record).to validate_presence_of(:traits)
+            end
           end
+        end
+
+        context 'and the serializer is JSON' do
+          it 'still works' do
+            record = record_validating_presence_of(:traits) do
+              serialize :traits, JSON
+            end
 
-          record = record_validating_presence_of(:data) do
-            serialize :data, serializer
+            expect(record).to validate_presence_of(:traits)
           end
+        end
+
+        context 'and the serializer is something custom' do
+          it 'still works' do
+            serializer = Class.new do
+              define_singleton_method(:dump) { |value| value }
+              define_singleton_method(:load) { |value| value }
+            end
+
+            record = record_validating_presence_of(:data) do
+              serialize :data, serializer
+            end
 
-          expect(record).to validate_presence_of(:data)
+            expect(record).to validate_presence_of(:data)
+          end
         end
       end
     end
diff --git a/spec/unit/shoulda/matchers/active_record/serialize_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/serialize_matcher_spec.rb
index 6f90583c3..7e63b6d1b 100644
--- a/spec/unit/shoulda/matchers/active_record/serialize_matcher_spec.rb
+++ b/spec/unit/shoulda/matchers/active_record/serialize_matcher_spec.rb
@@ -41,38 +41,40 @@ def unserialized_model
 
   context 'an attribute that will end up being serialized as YAML' do
     it 'accepts when the types match' do
-      expect(with_serialized_attr(Hash)).to serialize(:attr).as(Hash)
+      expect(with_serialized_attr(type: Hash, coder: JSON)).to serialize(:attr).as(Hash)
     end
 
     it 'rejects when the types do not match' do
-      expect(with_serialized_attr(Hash)).not_to serialize(:attr).as(String)
+      expect(with_serialized_attr(type: Hash)).not_to serialize(:attr).as(String)
     end
 
     it 'rejects when using as_instance_of' do
-      expect(with_serialized_attr(Hash)).not_to serialize(:attr).as_instance_of(Hash)
+      expect(with_serialized_attr(type: Hash)).not_to serialize(:attr).as_instance_of(Hash)
     end
   end
 
   context 'a serializer that is an instance of a class' do
     it 'accepts when using #as_instance_of' do
       define_serializer(:ExampleSerializer)
-      expect(with_serialized_attr(ExampleSerializer.new)).
+      expect(with_serialized_attr(coder: ExampleSerializer.new)).
         to serialize(:attr).as_instance_of(ExampleSerializer)
     end
 
     it 'rejects when using #as' do
       define_serializer(:ExampleSerializer)
-      expect(with_serialized_attr(ExampleSerializer.new)).
+      expect(with_serialized_attr(coder: ExampleSerializer.new)).
         not_to serialize(:attr).as(ExampleSerializer)
     end
   end
 
-  def with_serialized_attr(type = nil)
+  def with_serialized_attr(type: nil, coder: YAML)
+    type_or_coder = rails_version >= 7.1 ? nil : type || coder
+
     define_model(:example, attr: :string) do
       if type
-        serialize :attr, type
+        serialize :attr, type_or_coder, type: type, coder: coder
       else
-        serialize :attr
+        serialize :attr, type_or_coder, coder: coder
       end
     end.new
   end

From d6d03927c4ed86901c1e9823d8a003af915b2b9e Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 27 Oct 2023 16:11:58 -0300
Subject: [PATCH 4/9] feat: Upadte workflow matrix

---
 .github/workflows/ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 52fa6cafb..ba6cfc8ba 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,6 +31,7 @@ jobs:
           - 3.1.4
           - 3.0.6
         appraisal:
+          - rails_7_1
           - rails_7_0
           - rails_6_1
         adapter:
@@ -38,9 +39,8 @@ jobs:
           - postgresql
         exclude:
           - { ruby: 3.2.2, appraisal: rails_6_1 }
-          - { ruby: 3.1.4, appraisal: rails_5_2 }
-          - { ruby: 3.0.6, appraisal: rails_5_2 }
           - { ruby: 3.0.6, appraisal: rails_7_0 }
+          - { ruby: 3.0.6, appraisal: rails_7_1 }
     env:
       DATABASE_ADAPTER: ${{ matrix.adapter }}
       BUNDLE_GEMFILE: gemfiles/${{ matrix.appraisal }}.gemfile

From 4cb1dab2437a2d19d9216523f3a3eaa23f268405 Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 27 Oct 2023 18:07:30 -0300
Subject: [PATCH 5/9] fix: Adjust comparison between hash and
 `ActionController::Parameters`

The behavior of the comparison between hash and a
`ActionController::Parameters` was changed and now will be deprecated.

Check this Rails PR for more context
https://github.com/rails/rails/pull/44826.
---
 .../shoulda/matchers/action_controller/permit_matcher_spec.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb b/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb
index 14bcc5006..d711f1b0b 100644
--- a/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb
+++ b/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb
@@ -230,7 +230,7 @@ def params_with_conditional_require(params, *filters)
 
         matcher.matches?(controller)
 
-        expect(actual_user_params).to eq('some' => 'params')
+        expect(actual_user_params).to eq ActionController::Parameters.new('some' => 'params')
         expect(actual_foo_param).to eq 'bar'
       end
 
@@ -252,7 +252,7 @@ def params_with_conditional_require(params, *filters)
 
         matcher.matches?(controller)
 
-        expect(actual_user_params).to eq expected_user_params
+        expect(actual_user_params).to eq ActionController::Parameters.new(expected_user_params)
       end
 
       it 'does not permanently stub the params hash' do

From d2b2d55bca7ee9471d32f3a778fcab98b909d8c2 Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 27 Oct 2023 18:14:29 -0300
Subject: [PATCH 6/9] fix: Adjust usage of `has_secure_token` without a token
 attr

The behaviour of this method was changed in Rails 7.1, and now
it'll search for a `token` attr in the initialization of the record
instead of the creation. To stay with the same behaviour as before
it's necessary to pass a new argument `on: : create` to the method.
---
 .../have_secure_token_matcher_spec.rb              | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/spec/unit/shoulda/matchers/active_record/have_secure_token_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/have_secure_token_matcher_spec.rb
index 574ee7bea..399a02a45 100644
--- a/spec/unit/shoulda/matchers/active_record/have_secure_token_matcher_spec.rb
+++ b/spec/unit/shoulda/matchers/active_record/have_secure_token_matcher_spec.rb
@@ -67,7 +67,11 @@
 
   it 'does not match when missing a token column' do
     create_table(:users)
-    invalid_model = define_model_class(:User) { has_secure_token }
+    invalid_model = if rails_version >= 7.1
+                      define_model_class(:User) { has_secure_token(on: :create) }
+                    else
+                      define_model_class(:User) { has_secure_token }
+                    end
 
     expected_message =
       'Expected User to have :token as a secure token but the following ' \
@@ -125,9 +129,11 @@
 
   it 'does not match when missing a column for a custom attribute' do
     create_table(:users)
-    invalid_model = define_model_class(:User) do
-      has_secure_token(:auth_token)
-    end
+    invalid_model = if rails_version >= 7.1
+                      define_model_class(:User) { has_secure_token(:auth_token, on: :create) }
+                    else
+                      define_model_class(:User) { has_secure_token(:auth_token) }
+                    end
 
     expected_message =
       'Expected User to have :auth_token as a secure token but the ' \

From d1a011bc784f4f3761c91968c83517e6769dcccb Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 27 Oct 2023 18:44:02 -0300
Subject: [PATCH 7/9] fix: Adjust specs for primary_key check

---
 .../active_record/have_db_column_matcher_spec.rb  | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/spec/unit/shoulda/matchers/active_record/have_db_column_matcher_spec.rb b/spec/unit/shoulda/matchers/active_record/have_db_column_matcher_spec.rb
index 13e564d00..04db4d3c6 100644
--- a/spec/unit/shoulda/matchers/active_record/have_db_column_matcher_spec.rb
+++ b/spec/unit/shoulda/matchers/active_record/have_db_column_matcher_spec.rb
@@ -124,12 +124,12 @@
 
   context 'with primary option' do
     it 'accepts a column that is primary' do
-      expect(with_table(:custom_id, :integer, primary: true)).
-        to have_db_column(:id).with_options(primary: true)
+      expect(with_table_custom_primary_key(:custom_id)).
+        to have_db_column(:custom_id).with_options(primary: true)
     end
 
     it 'rejects a column that is not primary' do
-      expect(with_table(:whatever, :integer, primary: false)).
+      expect(with_table(:whatever, :integer, {})).
         not_to have_db_column(:whatever).with_options(primary: true)
     end
   end
@@ -161,9 +161,16 @@ def model(options = {})
   end
 
   def with_table(column_name, column_type, options)
-    create_table 'employees' do |table|
+    create_table 'employees', id: false do |table|
       table.__send__(column_type, column_name, **options)
     end
     define_model_class('Employee').new
   end
+
+  def with_table_custom_primary_key(column_name, options = {})
+    create_table 'employees', id: false do |table|
+      table.__send__(:primary_key, column_name, **options)
+    end
+    define_model_class('Employee').new
+  end
 end

From 75612f6eee76214df72eee531e6ce03eadb1014b Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 27 Oct 2023 21:50:54 -0300
Subject: [PATCH 8/9] fix: Test updating error_highlight gem

---
 gemfiles/rails_7_1.gemfile | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/gemfiles/rails_7_1.gemfile b/gemfiles/rails_7_1.gemfile
index 0a21a31f9..22a183d93 100644
--- a/gemfiles/rails_7_1.gemfile
+++ b/gemfiles/rails_7_1.gemfile
@@ -36,3 +36,7 @@ gem "shoulda-context", "~> 2.0.0"
 gem "bcrypt", "~> 3.1.7"
 gem "sqlite3", "~> 1.4"
 gem "pg", "~> 1.1"
+
+if RUBY_VERSION >= "3.1" && RUBY_VERSION < "3.2"
+  gem "error_highlight", ">= 0.4.0", platforms: [:ruby]
+end

From aae4ed2d09693f59d034623148ed922bfc79f5ff Mon Sep 17 00:00:00 2001
From: Matheus Sales <matheus_usales@hotmail.com>
Date: Fri, 10 Nov 2023 11:44:33 -0300
Subject: [PATCH 9/9] fix: Add default Rails tables to acceptance spec

This commit fixes the problem of not finding the tables
when running the specs in eager load mode, this was introduced
by Rails 7.1.

Load the model schema when running test in eager load
context https://github.com/rails/rails/pull/49470
---
 spec/acceptance/rails_integration_spec.rb | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/spec/acceptance/rails_integration_spec.rb b/spec/acceptance/rails_integration_spec.rb
index ec761f5ac..9f16600da 100644
--- a/spec/acceptance/rails_integration_spec.rb
+++ b/spec/acceptance/rails_integration_spec.rb
@@ -4,7 +4,21 @@
   before do
     create_rails_application
 
-    write_file 'db/migrate/1_create_users.rb', <<-FILE
+    write_file 'db/migrate/1_create_defaults.rb', <<-FILE
+      class CreateDefaults < #{migration_class_name}
+        def self.up
+          create_table :action_text_rich_texts
+          create_table :active_storage_variant_records
+          create_table :active_storage_blobs
+          create_table :active_storage_attachments
+          create_table :action_mailbox_inbound_emails do |t|
+            t.integer :status
+          end
+        end
+      end
+    FILE
+
+    write_file 'db/migrate/2_create_users.rb', <<-FILE
       class CreateUsers < #{migration_class_name}
         def self.up
           create_table :users do |t|