seed_dump 3.3.1 → 3.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 +4 -4
- data/.github/workflows/release.yml +38 -0
- data/Appraisals +47 -0
- data/Gemfile +23 -7
- data/README.md +53 -19
- data/VERSION +1 -1
- data/find_ruby_compat.sh +237 -0
- data/gemfiles/rails_6.1.gemfile +28 -0
- data/gemfiles/rails_6.1.gemfile.lock +150 -0
- data/gemfiles/rails_7.0.gemfile +28 -0
- data/gemfiles/rails_7.0.gemfile.lock +148 -0
- data/gemfiles/rails_7.1.gemfile +28 -0
- data/gemfiles/rails_7.1.gemfile.lock +161 -0
- data/gemfiles/rails_7.2.gemfile +28 -0
- data/gemfiles/rails_7.2.gemfile.lock +164 -0
- data/gemfiles/rails_8.0.gemfile +28 -0
- data/gemfiles/rails_8.0.gemfile.lock +163 -0
- data/lib/seed_dump/dump_methods/enumeration.rb +11 -3
- data/lib/seed_dump/dump_methods.rb +421 -64
- data/lib/seed_dump/environment.rb +294 -8
- data/seed_dump.gemspec +30 -29
- data/spec/dump_methods_spec.rb +1012 -74
- data/spec/environment_spec.rb +515 -46
- data/spec/factories/another_samples.rb +17 -10
- data/spec/factories/authors.rb +5 -0
- data/spec/factories/base_users.rb +16 -0
- data/spec/factories/books.rb +6 -0
- data/spec/factories/bosses.rb +5 -0
- data/spec/factories/reviews.rb +7 -0
- data/spec/factories/samples.rb +16 -12
- data/spec/factories/yet_another_samples.rb +17 -10
- data/spec/helpers.rb +169 -9
- data/spec/spec_helper.rb +28 -5
- metadata +46 -15
data/spec/environment_spec.rb
CHANGED
|
@@ -2,71 +2,110 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe SeedDump do
|
|
4
4
|
describe '.dump_using_environment' do
|
|
5
|
-
before(:
|
|
6
|
-
create_db
|
|
7
|
-
end
|
|
5
|
+
# Schema creation and model loading are handled in spec_helper's before(:suite).
|
|
8
6
|
|
|
9
7
|
before(:each) do
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
# Clean DB and create a fresh sample before each example
|
|
9
|
+
DatabaseCleaner.start
|
|
12
10
|
FactoryBot.create(:sample)
|
|
13
11
|
end
|
|
14
12
|
|
|
13
|
+
after(:each) do
|
|
14
|
+
# Clean DB after each example
|
|
15
|
+
DatabaseCleaner.clean
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
15
19
|
describe 'APPEND' do
|
|
16
20
|
it "should specify append as true if the APPEND env var is 'true'" do
|
|
17
|
-
SeedDump.
|
|
18
|
-
|
|
21
|
+
expect(SeedDump).to receive(:dump).with(anything, include(append: true))
|
|
22
|
+
# Need to stub dump for other models if they exist in this context
|
|
23
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
24
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
19
25
|
SeedDump.dump_using_environment('APPEND' => 'true')
|
|
20
26
|
end
|
|
21
27
|
|
|
22
28
|
it "should specify append as true if the APPEND env var is 'TRUE'" do
|
|
23
|
-
SeedDump.
|
|
24
|
-
|
|
29
|
+
expect(SeedDump).to receive(:dump).with(anything, include(append: true))
|
|
30
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
31
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
25
32
|
SeedDump.dump_using_environment('APPEND' => 'TRUE')
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
it "should specify append as false the first time if the APPEND env var is not 'true' (and true after that)" do
|
|
29
36
|
FactoryBot.create(:another_sample)
|
|
30
|
-
|
|
31
|
-
SeedDump.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
SeedDump.dump_using_environment('APPEND' => 'false')
|
|
37
|
+
expect(SeedDump).to receive(:dump).with(Sample, include(append: false)).ordered
|
|
38
|
+
expect(SeedDump).to receive(:dump).with(AnotherSample, include(append: true)).ordered
|
|
39
|
+
# Explicitly set MODELS to control order and prevent other models interfering
|
|
40
|
+
SeedDump.dump_using_environment('APPEND' => 'false', 'MODELS' => 'Sample,AnotherSample')
|
|
35
41
|
end
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
describe 'BATCH_SIZE' do
|
|
39
45
|
it 'should pass along the specified batch size' do
|
|
40
|
-
SeedDump.
|
|
41
|
-
|
|
46
|
+
expect(SeedDump).to receive(:dump).with(anything, include(batch_size: 17))
|
|
47
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
48
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
42
49
|
SeedDump.dump_using_environment('BATCH_SIZE' => '17')
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
it 'should pass along a nil batch size if BATCH_SIZE is not specified' do
|
|
46
|
-
SeedDump.
|
|
47
|
-
|
|
53
|
+
expect(SeedDump).to receive(:dump).with(anything, include(batch_size: nil))
|
|
54
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
55
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
48
56
|
SeedDump.dump_using_environment
|
|
49
57
|
end
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
describe 'EXCLUDE' do
|
|
53
61
|
it 'should pass along any attributes to be excluded' do
|
|
54
|
-
SeedDump.
|
|
55
|
-
|
|
62
|
+
expect(SeedDump).to receive(:dump).with(anything, include(exclude: [:baggins, :saggins]))
|
|
63
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
64
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
56
65
|
SeedDump.dump_using_environment('EXCLUDE' => 'baggins,saggins')
|
|
57
66
|
end
|
|
67
|
+
|
|
68
|
+
it 'should pass an empty array when EXCLUDE is set to empty string (issue #147)' do
|
|
69
|
+
expect(SeedDump).to receive(:dump).with(anything, include(exclude: []))
|
|
70
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
71
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
72
|
+
SeedDump.dump_using_environment('EXCLUDE' => '')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'should pass nil when EXCLUDE is not set (to use default excludes)' do
|
|
76
|
+
expect(SeedDump).to receive(:dump).with(anything, include(exclude: nil))
|
|
77
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
78
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
79
|
+
SeedDump.dump_using_environment
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'should pass an empty array when INCLUDE_ALL is true (issue #147)' do
|
|
83
|
+
expect(SeedDump).to receive(:dump).with(anything, include(exclude: []))
|
|
84
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
85
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
86
|
+
SeedDump.dump_using_environment('INCLUDE_ALL' => 'true')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'should let explicit EXCLUDE override INCLUDE_ALL' do
|
|
90
|
+
expect(SeedDump).to receive(:dump).with(anything, include(exclude: [:some_field]))
|
|
91
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
92
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
93
|
+
SeedDump.dump_using_environment('INCLUDE_ALL' => 'true', 'EXCLUDE' => 'some_field')
|
|
94
|
+
end
|
|
58
95
|
end
|
|
59
96
|
|
|
60
97
|
describe 'FILE' do
|
|
61
98
|
it 'should pass the FILE parameter to the dump method correctly' do
|
|
62
|
-
SeedDump.
|
|
63
|
-
|
|
99
|
+
expect(SeedDump).to receive(:dump).with(anything, include(file: 'blargle'))
|
|
100
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
101
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
64
102
|
SeedDump.dump_using_environment('FILE' => 'blargle')
|
|
65
103
|
end
|
|
66
104
|
|
|
67
105
|
it 'should pass db/seeds.rb as the file parameter if no FILE is specified' do
|
|
68
|
-
SeedDump.
|
|
69
|
-
|
|
106
|
+
expect(SeedDump).to receive(:dump).with(anything, include(file: 'db/seeds.rb'))
|
|
107
|
+
allow(SeedDump).to receive(:dump).with(AnotherSample, anything) if defined?(AnotherSample)
|
|
108
|
+
allow(SeedDump).to receive(:dump).with(YetAnotherSample, anything) if defined?(YetAnotherSample)
|
|
70
109
|
SeedDump.dump_using_environment
|
|
71
110
|
end
|
|
72
111
|
end
|
|
@@ -74,15 +113,100 @@ describe SeedDump do
|
|
|
74
113
|
describe 'LIMIT' do
|
|
75
114
|
it 'should apply the specified limit to the records' do
|
|
76
115
|
relation_double = double('ActiveRecord relation double')
|
|
77
|
-
Sample.
|
|
116
|
+
allow(Sample).to receive(:limit).with(5).and_return(relation_double)
|
|
117
|
+
expect(SeedDump).to receive(:dump).with(relation_double, anything)
|
|
118
|
+
# Allow other calls if necessary
|
|
119
|
+
allow(SeedDump).to receive(:dump).with(instance_of(Class), anything) unless relation_double.is_a?(Class)
|
|
78
120
|
|
|
79
|
-
SeedDump.should_receive(:dump).with(relation_double, anything)
|
|
80
|
-
SeedDump.stub(:dump)
|
|
81
121
|
|
|
82
122
|
SeedDump.dump_using_environment('LIMIT' => '5')
|
|
83
123
|
end
|
|
84
124
|
end
|
|
85
125
|
|
|
126
|
+
describe 'MODEL_LIMITS (issue #142)' do
|
|
127
|
+
# MODEL_LIMITS allows per-model limit overrides to prevent LIMIT from breaking
|
|
128
|
+
# associations. For example, if Teacher has_many Students, you can set
|
|
129
|
+
# MODEL_LIMITS=Teacher:0 to dump all teachers while limiting other models.
|
|
130
|
+
|
|
131
|
+
it 'should apply per-model limit when specified' do
|
|
132
|
+
FactoryBot.create(:another_sample)
|
|
133
|
+
|
|
134
|
+
sample_relation = double('Sample relation')
|
|
135
|
+
another_sample_relation = double('AnotherSample relation')
|
|
136
|
+
|
|
137
|
+
allow(Sample).to receive(:limit).with(5).and_return(sample_relation)
|
|
138
|
+
allow(AnotherSample).to receive(:limit).with(20).and_return(another_sample_relation)
|
|
139
|
+
|
|
140
|
+
expect(SeedDump).to receive(:dump).with(sample_relation, anything)
|
|
141
|
+
expect(SeedDump).to receive(:dump).with(another_sample_relation, anything)
|
|
142
|
+
|
|
143
|
+
SeedDump.dump_using_environment(
|
|
144
|
+
'MODELS' => 'Sample,AnotherSample',
|
|
145
|
+
'MODEL_LIMITS' => 'Sample:5,AnotherSample:20'
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'should interpret 0 as unlimited (dump all records)' do
|
|
150
|
+
# When MODEL_LIMITS=Sample:0, Sample should not have limit applied
|
|
151
|
+
expect(Sample).not_to receive(:limit)
|
|
152
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
153
|
+
|
|
154
|
+
SeedDump.dump_using_environment(
|
|
155
|
+
'MODELS' => 'Sample',
|
|
156
|
+
'MODEL_LIMITS' => 'Sample:0'
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'should fall back to global LIMIT for models not in MODEL_LIMITS' do
|
|
161
|
+
FactoryBot.create(:another_sample)
|
|
162
|
+
|
|
163
|
+
# Sample has specific limit of 5, AnotherSample falls back to global LIMIT of 10
|
|
164
|
+
sample_relation = double('Sample relation')
|
|
165
|
+
another_sample_relation = double('AnotherSample relation')
|
|
166
|
+
|
|
167
|
+
allow(Sample).to receive(:limit).with(5).and_return(sample_relation)
|
|
168
|
+
allow(AnotherSample).to receive(:limit).with(10).and_return(another_sample_relation)
|
|
169
|
+
|
|
170
|
+
expect(SeedDump).to receive(:dump).with(sample_relation, anything)
|
|
171
|
+
expect(SeedDump).to receive(:dump).with(another_sample_relation, anything)
|
|
172
|
+
|
|
173
|
+
SeedDump.dump_using_environment(
|
|
174
|
+
'MODELS' => 'Sample,AnotherSample',
|
|
175
|
+
'LIMIT' => '10',
|
|
176
|
+
'MODEL_LIMITS' => 'Sample:5'
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'should work with MODEL_LIMITS alone (no global LIMIT)' do
|
|
181
|
+
FactoryBot.create(:another_sample)
|
|
182
|
+
|
|
183
|
+
# Sample has limit of 5, AnotherSample has no limit (dumps all)
|
|
184
|
+
sample_relation = double('Sample relation')
|
|
185
|
+
|
|
186
|
+
allow(Sample).to receive(:limit).with(5).and_return(sample_relation)
|
|
187
|
+
expect(AnotherSample).not_to receive(:limit)
|
|
188
|
+
|
|
189
|
+
expect(SeedDump).to receive(:dump).with(sample_relation, anything)
|
|
190
|
+
expect(SeedDump).to receive(:dump).with(AnotherSample, anything)
|
|
191
|
+
|
|
192
|
+
SeedDump.dump_using_environment(
|
|
193
|
+
'MODELS' => 'Sample,AnotherSample',
|
|
194
|
+
'MODEL_LIMITS' => 'Sample:5'
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'should handle whitespace in MODEL_LIMITS' do
|
|
199
|
+
sample_relation = double('Sample relation')
|
|
200
|
+
allow(Sample).to receive(:limit).with(5).and_return(sample_relation)
|
|
201
|
+
expect(SeedDump).to receive(:dump).with(sample_relation, anything)
|
|
202
|
+
|
|
203
|
+
SeedDump.dump_using_environment(
|
|
204
|
+
'MODELS' => 'Sample',
|
|
205
|
+
'MODEL_LIMITS' => ' Sample : 5 '
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
86
210
|
['', 'S'].each do |model_suffix|
|
|
87
211
|
model_env = 'MODEL' + model_suffix
|
|
88
212
|
|
|
@@ -90,11 +214,8 @@ describe SeedDump do
|
|
|
90
214
|
context "if #{model_env} is not specified" do
|
|
91
215
|
it "should dump all non-empty models" do
|
|
92
216
|
FactoryBot.create(:another_sample)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
SeedDump.should_receive(:dump).with(model, anything)
|
|
96
|
-
end
|
|
97
|
-
|
|
217
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
218
|
+
expect(SeedDump).to receive(:dump).with(AnotherSample, anything)
|
|
98
219
|
SeedDump.dump_using_environment
|
|
99
220
|
end
|
|
100
221
|
end
|
|
@@ -102,15 +223,16 @@ describe SeedDump do
|
|
|
102
223
|
context "if #{model_env} is specified" do
|
|
103
224
|
it "should dump only the specified model" do
|
|
104
225
|
FactoryBot.create(:another_sample)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
226
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
227
|
+
# Ensure the other model is NOT dumped
|
|
228
|
+
expect(SeedDump).not_to receive(:dump).with(AnotherSample, anything)
|
|
108
229
|
SeedDump.dump_using_environment(model_env => 'Sample')
|
|
109
230
|
end
|
|
110
231
|
|
|
111
232
|
it "should not dump empty models" do
|
|
112
|
-
SeedDump.
|
|
113
|
-
|
|
233
|
+
expect(SeedDump).not_to receive(:dump).with(EmptyModel, anything)
|
|
234
|
+
# Ensure Sample is still dumped
|
|
235
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
114
236
|
SeedDump.dump_using_environment(model_env => 'EmptyModel, Sample')
|
|
115
237
|
end
|
|
116
238
|
end
|
|
@@ -120,25 +242,372 @@ describe SeedDump do
|
|
|
120
242
|
describe "MODELS_EXCLUDE" do
|
|
121
243
|
it "should dump all non-empty models except the specified models" do
|
|
122
244
|
FactoryBot.create(:another_sample)
|
|
245
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
246
|
+
# Ensure the excluded model is NOT dumped
|
|
247
|
+
expect(SeedDump).not_to receive(:dump).with(AnotherSample, anything)
|
|
248
|
+
SeedDump.dump_using_environment('MODELS_EXCLUDE' => 'AnotherSample')
|
|
249
|
+
end
|
|
250
|
+
end
|
|
123
251
|
|
|
124
|
-
|
|
252
|
+
describe 'model names ending in s (issue #121)' do
|
|
253
|
+
# Model names like "Boss" are incorrectly singularized to "Bos" when
|
|
254
|
+
# processing MODELS=Boss, causing NameError: uninitialized constant Bos.
|
|
255
|
+
# The fix should use the exact model name if it resolves to a valid constant.
|
|
125
256
|
|
|
126
|
-
|
|
257
|
+
it 'should handle model name "Boss" without extra s' do
|
|
258
|
+
FactoryBot.create(:boss)
|
|
259
|
+
expect(SeedDump).to receive(:dump).with(Boss, anything)
|
|
260
|
+
SeedDump.dump_using_environment('MODELS' => 'Boss')
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
it 'should handle model name "boss" (lowercase) without extra s' do
|
|
264
|
+
FactoryBot.create(:boss)
|
|
265
|
+
expect(SeedDump).to receive(:dump).with(Boss, anything)
|
|
266
|
+
SeedDump.dump_using_environment('MODELS' => 'boss')
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it 'should handle MODELS_EXCLUDE with model names ending in s' do
|
|
270
|
+
FactoryBot.create(:boss)
|
|
271
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
272
|
+
expect(SeedDump).not_to receive(:dump).with(Boss, anything)
|
|
273
|
+
SeedDump.dump_using_environment('MODELS_EXCLUDE' => 'Boss')
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it 'should still handle plural model names (e.g., "samples" -> Sample)' do
|
|
277
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
278
|
+
SeedDump.dump_using_environment('MODELS' => 'samples')
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it 'should still handle plural model names in MODELS_EXCLUDE' do
|
|
282
|
+
FactoryBot.create(:another_sample)
|
|
283
|
+
expect(SeedDump).to receive(:dump).with(AnotherSample, anything)
|
|
284
|
+
expect(SeedDump).not_to receive(:dump).with(Sample, anything)
|
|
285
|
+
SeedDump.dump_using_environment('MODELS_EXCLUDE' => 'samples')
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
describe 'INSERT_ALL (issue #153)' do
|
|
290
|
+
it "should specify insert_all as true if the INSERT_ALL env var is 'true'" do
|
|
291
|
+
expect(SeedDump).to receive(:dump).with(anything, include(insert_all: true))
|
|
292
|
+
SeedDump.dump_using_environment('INSERT_ALL' => 'true')
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it "should specify insert_all as true if the INSERT_ALL env var is 'TRUE'" do
|
|
296
|
+
expect(SeedDump).to receive(:dump).with(anything, include(insert_all: true))
|
|
297
|
+
SeedDump.dump_using_environment('INSERT_ALL' => 'TRUE')
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it "should specify insert_all as false if the INSERT_ALL env var is not 'true'" do
|
|
301
|
+
expect(SeedDump).to receive(:dump).with(anything, include(insert_all: false))
|
|
302
|
+
SeedDump.dump_using_environment('INSERT_ALL' => 'false')
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it "should specify insert_all as false if the INSERT_ALL env var is not set" do
|
|
306
|
+
expect(SeedDump).to receive(:dump).with(anything, include(insert_all: false))
|
|
307
|
+
SeedDump.dump_using_environment
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
describe 'HEADER (issue #126 - comment header in seed file)' do
|
|
312
|
+
# HEADER adds a comment at the top of the seed file showing that seed_dump
|
|
313
|
+
# was used and what options were specified for traceability.
|
|
314
|
+
|
|
315
|
+
it "should specify header as true if the HEADER env var is 'true'" do
|
|
316
|
+
expect(SeedDump).to receive(:dump).with(anything, include(header: true))
|
|
317
|
+
SeedDump.dump_using_environment('HEADER' => 'true')
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it "should specify header as true if the HEADER env var is 'TRUE'" do
|
|
321
|
+
expect(SeedDump).to receive(:dump).with(anything, include(header: true))
|
|
322
|
+
SeedDump.dump_using_environment('HEADER' => 'TRUE')
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
it "should specify header as false if the HEADER env var is not 'true'" do
|
|
326
|
+
expect(SeedDump).to receive(:dump).with(anything, include(header: false))
|
|
327
|
+
SeedDump.dump_using_environment('HEADER' => 'false')
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it "should specify header as false if the HEADER env var is not set" do
|
|
331
|
+
expect(SeedDump).to receive(:dump).with(anything, include(header: false))
|
|
332
|
+
SeedDump.dump_using_environment
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
describe 'UPSERT_ALL (issue #104 - non-continuous IDs / foreign key preservation)' do
|
|
337
|
+
# UPSERT_ALL solves the problem where deleted rows cause foreign key
|
|
338
|
+
# references to become invalid after reimporting seeds. When IDs are
|
|
339
|
+
# preserved via upsert_all, foreign key references remain correct.
|
|
340
|
+
|
|
341
|
+
it "should specify upsert_all as true if the UPSERT_ALL env var is 'true'" do
|
|
342
|
+
expect(SeedDump).to receive(:dump).with(anything, include(upsert_all: true))
|
|
343
|
+
SeedDump.dump_using_environment('UPSERT_ALL' => 'true')
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "should specify upsert_all as true if the UPSERT_ALL env var is 'TRUE'" do
|
|
347
|
+
expect(SeedDump).to receive(:dump).with(anything, include(upsert_all: true))
|
|
348
|
+
SeedDump.dump_using_environment('UPSERT_ALL' => 'TRUE')
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
it "should specify upsert_all as false if the UPSERT_ALL env var is not 'true'" do
|
|
352
|
+
expect(SeedDump).to receive(:dump).with(anything, include(upsert_all: false))
|
|
353
|
+
SeedDump.dump_using_environment('UPSERT_ALL' => 'false')
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it "should specify upsert_all as false if the UPSERT_ALL env var is not set" do
|
|
357
|
+
expect(SeedDump).to receive(:dump).with(anything, include(upsert_all: false))
|
|
358
|
+
SeedDump.dump_using_environment
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'should handle non-model classes in ActiveRecord::Base.descendants (issue #112)' do
|
|
363
|
+
# Create a class that inherits from ActiveRecord::Base but doesn't respond to exists?
|
|
364
|
+
# This simulates edge cases like abstract classes or improperly configured models
|
|
365
|
+
non_model_class = Class.new(ActiveRecord::Base) do
|
|
366
|
+
def self.exists?
|
|
367
|
+
raise NoMethodError, "undefined method `exists?' for #{self}"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def self.table_exists?
|
|
371
|
+
raise NoMethodError, "undefined method `table_exists?' for #{self}"
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
Object.const_set('NonModelClass', non_model_class)
|
|
375
|
+
|
|
376
|
+
allow(SeedDump).to receive(:dump)
|
|
377
|
+
|
|
378
|
+
begin
|
|
379
|
+
expect { SeedDump.dump_using_environment }.not_to raise_error
|
|
380
|
+
ensure
|
|
381
|
+
Object.send(:remove_const, :NonModelClass)
|
|
127
382
|
end
|
|
128
383
|
end
|
|
129
384
|
|
|
130
385
|
it 'should run ok without ActiveRecord::SchemaMigration being set (needed for Rails Engines)' do
|
|
131
|
-
|
|
386
|
+
# Ensure Sample model exists before trying to remove SchemaMigration
|
|
387
|
+
expect(defined?(Sample)).to be_truthy
|
|
388
|
+
schema_migration_defined = defined?(ActiveRecord::SchemaMigration)
|
|
389
|
+
schema_migration = ActiveRecord::SchemaMigration if schema_migration_defined
|
|
132
390
|
|
|
133
|
-
|
|
391
|
+
# Stub the dump method before removing the constant
|
|
392
|
+
allow(SeedDump).to receive(:dump)
|
|
134
393
|
|
|
135
|
-
|
|
394
|
+
# Use remove_const carefully only if it's defined
|
|
395
|
+
ActiveRecord.send(:remove_const, :SchemaMigration) if schema_migration_defined
|
|
136
396
|
|
|
137
397
|
begin
|
|
138
|
-
SeedDump.dump_using_environment
|
|
398
|
+
expect { SeedDump.dump_using_environment }.not_to raise_error
|
|
139
399
|
ensure
|
|
140
|
-
|
|
400
|
+
# Ensure the constant is restored only if it was originally defined
|
|
401
|
+
ActiveRecord.const_set(:SchemaMigration, schema_migration) if schema_migration_defined && !defined?(ActiveRecord::SchemaMigration)
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
describe 'HABTM deduplication (issues #26, #114)' do
|
|
406
|
+
# When using has_and_belongs_to_many, Rails creates two auto-generated models
|
|
407
|
+
# that point to the same join table (e.g., User::HABTM_Roles and Role::HABTM_Users).
|
|
408
|
+
# We should only dump one of them to avoid duplicate seed data.
|
|
409
|
+
|
|
410
|
+
it 'should deduplicate HABTM models that share the same table' do
|
|
411
|
+
# Create mock HABTM classes that share the same table_name
|
|
412
|
+
habtm_class_1 = Class.new(ActiveRecord::Base) do
|
|
413
|
+
self.table_name = 'roles_users'
|
|
414
|
+
def self.name; 'User::HABTM_Roles'; end
|
|
415
|
+
def self.to_s; name; end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
habtm_class_2 = Class.new(ActiveRecord::Base) do
|
|
419
|
+
self.table_name = 'roles_users'
|
|
420
|
+
def self.name; 'Role::HABTM_Users'; end
|
|
421
|
+
def self.to_s; name; end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Temporarily add these to AR descendants by setting constants
|
|
425
|
+
User = Class.new unless defined?(User)
|
|
426
|
+
Role = Class.new unless defined?(Role)
|
|
427
|
+
User.const_set('HABTM_Roles', habtm_class_1)
|
|
428
|
+
Role.const_set('HABTM_Users', habtm_class_2)
|
|
429
|
+
|
|
430
|
+
begin
|
|
431
|
+
# Stub exists? and table_exists? to return true
|
|
432
|
+
allow(habtm_class_1).to receive(:table_exists?).and_return(true)
|
|
433
|
+
allow(habtm_class_1).to receive(:exists?).and_return(true)
|
|
434
|
+
allow(habtm_class_2).to receive(:table_exists?).and_return(true)
|
|
435
|
+
allow(habtm_class_2).to receive(:exists?).and_return(true)
|
|
436
|
+
|
|
437
|
+
# Track which models get dumped
|
|
438
|
+
dumped_models = []
|
|
439
|
+
allow(SeedDump).to receive(:dump) do |model, _options|
|
|
440
|
+
dumped_models << model.to_s
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
SeedDump.dump_using_environment
|
|
444
|
+
|
|
445
|
+
# Only one of the HABTM models should be dumped, not both
|
|
446
|
+
habtm_dumps = dumped_models.select { |m| m.include?('HABTM_') }
|
|
447
|
+
habtm_tables = habtm_dumps.map { |m| m.include?('HABTM_Roles') ? 'roles_users' : 'roles_users' }
|
|
448
|
+
|
|
449
|
+
expect(habtm_dumps.size).to eq(1), "Expected 1 HABTM model to be dumped, got #{habtm_dumps.size}: #{habtm_dumps}"
|
|
450
|
+
ensure
|
|
451
|
+
User.send(:remove_const, 'HABTM_Roles') if defined?(User::HABTM_Roles)
|
|
452
|
+
Role.send(:remove_const, 'HABTM_Users') if defined?(Role::HABTM_Users)
|
|
453
|
+
Object.send(:remove_const, 'User') if defined?(User) && User.is_a?(Class) && User.superclass == Object
|
|
454
|
+
Object.send(:remove_const, 'Role') if defined?(Role) && Role.is_a?(Class) && Role.superclass == Object
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
it 'should not affect non-HABTM models with different tables' do
|
|
459
|
+
# Sample and AnotherSample have different tables, so both should dump
|
|
460
|
+
allow(SeedDump).to receive(:dump)
|
|
461
|
+
|
|
462
|
+
FactoryBot.create(:another_sample)
|
|
463
|
+
expect(SeedDump).to receive(:dump).with(Sample, anything)
|
|
464
|
+
expect(SeedDump).to receive(:dump).with(AnotherSample, anything)
|
|
465
|
+
|
|
466
|
+
SeedDump.dump_using_environment
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
describe 'foreign key dependency ordering (issues #78, #83)' do
|
|
471
|
+
# Models with foreign key dependencies should be dumped in the correct order
|
|
472
|
+
# so that seeds can be imported without foreign key violations.
|
|
473
|
+
# For example: Author -> Book -> Review means Author should be dumped first,
|
|
474
|
+
# then Book, then Review.
|
|
475
|
+
|
|
476
|
+
it 'should order models by foreign key dependencies' do
|
|
477
|
+
# Create records with dependencies
|
|
478
|
+
author = FactoryBot.create(:author)
|
|
479
|
+
book = FactoryBot.create(:book, author: author)
|
|
480
|
+
FactoryBot.create(:review, book: book)
|
|
481
|
+
|
|
482
|
+
# Track which models get dumped and in what order
|
|
483
|
+
dumped_models = []
|
|
484
|
+
allow(SeedDump).to receive(:dump) do |model, _options|
|
|
485
|
+
dumped_models << model.to_s
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
SeedDump.dump_using_environment('MODELS' => 'Review,Book,Author')
|
|
489
|
+
|
|
490
|
+
# Verify the order: Author must come before Book, Book must come before Review
|
|
491
|
+
author_index = dumped_models.index('Author')
|
|
492
|
+
book_index = dumped_models.index('Book')
|
|
493
|
+
review_index = dumped_models.index('Review')
|
|
494
|
+
|
|
495
|
+
expect(author_index).not_to be_nil, "Author should be in the dump"
|
|
496
|
+
expect(book_index).not_to be_nil, "Book should be in the dump"
|
|
497
|
+
expect(review_index).not_to be_nil, "Review should be in the dump"
|
|
498
|
+
|
|
499
|
+
expect(author_index).to be < book_index,
|
|
500
|
+
"Author (index #{author_index}) should be dumped before Book (index #{book_index})"
|
|
501
|
+
expect(book_index).to be < review_index,
|
|
502
|
+
"Book (index #{book_index}) should be dumped before Review (index #{review_index})"
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
it 'should handle models without foreign key dependencies' do
|
|
506
|
+
# Sample has no foreign keys, should still be dumped normally
|
|
507
|
+
FactoryBot.create(:author)
|
|
508
|
+
|
|
509
|
+
dumped_models = []
|
|
510
|
+
allow(SeedDump).to receive(:dump) do |model, _options|
|
|
511
|
+
dumped_models << model.to_s
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
SeedDump.dump_using_environment('MODELS' => 'Sample,Author')
|
|
515
|
+
|
|
516
|
+
expect(dumped_models).to include('Sample')
|
|
517
|
+
expect(dumped_models).to include('Author')
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
it 'should handle circular dependencies gracefully' do
|
|
521
|
+
# Create models with circular dependency for testing
|
|
522
|
+
# PersonA belongs_to PersonB, PersonB belongs_to PersonA
|
|
523
|
+
person_a_class = Class.new(ActiveRecord::Base) do
|
|
524
|
+
self.table_name = 'person_as'
|
|
525
|
+
end
|
|
526
|
+
person_b_class = Class.new(ActiveRecord::Base) do
|
|
527
|
+
self.table_name = 'person_bs'
|
|
528
|
+
end
|
|
529
|
+
Object.const_set('PersonA', person_a_class)
|
|
530
|
+
Object.const_set('PersonB', person_b_class)
|
|
531
|
+
|
|
532
|
+
# Add circular associations after both classes exist
|
|
533
|
+
PersonA.belongs_to :person_b, optional: true
|
|
534
|
+
PersonB.belongs_to :person_a, optional: true
|
|
535
|
+
|
|
536
|
+
# Create tables
|
|
537
|
+
ActiveRecord::Schema.define do
|
|
538
|
+
create_table 'person_as', force: true do |t|
|
|
539
|
+
t.references :person_b
|
|
540
|
+
end
|
|
541
|
+
create_table 'person_bs', force: true do |t|
|
|
542
|
+
t.references :person_a
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Create records
|
|
547
|
+
PersonA.create!
|
|
548
|
+
PersonB.create!
|
|
549
|
+
|
|
550
|
+
begin
|
|
551
|
+
dumped_models = []
|
|
552
|
+
allow(SeedDump).to receive(:dump) do |model, _options|
|
|
553
|
+
dumped_models << model.to_s
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Should not raise an error despite circular dependency
|
|
557
|
+
expect {
|
|
558
|
+
SeedDump.dump_using_environment('MODELS' => 'PersonA,PersonB')
|
|
559
|
+
}.not_to raise_error
|
|
560
|
+
|
|
561
|
+
# Both models should be dumped
|
|
562
|
+
expect(dumped_models).to include('PersonA')
|
|
563
|
+
expect(dumped_models).to include('PersonB')
|
|
564
|
+
ensure
|
|
565
|
+
Object.send(:remove_const, :PersonA)
|
|
566
|
+
Object.send(:remove_const, :PersonB)
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
describe 'STI deduplication (issue #120)' do
|
|
572
|
+
# When using STI (Single Table Inheritance), multiple model classes share
|
|
573
|
+
# the same database table. For example, AdminUser < BaseUser and
|
|
574
|
+
# GuestUser < BaseUser all use the 'base_users' table.
|
|
575
|
+
# Without deduplication, each STI subclass would be dumped separately,
|
|
576
|
+
# creating duplicate records in the seeds file.
|
|
577
|
+
|
|
578
|
+
it 'should deduplicate STI models by keeping only the base class' do
|
|
579
|
+
# Create records of different STI types
|
|
580
|
+
FactoryBot.create(:admin_user)
|
|
581
|
+
FactoryBot.create(:guest_user)
|
|
582
|
+
|
|
583
|
+
# Track which models get dumped
|
|
584
|
+
dumped_models = []
|
|
585
|
+
allow(SeedDump).to receive(:dump) do |model, _options|
|
|
586
|
+
dumped_models << model.to_s
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
SeedDump.dump_using_environment
|
|
590
|
+
|
|
591
|
+
# Only BaseUser should be dumped, not AdminUser or GuestUser
|
|
592
|
+
expect(dumped_models).to include('BaseUser')
|
|
593
|
+
expect(dumped_models).not_to include('AdminUser')
|
|
594
|
+
expect(dumped_models).not_to include('GuestUser')
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
it 'should dump all records through the base class' do
|
|
598
|
+
# Create records of different STI types
|
|
599
|
+
FactoryBot.create(:admin_user)
|
|
600
|
+
FactoryBot.create(:guest_user)
|
|
601
|
+
FactoryBot.create(:base_user)
|
|
602
|
+
|
|
603
|
+
# The base class should have access to all records
|
|
604
|
+
result = SeedDump.dump(BaseUser)
|
|
605
|
+
|
|
606
|
+
# All three records should be in the output
|
|
607
|
+
expect(result).to include('AdminUser')
|
|
608
|
+
expect(result).to include('GuestUser')
|
|
609
|
+
expect(result).to include('BaseUser')
|
|
141
610
|
end
|
|
142
611
|
end
|
|
143
|
-
|
|
144
|
-
|
|
612
|
+
end
|
|
613
|
+
end
|