with_model 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bac7bd2d2e529887f1e9bd82117cc809f23b336a8acaa2b90e4fb9305c96fe2
4
- data.tar.gz: 1867b36f42957d744858fcefe70f53c58b0c5f39b951a4749a969c39c46b42f0
3
+ metadata.gz: f788d32a4cd252a5254b82928cab9b62b66cfd80e00dd010bdb901b60a6ddc6c
4
+ data.tar.gz: 2c44d64d8f3225e809c113894ec77df2e64c6dae513a12065e171bd892c80c7d
5
5
  SHA512:
6
- metadata.gz: eec34fe045c932273600775ccf7b2719dad1d10d6631baf43b9b7d54eec50bc4592d24cc1da1e9af7a844e7fbc73f707a0027d9f58c891afd18fb934e1f796d9
7
- data.tar.gz: 5720ad24f60c91dee5b1eac354a0ca3d3f00d6c65e5d2f45fc76a310d6330564f43ec14924f168edc064121e9cc70b4ca7483c08a33587ea90f20031fff3fba2
6
+ metadata.gz: bbf514f03d362582bf15cd38301a9f2929c559ea702c283bae2c01720f48e3f613e93ce1580906983d567048d7241d70af92b94f54f4d992da06f3497c9afc8e
7
+ data.tar.gz: 66200b79760795f6d3e622b4b29c13285fb042e4e168f1fea34ac38161bca41e4330a451add6f9a0db777dd20cbde97567cd687c811dff1675a1090a24dc7704
@@ -29,8 +29,6 @@ jobs:
29
29
  matrix:
30
30
  ruby-version: ["3.3", "3.4", "4.0"]
31
31
  active-record:
32
- - label: AR 7.2
33
- env: ACTIVE_RECORD_VERSION=~> 7.2.0
34
32
  - label: AR 8.0
35
33
  env: ACTIVE_RECORD_VERSION=~> 8.0.0
36
34
  - label: AR 8.1
@@ -52,11 +50,6 @@ jobs:
52
50
  label: AR 8-0-stable
53
51
  env: ACTIVE_RECORD_BRANCH=8-0-stable
54
52
  allow-failure: true
55
- - ruby-version: "4.0"
56
- active-record:
57
- label: AR 7-2-stable
58
- env: ACTIVE_RECORD_BRANCH=7-2-stable
59
- allow-failure: true
60
53
  # A canary for the next Ruby, which cannot fail the build.
61
54
  - ruby-version: ruby-head
62
55
  active-record:
@@ -78,11 +71,6 @@ jobs:
78
71
  ruby-version: ${{ matrix.ruby-version }}
79
72
  bundler-cache: true
80
73
  cache-version: ${{ matrix.active-record.env }}
81
- # Gemfile.lock is not checked in, so this resolves the newest gems that
82
- # satisfy the Gemfile on each run rather than a recorded set. `--all` is
83
- # what Bundler 4, shipped with Ruby 4.0, wants for that.
84
- - name: Update bundle
85
- run: bundle update --all
86
74
  # Not bin/rake: bin/ holds generated binstubs and is not checked in, and
87
75
  # whether bundling writes them there varies by Bundler version - Bundler 4
88
76
  # does not, which left Ruby 4.0 with no binstub to run.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ### Unreleased
2
+
3
+ ### 2.4.0
4
+
5
+ - Add Test::Unit support, including lifecycle integration for dynamic models and
6
+ tables.
7
+ - Add `with_model/rspec`, `with_model/minitest`, and `with_model/test_unit`
8
+ convenience entrypoints.
9
+ - Add runner integration guides for RSpec, Minitest, and Test::Unit.
10
+ - Skip temporary-table teardown when the table has already disappeared.
11
+ - Require Active Record 8.0 or later.
12
+
13
+ ### 2.3.1
14
+
15
+ - Honor Active Record destruction callbacks and dependent associations when
16
+ cleaning up rows for a `table(false)` model.
17
+
1
18
  ### 2.3.0
2
19
 
3
20
  - Add `table(false)` to create no table, so that a model whose superclass is a
data/CONTRIBUTING.md CHANGED
@@ -24,12 +24,22 @@ avoid anything that only works on the newest of either.
24
24
 
25
25
  ## Running the tests
26
26
 
27
- The suite runs against both supported test harnesses, so `with_model` has to work
28
- under each:
27
+ The suite runs against all three supported test harnesses, so `with_model` has
28
+ to work under each:
29
29
 
