with_model 2.1.7 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83aa1a692c521015759725b2641f53304ab13f6c3aac17550dc7b802c8e015ea
4
- data.tar.gz: e8c44ffe440d68a3f8311f2250a2184e88fffbe1d9713f0b870a5c15fcf8ec2e
3
+ metadata.gz: 809e5984c60e2fa27b71d3d3ce7664feae0a3b2c7a59f345e2a94a819affb623
4
+ data.tar.gz: 5e8b519ef7d338aa754755efd065af68b718e6dc1f96fe27632266752e7b4990
5
5
  SHA512:
6
- metadata.gz: 3acf78cc398a2c3c7612a1ee534d3d11ed93f701b8cbfaa218d8b64fb5c3dc0a3a5cf23080f3853e97cf1ba72a9cc202673f058a7c784213287bf9b81e0a1e85
7
- data.tar.gz: 0b8ccf8fa27e19880fbfdd166d3eb0974cecaecc94955c2d32204306eb3ca48f050f2b578a9042d81a34e0bfc7d6ab14e5c42b16d802e4b16cfc7efeb38bfecc
6
+ metadata.gz: cf09e4aabe724638f054a54199d62a61ae9efd0cca28485412f85fd082a99f104d4f538bd7041f1777c9380398e7d1497d5f01dcc07cb5fdacc116ad3e7dd69f
7
+ data.tar.gz: 6f795b756e9b33abd148d51010a0afb80e01660af35c76d1a98a634c6b2d31c38a616e94aeff99c6d8b19b019c69b994c9b5dc21e69439fbbcb004528dc3606b
@@ -6,8 +6,6 @@ on:
6
6
  - master
7
7
  - github-actions
8
8
  pull_request:
9
- branches:
10
- - master
11
9
 
12
10
  jobs:
13
11
  test:
@@ -17,18 +15,31 @@ jobs:
17
15
  strategy:
18
16
  fail-fast: false
19
17
  matrix:
20
- ruby-version: ['2.7', '3.0', '3.1', '3.2']
18
+ ruby-version: ['3.1', '3.2', '3.3', '3.4']
21
19
  active-record-version-env:
22
- - ACTIVE_RECORD_VERSION="~> 6.0.0"
23
- - ACTIVE_RECORD_VERSION="~> 6.1.0"
24
20
  - ACTIVE_RECORD_VERSION="~> 7.0.0"
21
+ - ACTIVE_RECORD_VERSION="~> 7.1.0"
22
+ - ACTIVE_RECORD_VERSION="~> 7.2.0"
23
+ - ACTIVE_RECORD_VERSION="~> 8.0.0"
25
24
  allow-failure: [false]
25
+ exclude:
26
+ - ruby-version: '3.1'
27
+ active-record-version-env: ACTIVE_RECORD_VERSION="~> 8.0.0"
26
28
  include:
27
- - ruby-version: '3.2'
28
- active-record-version-env: ACTIVE_RECORD_BRANCH="7-0-stable"
29
+ - ruby-version: '3.4'
30
+ active-record-version-env: ACTIVE_RECORD_BRANCH="main"
31
+ allow-failure: true
32
+ - ruby-version: '3.4'
33
+ active-record-version-env: ACTIVE_RECORD_BRANCH="8-0-stable"
29
34
  allow-failure: true
30
- - ruby-version: '3.2'
31
- active-record-version-env: ACTIVE_RECORD_BRANCH="6-1-stable"
35
+ - ruby-version: '3.4'
36
+ active-record-version-env: ACTIVE_RECORD_BRANCH="7-2-stable"
37
+ allow-failure: true
38
+ - ruby-version: '3.4'
39
+ active-record-version-env: ACTIVE_RECORD_BRANCH="7-1-stable"
40
+ allow-failure: true
41
+ - ruby-version: '3.4'
42
+ active-record-version-env: ACTIVE_RECORD_BRANCH="7-0-stable"
32
43
  allow-failure: true
