Skip to content

Commit 70d2cc8

Browse files
Introduce support for deprecated associations API
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) } ```
1 parent 2a50331 commit 70d2cc8

File tree

2 files changed

+375
-2
lines changed

2 files changed

+375
-2
lines changed

lib/shoulda/matchers/active_record/association_matcher.rb

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,25 @@ module ActiveRecord
374374
#
375375
# @return [AssociationMatcher]
376376
#
377+
# ##### deprecated
378+
#
379+
# Use `deprecated` to assert that the `:deprecated` option was specified.
380+
# (Enabled by default in Rails 8.1+).
381+
#
382+
# class Account < ActiveRecord::Base
383+
# belongs_to :bank, deprecated: true
384+
# end
385+
#
386+
# # RSpec
387+
# RSpec.describe Account, type: :model do
388+
# it { should belong_to(:bank).deprecated(true) }
389+
# end
390+
#
391+
# # Minitest (Shoulda)
392+
# class AccountTest < ActiveSupport::TestCase
393+
# should belong_to(:bank).deprecated(true)
394+
# end
395+
#
377396
def belong_to(name)
378397
AssociationMatcher.new(:belongs_to, name)
379398
end
@@ -683,7 +702,25 @@ def belong_to(name)
683702
#
684703
# @return [AssociationMatcher]
685704
#
686-
705+
# ##### deprecated
706+
#
707+
# Use `deprecated` to assert that the association is not allowed to be nil.
708+
# (Enabled by default in Rails 8.1+).
709+
#
710+
# class Vehicle < ActiveRecord::Base
711+
# delegated_type :drivable, types: %w(Car Truck), deprecated: true
712+
# end
713+
#
714+
# # RSpec
715+
# describe Vehicle
716+
# it { should have_delegated_type(:drivable).deprecated }
717+
# end
718+
#
719+
# # Minitest (Shoulda)
720+
# class VehicleTest < ActiveSupport::TestCase
721+
# should have_delegated_type(:drivable).deprecated
722+
# end
723+
#
687724
def have_delegated_type(name)
688725
AssociationMatcher.new(:belongs_to, name)
689726
end
@@ -972,6 +1009,25 @@ def have_delegated_type(name)
9721009
#
9731010
# @return [AssociationMatcher]
9741011
#
1012+
# ##### deprecated
1013+
#
1014+
# Use `deprecated` to assert that the `:deprecated` option was specified.
1015+
# (Enabled by default in Rails 8.1+)
1016+
#
1017+
# class Player < ActiveRecord::Base
1018+
# has_many :games, deprecated: true
1019+
# end
1020+
#
1021+
# # RSpec
1022+
# RSpec.describe Player, type: :model do
1023+
# it { should have_many(:games).deprecated(true) }
1024+
# end
1025+
#
1026+
# # Minitest (Shoulda)
1027+
# class PlayerTest < ActiveSupport::TestCase
1028+
# should have_many(:games).deprecated(true)
1029+
# end
1030+
#
9751031
def have_many(name)
9761032
AssociationMatcher.new(:has_many, name)
9771033
end
@@ -1219,6 +1275,25 @@ def have_many(name)
12191275
#
12201276
# @return [AssociationMatcher]
12211277
#
1278+
# ##### deprecated
1279+
#
1280+
# Use `deprecated` to assert that the `:deprecated` option was specified.
1281+
# (Enabled by default in Rails 8.1+).
1282+
#
1283+
# class Account < ActiveRecord::Base
1284+
# has_one :bank, deprecated: true
1285+
# end
1286+
#
1287+
# # RSpec
1288+
# RSpec.describe Account, type: :model do
1289+
# it { should have_one(:bank).deprecated(true) }
1290+
# end
1291+
#
1292+
# # Minitest (Shoulda)
1293+
# class AccountTest < ActiveSupport::TestCase
1294+
# should have_one(:bank).deprecated(true)
1295+
# end
1296+
#
12221297
def have_one(name)
12231298
AssociationMatcher.new(:has_one, name)
12241299
end
@@ -1377,6 +1452,27 @@ def have_one(name)
13771452
#
13781453
# @return [AssociationMatcher]
13791454
#
1455+
# ##### deprecated
1456+
#
1457+
# Use `deprecated` to assert that the `:deprecated` option was specified.
1458+
# (Enabled by default in Rails 8.1+).
1459+
#
1460+
# class Publisher < ActiveRecord::Base
1461+
# has_and_belongs_to_many :advertisers, deprecated: true
1462+
# end
1463+
#
1464+
# # RSpec
1465+
# RSpec.describe Publisher, type: :model do
1466+
# it { should have_and_belong_to_many(:advertisers).deprecated(true) }
1467+
# end
1468+
#
1469+
# # Minitest (Shoulda)
1470+
# class AccountTest < ActiveSupport::TestCase
1471+
# should have_and_belong_to_many(:advertisers).deprecated(true)
1472+
# end
1473+
#
1474+
# @return [AssociationMatcher]
1475+
#
13801476
def have_and_belong_to_many(name)
13811477
AssociationMatcher.new(:has_and_belongs_to_many, name)
13821478
end
@@ -1546,6 +1642,16 @@ def join_table(join_table_name)
15461642
self
15471643
end
15481644

1645+
def deprecated(deprecated = true)
1646+
if ::ActiveRecord::VERSION::STRING >= '8.1'
1647+
@options[:deprecated] = deprecated
1648+
self
1649+
else
1650+
raise NotImplementedError,
1651+
'`deprecated` association matcher is only available on Active Record >= 8.1.'
1652+
end
1653+
end
1654+
15491655
def without_validating_presence
15501656
remove_submatcher(AssociationMatchers::RequiredMatcher)
15511657
self
@@ -1586,7 +1692,8 @@ def matches?(subject)
15861692
touch_correct? &&
15871693
types_correct? &&
15881694
strict_loading_correct? &&
1589-
submatchers_match?
1695+
submatchers_match? &&
1696+
deprecated_correct?
15901697
end
15911698

15921699
def join_table_name
@@ -1848,6 +1955,15 @@ def touch_correct?
18481955
end
18491956
end
18501957

1958+
def deprecated_correct?
1959+
if option_verifier.correct_for_boolean?(:deprecated, options[:deprecated])
1960+
true
1961+
else
1962+
@missing = "#{name} should have deprecated: #{options[:deprecated]}"
1963+
false
1964+
end
1965+
end
1966+
18511967
def types_correct?
18521968
if options.key?(:types)
18531969
types = options[:types]

0 commit comments

Comments
 (0)