30
30
  - `spec/` covers behavior under RSpec.
31
- - `test/` covers the minitest life cycle — the setup and teardown hooks, and
31
+ - `test/` covers the Minitest life cycle — the setup and teardown hooks, and
32
32
  their ordering — rather than repeating the specs.
33
+ - `test_unit/` covers Test::Unit integration in a dedicated process, including
34
+ native fixture callback ordering.
35
+
36
+ Run one harness with its scoped Rake task:
37
+
38
+ ```sh
39
+ bundle exec rake spec
40
+ bundle exec rake test
41
+ bundle exec rake test_unit
42
+ ```
33
43
 
34
44
  Run everything, including the linter, with:
35
45
 
@@ -37,6 +47,13 @@ Run everything, including the linter, with:
37
47
  bundle exec rake
38
48
  ```
39
49
 
50
+ Validate the README and runner guides with RDoc before submitting documentation
51
+ changes:
52
+
53
+ ```sh
54
+ rm -rf tmp/rdoc && bundle exec rdoc --markup markdown --op tmp/rdoc README.md docs/rspec.md docs/minitest.md docs/test-unit.md lib/**/*.rb
55
+ ```
56
+
40
57
  `bin/` holds generated binstubs and is not checked in. Whether bundling writes
41
58
  them for you depends on your Bundler version, so create them once if you would
42
59
  rather type `bin/rake`:
data/Gemfile CHANGED
@@ -24,3 +24,4 @@ gem "rspec"
24
24
  gem "ruby-lsp"
25
25
  gem "sqlite3"
26
26
  gem "standard"
27
+ gem "test-unit"
data/README.md CHANGED
@@ -14,32 +14,63 @@
14
14
 
15
15
  Install as usual: `gem install with_model` or add `gem 'with_model'` to your Gemfile. See [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) for supported (tested) Ruby versions.
16
16
 
17
+ ## Test runner setup
18
+
19
+ Choose the entrypoint for the test runner that owns the declarations. Each
20
+ performs complete global setup for one runner; see its guide for lifecycle and
21
+ manual-setup details.
22
+
17
23
  ### RSpec
18
24
 
19
- Extend `WithModel` into RSpec:
25
+ ```ruby
26
+ require "with_model/rspec"
27
+ ```
28
+
29
+ See the [RSpec guide](docs/rspec.md).
30
+
31
+ ### Minitest
20
32
 
21
33
  ```ruby
22
- require "with_model"
34
+ require "with_model/minitest"
35
+ ```
23
36
 
24
- RSpec.configure do |config|
25
- config.extend WithModel
26
- end
37
+ See the [Minitest guide](docs/minitest.md).
38
+
39
+ ### Test::Unit
40
+
41
+ ```ruby
42
+ require "with_model/test_unit"
27
43
  ```
28
44
 
29
- ### minitest/spec
45
+ See the [Test::Unit guide](docs/test-unit.md).
30
46
 
31
- Extend `WithModel` into minitest/spec and set the test runner explicitly:
47
+ ### Advanced and mixed-runner setup
48
+
49
+ Plain `require "with_model"` remains framework-neutral. For manual setup,
50
+ select a runner and extend its test context directly:
32
51
 
33
52
  ```ruby
53
+ require "minitest"
34
54
  require "with_model"
35
55
 
36
56
  WithModel.runner = :minitest
57
+ Minitest::Test.extend WithModel
58
+ ```
37
59
 
38
- class Minitest::Spec
39
- extend WithModel
60
+ RSpec uses `RSpec.configure { |config| config.extend WithModel }`; Test::Unit
61
+ uses `Test::Unit::TestCase.extend WithModel`. A declaration can override the
62
+ global runner when needed:
63
+
64
+ ```ruby
65
+ with_model :Post, runner: :test_unit do
66
+ table
40
67
  end