33
44
  continue-on-error: ${{ matrix.allow-failure }}
34
45
  steps:
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
1
  --color
2
2
  --format documentation
3
- --order default
3
+ --order defined
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 2.2.0
2
+
3
+ - Fix dependency tracking issue when `cache_classes: true` is set in Rails 7+.
4
+ - Create table in same database as `superclass` (Joe Lind)
5
+ - Switch to standard instead of rubocop for linting.
6
+ - Add support for Active Record 7 and 8.
7
+ - Require Active Record 7 or later.
8
+ - Add support for Ruby 3.3.
9
+ - Require Ruby 3.1 or later.
10
+
1
11
  ### 2.1.7
2
12
  - Require Ruby 2.7 or later
3
13
  - Add Ruby 3.2 support
data/Gemfile CHANGED
@@ -1,33 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- ar_branch = ENV.fetch('ACTIVE_RECORD_BRANCH', nil)
8
- ar_version = ENV.fetch('ACTIVE_RECORD_VERSION', nil)
9
- is_jruby = RUBY_PLATFORM == 'java'
7
+ ar_branch = ENV.fetch("ACTIVE_RECORD_BRANCH", nil)
8
+ ar_version = ENV.fetch("ACTIVE_RECORD_VERSION", nil)
10
9
 
11
10
  if ar_branch
12
- gem 'activerecord', git: 'https://github.com/rails/rails.git', branch: ar_branch
13
- if ar_branch == 'master'
14
- gem 'arel', git: 'https://github.com/rails/arel.git'
15
- gem 'activerecord-jdbcsqlite3-adapter', git: 'https://github.com/jruby/activerecord-jdbc-adapter.git' if is_jruby
16
- end
11
+ gem "activerecord", git: "https://github.com/rails/rails.git", branch: ar_branch
12
+ gem "arel", git: "https://github.com/rails/arel.git" if ar_branch == "master"
17
13
  elsif ar_version
18
- gem 'activerecord', ar_version
19
- if is_jruby && !Gem::Requirement.new(ar_version).satisfied_by?(Gem::Version.new('5.2.0'))
20
- gem 'activerecord-jdbcsqlite3-adapter', git: 'https://github.com/jruby/activerecord-jdbc-adapter.git'
21
- end
14
+ gem "activerecord", ar_version
22
15
  end
23
16
 
24
- gem 'bundler'
25
- gem 'minitest'
26
- gem 'rake'
27
- gem 'rspec'
28
- gem 'rubocop'
29
- gem 'rubocop-minitest'
30
- gem 'rubocop-rake'
31
- gem 'rubocop-rspec'
32
- gem 'simplecov'
33
- gem 'sqlite3', '~> 1.6.0' unless is_jruby
17
+ gem "bigdecimal"
18
+ gem "bundler"
19
+ gem "debug"
20
+ gem "logger"
21
+ gem "minitest"
22
+ gem "mutex_m"
23
+ gem "rake"
24
+ gem "rspec"
25
+ gem "standard"
26
+
27
+ if ar_branch == "7-0-stable" || ar_version == "~> 7.0.0"
28
+ gem "sqlite3", "< 2"
29
+ else
30
+ gem "sqlite3"
31
+ end
data/Rakefile CHANGED
@@ -1,23 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
- require 'rake/testtask'
6
- require 'rubocop/rake_task'
3
+ require "bundler/gem_tasks"
7
4
 
8
- desc 'Run specs'
9
- RSpec::Core::RakeTask.new(:spec) do |t|
10
- t.pattern = Dir.glob('spec/**/*_spec.rb')
11
- end
5
+ require "rspec/core/rake_task"
6
+ RSpec::Core::RakeTask.new
12
7
 
