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
data/spec/with_model_spec.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "active_model"
|
|
4
|
+
require "spec_helper"
|
|
5
5
|
|
|
6
|
-
shared_examples_for
|
|
7
|
-
require
|
|
6
|
+
shared_examples_for "ActiveModel" do
|
|
7
|
+
require "active_model/lint"
|
|
8
8
|
include SpecHelper::RailsTestCompatibility
|
|
9
9
|
include ActiveModel::Lint::Tests
|
|
10
10
|
|
|
@@ -12,26 +12,24 @@ shared_examples_for 'ActiveModel' do
|
|
|
12
12
|
active_model_lint_tests = active_model_methods.map(&:to_s).grep(/^test/)
|
|
13
13
|
|
|
14
14
|
active_model_lint_tests.each do |method_name|
|
|
15
|
-
friendly_name = method_name.tr(
|
|
15
|
+
friendly_name = method_name.tr("_", " ")
|
|
16
16
|
|
|
17
|
-
# rubocop:disable RSpec/NoExpectationExample
|
|
18
17
|
example friendly_name do
|
|
19
18
|
public_send method_name.to_sym
|
|
20
19
|
end
|
|
21
|
-
# rubocop:enable RSpec/NoExpectationExample
|
|
22
20
|
end
|
|
23
21
|
|
|
24
22
|
before { @model = subject }
|
|
25
23
|
end
|
|
26
24
|
|
|
27
|
-
describe
|
|
25
|
+
describe "a temporary ActiveRecord model created with with_model" do
|
|
28
26
|
non_shadowing_example_ran = false
|
|
29
27
|
|
|
30
28
|
describe "which doesn't shadow an existing class" do
|
|
31
29
|
with_model :BlogPost do
|
|
32
30
|
table do |t|
|
|
33
|
-
t.string
|
|
34
|
-
t.text
|
|
31
|
+
t.string "title"
|
|
32
|
+
t.text "content"
|
|
35
33
|
t.timestamps null: false
|
|
36
34
|
end
|
|
37
35
|
|
|
@@ -46,13 +44,13 @@ describe 'a temporary ActiveRecord model created with with_model' do
|
|
|
46
44
|
non_shadowing_example_ran = true
|
|
47
45
|
end
|
|
48
46
|
|
|
49
|
-
it
|
|
50
|
-
record = BlogPost.create!(title:
|
|
47
|
+
it "acts like a normal ActiveRecord model" do
|
|
48
|
+
record = BlogPost.create!(title: "New blog post", content: "Hello, world!")
|
|
51
49
|
|
|
52
50
|
record.reload
|
|
53
51
|
|
|
54
|
-
expect(record.title).to eq
|
|
55
|
-
expect(record.content).to eq
|
|
52
|
+
expect(record.title).to eq "New blog post"
|
|
53
|
+
expect(record.content).to eq "Hello, world!"
|
|
56
54
|
expect(record.updated_at).to be_present
|
|
57
55
|
|
|
58
56
|
record.destroy
|
|
@@ -60,232 +58,257 @@ describe 'a temporary ActiveRecord model created with with_model' do
|
|
|
60
58
|
expect { record.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
describe
|
|
61
|
+
describe "the class" do
|
|
64
62
|
subject { BlogPost.new }
|
|
65
63
|
|
|
66
|
-
it_behaves_like
|
|
64
|
+
it_behaves_like "ActiveModel"
|
|
67
65
|
end
|
|
68
66
|
|
|
69
|
-
it
|
|
70
|
-
expect(BlogPost.new(title:
|
|
67
|
+
it "has the methods defined in its model block" do
|
|
68
|
+
expect(BlogPost.new(title: "New blog post").fancy_title).to eq "Title: New blog post"
|
|
71
69
|
end
|
|
72
70
|
|
|
73
|
-
it
|
|
71
|
+
it "defines a constant" do
|
|
74
72
|
expect(BlogPost).to be_a(Class)
|
|
75
73
|
end
|
|
76
74
|
|
|
77
|
-
describe
|
|
78
|
-
it
|
|
75
|
+
describe ".with_model?" do
|
|
76
|
+
it "returns true" do
|
|
79
77
|
expect(BlogPost.with_model?).to be true
|
|
80
78
|
end
|
|
81
79
|
end
|
|
82
80
|
|
|
83
|
-
it
|
|
81
|
+
it "is its own base_class" do
|
|
84
82
|
expect(BlogPost.base_class).to eq BlogPost
|
|
85
83
|
end
|
|
86
84
|
end
|
|
87
85
|
|
|
88
|
-
context
|
|
89
|
-
it
|
|
86
|
+
context "after an example which uses with_model without shadowing an existing constant" do
|
|
87
|
+
it "returns the constant to its undefined state" do
|
|
90
88
|
expect(non_shadowing_example_ran).to be true
|
|
91
89
|
expect(defined?(BlogPost)).to be_falsy
|
|
92
90
|
end
|
|
93
91
|
end
|
|
94
92
|
|
|
95
|
-
describe
|
|
96
|
-
before { stub_const(
|
|
93
|
+
describe "constant restoration" do
|
|
94
|
+
before { stub_const("MyConst", 1) }
|
|
97
95
|
|
|
98
96
|
shadowing_example_ran = false
|
|
99
97
|
|
|
100
|
-
context
|
|
101
|
-
with_model :MyConst
|
|
98
|
+
context "with the with_model block" do
|
|
99
|
+
with_model :MyConst do
|
|
100
|
+
table
|
|
101
|
+
end
|
|
102
102
|
|
|
103
103
|
after do
|
|
104
104
|
shadowing_example_ran = true
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
it
|
|
107
|
+
it "shadows that constant" do
|
|
108
108
|
expect(MyConst).to be_a(Class)
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
context
|
|
113
|
-
it
|
|
112
|
+
context "without the with_model block" do
|
|
113
|
+
it "returns the constant to its original value" do
|
|
114
114
|
expect(shadowing_example_ran).to be true
|
|
115
115
|
expect(MyConst).to eq 1
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
describe
|
|
121
|
-
with_model :BlogPosts
|
|
120
|
+
describe "with a plural name" do
|
|
121
|
+
with_model :BlogPosts do
|
|
122
|
+
table
|
|
123
|
+
end
|
|
122
124
|
|
|
123
|
-
it
|
|
125
|
+
it "does not singularize the constant name" do
|
|
124
126
|
expect(BlogPosts).to be
|
|
125
127
|
expect(-> { BlogPost }).to raise_error(NameError)
|
|
126
128
|
end
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
describe
|
|
130
|
-
with_model :BlogPost
|
|
131
|
+
describe "with a name containing capital letters" do
|
|
132
|
+
with_model :BlogPost do
|
|
133
|
+
table
|
|
134
|
+
end
|
|
131
135
|
|
|
132
|
-
it
|
|
136
|
+
it "tableizes the table name" do
|
|
133
137
|
expect(BlogPost.table_name).to match(/_blog_posts_/)
|
|
134
138
|
expect(BlogPost.table_name).to eq BlogPost.table_name.downcase
|
|
135
139
|
end
|
|
136
140
|
end
|
|
137
141
|
|
|
138
|
-
describe
|
|
139
|
-
with_model :blog_post
|
|
142
|
+
describe "with a name with underscores" do
|
|
143
|
+
with_model :blog_post do
|
|
144
|
+
table
|
|
145
|
+
end
|
|
140
146
|
|
|
141
|
-
it
|
|
147
|
+
it "constantizes the name" do
|
|
142
148
|
expect(BlogPost).to be
|
|
143
149
|
end
|
|
144
150
|
|
|
145
|
-
it
|
|
151
|
+
it "tableizes the table name" do
|
|
146
152
|
expect(BlogPost.table_name).to match(/_blog_posts_/)
|
|
147
153
|
expect(BlogPost.table_name).to eq BlogPost.table_name.downcase
|
|
148
154
|
end
|
|
149
155
|
end
|
|
150
156
|
|
|
151
|
-
describe
|
|
152
|
-
before { stub_const(
|
|
157
|
+
describe "with a name which is namespaced" do
|
|
158
|
+
before { stub_const("Stuff", Module.new) }
|
|
153
159
|
|
|
154
|
-
with_model :
|
|
160
|
+
with_model :"Stuff::BlogPost" do
|
|
161
|
+
table
|
|
162
|
+
end
|
|
155
163
|
|
|
156
|
-
it
|
|
164
|
+
it "creates the model in the namespace" do
|
|
157
165
|
expect(defined?(BlogPost)).to be_falsey
|
|
158
166
|
expect(defined?(Stuff::BlogPost)).to be_truthy
|
|
159
167
|
end
|
|
160
168
|
end
|
|
161
169
|
|
|
162
|
-
describe
|
|
170
|
+
describe "using the constant in the model block" do
|
|
163
171
|
with_model :BlogPost do
|
|
172
|
+
table
|
|
164
173
|
model do
|
|
165
|
-
raise
|
|
174
|
+
raise "I am not myself!" unless self == BlogPost
|
|
166
175
|
end
|
|
167
176
|
end
|
|
168
177
|
|
|
169
|
-
it
|
|
178
|
+
it "is available" do
|
|
170
179
|
expect(BlogPost).to be
|
|
171
180
|
end
|
|
172
181
|
end
|
|
173
182
|
|
|
174
|
-
context
|
|
183
|
+
context "with a mixin" do
|
|
175
184
|
let(:mixin) do
|
|
176
|
-
Module.new {
|
|
185
|
+
Module.new {
|
|
186
|
+
def foo
|
|
187
|
+
end
|
|
188
|
+
}
|
|
177
189
|
end
|
|
178
190
|
|
|
179
|
-
before { stub_const(
|
|
191
|
+
before { stub_const("AMixin", mixin) }
|
|
180
192
|
|
|
181
193
|
with_model :WithAMixin do
|
|
194
|
+
table
|
|
182
195
|
model do
|
|
183
196
|
include AMixin
|
|
184
197
|
end
|
|
185
198
|
end
|
|
186
199
|
|
|
187
|
-
it
|
|
200
|
+
it "has the mixin" do
|
|
188
201
|
expect(-> { WithAMixin.new.foo }).not_to raise_error
|
|
189
202
|
expect(WithAMixin.include?(AMixin)).to be true
|
|
190
203
|
end
|
|
191
204
|
end
|
|
192
205
|
|
|
193
|
-
context
|
|
206
|
+
context "with a mixin that has a class_eval" do
|
|
194
207
|
subject { WithAClassEval.new }
|
|
195
208
|
|
|
196
209
|
let(:mixin) do
|
|
197
210
|
Module.new do
|
|
198
211
|
def self.included(klass)
|
|
199
212
|
klass.class_eval do
|
|
200
|
-
after_save { |object| object.my_method }
|
|
213
|
+
after_save { |object| object.my_method }
|
|
201
214
|
end
|
|
202
215
|
end
|
|
203
216
|
end
|
|
204
217
|
end
|
|
205
218
|
|
|
206
|
-
before { stub_const(
|
|
219
|
+
before { stub_const("AMixin", mixin) }
|
|
207
220
|
|
|
208
221
|
with_model :WithAClassEval do
|
|
222
|
+
table
|
|
209
223
|
model do
|
|
210
224
|
include AMixin
|
|
211
|
-
|
|
225
|
+
|
|
226
|
+
def my_method
|
|
227
|
+
end
|
|
212
228
|
end
|
|
213
229
|
end
|
|
214
230
|
|
|
215
|
-
it
|
|
231
|
+
it "only has one after_save callback" do
|
|
216
232
|
expect(subject).to receive(:my_method).once
|
|
217
233
|
subject.save
|
|
218
234
|
end
|
|
219
235
|
|
|
220
|
-
it
|
|
236
|
+
it "still only has one after_save callback in future tests" do
|
|
221
237
|
expect(subject).to receive(:my_method).once
|
|
222
238
|
subject.save
|
|
223
239
|
end
|
|
224
240
|
end
|
|
225
241
|
|
|
226
|
-
context
|
|
242
|
+
context "with table options" do
|
|
227
243
|
with_model :WithOptions do
|
|
228
244
|
table id: false do |t|
|
|
229
|
-
t.string
|
|
245
|
+
t.string "foo"
|
|
230
246
|
t.timestamps null: false
|
|
231
247
|
end
|
|
232
248
|
end
|
|
233
249
|
|
|
234
|
-
it
|
|
235
|
-
expect(WithOptions.columns.map(&:name)).not_to include(
|
|
250
|
+
it "respects the additional options" do
|
|
251
|
+
expect(WithOptions.columns.map(&:name)).not_to include("id")
|
|
236
252
|
end
|
|
237
253
|
end
|
|
238
254
|
|
|
239
|
-
|
|
240
|
-
|
|
255
|
+
# Omitting `table` is deprecated, but still supported for the rest of 2.x.
|
|
256
|
+
# These three contexts are its only coverage, so they keep omitting it and
|
|
257
|
+
# silence the warning rather than adopting the replacement spelling.
|
|
258
|
+
context "without a block" do
|
|
259
|
+
WithModel.deprecator.silence do
|
|
260
|
+
with_model :BlogPost
|
|
261
|
+
end
|
|
241
262
|
|
|
242
|
-
it
|
|
263
|
+
it "acts like a normal ActiveRecord model" do
|
|
243
264
|
record = BlogPost.create!
|
|
244
265
|
record.reload
|
|
245
266
|
record.destroy
|
|
246
267
|
expect { record.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
|
247
268
|
end
|
|
248
269
|
|
|
249
|
-
describe
|
|
270
|
+
describe "the class" do
|
|
250
271
|
subject { BlogPost.new }
|
|
251
272
|
|
|
252
|
-
it_behaves_like
|
|
273
|
+
it_behaves_like "ActiveModel"
|
|
253
274
|
end
|
|
254
275
|
end
|
|
255
276
|
|
|
256
|
-
context
|
|
257
|
-
|
|
277
|
+
context "with an empty block" do
|
|
278
|
+
WithModel.deprecator.silence do
|
|
279
|
+
with_model(:BlogPost) {}
|
|
280
|
+
end
|
|
258
281
|
|
|
259
|
-
it
|
|
282
|
+
it "acts like a normal ActiveRecord model" do
|
|
260
283
|
record = BlogPost.create!
|
|
261
284
|
record.reload
|
|
262
285
|
record.destroy
|
|
263
286
|
expect { record.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
|
264
287
|
end
|
|
265
288
|
|
|
266
|
-
describe
|
|
289
|
+
describe "the class" do
|
|
267
290
|
subject { BlogPost.new }
|
|
268
291
|
|
|
269
|
-
it_behaves_like
|
|
292
|
+
it_behaves_like "ActiveModel"
|
|
270
293
|
end
|
|
271
294
|
end
|
|
272
295
|
|
|
273
|
-
context
|
|
296
|
+
context "without a model block" do
|
|
274
297
|
with_model :BlogPost do
|
|
275
298
|
table do |t|
|
|
276
|
-
t.string
|
|
277
|
-
t.text
|
|
299
|
+
t.string "title"
|
|
300
|
+
t.text "content"
|
|
278
301
|
t.timestamps null: false
|
|
279
302
|
end
|
|
280
303
|
end
|
|
281
304
|
|
|
282
|
-
it
|
|
283
|
-
record = BlogPost.create!(title:
|
|
305
|
+
it "acts like a normal ActiveRecord model" do
|
|
306
|
+
record = BlogPost.create!(title: "New blog post", content: "Hello, world!")
|
|
284
307
|
|
|
285
308
|
record.reload
|
|
286
309
|
|
|
287
|
-
expect(record.title).to eq
|
|
288
|
-
expect(record.content).to eq
|
|
310
|
+
expect(record.title).to eq "New blog post"
|
|
311
|
+
expect(record.content).to eq "Hello, world!"
|
|
289
312
|
expect(record.updated_at).to be_present
|
|
290
313
|
|
|
291
314
|
record.destroy
|
|
@@ -293,56 +316,34 @@ describe 'a temporary ActiveRecord model created with with_model' do
|
|
|
293
316
|
expect { record.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
|
294
317
|
end
|
|
295
318
|
|
|
296
|
-
describe
|
|
319
|
+
describe "the class" do
|
|
297
320
|
subject { BlogPost.new }
|
|
298
321
|
|
|
299
|
-
it_behaves_like
|
|
322
|
+
it_behaves_like "ActiveModel"
|
|
300
323
|
end
|
|
301
324
|
end
|
|
302
325
|
|
|
303
|
-
context
|
|
304
|
-
|
|
326
|
+
context "without a table or model block" do
|
|
327
|
+
WithModel.deprecator.silence do
|
|
328
|
+
with_model :BlogPost
|
|
329
|
+
end
|
|
305
330
|
|
|
306
|
-
it
|
|
307
|
-
expect(BlogPost.columns.map(&:name)).to eq [
|
|
331
|
+
it "acts like a normal ActiveRecord model" do
|
|
332
|
+
expect(BlogPost.columns.map(&:name)).to eq ["id"]
|
|
308
333
|
record = BlogPost.create!
|
|
309
334
|
record.reload
|
|
310
335
|
record.destroy
|
|
311
336
|
expect { record.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
|
312
337
|
end
|
|
313
338
|
|
|
314
|
-
describe
|
|
339
|
+
describe "the class" do
|
|
315
340
|
subject { BlogPost.new }
|
|
316
341
|
|
|
317
|
-
it_behaves_like
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
context 'with ActiveSupport::DescendantsTracker' do
|
|
322
|
-
with_model :BlogPost do
|
|
323
|
-
model do
|
|
324
|
-
def self.inspect
|
|
325
|
-
"BlogPost class #{object_id}"
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
def blog_post_classes
|
|
331
|
-
ActiveRecord::Base.descendants.select do |c|
|
|
332
|
-
c.table_name == BlogPost.table_name
|
|
333
|
-
end
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
it 'includes the correct model class in descendants on the first test run' do
|
|
337
|
-
expect(blog_post_classes).to eq [BlogPost]
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
it 'includes the correct model class in descendants on the second test run' do
|
|
341
|
-
expect(blog_post_classes).to eq [BlogPost]
|
|
342
|
+
it_behaves_like "ActiveModel"
|
|
342
343
|
end
|
|
343
344
|
end
|
|
344
345
|
|
|
345
|
-
context
|
|
346
|
+
context "with_model can be run within RSpec :all hook" do
|
|
346
347
|
with_model :BlogPost, scope: :all do
|
|
347
348
|
table do |t|
|
|
348
349
|
t.string :title
|
|
@@ -353,42 +354,55 @@ describe 'a temporary ActiveRecord model created with with_model' do
|
|
|
353
354
|
BlogPost.create # without scope: :all these will fail
|
|
354
355
|
end
|
|
355
356
|
|
|
356
|
-
it
|
|
357
|
+
it "has been initialized within before(:all)" do
|
|
357
358
|
expect(BlogPost.count).to eq 1
|
|
358
359
|
end
|
|
359
360
|
end
|
|
360
361
|
|
|
361
362
|
context "with 'superclass' option" do
|
|
362
|
-
class BlogPostParent < ActiveRecord::Base
|
|
363
|
+
class BlogPostParent < ActiveRecord::Base # standard:disable Lint/ConstantDefinitionInBlock
|
|
363
364
|
self.abstract_class = true
|
|
364
365
|
end
|
|
365
366
|
|
|
366
|
-
after(:all) do
|
|
367
|
-
Object.__send__(:remove_const, 'BlogPostParent')
|
|
368
|
-
end
|
|
369
|
-
|
|
370
367
|
with_model :BlogPost, superclass: BlogPostParent do
|
|
371
368
|
table do |t|
|
|
372
|
-
t.string
|
|
369
|
+
t.string "title"
|
|
373
370
|
end
|
|
374
371
|
end
|
|
375
372
|
|
|
376
|
-
describe
|
|
373
|
+
describe "the class" do
|
|
377
374
|
subject { BlogPost.new }
|
|
378
375
|
|
|
379
|
-
it_behaves_like
|
|
376
|
+
it_behaves_like "ActiveModel"
|
|
380
377
|
end
|
|
381
378
|
|
|
382
|
-
it
|
|
379
|
+
it "is a subclass of the supplied superclass" do
|
|
383
380
|
expect(BlogPost < BlogPostParent).to be true
|
|
384
381
|
end
|
|
385
382
|
|
|
386
|
-
it
|
|
383
|
+
it "is its own base_class" do
|
|
387
384
|
expect(BlogPost.base_class).to eq BlogPost
|
|
388
385
|
end
|
|
389
386
|
|
|
390
|
-
it
|
|
387
|
+
it "responds to .with_model? with true" do
|
|
391
388
|
expect(BlogPost.with_model?).to be true
|
|
392
389
|
end
|
|
393
390
|
end
|
|
391
|
+
|
|
392
|
+
context "with 'superclass' that connects to a different database" do
|
|
393
|
+
class ApplicationRecordInDifferentDatabase < ActiveRecord::Base # standard:disable Lint/ConstantDefinitionInBlock
|
|
394
|
+
self.abstract_class = true
|
|
395
|
+
establish_connection(ActiveRecord::Base.connection_pool.db_config.configuration_hash)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
with_model :BlogPost, superclass: ApplicationRecordInDifferentDatabase do
|
|
399
|
+
table do |t|
|
|
400
|
+
t.string "title"
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
it "uses the superclass connection" do
|
|
405
|
+
expect(BlogPost.connection.tables).to include(BlogPost.table_name)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
394
408
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# Models are created in declaration order and destroyed in reverse, so each one
|
|
6
|
+
# can depend on the ones above it for as long as it exists. Both halves of that
|
|
7
|
+
# are tested here.
|
|
8
|
+
#
|
|
9
|
+
# The model block is evaluated when the model is created, so a model that
|
|
10
|
+
# refers to another one by constant can only work if the model it refers to
|
|
11
|
+
# was created first.
|
|
12
|
+
class DeclarationOrderTest < Minitest::Test
|
|
13
|
+
with_model :Author do
|
|
14
|
+
table do |t|
|
|
15
|
+
t.string "name"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
with_model :Book do
|
|
20
|
+
table do |t|
|
|
21
|
+
t.integer "author_id"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
model do
|
|
25
|
+
belongs_to :author, class_name: Author.name
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
with_model :Shelf do
|
|
30
|
+
table do |t|
|
|
31
|
+
t.string "name"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# `to_table` because `foreign_key: true` would infer a table name from the
|
|
36
|
+
# column rather than asking the model, and with_model's table names are
|
|
37
|
+
# generated.
|
|
38
|
+
with_model :Jar do
|
|
39
|
+
table do |t|
|
|
40
|
+
t.references "shelf", foreign_key: {to_table: Shelf.table_name}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_a_model_can_refer_to_one_declared_above_it
|
|
45
|
+
author = Author.create!(name: "Ursula K. Le Guin")
|
|
46
|
+
book = Book.create!(author: author)
|
|
47
|
+
|
|
48
|
+
assert_equal author, book.reload.author
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The assertion for teardown order is that this test finishes cleanly. A
|
|
52
|
+
# referenced table cannot be dropped while a row still points at it, so tearing
|
|
53
|
+
# these down in declaration order raises ActiveRecord::InvalidForeignKey after
|
|
54
|
+
# the body has already passed.
|
|
55
|
+
def test_a_table_can_point_at_one_declared_above_it
|
|
56
|
+
shelf = Shelf.create!(name: "pantry")
|
|
57
|
+
jar = Jar.create!(shelf_id: shelf.id)
|
|
58
|
+
|
|
59
|
+
assert_equal shelf.id, jar.reload.shelf_id
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# The README offers Minitest::Spec as well as Minitest::Test. Minitest::Spec
|
|
6
|
+
# inherits from Minitest::Test, so extending Minitest::Test reaches describe
|
|
7
|
+
# blocks too, and with_model needs no separate wiring for them.
|
|
8
|
+
describe "with_model in a spec-style suite" do
|
|
9
|
+
with_model :Sofa do
|
|
10
|
+
table do |t|
|
|
11
|
+
t.string "type"
|
|
12
|
+
t.string "fabric"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
with_model :Loveseat, superclass: :Sofa do
|
|
17
|
+
table(false)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "creates the models a describe block declares" do
|
|
21
|
+
assert_empty Sofa.all, "another test's rows outlived it"
|
|
22
|
+
|
|
23
|
+
Loveseat.create!(fabric: "tweed")
|
|
24
|
+
|
|
25
|
+
assert_equal Sofa.table_name, Loveseat.table_name
|
|
26
|
+
assert_instance_of Loveseat, Sofa.first
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Every describe is its own subclass of the one around it, so a nested block
|
|
30
|
+
# inherits the models declared above it and can add more of its own.
|
|
31
|
+
describe "and a nested describe" do
|
|
32
|
+
with_model :Ottoman do
|
|
33
|
+
table do |t|
|
|
34
|
+
t.string "name"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "sees the models from both levels" do
|
|
39
|
+
assert_empty Sofa.all, "another test's rows outlived it"
|
|
40
|
+
|
|
41
|
+
Ottoman.create!(name: "pouf")
|
|
42
|
+
Loveseat.create!(fabric: "velvet")
|
|
43
|
+
|
|
44
|
+
assert_equal 1, Ottoman.count
|
|
45
|
+
assert_equal 1, Sofa.count
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|