with_model 2.1.7 → 2.3.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 +4 -4
- data/.github/dependabot.yml +22 -6
- data/.github/workflows/ci.yml +67 -21
- data/.rspec +1 -1
- data/CHANGELOG.md +29 -0
- data/CONTRIBUTING.md +83 -0
- data/Gemfile +16 -23
- data/LICENSE +1 -1
- data/README.md +116 -22
- data/Rakefile +8 -17
- data/lib/with_model/constant_stubber.rb +1 -1
- data/lib/with_model/descendants_tracker.rb +87 -0
- data/lib/with_model/invalid_superclass.rb +15 -0
- data/lib/with_model/missing_superclass.rb +14 -0
- data/lib/with_model/model/dsl.rb +5 -2
- data/lib/with_model/model.rb +97 -21
- data/lib/with_model/null_table.rb +98 -0
- data/lib/with_model/table.rb +18 -6
- data/lib/with_model/version.rb +1 -1
- data/lib/with_model.rb +45 -7
- data/spec/active_record_behaviors_spec.rb +31 -28
- data/spec/constant_stubber_spec.rb +5 -5
- data/spec/descendants_tracking_spec.rb +49 -0
- data/spec/readme_spec.rb +82 -33
- data/spec/spec_helper.rb +8 -18
- data/spec/table_false_spec.rb +516 -0
- data/spec/with_model_spec.rb +138 -124
- data/test/declaration_order_test.rb +61 -0
- data/test/spec_dsl_test.rb +48 -0
- data/test/sti_test.rb +68 -0
- data/test/test_helper.rb +9 -18
- data/test/with_model_test.rb +9 -9
- data/with_model.gemspec +15 -15
- metadata +16 -12
- data/.rubocop.yml +0 -33
- data/.rubocop_todo.yml +0 -78
- data/spec/.rubocop.yml +0 -5
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "spec_helper"
|
|
4
4
|
|
|
5
|
-
describe
|
|
6
|
-
describe
|
|
7
|
-
context
|
|
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 =
|
|
11
|
-
scope :title_is_foo, -> { where(title:
|
|
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
|
|
16
|
-
t.text
|
|
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
|
|
28
|
-
t.text
|
|
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:
|
|
33
|
+
scope :title_is_foo, -> { where(title: "foo") }
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
describe
|
|
38
|
-
it
|
|
39
|
-
included = @regular_model.create!(title:
|
|
40
|
-
@regular_model.create!(title:
|
|
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:
|
|
45
|
-
BlogPost.create!(title:
|
|
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
|
|
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
|
|
57
|
+
stub_const "Animal", animal
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
with_model :TeaCup do
|
|
@@ -69,29 +69,30 @@ describe 'ActiveRecord behaviors' do
|
|
|
69
69
|
with_table :animals
|
|
70
70
|
|
|
71
71
|
with_model :StuffedAnimal do
|
|
72
|
+
table
|
|
72
73
|
model do
|
|
73
74
|
has_many :tea_cups, as: :pet
|
|
74
75
|
end
|
|
75
76
|
end
|
|
76
77
|
|
|
77
|
-
describe
|
|
78
|
-
it
|
|
78
|
+
describe "the polymorphic belongs_to" do
|
|
79
|
+
it "works like a regular polymorphic belongs_to" do
|
|
79
80
|
animal = Animal.create!
|
|
80
81
|
stuffed_animal = StuffedAnimal.create!
|
|
81
82
|
|
|
82
83
|
tea_cup_for_animal = TeaCup.create!(pet: animal)
|
|
83
|
-
expect(tea_cup_for_animal.pet_type).to eq
|
|
84
|
+
expect(tea_cup_for_animal.pet_type).to eq "Animal"
|
|
84
85
|
expect(animal.tea_cups).to include(tea_cup_for_animal)
|
|
85
86
|
|
|
86
87
|
tea_cup_for_stuffed_animal = TeaCup.create!(pet: stuffed_animal)
|
|
87
|
-
expect(tea_cup_for_stuffed_animal.pet_type).to eq
|
|
88
|
+
expect(tea_cup_for_stuffed_animal.pet_type).to eq "StuffedAnimal"
|
|
88
89
|
expect(stuffed_animal.tea_cups).to include(tea_cup_for_stuffed_animal)
|
|
89
90
|
end
|
|
90
91
|
end
|
|
91
92
|
end
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
context
|
|
95
|
+
context "with an association" do
|
|
95
96
|
with_model :Province do
|
|
96
97
|
table do |t|
|
|
97
98
|
t.belongs_to :country
|
|
@@ -101,15 +102,17 @@ describe 'ActiveRecord behaviors' do
|
|
|
101
102
|
end
|
|
102
103
|
end
|
|
103
104
|
|
|
104
|
-
with_model :Country
|
|
105
|
+
with_model :Country do
|
|
106
|
+
table
|
|
107
|
+
end
|
|
105
108
|
|
|
106
|
-
context
|
|
107
|
-
it
|
|
109
|
+
context "in earlier examples" do
|
|
110
|
+
it "works as normal" do
|
|
108
111
|
expect { Province.create!(country: Country.create!) }.not_to raise_error
|
|
109
112
|
end
|
|
110
113
|
end
|
|
111
114
|
|
|
112
|
-
context
|
|
115
|
+
context "in later examples" do
|
|
113
116
|
it "does not hold a reference to earlier example groups' classes" do
|
|
114
117
|
expect(Province.reflect_on_association(:country).klass).to eq Country
|
|
115
118
|
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "spec_helper"
|
|
4
4
|
|
|
5
5
|
module WithModel
|
|
6
6
|
describe ConstantStubber do
|
|
7
|
-
it
|
|
8
|
-
stubber = described_class.new(
|
|
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
|
|
14
|
-
stubber = described_class.new(
|
|
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,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
describe "Descendants tracking" do
|
|
6
|
+
with_model :BlogPost do
|
|
7
|
+
table
|
|
8
|
+
model do
|
|
9
|
+
def self.inspect
|
|
10
|
+
"BlogPost class #{object_id}"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def blog_post_classes
|
|
16
|
+
ActiveRecord::Base.descendants.select do |c|
|
|
17
|
+
c.table_name == BlogPost.table_name
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
shared_examples "clearing descendants between test runs" do
|
|
22
|
+
it "includes the correct model class in descendants on the first test run" do
|
|
23
|
+
expect(blog_post_classes).to eq [BlogPost]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "includes the correct model class in descendants on the second test run" do
|
|
27
|
+
expect(blog_post_classes).to eq [BlogPost]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "with ActiveSupport::DescendantsTracker (cache_classes: true)" do
|
|
32
|
+
before do
|
|
33
|
+
expect(ActiveSupport::DescendantsTracker.clear_disabled).to be_falsey
|
|
34
|
+
expect { ActiveSupport::DescendantsTracker.clear([]) }.not_to raise_exception
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
include_examples "clearing descendants between test runs"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "without ActiveSupport::DescendantsTracker (cache_classes: false)" do
|
|
41
|
+
before do
|
|
42
|
+
ActiveSupport::DescendantsTracker.disable_clear!
|
|
43
|
+
expect(ActiveSupport::DescendantsTracker.clear_disabled).to be_truthy
|
|
44
|
+
expect { ActiveSupport::DescendantsTracker.clear([]) }.to raise_exception(RuntimeError)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
include_examples "clearing descendants between test runs"
|
|
48
|
+
end
|
|
49
|
+
end
|
data/spec/readme_spec.rb
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "spec_helper"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
module MyModule; end
|
|
6
|
+
|
|
7
|
+
# A pre-existing model
|
|
8
|
+
class Car < ActiveRecord::Base
|
|
9
|
+
self.abstract_class = true
|
|
10
|
+
end
|
|
9
11
|
|
|
12
|
+
describe "A blog post" do
|
|
10
13
|
with_model :BlogPost do
|
|
11
|
-
# The table block
|
|
14
|
+
# The table block (and an options hash) is passed to Active Record migration’s `create_table`.
|
|
12
15
|
table do |t|
|
|
13
16
|
t.string :title
|
|
14
17
|
t.timestamps null: false
|
|
15
18
|
end
|
|
16
19
|
|
|
17
|
-
# The model block
|
|
20
|
+
# The model block is the Active Record model’s class body.
|
|
18
21
|
model do
|
|
19
22
|
include MyModule
|
|
23
|
+
|
|
20
24
|
has_many :comments
|
|
21
25
|
validates_presence_of :title
|
|
22
26
|
|
|
23
27
|
def self.some_class_method
|
|
24
|
-
|
|
28
|
+
"chunky"
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def some_instance_method
|
|
28
|
-
|
|
32
|
+
"bacon"
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
end
|
|
@@ -43,46 +47,46 @@ describe 'A blog post' do
|
|
|
43
47
|
end
|
|
44
48
|
end
|
|
45
49
|
|
|
46
|
-
it
|
|
50
|
+
it "can be accessed as a constant" do
|
|
47
51
|
expect(BlogPost).to be
|
|
48
52
|
end
|
|
49
53
|
|
|
50
|
-
it
|
|
54
|
+
it "has the module" do
|
|
51
55
|
expect(BlogPost.include?(MyModule)).to be true
|
|
52
56
|
end
|
|
53
57
|
|
|
54
|
-
it
|
|
55
|
-
expect(BlogPost.some_class_method).to eq
|
|
58
|
+
it "has the class method" do
|
|
59
|
+
expect(BlogPost.some_class_method).to eq "chunky"
|
|
56
60
|
end
|
|
57
61
|
|
|
58
|
-
it
|
|
59
|
-
expect(BlogPost.new.some_instance_method).to eq
|
|
62
|
+
it "has the instance method" do
|
|
63
|
+
expect(BlogPost.new.some_instance_method).to eq "bacon"
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
it
|
|
66
|
+
it "can do all the things a regular model can" do
|
|
63
67
|
record = BlogPost.new
|
|
64
68
|
expect(record).not_to be_valid
|
|
65
|
-
record.title =
|
|
69
|
+
record.title = "foo"
|
|
66
70
|
expect(record).to be_valid
|
|
67
71
|
expect(record.save).to be true
|
|
68
72
|
expect(record.reload).to eq record
|
|
69
|
-
record.comments.create!(text:
|
|
73
|
+
record.comments.create!(text: "Lorem ipsum")
|
|
70
74
|
expect(record.comments.count).to eq 1
|
|
71
75
|
end
|
|
72
76
|
|
|
73
|
-
# with_model classes can have inheritance.
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
# with_model classes can have inheritance. Car is abstract, so it has no table
|
|
78
|
+
# and Ford gets one of its own. To inherit a concrete superclass's table
|
|
79
|
+
# instead, see "Single table inheritance" below.
|
|
80
|
+
with_model :Ford, superclass: Car do
|
|
81
|
+
table
|
|
76
82
|
end
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
it 'has a specified superclass' do
|
|
81
|
-
expect(Ford < Car).to be true
|
|
84
|
+
it "has a specified superclass" do
|
|
85
|
+
expect(Ford.new).to be_a(Car)
|
|
82
86
|
end
|
|
83
87
|
end
|
|
84
88
|
|
|
85
|
-
describe
|
|
89
|
+
describe "with_model can be run within RSpec :all hook" do
|
|
86
90
|
with_model :BlogPost, scope: :all do
|
|
87
91
|
table do |t|
|
|
88
92
|
t.string :title
|
|
@@ -93,26 +97,71 @@ describe 'with_model can be run within RSpec :all hook' do
|
|
|
93
97
|
BlogPost.create # without scope: :all these will fail
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
it
|
|
100
|
+
it "has been initialized within before(:all)" do
|
|
97
101
|
expect(BlogPost.count).to eq 1
|
|
98
102
|
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
|
-
describe
|
|
102
|
-
it
|
|
105
|
+
describe "another example group" do
|
|
106
|
+
it "does not have the constant anymore" do
|
|
103
107
|
expect(defined?(BlogPost)).to be_falsy
|
|
104
108
|
end
|
|
105
109
|
end
|
|
106
110
|
|
|
107
|
-
describe
|
|
111
|
+
describe "with table options" do
|
|
108
112
|
with_model :WithOptions do
|
|
109
113
|
table id: false do |t|
|
|
110
|
-
t.string
|
|
114
|
+
t.string "foo"
|
|
111
115
|
t.timestamps null: false
|
|
112
116
|
end
|
|
113
117
|
end
|
|
114
118
|
|
|
115
|
-
it
|
|
116
|
-
expect(WithOptions.columns.map(&:name)).not_to include(
|
|
119
|
+
it "respects the additional options" do
|
|
120
|
+
expect(WithOptions.columns.map(&:name)).not_to include("id")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe "with_model supports Single Table Inheritance" do
|
|
125
|
+
with_model :Sandwich do
|
|
126
|
+
table do |t|
|
|
127
|
+
t.string "type"
|
|
128
|
+
t.string "bread"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
with_model :ChunkyBacon, superclass: :Sandwich do
|
|
133
|
+
table(false)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "shares the superclass's table" do
|
|
137
|
+
expect(ChunkyBacon.table_name).to eq Sandwich.table_name
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "stores its own type" do
|
|
141
|
+
sandwich = ChunkyBacon.create!(bread: "rye")
|
|
142
|
+
|
|
143
|
+
expect(sandwich.reload.type).to eq "ChunkyBacon"
|
|
144
|
+
expect(Sandwich.first).to be_a ChunkyBacon
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe "with_model supports foreign keys" do
|
|
149
|
+
with_model :Author do
|
|
150
|
+
table
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
with_model :Book do
|
|
154
|
+
table do |t|
|
|
155
|
+
t.references :author, foreign_key: {to_table: Author.table_name}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
model do
|
|
159
|
+
belongs_to :author
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "has a foreign key" do
|
|
164
|
+
expect { Book.create!(author_id: 0) }
|
|
165
|
+
.to raise_error ActiveRecord::InvalidForeignKey
|
|
117
166
|
end
|
|
118
167
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require 'i18n/backend'
|
|
6
|
-
require 'i18n/backend/simple'
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
require 'simplecov'
|
|
10
|
-
SimpleCov.start
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "with_model"
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
require 'with_model'
|
|
6
|
+
WithModel.deprecator.behavior = :raise
|
|
14
7
|
|
|
15
8
|
RSpec.configure do |config|
|
|
16
9
|
config.extend WithModel
|
|
@@ -26,24 +19,21 @@ RSpec.configure do |config|
|
|
|
26
19
|
end
|
|
27
20
|
end
|
|
28
21
|
|
|
29
|
-
is_jruby = RUBY_PLATFORM == 'java'
|
|
30
|
-
adapter = is_jruby ? 'jdbcsqlite3' : 'sqlite3'
|
|
31
|
-
|
|
32
22
|
# WithModel requires ActiveRecord::Base.connection to be established.
|
|
33
23
|
# If ActiveRecord already has a connection, as in a Rails app, this is unnecessary.
|
|
34
|
-
require
|
|
35
|
-
ActiveRecord::Base.establish_connection(adapter:
|
|
24
|
+
require "active_record"
|
|
25
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
|
36
26
|
|
|
37
27
|
I18n.enforce_available_locales = true if defined?(I18n) && I18n.respond_to?(:enforce_available_locales=)
|
|
38
28
|
|
|
39
|
-
if ENV[
|
|
40
|
-
require
|
|
29
|
+
if ENV["LOGGER"]
|
|
30
|
+
require "logger"
|
|
41
31
|
ActiveRecord::Base.logger = Logger.new($stdout)
|
|
42
32
|
end
|
|
43
33
|
|
|
44
34
|
module SpecHelper
|
|
45
35
|
module RailsTestCompatibility
|
|
46
|
-
require
|
|
36
|
+
require "minitest"
|
|
47
37
|
include Minitest::Assertions
|
|
48
38
|
|
|
49
39
|
def assertions
|