13
- desc 'Run tests'
14
- Rake::TestTask.new(:test) do |t|
15
- t.libs << 'test'
16
- t.libs << 'lib'
17
- t.pattern = 'test/**/*_test.rb'
18
- end
8
+ require "minitest/test_task"
9
+ Minitest::TestTask.create
19
10
 
20
- desc 'Run lint'
21
- RuboCop::RakeTask.new
11
+ # standard rake task
12
+ require "standard/rake"
22
13
 
23
- task default: %i[spec test rubocop]
14
+ task default: %i[spec test standard]
@@ -37,7 +37,7 @@ module WithModel
37
37
  end
38
38
 
39
39
  def lookup_list
40
- @const_name.to_s.split('::')
40
+ @const_name.to_s.split("::")
41
41
  end
42
42
 
43
43
  def basename
@@ -0,0 +1,87 @@
1
+ require "active_support/descendants_tracker"
2
+
3
+ module WithModel
4
+ # Based on https://github.com/rails/rails/blob/491afff27e2dd3d5f301b478b9a43d3c31709af8/activesupport/lib/active_support/descendants_tracker.rb
5
+ module DescendantsTracker
6
+ if RUBY_ENGINE == "ruby"
7
+ # On MRI `ObjectSpace::WeakMap` keys are weak references.
8
+ # So we can simply use WeakMap as a `Set`.
9
+ class WeakSet < ObjectSpace::WeakMap # :nodoc:
10
+ alias_method :to_a, :keys
11
+
12
+ def <<(object)
13
+ self[object] = true
14
+ end
15
+ end
16
+ else
17
+ # On TruffleRuby `ObjectSpace::WeakMap` keys are strong references.
18
+ # So we use `object_id` as a key and the actual object as a value.
19
+ #
20
+ # JRuby for now doesn't have Class#descendant, but when it will, it will likely
21
+ # have the same WeakMap semantic than Truffle so we future proof this as much as possible.
22
+ class WeakSet # :nodoc:
23
+ def initialize
24
+ @map = ObjectSpace::WeakMap.new
25
+ end
26
+
27
+ def [](object)
28
+ @map.key?(object.object_id)
29
+ end
30
+ alias_method :include?, :[]
31
+
32
+ def []=(object, _present)
33
+ @map[object.object_id] = object
34
+ end
35
+
36
+ def to_a
37
+ @map.values
38
+ end
39
+
40
+ def <<(object)
41
+ self[object] = true
42
+ end
43
+ end
44
+ end
45
+ @excluded_descendants = WeakSet.new
46
+
47
+ class << self
48
+ def clear(classes) # :nodoc:
49
+ classes.each do |klass|
50
+ @excluded_descendants << klass
51
+ klass.descendants.each do |descendant|
52
+ @excluded_descendants << descendant
53
+ end
54
+ end
55
+ end
56
+
57
+ def reject!(classes) # :nodoc:
58
+ if @excluded_descendants
59
+ classes.reject! { |d| @excluded_descendants.include?(d) }
60
+ end
61
+ classes
62
+ end
63
+ end
64
+
65
+ module DestroyedClassesFiltering
66
+ def subclasses
67
+ WithModel::DescendantsTracker.reject!(super)
68
+ end
69
+
70
+ def descendants
71
+ WithModel::DescendantsTracker.reject!(super)
72
+ end
73
+ end
74
+ end
75
+ end
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
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record'
4
- require 'active_support/core_ext/string/inflections'
5
- require 'English'
6
- require 'with_model/constant_stubber'
7
- require 'with_model/methods'
8
- require 'with_model/table'
3
+ require "logger"
4
+ require "active_record"
5
+ require "active_support/core_ext/string/inflections"
6
+ require "English"
7
+ require "with_model/constant_stubber"
8
+ require "with_model/descendants_tracker"
9
+ require "with_model/methods"
10
+ require "with_model/table"
9
11
 
10
12
  module WithModel
11
13
  # In general, direct use of this class should be avoided. Instead use
