Skip to content

Conversation

@stefannibrasil
Copy link

@stefannibrasil stefannibrasil commented Nov 18, 2025

Closes #1681

Rails 8.1 introduced the ability to mark Active Record associations as deprecated. This allows developers to mark associations for deprecation while maintaining backward compatibility.

Example test cases

Assert association is deprecated:

it { should have_many(:posts).deprecated }
it { should have_one(:profile).deprecated }
it { should belong_to(:author).deprecated }

Assert association is NOT deprecated:

it { should have_many(:posts).deprecated(false) }
it { should_not have_many(:posts).deprecated }

Notes and Questions

  • Added docs and coverage for all associations, let me know if I missed anything!
  • What type of exception should I raise when the Active Record version is < 8.1? I didn't find an existing case to follow. NotImplementedError makes sense to me, although I debated on returning ArgumentError, since that's what ActiveRecord returns when trying to use it on versions < 8.1. Open to suggestions to provide more helpful error on older
    AR versions.
  • I copied the rails_version check in the tests. Do you usually just check for rails versions or should I create a helper for checking active record instead? I am checking for ActiveRecord's version in the association class, since shoulda-matchers can be used in a non-Rails app.

Tests with a real app

I tested the code changes with these scripts: https://gist.github.com/stefannibrasil/02809758148838bf3950d34a64c37106 (Rails 8.0 and 8.1.1). Both provide lots of examples of how to use the new matcher.

end

context 'have_many' do
if rails_version <= 8.1
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to test the error message, but I can remove this context if it's too much.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, the only missing thing is that the deprecated association option will not exist in older rails versions, so you should only use the deprecated modifier method in the test.

expect do
            expect(having_many_children).to have_many(:children).deprecated(false)
          end.to raise_error(NotImplementedError, expected_message)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Fixed it in 9450ae8

Rails 8.1 introduced the ability to mark Active Record
associations as deprecated. This allows
developers to mark associations for deprecation while maintaining
backward compatibility.

Assert association is deprecated:

```ruby
it { should have_many(:posts).deprecated }
it { should have_one(:profile).deprecated }
it { should belong_to(:author).deprecated }
```

Assert association is NOT deprecated:

```ruby
it { should have_many(:posts).deprecated(false) }
```
@stefannibrasil stefannibrasil force-pushed the sb-1681-deprecated-associations branch from d6ed0e5 to 70d2cc8 Compare November 19, 2025 00:06
Comment on lines +377 to +395
# ##### deprecated
#
# Use `deprecated` to assert that the `:deprecated` option was specified.
# (Enabled by default in Rails 8.1+).
#
# class Account < ActiveRecord::Base
# belongs_to :bank, deprecated: true
# end
#
# # RSpec
# RSpec.describe Account, type: :model do
# it { should belong_to(:bank).deprecated(true) }
# end
#
# # Minitest (Shoulda)
# class AccountTest < ActiveSupport::TestCase
# should belong_to(:bank).deprecated(true)
# end
#
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this up before the line 375

# ... new doc
#
# @return [AssociationMatcher]

The same comment applies to the other doc comments.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks! Done in 2239770

Comment on lines 1646 to 1649
if ::ActiveRecord::VERSION::STRING >= '8.1'
@options[:deprecated] = deprecated
self
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have a new method in the lib/shoulda/matchers/rails_shim.rb file to have that active record version check.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! Refactored this in 9450ae8

Comment on lines 1695 to 1696
submatchers_match? &&
deprecated_correct?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave the submatchers_match? as the last check, as they're usually a slower check.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in df61574

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need tests that assert that the correct message output, e.g.

 it 'rejects an association with a non-matching :autosave option with the correct message' do
      message = 'Expected Vehicle to have a belongs_to association called drivable (drivable should have autosave set to true)'

      expect {
        expect(delegating_type_to_drivable(autosave: false)).to have_delegated_type(:drivable).autosave(true)
      }.to fail_with_message(message)
    end

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another great catch. I missed this test 🙈 Added these in 4ea3d9c

@stefannibrasil
Copy link
Author

stefannibrasil commented Nov 19, 2025

thanks @matsales28 for the review! I will come back to this tomorrow. Had some issues locally after pushing the branch and need to figure it out to fix the test.

Let's use the Shoulda::Matchers::RailsShim class
to encapsulate this logic. I also updated
the unit tests to have an active_record_version
helper.
@stefannibrasil stefannibrasil force-pushed the sb-1681-deprecated-associations branch from 147e89a to bcba32b Compare November 19, 2025 20:11
@stefannibrasil stefannibrasil marked this pull request as ready for review November 19, 2025 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce support for deprecated associations API

2 participants