41
68
  ```
42
69
 
70
+ Loading multiple convenience entrypoints in one process is unsupported because
71
+ the last one changes the global runner. For mixed runners, use plain
72
+ `with_model`, direct extension, and explicit `runner:` options.
73
+
43
74
  ## Usage
44
75
 
45
76
  After setting up as above, call `with_model` and inside its block pass it a `table` block and a `model` block.
@@ -252,7 +283,13 @@ the order they are declared, and destroyed in the reverse order.
252
283
 
253
284
  ## Requirements
254
285
 
255
- See the [gemspec metadata](https://rubygems.org/gems/with_model) for dependency requirements. RSpec and minitest are indirect dependencies, and `with_model` should support any maintained version of both.
286
+ See the [gemspec metadata](https://rubygems.org/gems/with_model) for Ruby and
287
+ Active Record requirements. RSpec, Minitest, and Test::Unit are optional
288
+ consumer framework dependencies; install the framework used by its selected
289
+ entrypoint. Requiring a selected entrypoint without its framework installed
290
+ raises the framework's ordinary `LoadError`.
291
+
292
+ `scope:` is RSpec-only and is silently ignored by Minitest and Test::Unit.
256
293
 
257
294
  ## Thread-safety
258
295
 
data/Rakefile CHANGED
@@ -8,7 +8,13 @@ RSpec::Core::RakeTask.new
8
8
  require "minitest/test_task"
9
9
  Minitest::TestTask.create
10
10
 
11
+ require "rake/testtask"
12
+ Rake::TestTask.new(:test_unit) do |task|
13
+ task.libs << "lib" << "test_unit" << "test"
14
+ task.pattern = "test_unit/**/*_test.rb"
15
+ end
16
+
11
17
  # standard rake task
12
18
  require "standard/rake"
13
19
 
14
- task default: %i[spec test standard]
20
+ task default: %i[spec test test_unit standard]
data/docs/minitest.md ADDED
@@ -0,0 +1,45 @@
1
+ # Minitest integration
2
+
3
+ Load the Minitest entrypoint before defining tests:
4
+
5
+ ```ruby
6
+ require "with_model/minitest"
7
+ ```
8
+
9
+ This requires `minitest`, then `with_model`, selects the
10
+ `:minitest` runner, and extends `Minitest::Test`. Its class-level DSL is
11
+ inherited by `Minitest::Test` subclasses, including `Minitest::Spec` and
12
+ `ActiveSupport::TestCase`. The entrypoint does not require or start Minitest's
13
+ autorun — your process or test framework owns Minitest startup.
14
+
15
+ ## Lifecycle
16
+
17
+ The Minitest runner creates declared models and tables in `before_setup` and
18
+ destroys them in `after_teardown`. This makes them available to ordinary setup
19
+ methods and keeps them available through teardown. `scope:` is silently ignored
20
+ by Minitest because it is an RSpec-only option.
21
+
22
+ ## Manual setup
23
+
24
+ If another process or tool owns Minitest configuration, configure manually:
25
+
26
+ ```ruby
27
+ require "minitest"
28
+ require "with_model"
29
+
30
+ WithModel.runner = :minitest
31
+ Minitest::Test.extend WithModel
32
+ ```
33
+
34
+ Minitest is an optional consumer dependency. Requiring `with_model/minitest`
35
+ without Minitest installed raises Minitest's ordinary `LoadError`.
36
+
37
+ ## Mixed runners
38
+
39
+ Do not load multiple convenience entrypoints in one process: each changes the
40
+ global runner, and the last one wins. For mixed runners, load plain
41
+ `with_model`, extend each framework boundary directly, and select a runner per
42
+ declaration with `runner:`.
43
+
44
+ See the [general DSL reference](../README.md#usage) for declaring models and
45
+ tables.
data/docs/rspec.md ADDED
@@ -0,0 +1,43 @@
1
+ # RSpec integration
2
+
3
+ Load the RSpec entrypoint before defining example groups:
4
+
5
+ ```ruby
6
+ require "with_model/rspec"
7
+ ```
8
+
9
+ The entrypoint requires `rspec/core` and `with_model`, selects the `:rspec`
10
+ runner, and extends RSpec example groups through `RSpec.configure`. It does not
11
+ start RSpec; the RSpec command-line runner remains in control.
12
+
13
+ ## Lifecycle
14
+
15
+ `with_model` and `with_table` register RSpec `before` and `after` hooks. Models
16
+ and tables are available to examples after setup and are removed after cleanup.
17
+ Use `scope:` when RSpec hook scope is needed, including `scope: :all` for a
18
+ model needed by `before(:all)`. `scope:` is an RSpec-only option.
19
+
20
+ ## Manual setup
21
+
22
+ For runner-owned setup, require the framework and `with_model` separately:
23
+
24
+ ```ruby
25
+ require "rspec/core"
26
+ require "with_model"
27
+
28
+ WithModel.runner = :rspec
29
+ RSpec.configure { |config| config.extend WithModel }
30
+ ```
31
+
32
+ RSpec is an optional consumer dependency. Requiring `with_model/rspec` without
33
+ RSpec installed raises RSpec's ordinary `LoadError`.
34
+
35
+ ## Mixed runners
36
+
37
+ Do not load multiple convenience entrypoints in one process: each changes the
38
+ global runner, and the last one wins. For mixed runners, load plain
39
+ `with_model`, extend each framework boundary directly, and select a runner per
40
+ declaration with `runner:`.
41
+
42
+ See the [general DSL reference](../README.md#usage) for declaring models and
43
+ tables.
data/docs/test-unit.md ADDED
@@ -0,0 +1,73 @@
1
+ # Test::Unit integration
2
+
3
+ Load the Test::Unit entrypoint before defining tests:
4
+
5
+ ```ruby
6
+ require "with_model/test_unit"
7
+ ```
8
+
9
+ The entrypoint requires `test/unit`, then `with_model`, selects the
10
+ `:test_unit` runner, and extends `Test::Unit::TestCase`. Test::Unit retains
11
+ ownership of autorun and fixture execution.
12
+
13
+ ## Lifecycle
14
+
15
+ `with_model` and `with_table` register Test::Unit fixture callbacks with exact
16
+ placement: setup uses `before: :append`, while teardown uses `after: :prepend`.
17
+ Declared models and tables therefore create in declaration order before user or
18
+ inherited setup methods and callbacks. They destroy in reverse order after user
19
+ or inherited teardown methods and callbacks.
20
+
21
+ Test::Unit runs teardown when setup raises. `WithModel::Model#destroy`
22
+ defensively handles creation that never assigned a model. `Table#destroy` checks
23
+ whether its table exists before dropping it, so teardown does not raise solely
24
+ because a reconnect replaced an in-memory database or the table otherwise
25
+ already disappeared. It does not rescue other database, connection, or drop
26
+ errors; those still propagate. This conditional drop check does not promise
27
+ generic partial-creation recovery for bare `with_table`. `scope:` is silently
28
+ ignored by Test::Unit because it is an RSpec-only option.
29
+
30
+ ## Errata and concerns
31
+
32
+ Establish the Active Record connection before `with_model`'s setup callback
33
+ creates tables and models. An ordinary `def setup` runs too late. Connect in
34
+ your test helper before test classes load, or register a connection callback
35
+ with `setup(before: :prepend)`.
36
+
37
+ ```ruby
38
+ class PostTest < Test::Unit::TestCase
39
+ setup(before: :prepend) do
40
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
41
+ end
42
+
43
+ with_model :Post do
44
+ table
45
+ end
46
+ end
47
+ ```
48
+
49
+ ## Manual setup
50
+
51
+ For runner-owned setup, require Test::Unit and `with_model` separately:
52
+
53
+ ```ruby
54
+ require "test/unit"
55
+ require "with_model"
56
+
57
+ WithModel.runner = :test_unit
58
+ Test::Unit::TestCase.extend WithModel
59
+ ```
60
+
61
+ Test::Unit is an optional consumer dependency. Requiring
62
+ `with_model/test_unit` without Test::Unit installed raises Test::Unit's ordinary
63
+ `LoadError`.
64
+
65
+ ## Mixed runners
66
+
67
+ Do not load multiple convenience entrypoints in one process: each changes the
68
+ global runner, and the last one wins. For mixed runners, load plain
69
+ `with_model`, extend each framework boundary directly, and select a runner per
70
+ declaration with `runner:`.
71
+
72
+ See the [general DSL reference](../README.md#usage) for declaring models and
73
+ tables.
@@ -74,14 +74,6 @@ module WithModel
74
74
  end
75
75
  end
76
76
 
77
- class ActiveRecord::Base
78
- extend WithModel::DescendantsTracker::DestroyedClassesFiltering
79
- end
80
-
81
- module ActiveSupport
82
- module DescendantsTracker
83
- class << self
84
- attr_reader :clear_disabled
85
- end
86
- end
87
- end
77
+ ActiveRecord::Base.singleton_class.prepend(
78
+ WithModel::DescendantsTracker::DestroyedClassesFiltering
79
+ )
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest"
4
+ require "with_model"
5
+
6
+ WithModel.runner = :minitest
7
+ Minitest::Test.extend WithModel
@@ -74,8 +74,6 @@ module WithModel
74
74
  # can only do so while the class still has its name.
75
75
  table.teardown(@model)
76
76
  stubber.unstub_const
77
- cleanup_descendants_tracking
78
- reset_dependencies_cache
79
77
  WithModel::DescendantsTracker.clear([@model])
80
78
  @model = nil
81
79
  end
@@ -132,17 +130,6 @@ module WithModel
132
130
  @model.reset_column_information
133
131
  end
134
132
 
135
- def cleanup_descendants_tracking
136
- ActiveSupport::DescendantsTracker.clear([@model]) \
137
- unless ActiveSupport::DescendantsTracker.clear_disabled
138
- end
139
-
140
- def reset_dependencies_cache
141
- return unless defined?(ActiveSupport::Dependencies::Reference)
142
-
143
- ActiveSupport::Dependencies::Reference.clear!
144
- end
145
-
146
133
  def stubber
147
134
  @stubber ||= ConstantStubber.new const_name
148
135
  end
@@ -13,17 +13,17 @@ module WithModel
13
13
  class NullTable
14
14
  # @param [Class] superclass The resolved superclass whose table will be
15
15
  # inherited.
16
- # @param [Symbol, String] name The model's name, so that a refusal can say
16
+ # @param [Symbol, String] name The model's name, so that an error can say
17
17
  # which model it is talking about.
18
18
  def initialize(superclass, name)
19
19
  @superclass = superclass
20
20
  @name = name
21
21
  end
22
22
 
23
- # Creates nothing, but refuses a superclass that cannot support single
24
- # table inheritance.
23
+ # Creates nothing, but raises {WithModel::InvalidSuperclass} when the
24
+ # superclass cannot support single table inheritance.
25
25
  #
26
- # Refusals describe the model's situation rather than the call that produced
26
+ # Errors describe the model's situation rather than the call that produced
27
27
  # it. Any expression that evaluates to false selects a NullTable, so there
28
28
  # is no call spelling to quote - and in with_model 3.0, omitting `table`
29
29
  # will arrive here too.
@@ -31,20 +31,20 @@ module WithModel
31
31
  # A superclass whose table does not exist *yet* is deliberately allowed: the
32
32
  # table may be created later in the example, and a test may legitimately
33
33
  # want a model whose table is missing. Active Record raises a clear
34
- # `StatementInvalid` naming the table if it never appears, so refusing here
34
+ # `StatementInvalid` naming the table if it never appears, so raising here
35
35
  # would only forbid working setups.
36
36
  #
37
- # What is left is {WithModel::InvalidSuperclass}: both refusals are permanent
37
+ # What is left is {WithModel::InvalidSuperclass}: both errors are permanent
38
38
  # facts about the superclass passed in, not about when it was looked at, so no
39
39
  # amount of waiting makes them work.
40
40
  def create
41
- refuse "#{@superclass} has none to inherit" unless table_name?
41
+ raise_invalid_superclass "#{@superclass} has none to inherit" unless table_name?
42
42
 
43
43
  # Nothing more can be checked until there is a table to look at.
44
44
  return unless table_exists?
45
45
  return if inheritance_column?
46
46
 
47
- refuse "#{@superclass}'s table #{@superclass.table_name.inspect} has no " \
47
+ raise_invalid_superclass "#{@superclass}'s table #{@superclass.table_name.inspect} has no " \
48
48
  "#{inheritance_column.inspect} column, so Active Record cannot tell its rows " \
49
49
  "apart from a subclass's"
50
50
  end
@@ -59,7 +59,7 @@ module WithModel
59
59
  # (`ActiveRecord::SubclassNotFound`).
60
60
  #
61
61
  # `unscoped` is required because a `default_scope` on the superclass would
62
- # otherwise hide rows from the delete; the inheritance-column condition is
62
+ # otherwise hide rows from cleanup; the inheritance-column condition is
63
63
  # then reapplied explicitly, since `unscoped` also discards the type
64
64
  # condition that keeps this from touching the superclass's own rows.
65
65
  #
@@ -68,7 +68,7 @@ module WithModel
68
68
  def teardown(klass)
69
69
  return unless klass.table_exists?
70
70
 
71
- klass.unscoped.where(klass.inheritance_column => klass.sti_name).delete_all
71
+ klass.unscoped.where(klass.inheritance_column => klass.sti_name).each(&:destroy!)
72
72
  end
73
73
 
74
74
  # Drops nothing; there is no table of our own to drop.
@@ -90,7 +90,7 @@ module WithModel
90
90
  inheritance_column.present? && @superclass.columns_hash.key?(inheritance_column)
91
91
  end
92
92
 
93
- def refuse(problem)
93
+ def raise_invalid_superclass(problem)
94
94
  raise InvalidSuperclass,
95
95
  "with_model #{@name.inspect} has no table of its own, but #{problem}"
96
96
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core"
4
+ require "with_model"
5
+
6
+ WithModel.runner = :rspec
7
+
8
+ RSpec.configure do |config|
9
+ config.extend WithModel
10
+ end
@@ -38,19 +38,13 @@ module WithModel
38
38
  end
39
39
 
40
40
  def destroy
41
- connection.drop_table(@name)
41
+ connection.drop_table(@name) if exists?
42
42
  end
43
43
 
44
44
  private
45
45
 
46
46
  attr_reader :connection
47
47
 
48
- def exists?
49
- if connection.respond_to?(:data_source_exists?)
50
- connection.data_source_exists?(@name)
51
- else
52
- connection.table_exists?(@name)
53
- end
54
- end
48
+ def exists? = connection.data_source_exists?(@name)
55
49
  end
56
50
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test/unit"
4
+ require "with_model"
5
+
6
+ WithModel.runner = :test_unit
7
+ Test::Unit::TestCase.extend WithModel
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WithModel
4
- VERSION = "2.3.0"
4
+ VERSION = "2.4.0"
5
5
  end
data/lib/with_model.rb CHANGED
@@ -113,8 +113,13 @@ module WithModel
113
113
  class_eval do
114
114
  include MiniTestLifeCycle.call(object)
115
115
  end
116
+ when :test_unit
117
+ # These options place creation before and destruction after user callbacks,
118
+ # preserving declaration and reverse order across inheritance.
119
+ setup(before: :append) { object.create }
120
+ teardown(after: :prepend) { object.destroy }
116
121
  else
117
- raise ArgumentError, "Unsupported test runner set, expected :rspec or :minitest"
122
+ raise ArgumentError, "Unsupported test runner set, expected :rspec, :minitest, or :test_unit"
118
123
  end
119
124
  end
120
125
  end
@@ -30,7 +30,6 @@ describe "Descendants tracking" do
30
30
 
31
31
  context "with ActiveSupport::DescendantsTracker (cache_classes: true)" do
32
32
  before do
33
- expect(ActiveSupport::DescendantsTracker.clear_disabled).to be_falsey
34
33
  expect { ActiveSupport::DescendantsTracker.clear([]) }.not_to raise_exception
35
34
  end
36
35
 
@@ -40,10 +39,29 @@ describe "Descendants tracking" do
40
39
  context "without ActiveSupport::DescendantsTracker (cache_classes: false)" do
41
40
  before do
42
41
  ActiveSupport::DescendantsTracker.disable_clear!
43
- expect(ActiveSupport::DescendantsTracker.clear_disabled).to be_truthy
44
42
  expect { ActiveSupport::DescendantsTracker.clear([]) }.to raise_exception(RuntimeError)
45
43
  end
46
44
 
47
45
  include_examples "clearing descendants between test runs"
48
46
  end
47
+
48
+ it "keeps filtering ahead of later class method extensions" do
49
+ raw_subclasses = Class.instance_method(:subclasses).super_method
50
+ raw_descendants = Class.instance_method(:descendants).super_method
51
+ override = Module.new do
52
+ define_method(:subclasses) { raw_subclasses.bind_call(self) }
53
+ define_method(:descendants) { raw_descendants.bind_call(self) }
54
+ end
55
+ destroyed_model = stub_const("DestroyedModel", Class.new(ActiveRecord::Base))
56
+ WithModel::DescendantsTracker.clear([destroyed_model])
57
+
58
+ ActiveRecord::Base.extend override
59
+
60
+ expect(ActiveRecord::Base.descendants).not_to include(destroyed_model)
61
+ ensure
62
+ override&.module_eval do
63
+ remove_method :subclasses
64
+ remove_method :descendants
65
+ end
66
+ end
49
67
  end