@@ -38,6 +40,7 @@ module WithModel
38
40
  cleanup_descendants_tracking
39
41
  reset_dependencies_cache
40
42
  table.destroy
43
+ WithModel::DescendantsTracker.clear([@model])
41
44
  @model = nil
42
45
  end
43
46
 
@@ -54,15 +57,8 @@ module WithModel
54
57
  end
55
58
 
56
59
  def cleanup_descendants_tracking
57
- if defined?(ActiveSupport::DescendantsTracker)
58
- if ActiveSupport::VERSION::MAJOR >= 7
59
- ActiveSupport::DescendantsTracker.clear([@model])
60
- else
61
- ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants).delete(ActiveRecord::Base)
62
- end
63
- elsif @model.superclass.respond_to?(:direct_descendants)
64
- @model.superclass.direct_descendants.delete(@model)
65
- end
60
+ ActiveSupport::DescendantsTracker.clear([@model]) \
61
+ unless ActiveSupport::DescendantsTracker.clear_disabled
66
62
  end
67
63
 
68
64
  def reset_dependencies_cache
@@ -76,7 +72,7 @@ module WithModel
76
72
  end
77
73
 
78
74
  def table
79
- @table ||= Table.new table_name, @table_options, &@table_block
75
+ @table ||= Table.new table_name, @table_options, connection: @superclass.connection, &@table_block
80
76
  end
81
77
 
82
78
  def table_name
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record'
3
+ require "active_record"
4
4
 
5
5
  module WithModel
6
6
  # In general, direct use of this class should be avoided. Instead use
@@ -8,12 +8,14 @@ module WithModel
8
8
  class Table
9
9
  # @param [Symbol] name The name of the table to create.
10
10
  # @param options Passed to ActiveRecord `create_table`.
11
+ # @param connection The connection to use for creating the table.
11
12
  # @param block Passed to ActiveRecord `create_table`.
12
13
  # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table
13
- def initialize(name, options = {}, &block)
14
+ def initialize(name, options = {}, connection: ActiveRecord::Base.connection, &block)
14
15
  @name = name.freeze
15
16
  @options = options.freeze
16
17
  @block = block
18
+ @connection = connection
17
19
  end
18
20
 
19
21
  # Creates the table with the initialized options. Drops the table if
@@ -29,6 +31,8 @@ module WithModel
29
31
 
30
32
  private
31
33
 
34
+ attr_reader :connection
35
+
32
36
  def exists?
33
37
  if connection.respond_to?(:data_source_exists?)
34
38
  connection.data_source_exists?(@name)
@@ -36,9 +40,5 @@ module WithModel
36
40
  connection.table_exists?(@name)
37
41
  end
38
42
  end
39
-
40
- def connection
41
- ActiveRecord::Base.connection
42
- end
43
43
  end
44
44
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WithModel
4
- VERSION = '2.1.7'
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/with_model.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'with_model/model'
4
- require 'with_model/model/dsl'
5
- require 'with_model/table'
6
- require 'with_model/version'
3
+ require "with_model/model"
4
+ require "with_model/model/dsl"
5
+ require "with_model/table"
6
+ require "with_model/version"
7
7
 
8
8
  module WithModel
9
9
  class MiniTestLifeCycle < Module
@@ -61,7 +61,7 @@ module WithModel
61
61
  # @param [Object] object The new model object instance to create
62
62
  # @param scope Passed to `before`/`after` in the test context. Rspec only.
63
63
  # @param [Symbol] runner The test running, either :rspec or :minitest, defaults to :rspec
64
- def setup_object(object, scope: nil, runner: nil) # rubocop:disable Metrics/MethodLength
64
+ def setup_object(object, scope: nil, runner: nil)
65
65
  case runner || WithModel.runner
66
66
  when :rspec
67
67
  before(*scope) do
@@ -76,7 +76,7 @@ module WithModel
76
76
  include MiniTestLifeCycle.call(object)
