vial 0.0.0 → 0.2026.1.1.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/README.md +301 -10
- data/examples/admin_users.vial.rb +9 -0
- data/examples/base_users.vial.rb +11 -0
- data/examples/company__users.vial.rb +10 -0
- data/examples/users.vial.rb +23 -0
- data/lib/tasks/vial.rake +303 -0
- data/lib/vial/compiler.rb +154 -0
- data/lib/vial/config.rb +33 -0
- data/lib/vial/definition.rb +462 -0
- data/lib/vial/dsl.rb +9 -0
- data/lib/vial/erb.rb +5 -0
- data/lib/vial/explain_id.rb +27 -0
- data/lib/vial/explicit_id.rb +5 -0
- data/lib/vial/fixture_analyzer.rb +222 -0
- data/lib/vial/fixture_id_standardizer.rb +211 -0
- data/lib/vial/loader.rb +14 -0
- data/lib/vial/railtie.rb +13 -0
- data/lib/vial/registry.rb +26 -0
- data/lib/vial/sequence.rb +24 -0
- data/lib/vial/validator.rb +192 -0
- data/lib/vial/version.rb +5 -0
- data/lib/vial/yaml_emitter.rb +133 -0
- data/lib/vial.rb +102 -3
- metadata +58 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f026c068653b089c9e389a48269f61b166df03350c2c483644d01d20449d75ac
|
|
4
|
+
data.tar.gz: ba3eb55ff916a330b79a7e23a33c64664384ac4b4c53bd9b3ca8c2cff48ddf01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb253aae25c559b82e53fce01af43eec1b378062b7d88163fb9967e3856bc35c4636213c8f08143e0f3baca7840f5d5c5df2d441f6e4cbd4cb3f537ed05496f8
|
|
7
|
+
data.tar.gz: 6c05034b6d5b9d9e39de6f61b59cd4e9e00d0b1acc88f36dc222ce7903a23a4728baa80813ca37b22b85d2eb9153f5ae2d42a638c516d81d507852af5c34abac
|
data/README.md
CHANGED
|
@@ -1,27 +1,318 @@
|
|
|
1
1
|
# Vial: Fixtures, Reinvented
|
|
2
2
|
|
|
3
|
-
Vial
|
|
3
|
+
Vial is a Ruby gem for Rails 8.1+ on Ruby 4.0+ that compiles programmable fixture intent into explicit, deterministic fixture files.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 4.0+ (Ruby Box optional via `RUBY_BOX=1`)
|
|
8
|
+
- Rails 8.1+
|
|
9
|
+
|
|
10
|
+
## Support Policy
|
|
11
|
+
|
|
12
|
+
- Ruby 4.0+ only
|
|
13
|
+
- Rails 8.1+ only
|
|
14
|
+
- No backward compatibility guarantees for older Ruby/Rails
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Vial Compiler**: Ruby DSL input → explicit YAML fixtures output
|
|
19
|
+
- **Fixture Discovery**: Automatically finds all YAML fixtures across configured paths
|
|
20
|
+
- **Model Mapping Analysis**: Determines which models fixtures belong to using Rails conventions
|
|
21
|
+
- **Validation**: Identifies orphaned fixtures that don't have corresponding models
|
|
22
|
+
- **Multiple Detection Methods**: Supports all Rails fixture-to-model mapping methods
|
|
4
23
|
|
|
5
24
|
## Installation
|
|
6
25
|
|
|
7
|
-
Add this
|
|
26
|
+
Add this gem to your Gemfile in the development/test group:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
group :development, :test do
|
|
30
|
+
gem 'vial'
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then run:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Compile Vial Sources
|
|
43
|
+
|
|
44
|
+
Vial reads `*.vial.rb` files from `test/vials` and writes YAML fixtures to `test/fixtures`:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bin/rails vial:compile
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If `RUBY_BOX=1` is set, Vial evaluates vial sources inside Ruby::Box for isolation.
|
|
51
|
+
|
|
52
|
+
Compile only specific vials:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bin/rails vial:compile:only[users,posts]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Note: `vial:compile:only` still validates the full dataset to catch global ID collisions; it only limits which files are written.
|
|
59
|
+
|
|
60
|
+
Dry run (validate and show output targets without writing files):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
bin/rails vial:compile:dry_run
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Example `test/vials/users.vial.rb`:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
vial :users do
|
|
70
|
+
base do
|
|
71
|
+
active true
|
|
72
|
+
country "MA"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
variant :admin do
|
|
76
|
+
role "admin"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
variant :guest do
|
|
80
|
+
role "guest"
|
|
81
|
+
active false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
sequence(:email) { |i| "user#{i}@test.local" }
|
|
85
|
+
|
|
86
|
+
generate 10, :admin
|
|
87
|
+
generate 50, :guest
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Output: `test/fixtures/users.yml` with deterministic IDs and explicit records.
|
|
92
|
+
|
|
93
|
+
### Global Validation
|
|
94
|
+
|
|
95
|
+
`vial:compile` validates the entire dataset before writing any fixtures. Any collision or duplicate label fails the compile with a precise error.
|
|
96
|
+
|
|
97
|
+
### Composition
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
vial :base_users, abstract: true do
|
|
101
|
+
base do
|
|
102
|
+
active true
|
|
103
|
+
country "MA"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
vial :admin_users do
|
|
108
|
+
include_vial :base_users do
|
|
109
|
+
override :role, "admin"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
generate 2
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`include_vial` composes base attributes (and sequences) only; variants are not imported.
|
|
117
|
+
Abstract Vials are compile-time only and do not emit fixture files.
|
|
118
|
+
|
|
119
|
+
### ID Strategy
|
|
120
|
+
|
|
121
|
+
Derived IDs are deterministic integers based on the identity tuple:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
[vial_name, record_type, label, variants[], index]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
You can set a namespace range per Vial:
|
|
8
128
|
|
|
9
129
|
```ruby
|
|
10
|
-
|
|
130
|
+
vial :users, id_base: 100_000, id_range: 90_000 do
|
|
131
|
+
base do
|
|
132
|
+
role "user"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
generate 3
|
|
136
|
+
end
|
|
11
137
|
```
|
|
12
138
|
|
|
13
|
-
|
|
139
|
+
Explicit IDs are supported and never rewritten:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
vial :users do
|
|
143
|
+
base do
|
|
144
|
+
id 1001
|
|
145
|
+
role "system"
|
|
146
|
+
end
|
|
14
147
|
|
|
15
|
-
|
|
148
|
+
generate 1
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Explain a derived ID:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bin/rails vial:explain_id['users.admin.eu[3]']
|
|
156
|
+
```
|
|
16
157
|
|
|
17
|
-
|
|
158
|
+
### Determinism & Faker
|
|
18
159
|
|
|
19
|
-
|
|
160
|
+
Vial seeds Ruby's RNG with `Vial.config.seed` (default: 1). As long as you avoid non-deterministic sources
|
|
161
|
+
(`Time.now`, `SecureRandom`, etc.), recompiles produce identical output.
|
|
162
|
+
|
|
163
|
+
If you use Faker, seed it explicitly:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
Faker::Config.random = Random.new(Vial.config.seed)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Troubleshooting
|
|
170
|
+
|
|
171
|
+
- `Vial: no vial definitions found` → No `*.vial.rb` files were found under `Vial.config.source_paths`.
|
|
172
|
+
- `Vial: no records generated` → All vials are abstract or missing `generate` calls.
|
|
173
|
+
- Fixture ID standardization skipped for ERB → ERB fixtures are not rewritten to avoid altering dynamic content.
|
|
174
|
+
- Output path looks wrong → `Vial.config.output_path` defaults to the first `ActiveSupport::TestCase.fixture_paths` entry.
|
|
175
|
+
|
|
176
|
+
### Configuration
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
Vial.configure do |config|
|
|
180
|
+
config.source_paths = [Rails.root.join("test/vials")]
|
|
181
|
+
config.output_path = Rails.root.join("test/fixtures")
|
|
182
|
+
config.seed = 1
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Example Vial sources live in `examples/`:
|
|
187
|
+
- `examples/users.vial.rb`
|
|
188
|
+
- `examples/company__users.vial.rb`
|
|
189
|
+
- `examples/base_users.vial.rb`
|
|
190
|
+
- `examples/admin_users.vial.rb`
|
|
191
|
+
|
|
192
|
+
Use `erb("...")` inside a Vial file to emit raw ERB (for associations or special IDs).
|
|
193
|
+
|
|
194
|
+
Vial provides several rake tasks for fixture analysis:
|
|
195
|
+
|
|
196
|
+
### Count Fixtures
|
|
197
|
+
|
|
198
|
+
Get an overview of all YAML fixtures in your project:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
bin/rails vial:count_fixtures
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This shows:
|
|
205
|
+
- Total number of fixture files
|
|
206
|
+
- Distribution by directory
|
|
207
|
+
- Configured fixture paths
|
|
208
|
+
|
|
209
|
+
Note: `Vial.config.output_path` defaults to the first configured `fixture_paths` when available.
|
|
210
|
+
|
|
211
|
+
### Clean Stale Vial Fixtures
|
|
212
|
+
|
|
213
|
+
Remove fixture files previously generated by Vial that no longer have a matching vial definition:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
bin/rails vial:clean
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Vial tracks generated fixture files in `test/fixtures/.vial_manifest.yml` and only removes files listed there.
|
|
220
|
+
|
|
221
|
+
### Analyze Fixtures
|
|
222
|
+
|
|
223
|
+
Get detailed fixture-to-model mapping information:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
bin/rails vial:analyze_fixtures
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
This shows:
|
|
230
|
+
- Mapped fixtures with their corresponding models
|
|
231
|
+
- Detection method used (fixture directive, set_fixture_class, or path inference)
|
|
232
|
+
- Unmapped fixtures that may need attention
|
|
233
|
+
- Any errors encountered during analysis
|
|
234
|
+
|
|
235
|
+
### Validate Fixtures
|
|
236
|
+
|
|
237
|
+
Check for orphaned fixtures without models:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
bin/rails vial:validate_fixtures
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
This task:
|
|
244
|
+
- Returns success (exit 0) if all fixtures are mapped
|
|
245
|
+
- Returns failure (exit 1) if unmapped fixtures are found
|
|
246
|
+
- Provides suggestions for fixing unmapped fixtures
|
|
247
|
+
|
|
248
|
+
### Analyze Fixture IDs
|
|
249
|
+
|
|
250
|
+
Check if fixtures use proper `ActiveRecord::FixtureSet.identify`:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
bin/rails vial:analyze_fixture_ids
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
This shows:
|
|
257
|
+
- Primary key type distribution (UUID, integer, string, etc.)
|
|
258
|
+
- Fixtures that need ID standardization
|
|
259
|
+
- Current vs expected ID values
|
|
260
|
+
|
|
261
|
+
### Standardize Fixture IDs
|
|
262
|
+
|
|
263
|
+
Convert hardcoded IDs to use `ActiveRecord::FixtureSet.identify`:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Preview changes without applying
|
|
267
|
+
bin/rails vial:standardize_fixture_ids:dry_run
|
|
268
|
+
|
|
269
|
+
# Apply changes (with confirmation prompt)
|
|
270
|
+
bin/rails vial:standardize_fixture_ids
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
This will:
|
|
274
|
+
- Replace hardcoded integer IDs with `<%= ActiveRecord::FixtureSet.identify(:label) %>`
|
|
275
|
+
- Replace hardcoded UUIDs with `<%= ActiveRecord::FixtureSet.identify(:label, :uuid) %>`
|
|
276
|
+
- Skip string primary keys (often manually managed like slugs)
|
|
277
|
+
- Ensure consistent, deterministic IDs across all fixtures
|
|
278
|
+
|
|
279
|
+
## How Fixture Detection Works
|
|
280
|
+
|
|
281
|
+
Vial uses three methods to determine which model a fixture belongs to:
|
|
282
|
+
|
|
283
|
+
1. **_fixture Directive**: Checks for `_fixture: model_class:` in YAML files
|
|
284
|
+
2. **set_fixture_class**: Uses mappings from `ActiveSupport::TestCase.set_fixture_class`
|
|
285
|
+
3. **Path Inference**: Infers models from fixture paths using Rails naming conventions
|
|
286
|
+
|
|
287
|
+
## Example Output
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
=== Vial Fixture Analysis ===
|
|
291
|
+
|
|
292
|
+
Total fixtures found: 114
|
|
293
|
+
Mapped to models: 93
|
|
294
|
+
Unmapped fixtures: 21
|
|
295
|
+
|
|
296
|
+
=== Mapped Fixtures ===
|
|
297
|
+
platform/countries:
|
|
298
|
+
Model: Platform::Country
|
|
299
|
+
Table: platform_countries
|
|
300
|
+
Detection: path_inference
|
|
301
|
+
|
|
302
|
+
active_llm_llms:
|
|
303
|
+
Model: ActiveLLM::LLM
|
|
304
|
+
Table: active_llm_llms
|
|
305
|
+
Detection: set_fixture_class
|
|
306
|
+
```
|
|
20
307
|
|
|
21
|
-
##
|
|
308
|
+
## Contributing
|
|
22
309
|
|
|
23
|
-
|
|
310
|
+
1. Fork it
|
|
311
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
312
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
313
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
314
|
+
5. Create new Pull Request
|
|
24
315
|
|
|
25
316
|
## License
|
|
26
317
|
|
|
27
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
318
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
vial :users do
|
|
4
|
+
base do
|
|
5
|
+
active true
|
|
6
|
+
country "MA"
|
|
7
|
+
email sequence(:email)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
variant :admin do
|
|
11
|
+
role "admin"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
variant :guest do
|
|
15
|
+
role "guest"
|
|
16
|
+
active false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
sequence(:email) { |i| "user#{i}@test.local" }
|
|
20
|
+
|
|
21
|
+
generate 2, :admin
|
|
22
|
+
generate 3, :guest
|
|
23
|
+
end
|
data/lib/tasks/vial.rake
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :vial do
|
|
4
|
+
desc 'Compile Vial sources into Rails fixtures'
|
|
5
|
+
task :compile, [:only] => :environment do |_, args|
|
|
6
|
+
only = args[:only] || ENV['ONLY']
|
|
7
|
+
result = Vial.compile!(only: only)
|
|
8
|
+
if result && result.status == :compiled
|
|
9
|
+
puts "Vial: fixtures compiled to #{Vial.config.output_path}"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc 'Compile Vial sources without writing files (dry run)'
|
|
14
|
+
task 'compile:dry_run', [:only] => :environment do |_, args|
|
|
15
|
+
only = args[:only] || ENV['ONLY']
|
|
16
|
+
result = Vial.compile!(dry_run: true, only: only)
|
|
17
|
+
return unless result
|
|
18
|
+
|
|
19
|
+
if result.status == :dry_run
|
|
20
|
+
puts "Vial: dry run - would write #{result.files.size} fixture files to #{result.output_path}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc 'Compile only selected vials (comma-separated, e.g. vial:compile:only[users,posts])'
|
|
25
|
+
task 'compile:only', [:names] => :environment do |_, args|
|
|
26
|
+
names = args[:names] || ENV['ONLY']
|
|
27
|
+
if names.nil? || names.strip.empty?
|
|
28
|
+
abort "Usage: bin/rails vial:compile:only[users,posts]"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
result = Vial.compile!(only: names)
|
|
32
|
+
if result && result.status == :compiled
|
|
33
|
+
puts "Vial: fixtures compiled to #{Vial.config.output_path}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc 'Remove fixture files generated by Vial that no longer have a matching vial definition'
|
|
38
|
+
task clean: :environment do
|
|
39
|
+
result = Vial.clean!
|
|
40
|
+
return unless result
|
|
41
|
+
|
|
42
|
+
if result.status == :cleaned
|
|
43
|
+
if result.removed.empty?
|
|
44
|
+
puts "Vial: no stale fixtures to remove"
|
|
45
|
+
else
|
|
46
|
+
puts "Vial: removed #{result.removed.size} stale fixture files"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
desc 'Explain a derived ID (format: vial.label.variant[index])'
|
|
52
|
+
task :explain_id, [:query] => :environment do |_, args|
|
|
53
|
+
query = args[:query] || ENV['QUERY']
|
|
54
|
+
if query.nil? || query.strip.empty?
|
|
55
|
+
abort "Usage: bin/rails vial:explain_id['users.admin.eu[3]']"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
vial_name, label, variants, index = Vial::ExplainId.parse(query)
|
|
60
|
+
rescue ArgumentError => e
|
|
61
|
+
abort e.message
|
|
62
|
+
end
|
|
63
|
+
Vial.load_sources!
|
|
64
|
+
definition = Vial.registry[vial_name]
|
|
65
|
+
abort "Unknown vial: #{vial_name}" unless definition
|
|
66
|
+
|
|
67
|
+
info = definition.explain_id(label: label, variant_stack: variants, index: index)
|
|
68
|
+
puts info[:normalized]
|
|
69
|
+
puts "hash: #{info[:hash]}"
|
|
70
|
+
puts "base: #{info[:base]}"
|
|
71
|
+
puts "range: #{info[:range]}"
|
|
72
|
+
puts "final: #{info[:final]}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
desc 'Count YAML fixture files across all configured fixture paths'
|
|
76
|
+
task count_fixtures: :environment do
|
|
77
|
+
# Need to load test environment to get fixture configuration
|
|
78
|
+
require Rails.root.join('test/test_helper')
|
|
79
|
+
|
|
80
|
+
# Rails 8+ uses fixture_paths as an attribute
|
|
81
|
+
fixture_paths = ActiveSupport::TestCase.fixture_paths
|
|
82
|
+
|
|
83
|
+
puts "Vial: Scanning fixture paths..."
|
|
84
|
+
puts "Configured paths: #{fixture_paths.join(', ')}"
|
|
85
|
+
puts
|
|
86
|
+
|
|
87
|
+
total_count = 0
|
|
88
|
+
fixture_counts = {}
|
|
89
|
+
|
|
90
|
+
fixture_paths.each do |path|
|
|
91
|
+
next unless File.directory?(path)
|
|
92
|
+
|
|
93
|
+
yaml_files = Dir.glob(File.join(path, '**', '*.yml'))
|
|
94
|
+
yaml_files += Dir.glob(File.join(path, '**', '*.yaml'))
|
|
95
|
+
|
|
96
|
+
count = yaml_files.size
|
|
97
|
+
total_count += count
|
|
98
|
+
|
|
99
|
+
puts "#{path}: #{count} YAML files"
|
|
100
|
+
|
|
101
|
+
# Group by subdirectory for better visibility
|
|
102
|
+
yaml_files.each do |file|
|
|
103
|
+
relative_path = Pathname.new(file).relative_path_from(Pathname.new(path))
|
|
104
|
+
dir = relative_path.dirname.to_s
|
|
105
|
+
fixture_counts[dir] ||= 0
|
|
106
|
+
fixture_counts[dir] += 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
puts
|
|
111
|
+
puts "Fixture distribution by directory:"
|
|
112
|
+
fixture_counts.sort.each do |dir, count|
|
|
113
|
+
puts " #{dir == '.' ? '(root)' : dir}: #{count} files"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
puts
|
|
117
|
+
puts "Total YAML fixtures found: #{total_count}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
desc 'Analyze fixtures and their model mappings'
|
|
121
|
+
task analyze_fixtures: :environment do
|
|
122
|
+
# Ensure test environment is loaded to get fixture configuration
|
|
123
|
+
require Rails.root.join('test/test_helper')
|
|
124
|
+
|
|
125
|
+
analyzer = Vial::FixtureAnalyzer.new
|
|
126
|
+
analyzer.analyze
|
|
127
|
+
|
|
128
|
+
puts "=== Vial Fixture Analysis ==="
|
|
129
|
+
puts
|
|
130
|
+
|
|
131
|
+
summary = analyzer.summary
|
|
132
|
+
puts "Total fixtures found: #{summary[:total_fixtures]}"
|
|
133
|
+
puts "Mapped to models: #{summary[:mapped_fixtures]}"
|
|
134
|
+
puts "Unmapped fixtures: #{summary[:unmapped_fixtures]}"
|
|
135
|
+
puts
|
|
136
|
+
|
|
137
|
+
if analyzer.mapped_fixtures.any?
|
|
138
|
+
puts "=== Mapped Fixtures ==="
|
|
139
|
+
analyzer.mapped_fixtures.sort_by { |k, _| k }.each do |fixture_name, info|
|
|
140
|
+
puts "#{fixture_name}:"
|
|
141
|
+
puts " Model: #{info.model_name}"
|
|
142
|
+
puts " Table: #{info.table_name}"
|
|
143
|
+
puts " Detection: #{info.detection_method}"
|
|
144
|
+
end
|
|
145
|
+
puts
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
if analyzer.unmapped_fixtures.any?
|
|
149
|
+
puts "=== Unmapped Fixtures (Potential Issues) ==="
|
|
150
|
+
analyzer.unmapped_fixtures.sort_by { |k, _| k }.each do |fixture_name, info|
|
|
151
|
+
puts "#{fixture_name}:"
|
|
152
|
+
puts " File: #{info.file}"
|
|
153
|
+
puts " Status: No model found"
|
|
154
|
+
end
|
|
155
|
+
puts
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if summary[:errors].any?
|
|
159
|
+
puts "=== Errors ==="
|
|
160
|
+
summary[:errors].each do |error|
|
|
161
|
+
puts "File: #{error[:file]}"
|
|
162
|
+
puts "Error: #{error[:error]}"
|
|
163
|
+
puts
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
desc 'Validate fixtures - check for orphaned fixtures without models'
|
|
169
|
+
task validate_fixtures: :environment do
|
|
170
|
+
require Rails.root.join('test/test_helper')
|
|
171
|
+
|
|
172
|
+
analyzer = Vial::FixtureAnalyzer.new
|
|
173
|
+
analyzer.analyze
|
|
174
|
+
|
|
175
|
+
unmapped = analyzer.unmapped_fixtures
|
|
176
|
+
|
|
177
|
+
if unmapped.empty?
|
|
178
|
+
puts "✅ All fixtures are properly mapped to models!"
|
|
179
|
+
exit 0
|
|
180
|
+
else
|
|
181
|
+
puts "❌ Found #{unmapped.size} unmapped fixtures:"
|
|
182
|
+
unmapped.sort_by { |k, _| k }.each do |fixture_name, info|
|
|
183
|
+
puts " - #{fixture_name}"
|
|
184
|
+
end
|
|
185
|
+
puts
|
|
186
|
+
puts "These fixtures may:"
|
|
187
|
+
puts " 1. Need a _fixture: model_class directive in the YAML"
|
|
188
|
+
puts " 2. Need to be added to set_fixture_class in test_helper.rb"
|
|
189
|
+
puts " 3. Be orphaned and should be removed"
|
|
190
|
+
puts " 4. Have a model that doesn't follow Rails naming conventions"
|
|
191
|
+
|
|
192
|
+
exit 1
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
desc 'Analyze fixture IDs and check if they use proper ActiveRecord::FixtureSet.identify'
|
|
197
|
+
task analyze_fixture_ids: :environment do
|
|
198
|
+
require Rails.root.join('test/test_helper')
|
|
199
|
+
|
|
200
|
+
standardizer = Vial::FixtureIdStandardizer.new
|
|
201
|
+
standardizer.analyze
|
|
202
|
+
|
|
203
|
+
summary = standardizer.summary
|
|
204
|
+
|
|
205
|
+
puts "=== Vial Fixture ID Analysis ==="
|
|
206
|
+
puts
|
|
207
|
+
puts "Total fixtures analyzed: #{summary[:total_fixtures]}"
|
|
208
|
+
puts "Fixtures needing ID updates: #{summary[:fixtures_needing_updates]}"
|
|
209
|
+
puts "Total records to update: #{summary[:records_to_update]}"
|
|
210
|
+
puts
|
|
211
|
+
|
|
212
|
+
if summary[:primary_key_types].any?
|
|
213
|
+
puts "Primary key types:"
|
|
214
|
+
summary[:primary_key_types].sort.each do |type, count|
|
|
215
|
+
puts " #{type}: #{count} fixtures"
|
|
216
|
+
end
|
|
217
|
+
puts
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if standardizer.updates_needed.any?
|
|
221
|
+
puts "=== Fixtures Needing ID Standardization ==="
|
|
222
|
+
standardizer.updates_needed.each do |update|
|
|
223
|
+
puts
|
|
224
|
+
puts "File: #{update.file}"
|
|
225
|
+
puts "Model: #{update.model_class.name}"
|
|
226
|
+
puts "Primary Key: #{update.primary_key} (#{update.primary_key_type})"
|
|
227
|
+
puts "Changes needed:"
|
|
228
|
+
update.changes.each do |change|
|
|
229
|
+
puts " #{change.label}:"
|
|
230
|
+
puts " Current: #{change.current_id.inspect}"
|
|
231
|
+
puts " Should be: #{change.new_id}"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
else
|
|
235
|
+
puts "✅ All fixture IDs are properly standardized!"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
if summary[:errors].any?
|
|
239
|
+
puts
|
|
240
|
+
puts "=== Errors ==="
|
|
241
|
+
summary[:errors].each do |error|
|
|
242
|
+
puts "File: #{error[:file]}"
|
|
243
|
+
puts "Error: #{error[:error]}"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
desc 'Standardize fixture IDs to use ActiveRecord::FixtureSet.identify'
|
|
249
|
+
task standardize_fixture_ids: :environment do
|
|
250
|
+
require Rails.root.join('test/test_helper')
|
|
251
|
+
|
|
252
|
+
standardizer = Vial::FixtureIdStandardizer.new
|
|
253
|
+
standardizer.analyze
|
|
254
|
+
|
|
255
|
+
summary = standardizer.summary
|
|
256
|
+
|
|
257
|
+
if summary[:records_to_update] == 0
|
|
258
|
+
puts "✅ All fixture IDs are already standardized!"
|
|
259
|
+
exit 0
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
puts "Will update #{summary[:records_to_update]} records across #{summary[:fixtures_needing_updates]} files."
|
|
263
|
+
puts
|
|
264
|
+
print "Continue? (y/N): "
|
|
265
|
+
|
|
266
|
+
response = STDIN.gets.chomp.downcase
|
|
267
|
+
|
|
268
|
+
if response == 'y'
|
|
269
|
+
puts
|
|
270
|
+
standardizer.standardize!(dry_run: false)
|
|
271
|
+
puts
|
|
272
|
+
puts "✅ Fixture IDs have been standardized!"
|
|
273
|
+
else
|
|
274
|
+
puts "Cancelled."
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
desc 'Dry run of fixture ID standardization (preview changes without applying)'
|
|
279
|
+
task 'standardize_fixture_ids:dry_run' => :environment do
|
|
280
|
+
require Rails.root.join('test/test_helper')
|
|
281
|
+
|
|
282
|
+
standardizer = Vial::FixtureIdStandardizer.new
|
|
283
|
+
standardizer.analyze
|
|
284
|
+
|
|
285
|
+
summary = standardizer.summary
|
|
286
|
+
|
|
287
|
+
puts "=== Vial Fixture ID Standardization (DRY RUN) ==="
|
|
288
|
+
puts
|
|
289
|
+
puts "Would update #{summary[:records_to_update]} records across #{summary[:fixtures_needing_updates]} files."
|
|
290
|
+
puts
|
|
291
|
+
|
|
292
|
+
standardizer.standardize!(dry_run: true)
|
|
293
|
+
|
|
294
|
+
if summary[:errors].any?
|
|
295
|
+
puts
|
|
296
|
+
puts "=== Errors ==="
|
|
297
|
+
summary[:errors].each do |error|
|
|
298
|
+
puts "File: #{error[:file]}"
|
|
299
|
+
puts "Error: #{error[:error]}"
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|