fixturebot-rails 0.2.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 +7 -0
- data/.rspec +4 -0
- data/CHANGELOG.md +36 -0
- data/LICENSE +21 -0
- data/README.md +322 -0
- data/Rakefile +8 -0
- data/exe/fixturebot +11 -0
- data/lib/fixturebot/cli.rb +57 -0
- data/lib/fixturebot/compiler.rb +30 -0
- data/lib/fixturebot/default.rb +39 -0
- data/lib/fixturebot/definition.rb +44 -0
- data/lib/fixturebot/fixture_set.rb +29 -0
- data/lib/fixturebot/key.rb +11 -0
- data/lib/fixturebot/minitest.rb +5 -0
- data/lib/fixturebot/rails/cli.rb +26 -0
- data/lib/fixturebot/rails/railtie.rb +18 -0
- data/lib/fixturebot/rails/schema_loader.rb +115 -0
- data/lib/fixturebot/rails.rb +55 -0
- data/lib/fixturebot/row.rb +127 -0
- data/lib/fixturebot/rspec.rb +9 -0
- data/lib/fixturebot/schema.rb +74 -0
- data/lib/fixturebot/version.rb +5 -0
- data/lib/fixturebot.rb +49 -0
- data/lib/generators/fixturebot/install_generator.rb +37 -0
- data/lib/generators/fixturebot/templates/fixtures.rb.tt +16 -0
- data/playground/blog/fixtures.rb +54 -0
- data/playground/blog/schema.rb +16 -0
- data/sig/fixturebot.rbs +4 -0
- metadata +116 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 47f7ad238dac229d37e2bdef254074b14d31657e57775db19f47cbddc06ffe16
|
|
4
|
+
data.tar.gz: e8c506013cc891b6f2f2d93e05d044c53ee5687ffa7cb628a7bf472f34fa4056
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f801adcf5885114ba66a13a9d7d472b908c9b6ed7de49426c007a70efced6293ba12683253b9cbe7bb38cceaf97902f2642acea3e44ec8522d74da21a57038bb
|
|
7
|
+
data.tar.gz: c6972d1aed753deed6d109126302ac2267cf99dddb4e5ff45f981bb63753831292e5a44f2d743f11a5a9680f9b088152234ccef8184f1c07ecb0b33de9ee0dfa
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- **Generator block parameter:** Generator blocks now receive a `fixture` object as a block parameter instead of the magic `name` method. Use `fixture.key` to get the record's symbol name. Column values are available as bare methods inside the block.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Before
|
|
11
|
+
user.email { "#{name}@example.com" }
|
|
12
|
+
|
|
13
|
+
# After
|
|
14
|
+
user.email { |fixture| "#{fixture.key}@example.com" }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Improvements
|
|
18
|
+
|
|
19
|
+
- Rename `YamlDumper` to `Compiler`, `generate`/`dump` to `compile`
|
|
20
|
+
- Rename rake task from `fixturebot:generate` to `fixturebot:compile`
|
|
21
|
+
- Strip YAML document prefix (`---`) from compiled output
|
|
22
|
+
- Fix CLI entrypoint to detect Rails app instead of rescuing LoadError
|
|
23
|
+
- Internal refactor: group classes into `Row` and `Default` modules, replace `method_missing` with `define_singleton_method`
|
|
24
|
+
|
|
25
|
+
## 0.1.1
|
|
26
|
+
|
|
27
|
+
- Internal refactoring and README updates
|
|
28
|
+
|
|
29
|
+
## 0.1.0
|
|
30
|
+
|
|
31
|
+
- Initial release
|
|
32
|
+
- Ruby DSL for defining fixtures with generators, associations, and join tables
|
|
33
|
+
- Auto-generates YAML fixture files from database schema
|
|
34
|
+
- RSpec and Minitest integration with auto-generation hooks
|
|
35
|
+
- Rails Railtie with `config.fixturebot` namespace and `fixturebot:generate` rake task
|
|
36
|
+
- Install generator: `rails generate fixturebot:install`
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brad Gessler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# FixtureBot
|
|
2
|
+
|
|
3
|
+
The syntactic sugar of factories with the speed of fixtures.
|
|
4
|
+
|
|
5
|
+
FixtureBot lets you define your test data in a Ruby DSL and compiles it into standard Rails fixture YAML files. The generated YAML is deterministic and should be checked into git, just like a lockfile. Your tests never see FixtureBot at runtime; Rails just loads the YAML fixtures as usual.
|
|
6
|
+
|
|
7
|
+
**Features:**
|
|
8
|
+
|
|
9
|
+
- **Ruby DSL** for defining records, associations, and join tables
|
|
10
|
+
- **Generators** for filling in required columns (like email) across all records
|
|
11
|
+
- **Stable IDs** so foreign keys are consistent and diffs are clean across runs
|
|
12
|
+
- **Schema auto-detection** from your Rails database (no manual column lists)
|
|
13
|
+
- **Auto-generates** before your test suite runs (RSpec and Minitest)
|
|
14
|
+
|
|
15
|
+
## Read the article
|
|
16
|
+
|
|
17
|
+
More background at [BeautifulRuby.com](https://beautifulruby.com/code/fixturebot).
|
|
18
|
+
|
|
19
|
+
[](https://beautifulruby.com/code/fixturebot)
|
|
20
|
+
|
|
21
|
+
## Quick example
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# spec/fixtures.rb
|
|
25
|
+
FixtureBot.define do
|
|
26
|
+
# Generators fill in required columns so you don't have to repeat yourself.
|
|
27
|
+
# |fixture| gives you the fixture key; bare methods give column values.
|
|
28
|
+
user.email { |fixture| "#{fixture.key}@example.com" }
|
|
29
|
+
|
|
30
|
+
user :brad do
|
|
31
|
+
name "Brad"
|
|
32
|
+
email "brad@example.com" # overrides the generator
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
user :alice do
|
|
36
|
+
name "Alice" # email filled in by generator: "alice@example.com"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
user :deactivated do
|
|
40
|
+
name "Ghost"
|
|
41
|
+
email nil # explicit nil, generator skipped, email set to null
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
post :hello_world do
|
|
45
|
+
title "Hello World"
|
|
46
|
+
body "Welcome to the blog!"
|
|
47
|
+
author :brad # sets author_id to brad's stable ID
|
|
48
|
+
tags :ruby, :rails # creates rows in posts_tags
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
tag.name { |fixture| fixture.key.to_s.capitalize }
|
|
52
|
+
|
|
53
|
+
tag :ruby # name: "Ruby"
|
|
54
|
+
tag :rails # name: "Rails"
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This generates YAML files like `users.yml`, `posts.yml`, `tags.yml`, and `posts_tags.yml` in your fixtures directory. Use them in tests like any other fixture:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
# RSpec
|
|
62
|
+
RSpec.describe Post, type: :model do
|
|
63
|
+
fixtures :all
|
|
64
|
+
|
|
65
|
+
it "belongs to an author" do
|
|
66
|
+
post = posts(:hello_world)
|
|
67
|
+
expect(post.author).to eq(users(:brad))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Minitest
|
|
72
|
+
class PostTest < ActiveSupport::TestCase
|
|
73
|
+
def test_belongs_to_author
|
|
74
|
+
post = posts(:hello_world)
|
|
75
|
+
assert_equal users(:brad), post.author
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
|
|
82
|
+
Add to your Gemfile:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
gem "fixturebot-rails"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Rails generator
|
|
89
|
+
|
|
90
|
+
The easiest way to get started:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
rails generate fixturebot:install
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This creates `spec/fixtures.rb` (RSpec) or `test/fixtures.rb` (Minitest) with a skeleton DSL file and adds the appropriate require to your test helper.
|
|
97
|
+
|
|
98
|
+
### Manual setup
|
|
99
|
+
|
|
100
|
+
#### RSpec
|
|
101
|
+
|
|
102
|
+
Add to `spec/rails_helper.rb`:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
require "fixturebot/rspec"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Create `spec/fixtures.rb` with your fixture definitions.
|
|
109
|
+
|
|
110
|
+
Fixtures are auto-generated before each suite run, no rake task needed.
|
|
111
|
+
|
|
112
|
+
#### Minitest
|
|
113
|
+
|
|
114
|
+
Add to `test/test_helper.rb`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
require "fixturebot/minitest"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Create `test/fixtures.rb` with your fixture definitions.
|
|
121
|
+
|
|
122
|
+
Fixtures are auto-generated when the helper is loaded, no rake task needed.
|
|
123
|
+
|
|
124
|
+
### Rake task
|
|
125
|
+
|
|
126
|
+
A `fixturebot:compile` rake task is also available if you prefer manual control:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
bundle exec rake fixturebot:compile
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Configuration
|
|
133
|
+
|
|
134
|
+
FixtureBot auto-detects your fixtures file (`test/fixtures.rb` or `spec/fixtures.rb`) and derives the output directory by stripping `.rb` (e.g. `spec/fixtures.rb` writes to `spec/fixtures/`). To override:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
# config/application.rb or config/environments/test.rb
|
|
138
|
+
config.fixturebot.fixtures_file = "test/my_fixtures.rb"
|
|
139
|
+
config.fixturebot.output_dir = "test/fixtures"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## The DSL
|
|
143
|
+
|
|
144
|
+
### Records
|
|
145
|
+
|
|
146
|
+
Define named records with literal column values:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
FixtureBot.define do
|
|
150
|
+
user :brad do
|
|
151
|
+
name "Brad"
|
|
152
|
+
email "brad@example.com"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Records without a block get an auto-generated ID and any generator defaults:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
FixtureBot.define do
|
|
161
|
+
user.email { |fixture| "#{fixture.key}@example.com" }
|
|
162
|
+
|
|
163
|
+
user :alice
|
|
164
|
+
# => { id: <stable_id>, email: "alice@example.com" }
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Generators
|
|
169
|
+
|
|
170
|
+
Generators set default column values. They run for each record that doesn't explicitly set that column. Generators are never created implicitly; columns without a value or generator are omitted from the YAML output (Rails uses the database column default).
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
FixtureBot.define do
|
|
174
|
+
user.email { |fixture| "#{fixture.key}@example.com" }
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The generator block receives a `fixture` object as a block parameter with access to `fixture.key` (the record's symbol name). Bare methods inside the block refer to column values set on the record.
|
|
179
|
+
|
|
180
|
+
When a generator covers all the columns you need, records don't need a block at all:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
FixtureBot.define do
|
|
184
|
+
tag.name { |fixture| fixture.key.to_s.capitalize }
|
|
185
|
+
|
|
186
|
+
tag :ruby # name: "Ruby"
|
|
187
|
+
tag :rails # name: "Rails"
|
|
188
|
+
tag :testing # name: "Testing"
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
A literal value shadows the generator. An explicit `nil` also shadows it:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
FixtureBot.define do
|
|
196
|
+
user.email { |fixture| "#{fixture.key}@example.com" }
|
|
197
|
+
|
|
198
|
+
user :brad do
|
|
199
|
+
name "Brad"
|
|
200
|
+
email "brad@hey.com" # literal, skips the generator
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
user :alice do
|
|
204
|
+
name "Alice"
|
|
205
|
+
# no email set, generator produces "alice@example.com"
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
user :deactivated do
|
|
209
|
+
name "Ghost"
|
|
210
|
+
email nil # explicit nil, skips the generator, sets email to null
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Associations
|
|
216
|
+
|
|
217
|
+
Reference other records by name for `belongs_to`:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
FixtureBot.define do
|
|
221
|
+
post :hello_world do
|
|
222
|
+
title "Hello World"
|
|
223
|
+
author :brad # sets author_id to brad's stable ID
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Join tables (HABTM)
|
|
229
|
+
|
|
230
|
+
Reference multiple records for join table associations:
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
FixtureBot.define do
|
|
234
|
+
post :hello_world do
|
|
235
|
+
title "Hello World"
|
|
236
|
+
tags :ruby, :rails # creates rows in posts_tags
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Implicit vs explicit style
|
|
242
|
+
|
|
243
|
+
By default, the block is evaluated implicitly. Table methods like `user` and `post` are available directly:
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
FixtureBot.define do
|
|
247
|
+
user :brad do
|
|
248
|
+
name "Brad"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
If you prefer an explicit receiver (useful for editor autocompletion or clarity in large files), pass a block argument:
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
FixtureBot.define do |t|
|
|
257
|
+
t.user :brad do
|
|
258
|
+
name "Brad"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Both styles are equivalent. Record blocks (the inner `do...end`) are always implicit.
|
|
264
|
+
|
|
265
|
+
### Schema
|
|
266
|
+
|
|
267
|
+
FixtureBot reads your database schema automatically in Rails. Outside of Rails, you can define it by hand:
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
FixtureBot::Schema.define do
|
|
271
|
+
table :users, singular: :user, columns: [:name, :email]
|
|
272
|
+
|
|
273
|
+
table :posts, singular: :post, columns: [:title, :body, :author_id] do
|
|
274
|
+
belongs_to :author, table: :users
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
table :tags, singular: :tag, columns: [:name]
|
|
278
|
+
|
|
279
|
+
join_table :posts_tags, :posts, :tags
|
|
280
|
+
end
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Prior art
|
|
284
|
+
|
|
285
|
+
### [Rails fixtures](https://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures)
|
|
286
|
+
|
|
287
|
+
Rails fixtures are YAML files that get loaded into the database once before your test suite runs. Each test wraps in a transaction and rolls back, so the data is always clean and tests are fast. This is the approach FixtureBot builds on.
|
|
288
|
+
|
|
289
|
+
The problem is writing YAML by hand. Foreign keys are magic strings (`author: brad`), there's no way to DRY up repeated columns, and large fixture files are hard to read. FixtureBot gives you a Ruby DSL that compiles down to the same YAML, so you keep the speed of fixtures without the pain of maintaining them.
|
|
290
|
+
|
|
291
|
+
### [FactoryBot](https://github.com/thoughtbot/factory_bot)
|
|
292
|
+
|
|
293
|
+
FactoryBot creates records on the fly inside each test. You call `create(:user)` and it inserts a row. This makes tests self-contained and easy to read, but it's slow. Every test that needs data pays the cost of inserting records, and complex object graphs lead to cascading `create` calls.
|
|
294
|
+
|
|
295
|
+
FixtureBot borrows the DSL feel of FactoryBot (named records, associations by symbol, default generators) but compiles to fixtures instead of inserting at runtime. You get the ergonomics of factories with the speed of fixtures.
|
|
296
|
+
|
|
297
|
+
### [Oaken](https://github.com/kaspth/oaken)
|
|
298
|
+
|
|
299
|
+
Oaken and FixtureBot share the same motivation: replace hand-written YAML fixtures with a Ruby DSL. They take very different approaches.
|
|
300
|
+
|
|
301
|
+
**Oaken inserts records into the database** at runtime using `ActiveRecord::Base#create!`. It also supports loading different seed files per test case (`seed "cases/pagination"`), which means your data set can vary across tests. This flexibility comes at a cost: you lose the "load once, wrap every test in a transaction" speed advantage that makes Rails fixtures fast. It's closer to factories in that regard, with more structure around organizing seed scripts.
|
|
302
|
+
|
|
303
|
+
**FixtureBot is more opinionated.** One fixture file, one data set, compiled to plain YAML and checked into git. At test time, FixtureBot is out of the picture entirely. Rails loads the YAML fixtures once and wraps each test in a transaction as usual. No runtime dependency, no per-test seeding, no seed file organization to manage.
|
|
304
|
+
|
|
305
|
+
| | FixtureBot | Rails fixtures | FactoryBot | Oaken |
|
|
306
|
+
|---|---|---|---|---|
|
|
307
|
+
| **Define data in** | Ruby DSL | YAML | Ruby DSL | Ruby scripts |
|
|
308
|
+
| **Output** | YAML files in git | YAML files in git | Database rows per test | Database rows at boot |
|
|
309
|
+
| **Runtime dependency** | None | None | Required per test | Required at boot |
|
|
310
|
+
| **Data set** | One set, loaded once | One set, loaded once | Built per test | Per-test via seed files |
|
|
311
|
+
| **Speed** | Fast (fixtures) | Fast (fixtures) | Slow (inserts per test) | Varies |
|
|
312
|
+
| **Stable IDs** | Deterministic | Deterministic | Database-assigned | Database-assigned |
|
|
313
|
+
|
|
314
|
+
## Development
|
|
315
|
+
|
|
316
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt.
|
|
317
|
+
|
|
318
|
+
Try the playground without Rails:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
bundle exec exe/fixturebot show ./playground/blog
|
|
322
|
+
```
|
data/Rakefile
ADDED
data/exe/fixturebot
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module FixtureBot
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
def self.exit_on_failure?
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
desc "compile DIR", "Compile DIR/schema.rb and DIR/fixtures.rb to YAML fixture files"
|
|
12
|
+
def compile(dir)
|
|
13
|
+
schema_path = File.join(dir, "schema.rb")
|
|
14
|
+
fixtures_path = File.join(dir, "fixtures.rb")
|
|
15
|
+
|
|
16
|
+
raise Thor::Error, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
|
17
|
+
raise Thor::Error, "Fixtures file not found: #{fixtures_path}" unless File.exist?(fixtures_path)
|
|
18
|
+
|
|
19
|
+
schema = eval(File.read(schema_path), binding, schema_path, 1)
|
|
20
|
+
fixture_set = FixtureBot.define_from_file(schema, fixtures_path)
|
|
21
|
+
|
|
22
|
+
output_dir = File.join(dir, "fixtures")
|
|
23
|
+
Compiler.new(fixture_set).compile(output_dir)
|
|
24
|
+
|
|
25
|
+
say "Compiled fixtures to #{output_dir}/"
|
|
26
|
+
fixture_set.tables.each do |table_name, records|
|
|
27
|
+
next if records.empty?
|
|
28
|
+
say " #{table_name}.yml (#{records.size} records)"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "show DIR", "Compile DIR/schema.rb and DIR/fixtures.rb, then print YAML to stdout"
|
|
33
|
+
def show(dir)
|
|
34
|
+
schema_path = File.join(dir, "schema.rb")
|
|
35
|
+
fixtures_path = File.join(dir, "fixtures.rb")
|
|
36
|
+
|
|
37
|
+
raise Thor::Error, "Schema file not found: #{schema_path}" unless File.exist?(schema_path)
|
|
38
|
+
raise Thor::Error, "Fixtures file not found: #{fixtures_path}" unless File.exist?(fixtures_path)
|
|
39
|
+
|
|
40
|
+
schema = eval(File.read(schema_path), binding, schema_path, 1)
|
|
41
|
+
fixture_set = FixtureBot.define_from_file(schema, fixtures_path)
|
|
42
|
+
compiler = FixtureBot::Compiler.new(fixture_set)
|
|
43
|
+
|
|
44
|
+
fixture_set.tables.each do |table_name, records|
|
|
45
|
+
next if records.empty?
|
|
46
|
+
puts "# #{table_name}.yml"
|
|
47
|
+
puts compiler.compile_table(table_name)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
map %w[-v --version] => :version
|
|
52
|
+
desc "version", "Show version"
|
|
53
|
+
def version
|
|
54
|
+
say "fixturebot #{FixtureBot::VERSION}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module FixtureBot
|
|
7
|
+
class Compiler
|
|
8
|
+
def initialize(fixture_set)
|
|
9
|
+
@fixture_set = fixture_set
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def compile(output_dir)
|
|
13
|
+
FileUtils.mkdir_p(output_dir)
|
|
14
|
+
@fixture_set.tables.each do |table_name, records|
|
|
15
|
+
next if records.empty?
|
|
16
|
+
path = File.join(output_dir, "#{table_name}.yml")
|
|
17
|
+
File.write(path, compile_table(table_name))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def compile_table(table_name)
|
|
22
|
+
records = @fixture_set.tables[table_name]
|
|
23
|
+
hash = {}
|
|
24
|
+
records.each do |record_name, columns|
|
|
25
|
+
hash[record_name.to_s] = columns.transform_keys(&:to_s)
|
|
26
|
+
end
|
|
27
|
+
YAML.dump(hash).delete_prefix("---\n")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FixtureBot
|
|
4
|
+
module Default
|
|
5
|
+
Fixture = Data.define(:key)
|
|
6
|
+
|
|
7
|
+
class Definition
|
|
8
|
+
def initialize(table, defaults)
|
|
9
|
+
@defaults = defaults
|
|
10
|
+
define_column_methods(table)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def define_column_methods(table)
|
|
16
|
+
table.columns.each do |col|
|
|
17
|
+
define_singleton_method(col) do |&block|
|
|
18
|
+
raise ArgumentError, "#{col} requires a block" unless block
|
|
19
|
+
@defaults[col] = block
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Context
|
|
26
|
+
def initialize(literal_values: {})
|
|
27
|
+
define_literal_value_methods(literal_values)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def define_literal_value_methods(literal_values)
|
|
33
|
+
literal_values.each do |col, val|
|
|
34
|
+
define_singleton_method(col) { val }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FixtureBot
|
|
4
|
+
class Definition
|
|
5
|
+
attr_reader :defaults, :rows
|
|
6
|
+
|
|
7
|
+
def initialize(schema)
|
|
8
|
+
@schema = schema
|
|
9
|
+
@defaults = {}
|
|
10
|
+
@rows = []
|
|
11
|
+
|
|
12
|
+
schema.tables.each_value do |table|
|
|
13
|
+
@defaults[table.name] = {}
|
|
14
|
+
define_table_method(table)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def define_table_method(table)
|
|
21
|
+
define_singleton_method(table.singular_name) do |record_name = nil, &block|
|
|
22
|
+
if record_name.nil? && block.nil?
|
|
23
|
+
Default::Definition.new(table, @defaults[table.name])
|
|
24
|
+
elsif record_name
|
|
25
|
+
add_row(table, record_name, block)
|
|
26
|
+
else
|
|
27
|
+
raise ArgumentError, "#{table.singular_name} requires a record name or no arguments"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def add_row(table, record_name, block)
|
|
33
|
+
row_def = Row::Definition.new(table, @schema)
|
|
34
|
+
row_def.instance_eval(&block) if block
|
|
35
|
+
@rows << Row::Declaration.new(
|
|
36
|
+
table: table.name,
|
|
37
|
+
name: record_name,
|
|
38
|
+
literal_values: row_def.literal_values,
|
|
39
|
+
association_refs: row_def.association_refs,
|
|
40
|
+
tag_refs: row_def.tag_refs
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FixtureBot
|
|
4
|
+
class FixtureSet
|
|
5
|
+
attr_reader :tables
|
|
6
|
+
|
|
7
|
+
def initialize(schema, definition)
|
|
8
|
+
@tables = {}
|
|
9
|
+
|
|
10
|
+
schema.tables.each_key { |name| @tables[name] = {} }
|
|
11
|
+
schema.join_tables.each_key { |name| @tables[name] = {} }
|
|
12
|
+
|
|
13
|
+
definition.rows.each do |row|
|
|
14
|
+
builder = Row::Builder.new(
|
|
15
|
+
row: row,
|
|
16
|
+
table: schema.tables[row.table],
|
|
17
|
+
defaults: definition.defaults[row.table],
|
|
18
|
+
join_tables: schema.join_tables
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@tables[row.table][row.name] = builder.record
|
|
22
|
+
|
|
23
|
+
builder.join_rows.each do |join_row|
|
|
24
|
+
@tables[join_row[:join_table]][join_row[:key]] = join_row[:row]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fixturebot/cli"
|
|
4
|
+
|
|
5
|
+
module FixtureBot
|
|
6
|
+
module Rails
|
|
7
|
+
class CLI < FixtureBot::CLI
|
|
8
|
+
desc "compile FIXTURES_FILE [OUTPUT_DIR]", "Compile FixtureBot DSL to YAML fixture files"
|
|
9
|
+
def compile(fixtures_path, output_dir = "test/fixtures")
|
|
10
|
+
unless File.exist?(fixtures_path)
|
|
11
|
+
raise Thor::Error, "Fixtures file not found: #{fixtures_path}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
schema = SchemaLoader.load
|
|
15
|
+
fixture_set = FixtureBot.define_from_file(schema, fixtures_path)
|
|
16
|
+
Compiler.new(fixture_set).compile(output_dir)
|
|
17
|
+
|
|
18
|
+
say "Compiled fixtures to #{output_dir}/"
|
|
19
|
+
fixture_set.tables.each do |table_name, records|
|
|
20
|
+
next if records.empty?
|
|
21
|
+
say " #{table_name}.yml (#{records.size} records)"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FixtureBot
|
|
4
|
+
module Rails
|
|
5
|
+
class Railtie < ::Rails::Railtie
|
|
6
|
+
config.fixturebot = ActiveSupport::OrderedOptions.new
|
|
7
|
+
|
|
8
|
+
rake_tasks do
|
|
9
|
+
namespace :fixturebot do
|
|
10
|
+
desc "Compile FixtureBot DSL to YAML fixture files"
|
|
11
|
+
task compile: :environment do
|
|
12
|
+
FixtureBot::Rails.compile
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|