77
77
  end
78
78
  else
79
- raise ArgumentError, 'Unsupported test runner set, expected :rspec or :minitest'
79
+ raise ArgumentError, "Unsupported test runner set, expected :rspec or :minitest"
80
80
  end
81
81
  end
82
82
  end
@@ -1,19 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
- describe 'ActiveRecord behaviors' do
6
- describe 'a temporary ActiveRecord model created with with_model' do
7
- context 'that has a named scope' do
5
+ describe "ActiveRecord behaviors" do
6
+ describe "a temporary ActiveRecord model created with with_model" do
7
+ context "that has a named scope" do
8
8
  before do
9
9
  @regular_model = Class.new ActiveRecord::Base do
10
- self.table_name = 'regular_models'
11
- scope :title_is_foo, -> { where(title: 'foo') }
10
+ self.table_name = "regular_models"
11
+ scope :title_is_foo, -> { where(title: "foo") }
12
12
  end
13
13
 
14
14
  @regular_model.connection.create_table(@regular_model.table_name, force: true) do |t|
15
- t.string 'title'
16
- t.text 'content'
15
+ t.string "title"
16
+ t.text "content"
17
17
  t.timestamps null: false
18
18
  end
19
19
  end
@@ -24,37 +24,37 @@ describe 'ActiveRecord behaviors' do
24
24
 
25
25
  with_model :BlogPost do
26
26
  table do |t|
27
- t.string 'title'
28
- t.text 'content'
27
+ t.string "title"
28
+ t.text "content"
29
29
  t.timestamps null: false
30
30
  end
31
31
 
32
32
  model do
33
- scope :title_is_foo, -> { where(title: 'foo') }
33
+ scope :title_is_foo, -> { where(title: "foo") }
34
34
  end
35
35
  end
36
36
 
37
- describe 'the named scope' do
38
- it 'works like a regular named scope' do
39
- included = @regular_model.create!(title: 'foo', content: 'Include me!')
40
- @regular_model.create!(title: 'bar', content: 'Include me!')
37
+ describe "the named scope" do
38
+ it "works like a regular named scope" do
39
+ included = @regular_model.create!(title: "foo", content: "Include me!")
40
+ @regular_model.create!(title: "bar", content: "Include me!")
41
41
 
42
42
  expect(@regular_model.title_is_foo).to eq [included]
43
43
 
44
- included = BlogPost.create!(title: 'foo', content: 'Include me!')
45
- BlogPost.create!(title: 'bar', content: 'Include me!')
44
+ included = BlogPost.create!(title: "foo", content: "Include me!")
45
+ BlogPost.create!(title: "bar", content: "Include me!")
46
46
 
47
47
  expect(BlogPost.title_is_foo).to eq [included]
48
48
  end
49
49
  end
50
50
  end
51
51
 
52
- context 'that has a polymorphic belongs_to' do
52
+ context "that has a polymorphic belongs_to" do
53
53
  before do
54
54
  animal = Class.new ActiveRecord::Base do
55
55
  has_many :tea_cups, as: :pet
56
56
  end
57
- stub_const 'Animal', animal
57
+ stub_const "Animal", animal
58
58
  end
59
59
 
60
60
  with_model :TeaCup do
@@ -74,24 +74,24 @@ describe 'ActiveRecord behaviors' do
74
74
  end
75
75
  end
76
76
 
77
- describe 'the polymorphic belongs_to' do
78
- it 'works like a regular polymorphic belongs_to' do
77
+ describe "the polymorphic belongs_to" do
78
+ it "works like a regular polymorphic belongs_to" do
79
79
  animal = Animal.create!
80
80
  stuffed_animal = StuffedAnimal.create!
81
81
 
82
82
  tea_cup_for_animal = TeaCup.create!(pet: animal)
83
- expect(tea_cup_for_animal.pet_type).to eq 'Animal'
83
+ expect(tea_cup_for_animal.pet_type).to eq "Animal"
84
84
  expect(animal.tea_cups).to include(tea_cup_for_animal)
85
85
 
86
86
  tea_cup_for_stuffed_animal = TeaCup.create!(pet: stuffed_animal)
87
- expect(tea_cup_for_stuffed_animal.pet_type).to eq 'StuffedAnimal'
87
+ expect(tea_cup_for_stuffed_animal.pet_type).to eq "StuffedAnimal"
88
88
  expect(stuffed_animal.tea_cups).to include(tea_cup_for_stuffed_animal)
89
89
  end
90
90
  end
91
91
  end
92
92
  end
93
93
 
94
- context 'with an association' do
94
+ context "with an association" do
95
95
  with_model :Province do
96
96
  table do |t|
97
97
  t.belongs_to :country
@@ -103,13 +103,13 @@ describe 'ActiveRecord behaviors' do
103
103
 
104
104
  with_model :Country
105
105
 
106
- context 'in earlier examples' do
107
- it 'works as normal' do
106
+ context "in earlier examples" do
107
+ it "works as normal" do
108
108
  expect { Province.create!(country: Country.create!) }.not_to raise_error
109
109
  end
110
110
  end
111
111
 
112
- context 'in later examples' do
112
+ context "in later examples" do
113
113
  it "does not hold a reference to earlier example groups' classes" do
114
114
  expect(Province.reflect_on_association(:country).klass).to eq Country
115
115
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  module WithModel
6
6
  describe ConstantStubber do
7
- it 'allows calling unstub_const multiple times' do
8
- stubber = described_class.new('Foo')
7
+ it "allows calling unstub_const multiple times" do
8
+ stubber = described_class.new("Foo")
9
9
  stubber.stub_const(1)
10
10
  expect { 2.times { stubber.unstub_const } }.not_to raise_error
11
11
  end
12
12
 
13
- it 'allows calling unstub_const without stub_const' do
14
- stubber = described_class.new('Foo')
13
+ it "allows calling unstub_const without stub_const" do
14
+ stubber = described_class.new("Foo")
15
15
  expect { stubber.unstub_const }.not_to raise_error
16
16
  end
17
17
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Descendants tracking" do
6
+ with_model :BlogPost do
7
+ model do
8
+ def self.inspect
9
+ "BlogPost class #{object_id}"
10
+ end
11
+ end
12
+ end
13
+
14
+ def blog_post_classes
15
+ ActiveRecord::Base.descendants.select do |c|
16
+ c.table_name == BlogPost.table_name
17
+ end
18
+ end
19
+
20
+ shared_examples "clearing descendants between test runs" do
21
+ it "includes the correct model class in descendants on the first test run" do
22
+ expect(blog_post_classes).to eq [BlogPost]
23
+ end
24
+
25
+ it "includes the correct model class in descendants on the second test run" do
26
+ expect(blog_post_classes).to eq [BlogPost]
27
+ end
28
+ end
29
+
30
+ context "with ActiveSupport::DescendantsTracker (cache_classes: true)" do
31
+ before do
32
+ expect(ActiveSupport::DescendantsTracker.clear_disabled).to be_falsey
33
+ expect { ActiveSupport::DescendantsTracker.clear([]) }.not_to raise_exception
34
+ end
35
+
36
+ include_examples "clearing descendants between test runs"
37
+ end
38
+
39
+ context "without ActiveSupport::DescendantsTracker (cache_classes: false)" do
40
+ before do
41
+ ActiveSupport::DescendantsTracker.disable_clear!
42
+ expect(ActiveSupport::DescendantsTracker.clear_disabled).to be_truthy
43
+ expect { ActiveSupport::DescendantsTracker.clear([]) }.to raise_exception(RuntimeError)
44
+ end
45
+
46
+ include_examples "clearing descendants between test runs"
47
+ end
48
+ end