fixture_kit 0.7.0 → 0.8.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 +26 -248
- data/lib/fixture_kit/cache.rb +1 -20
- data/lib/fixture_kit/callbacks.rb +37 -0
- data/lib/fixture_kit/configuration.rb +18 -19
- data/lib/fixture_kit/fixture.rb +23 -4
- data/lib/fixture_kit/repository.rb +27 -6
- data/lib/fixture_kit/version.rb +1 -1
- data/lib/fixture_kit.rb +11 -11
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed0ea806a1b28f87c19ac24766790a392042ea6e4bd4888e9c7dd6e0787b70ec
|
|
4
|
+
data.tar.gz: f726a2965d20fbcd35b12f561186b1130bd32cc1b0825877ef0900e824867170
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bf15388944864caa344d383733162ba262c6198be2845b1a8ada616966c725bbd0515daaf3788ab4a53564148cc18b10bef244980c2fc6f8d6993f7d252b1df
|
|
7
|
+
data.tar.gz: c4b2530715dc65912c6fb5f22ece5f4b1cf76bd4bb20e816242365c8ef2c53f9936dc57eb48092205228afe079a1782626ce241b9183b24d37b31a1d87758d8c
|
data/README.md
CHANGED
|
@@ -2,20 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Fast test fixtures with SQL caching.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Test data setup is slow. Every `Model.create!` or `FactoryBot.create` hits the database, and complex test scenarios can require dozens of inserts per test.
|
|
8
|
-
|
|
9
|
-
## The Solution
|
|
10
|
-
|
|
11
|
-
FixtureKit caches database records as raw SQL INSERT statements. It executes your fixture definition once, captures the resulting database state, and generates optimized batch INSERT statements. Fixture loads then replay these statements directly: no ORM overhead, no callbacks, just fast SQL.
|
|
12
|
-
|
|
13
|
-
Combined with framework transactions (`use_transactional_fixtures` in RSpec, `use_transactional_tests` in Minitest), each test runs in a transaction that rolls back, so cached data can be reused safely between tests.
|
|
5
|
+
- Full documentation (guides): [GitHub Wiki](https://github.com/Gusto/fixture_kit/wiki)
|
|
6
|
+
- API/reference (canonical): [docs/reference.md](docs/reference.md)
|
|
14
7
|
|
|
15
8
|
## Installation
|
|
16
9
|
|
|
17
|
-
Add to your Gemfile:
|
|
18
|
-
|
|
19
10
|
```ruby
|
|
20
11
|
group :test do
|
|
21
12
|
gem "fixture_kit"
|
|
@@ -24,105 +15,21 @@ end
|
|
|
24
15
|
|
|
25
16
|
## Quick Start
|
|
26
17
|
|
|
27
|
-
### 1. Define a
|
|
28
|
-
|
|
29
|
-
Create fixture files in `spec/fixture_kit/` (or `test/fixture_kit/` for test-unit/minitest-style setups). Use whatever method you prefer to create records.
|
|
30
|
-
|
|
31
|
-
**Using ActiveRecord directly:**
|
|
32
|
-
|
|
33
|
-
```ruby
|
|
34
|
-
# spec/fixture_kit/bookstore.rb
|
|
35
|
-
FixtureKit.define do
|
|
36
|
-
store = Store.create!(name: "Powell's Books")
|
|
37
|
-
owner = User.create!(name: "Alice", email: "alice@example.com", store: store)
|
|
38
|
-
|
|
39
|
-
books = 3.times.map do |i|
|
|
40
|
-
Book.create!(title: "Book #{i + 1}", store: store)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
featured = Book.create!(title: "Dune", store: store, featured: true)
|
|
44
|
-
|
|
45
|
-
expose(store: store, owner: owner, books: books, featured: featured)
|
|
46
|
-
end
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Using FactoryBot:**
|
|
18
|
+
### 1. Define a fixture
|
|
50
19
|
|
|
51
20
|
```ruby
|
|
52
|
-
# spec/fixture_kit/
|
|
21
|
+
# spec/fixture_kit/project_management.rb
|
|
53
22
|
FixtureKit.define do
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
books = FactoryBot.create_list(:book, 3, store: store)
|
|
57
|
-
featured = FactoryBot.create(:book, :bestseller, store: store, title: "Dune")
|
|
23
|
+
owner = User.create!(name: "Alice", email: "alice@example.com")
|
|
24
|
+
project = Project.create!(name: "Roadmap", owner: owner)
|
|
58
25
|
|
|
59
|
-
expose(
|
|
26
|
+
expose(owner: owner, project: project)
|
|
60
27
|
end
|
|
61
28
|
```
|
|
62
29
|
|
|
63
|
-
|
|
30
|
+
### 2. Configure your framework
|
|
64
31
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
FixtureKit.define do
|
|
69
|
-
# Set up users
|
|
70
|
-
admin = User.create!(name: "Admin", role: "admin")
|
|
71
|
-
member = User.create!(name: "Member", role: "member")
|
|
72
|
-
expose(admin: admin, member: member)
|
|
73
|
-
|
|
74
|
-
# Set up projects
|
|
75
|
-
project = Project.create!(name: "Website", owner: admin)
|
|
76
|
-
expose(project: project)
|
|
77
|
-
|
|
78
|
-
# Set up tasks
|
|
79
|
-
tasks = 3.times.map { |i| Task.create!(title: "Task #{i + 1}", project: project) }
|
|
80
|
-
expose(tasks: tasks)
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Exposing the same name twice raises `FixtureKit::DuplicateNameError`.
|
|
85
|
-
|
|
86
|
-
### 2. Use in Tests
|
|
87
|
-
|
|
88
|
-
```ruby
|
|
89
|
-
# spec/models/book_spec.rb
|
|
90
|
-
RSpec.describe Book do
|
|
91
|
-
fixture "bookstore"
|
|
92
|
-
|
|
93
|
-
it "belongs to a store" do
|
|
94
|
-
expect(fixture.featured.store).to eq(fixture.store)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
it "has multiple books" do
|
|
98
|
-
expect(fixture.books.size).to eq(3)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "exposes records as methods" do
|
|
102
|
-
expect(fixture.owner.email).to eq("alice@example.com")
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
`fixture` returns a `Repository` and exposes records as methods (for example, `fixture.owner`).
|
|
108
|
-
|
|
109
|
-
You can also define fixtures anonymously inline:
|
|
110
|
-
|
|
111
|
-
```ruby
|
|
112
|
-
RSpec.describe Book do
|
|
113
|
-
fixture do
|
|
114
|
-
owner = User.create!(name: "Alice", email: "alice@example.com")
|
|
115
|
-
featured = Book.create!(title: "Dune", owner: owner)
|
|
116
|
-
expose(owner: owner, featured: featured)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
it "uses inline fixture data" do
|
|
120
|
-
expect(fixture.featured.owner).to eq(fixture.owner)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### 3. Configure RSpec
|
|
32
|
+
RSpec:
|
|
126
33
|
|
|
127
34
|
```ruby
|
|
128
35
|
# spec/rails_helper.rb
|
|
@@ -133,9 +40,7 @@ RSpec.configure do |config|
|
|
|
133
40
|
end
|
|
134
41
|
```
|
|
135
42
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
### 4. Configure Minitest
|
|
43
|
+
Minitest:
|
|
139
44
|
|
|
140
45
|
```ruby
|
|
141
46
|
# test/test_helper.rb
|
|
@@ -146,160 +51,33 @@ class ActiveSupport::TestCase
|
|
|
146
51
|
end
|
|
147
52
|
```
|
|
148
53
|
|
|
149
|
-
|
|
54
|
+
### 3. Use the fixture in tests
|
|
150
55
|
|
|
151
|
-
|
|
56
|
+
RSpec:
|
|
152
57
|
|
|
153
58
|
```ruby
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# Where fixture definitions live (default: fixture_kit).
|
|
157
|
-
# Framework entrypoints set a framework-specific default:
|
|
158
|
-
# - fixture_kit/rspec -> spec/fixture_kit
|
|
159
|
-
# - fixture_kit/minitest -> test/fixture_kit
|
|
160
|
-
config.fixture_path = Rails.root.join("spec/fixture_kit").to_s
|
|
59
|
+
RSpec.describe Project do
|
|
60
|
+
fixture "project_management"
|
|
161
61
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
# Adapter used to isolate generation work (default: FixtureKit::MinitestAdapter)
|
|
166
|
-
# config.adapter(FixtureKit::MinitestAdapter)
|
|
167
|
-
# config.adapter(FixtureKit::RSpecAdapter)
|
|
168
|
-
# config.adapter(CustomAdapter, option1: "value1")
|
|
169
|
-
#
|
|
170
|
-
# Calling `adapter` with args sets adapter class + options.
|
|
171
|
-
# Calling `adapter` with no args returns the configured adapter class.
|
|
172
|
-
|
|
173
|
-
# Optional callback, called right before a fixture cache is generated.
|
|
174
|
-
# Called on first generation and forced regeneration.
|
|
175
|
-
# Receives the cache identifier as a String (path-like, without ".json").
|
|
176
|
-
# - named fixtures: "teams/basic"
|
|
177
|
-
# - anonymous fixtures: "_anonymous/foo/with_fixture_kit/hello"
|
|
178
|
-
# config.on_cache_save = ->(identifier) { puts "cached #{identifier}" }
|
|
179
|
-
|
|
180
|
-
# Optional callback, called right before a fixture cache is mounted.
|
|
181
|
-
# Receives the same String cache identifier as on_cache_save.
|
|
182
|
-
# config.on_cache_mount = ->(identifier) { puts "mounted #{identifier}" }
|
|
62
|
+
it "loads exposed records" do
|
|
63
|
+
expect(fixture.project.owner).to eq(fixture.owner)
|
|
64
|
+
end
|
|
183
65
|
end
|
|
184
66
|
```
|
|
185
67
|
|
|
186
|
-
|
|
187
|
-
- `#execute`
|
|
188
|
-
- `#identifier_for`
|
|
189
|
-
|
|
190
|
-
`#execute` receives the generation block and should run it in whatever lifecycle you need.
|
|
191
|
-
`#identifier_for` receives a non-string fixture identifier (for anonymous fixtures) and must return a normalized String identifier. Cache namespace/prefixing is applied by `FixtureKit::Cache`.
|
|
192
|
-
|
|
193
|
-
Options passed via `config.adapter(...)` are provided to your adapter initializer as a hash and available as `options`.
|
|
194
|
-
|
|
195
|
-
By default, FixtureKit uses `FixtureKit::MinitestAdapter`, which runs generation inside an internal `ActiveSupport::TestCase` and removes that harness case from minitest runnables.
|
|
196
|
-
|
|
197
|
-
When using `fixture_kit/rspec`, FixtureKit sets `FixtureKit::RSpecAdapter`. It runs generation inside an internal RSpec example, and uses a null reporter so harness runs do not count toward suite example totals.
|
|
198
|
-
|
|
199
|
-
## Lifecycle
|
|
200
|
-
|
|
201
|
-
Fixture generation is managed by `FixtureKit::Runner`.
|
|
202
|
-
|
|
203
|
-
1. Calling `fixture "name"` or `fixture do ... end` registers the fixture with the runner.
|
|
204
|
-
2. Runner `start`:
|
|
205
|
-
- clears `cache_path` (unless preserve-cache is enabled).
|
|
206
|
-
3. Cache generation is framework-driven:
|
|
207
|
-
- RSpec: each declaring example group runs `fixture.cache` in `before(:context)`.
|
|
208
|
-
- Minitest: each declaring test class runs `fixture.cache` in class-level `run_suite` before test methods execute.
|
|
209
|
-
4. At test runtime, `fixture` mounts from cache and returns a `Repository`.
|
|
210
|
-
|
|
211
|
-
When runner start happens:
|
|
212
|
-
|
|
213
|
-
- `fixture_kit/rspec`: in `before(:suite)`.
|
|
214
|
-
- `fixture_kit/minitest`: in class-level `run_suite` for test classes that declare `fixture`.
|
|
215
|
-
|
|
216
|
-
## Fixture Declaration Rules
|
|
217
|
-
|
|
218
|
-
- Only one `fixture` declaration is allowed per test context.
|
|
219
|
-
- Declaring a fixture twice in the same context raises `FixtureKit::MultipleFixtures`.
|
|
220
|
-
- Child contexts/classes can declare their own fixture and override parent declarations.
|
|
221
|
-
- Providing both a name and a block (or neither) raises `FixtureKit::InvalidFixtureDeclaration`.
|
|
222
|
-
|
|
223
|
-
### Preserving Cache Locally
|
|
224
|
-
|
|
225
|
-
If you want to skip cache clearing when the runner starts (e.g., to reuse caches across test runs during local development), set the `FIXTURE_KIT_PRESERVE_CACHE` environment variable:
|
|
226
|
-
|
|
227
|
-
```bash
|
|
228
|
-
FIXTURE_KIT_PRESERVE_CACHE=1 bundle exec rspec
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Truthy values are case-insensitive: `1`, `true`, `yes`.
|
|
232
|
-
|
|
233
|
-
This is useful when you're iterating on tests and your fixture definitions haven't changed.
|
|
234
|
-
|
|
235
|
-
## Nested Fixtures
|
|
236
|
-
|
|
237
|
-
Organize fixtures in subdirectories:
|
|
68
|
+
Minitest:
|
|
238
69
|
|
|
239
70
|
```ruby
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
# ...
|
|
243
|
-
end
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
```ruby
|
|
247
|
-
fixture "teams/sales"
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
## Anonymous Fixture Cache Paths
|
|
251
|
-
|
|
252
|
-
Anonymous fixture caches are written under the `_anonymous/` directory inside `cache_path`.
|
|
253
|
-
That `_anonymous/...` value is also the cache identifier passed to `on_cache_save` and `on_cache_mount`.
|
|
254
|
-
|
|
255
|
-
- Minitest: class name is underscored into a path.
|
|
256
|
-
- `MyFeatureTest` -> `_anonymous/my_feature_test.json`
|
|
257
|
-
- RSpec: class name is underscored after removing `RSpec::ExampleGroups::`.
|
|
258
|
-
- `RSpec::ExampleGroups::Foo::WithFixtureKit::Hello` -> `_anonymous/foo/with_fixture_kit/hello.json`
|
|
259
|
-
|
|
260
|
-
## How It Works
|
|
261
|
-
|
|
262
|
-
1. **Cache generation**: FixtureKit executes your definition block inside the configured adapter, subscribes to `sql.active_record` notifications to track model writes (`Insert`, `Upsert`, `Create`, `Update`, `Delete`, `Destroy`, including bulk variants), queries those model tables, and caches SQL statements for current table contents.
|
|
263
|
-
|
|
264
|
-
2. **Mounting**: FixtureKit loads the cached JSON file, clears each tracked table, and executes the raw SQL INSERT statements directly. No ORM instantiation, no callbacks.
|
|
265
|
-
|
|
266
|
-
3. **Repository build**: FixtureKit resolves exposed records by model + id and returns a `Repository` for method-based access.
|
|
71
|
+
class ProjectTest < ActiveSupport::TestCase
|
|
72
|
+
fixture "project_management"
|
|
267
73
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
Caches are stored as JSON files in `tmp/cache/fixture_kit/`:
|
|
273
|
-
|
|
274
|
-
```json
|
|
275
|
-
{
|
|
276
|
-
"records": {
|
|
277
|
-
"User": "INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com'), (2, 'Bob', 'bob@example.com')",
|
|
278
|
-
"Project": "INSERT INTO projects (id, name, user_id) VALUES (1, 'Website', 1)"
|
|
279
|
-
},
|
|
280
|
-
"exposed": {
|
|
281
|
-
"alice": { "model": "User", "id": 1 },
|
|
282
|
-
"bob": { "model": "User", "id": 2 },
|
|
283
|
-
"project": { "model": "Project", "id": 1 }
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
- **records**: Maps model names to their INSERT statements (or `null` when a tracked table is empty). Using model names (not table names) allows FixtureKit to use the correct database connection for multi-database setups.
|
|
289
|
-
- **exposed**: Maps fixture accessor names to their model class and ID for querying after cache replay.
|
|
290
|
-
|
|
291
|
-
## Cache Management
|
|
292
|
-
|
|
293
|
-
Delete the cache directory to force regeneration:
|
|
294
|
-
```bash
|
|
295
|
-
rm -rf tmp/cache/fixture_kit
|
|
74
|
+
test "loads exposed records" do
|
|
75
|
+
assert_equal fixture.owner, fixture.project.owner
|
|
76
|
+
end
|
|
77
|
+
end
|
|
296
78
|
```
|
|
297
79
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
## Multi-Database Support
|
|
301
|
-
|
|
302
|
-
FixtureKit automatically handles multiple databases. Records are stored by model name in the cache, and when replaying, FixtureKit uses each model's database connection to execute the INSERT statements. This means records are automatically inserted into the correct database without any additional configuration.
|
|
80
|
+
`fixture` returns a `Repository`, and exposed names become reader methods.
|
|
303
81
|
|
|
304
82
|
## Requirements
|
|
305
83
|
|
|
@@ -309,4 +87,4 @@ FixtureKit automatically handles multiple databases. Records are stored by model
|
|
|
309
87
|
|
|
310
88
|
## License
|
|
311
89
|
|
|
312
|
-
MIT
|
|
90
|
+
MIT. See [LICENSE](LICENSE).
|
data/lib/fixture_kit/cache.rb
CHANGED
|
@@ -50,7 +50,7 @@ module FixtureKit
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
Repository.new(@data.fetch("exposed"))
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def save
|
|
@@ -69,27 +69,8 @@ module FixtureKit
|
|
|
69
69
|
File.write(path, JSON.pretty_generate(@data))
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
# Query exposed records from the database and return a Repository.
|
|
73
|
-
def build_repository(exposed)
|
|
74
|
-
exposed_records = exposed.each_with_object({}) do |(name, value), hash|
|
|
75
|
-
was_array = value.is_a?(Array)
|
|
76
|
-
records = Array.wrap(value).map { |record_info| find_exposed_record(record_info.fetch("model"), record_info.fetch("id"), name) }
|
|
77
|
-
hash[name.to_sym] = was_array ? records : records.first
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
Repository.new(exposed_records)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
72
|
private
|
|
84
73
|
|
|
85
|
-
def find_exposed_record(model_name, id, exposed_name)
|
|
86
|
-
model = ActiveSupport::Inflector.constantize(model_name)
|
|
87
|
-
model.find(id)
|
|
88
|
-
rescue ActiveRecord::RecordNotFound
|
|
89
|
-
raise FixtureKit::ExposedRecordNotFound,
|
|
90
|
-
"Could not find #{model_name} with id=#{id} for exposed record '#{exposed_name}' in fixture '#{@fixture.identifier}'"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
74
|
def generate_statements(models)
|
|
94
75
|
statements_by_model = {}
|
|
95
76
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FixtureKit
|
|
4
|
+
class Callbacks
|
|
5
|
+
EVENTS = [
|
|
6
|
+
:cache_save,
|
|
7
|
+
:cache_saved,
|
|
8
|
+
:cache_mount,
|
|
9
|
+
:cache_mounted
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@callbacks = Hash.new { |hash, key| hash[key] = [] }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(event, &block)
|
|
17
|
+
validate_event!(event)
|
|
18
|
+
@callbacks[event] << block if block
|
|
19
|
+
@callbacks[event]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run(event, *args)
|
|
23
|
+
validate_event!(event)
|
|
24
|
+
@callbacks[event].each do |callback|
|
|
25
|
+
callback.call(*args)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def validate_event!(event)
|
|
32
|
+
unless EVENTS.include?(event)
|
|
33
|
+
raise ArgumentError, "Unknown callback event: #{event}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -2,27 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module FixtureKit
|
|
4
4
|
class Configuration
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
attr_accessor :on_cache_mount
|
|
9
|
-
attr_reader :adapter_options
|
|
5
|
+
attr_accessor :fixture_path
|
|
6
|
+
attr_accessor :cache_path
|
|
7
|
+
attr_reader :adapter_options, :callbacks
|
|
10
8
|
|
|
11
9
|
def initialize
|
|
12
10
|
@fixture_path = "fixture_kit"
|
|
13
|
-
@cache_path =
|
|
11
|
+
@cache_path = "tmp/cache/fixture_kit"
|
|
14
12
|
@adapter_class = FixtureKit::MinitestAdapter
|
|
15
13
|
@adapter_options = {}
|
|
16
|
-
@
|
|
17
|
-
@on_cache_mount = nil
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def fixture_path
|
|
21
|
-
@fixture_path
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def cache_path
|
|
25
|
-
@cache_path ||= detect_cache_path
|
|
14
|
+
@callbacks = Callbacks.new
|
|
26
15
|
end
|
|
27
16
|
|
|
28
17
|
def adapter(adapter_class = nil, **options)
|
|
@@ -32,10 +21,20 @@ module FixtureKit
|
|
|
32
21
|
@adapter_options = options
|
|
33
22
|
end
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
def on_cache_save(&block)
|
|
25
|
+
callbacks.register(:cache_save, &block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_cache_saved(&block)
|
|
29
|
+
callbacks.register(:cache_saved, &block)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def on_cache_mount(&block)
|
|
33
|
+
callbacks.register(:cache_mount, &block)
|
|
34
|
+
end
|
|
36
35
|
|
|
37
|
-
def
|
|
38
|
-
|
|
36
|
+
def on_cache_mounted(&block)
|
|
37
|
+
callbacks.register(:cache_mounted, &block)
|
|
39
38
|
end
|
|
40
39
|
end
|
|
41
40
|
end
|
data/lib/fixture_kit/fixture.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "benchmark"
|
|
4
|
+
|
|
3
5
|
module FixtureKit
|
|
4
6
|
class Fixture
|
|
5
7
|
include ConfigurationHelper
|
|
@@ -15,8 +17,8 @@ module FixtureKit
|
|
|
15
17
|
def cache(force: false)
|
|
16
18
|
return if @cache.exists? && !force
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
@cache.save
|
|
20
|
+
emit(:cache_save)
|
|
21
|
+
emit(:cache_saved) { @cache.save }
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def mount
|
|
@@ -24,8 +26,25 @@ module FixtureKit
|
|
|
24
26
|
raise FixtureKit::CacheMissingError, "Cache does not exist for fixture '#{identifier}'"
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
@cache.load
|
|
29
|
+
emit(:cache_mount)
|
|
30
|
+
emit(:cache_mounted) { @cache.load }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def emit(event)
|
|
36
|
+
cache_identifier = @cache.identifier
|
|
37
|
+
unless block_given?
|
|
38
|
+
configuration.callbacks.run(event, cache_identifier)
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
value = nil
|
|
43
|
+
elapsed = Benchmark.realtime do
|
|
44
|
+
value = yield
|
|
45
|
+
end
|
|
46
|
+
configuration.callbacks.run(event, cache_identifier, elapsed)
|
|
47
|
+
value
|
|
29
48
|
end
|
|
30
49
|
end
|
|
31
50
|
end
|
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/inflector"
|
|
4
|
+
|
|
3
5
|
module FixtureKit
|
|
4
6
|
class Repository
|
|
5
7
|
def initialize(exposed_records)
|
|
6
|
-
@records = exposed_records
|
|
7
|
-
@
|
|
8
|
-
|
|
8
|
+
@records = exposed_records.transform_keys(&:to_sym)
|
|
9
|
+
@loaded_records = {}
|
|
10
|
+
define_readers
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
private
|
|
12
14
|
|
|
13
|
-
def
|
|
14
|
-
@records.
|
|
15
|
-
define_singleton_method(name) {
|
|
15
|
+
def define_readers
|
|
16
|
+
@records.each_key do |name|
|
|
17
|
+
define_singleton_method(name) { fetch(name) }
|
|
16
18
|
end
|
|
17
19
|
end
|
|
20
|
+
|
|
21
|
+
def fetch(name)
|
|
22
|
+
return @loaded_records[name] if @loaded_records.key?(name)
|
|
23
|
+
|
|
24
|
+
@loaded_records[name] = materialize(@records.fetch(name))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def materialize(value)
|
|
28
|
+
if value.is_a?(Array)
|
|
29
|
+
value.map { |record_info| load_record(record_info) }.freeze
|
|
30
|
+
else
|
|
31
|
+
load_record(value)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def load_record(record_info)
|
|
36
|
+
model = ActiveSupport::Inflector.constantize(record_info.fetch("model"))
|
|
37
|
+
model.find_by(id: record_info.fetch("id"))
|
|
38
|
+
end
|
|
18
39
|
end
|
|
19
40
|
end
|
data/lib/fixture_kit/version.rb
CHANGED
data/lib/fixture_kit.rb
CHANGED
|
@@ -7,23 +7,23 @@ module FixtureKit
|
|
|
7
7
|
class MultipleFixtures < Error; end
|
|
8
8
|
class CacheMissingError < Error; end
|
|
9
9
|
class FixtureDefinitionNotFound < Error; end
|
|
10
|
-
class ExposedRecordNotFound < Error; end
|
|
11
10
|
class RunnerAlreadyStartedError < Error; end
|
|
12
11
|
|
|
13
|
-
autoload :VERSION,
|
|
12
|
+
autoload :VERSION, File.expand_path("fixture_kit/version", __dir__)
|
|
14
13
|
autoload :Configuration, File.expand_path("fixture_kit/configuration", __dir__)
|
|
14
|
+
autoload :Callbacks, File.expand_path("fixture_kit/callbacks", __dir__)
|
|
15
15
|
autoload :ConfigurationHelper, File.expand_path("fixture_kit/configuration_helper", __dir__)
|
|
16
|
-
autoload :Singleton,
|
|
17
|
-
autoload :Fixture,
|
|
18
|
-
autoload :Definition,
|
|
19
|
-
autoload :Registry,
|
|
20
|
-
autoload :Repository,
|
|
16
|
+
autoload :Singleton, File.expand_path("fixture_kit/singleton", __dir__)
|
|
17
|
+
autoload :Fixture, File.expand_path("fixture_kit/fixture", __dir__)
|
|
18
|
+
autoload :Definition, File.expand_path("fixture_kit/definition", __dir__)
|
|
19
|
+
autoload :Registry, File.expand_path("fixture_kit/registry", __dir__)
|
|
20
|
+
autoload :Repository, File.expand_path("fixture_kit/repository", __dir__)
|
|
21
21
|
autoload :SqlSubscriber, File.expand_path("fixture_kit/sql_subscriber", __dir__)
|
|
22
|
-
autoload :Cache,
|
|
23
|
-
autoload :Runner,
|
|
24
|
-
autoload :Adapter,
|
|
22
|
+
autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
|
|
23
|
+
autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
|
|
24
|
+
autoload :Adapter, File.expand_path("fixture_kit/adapter", __dir__)
|
|
25
25
|
autoload :MinitestAdapter, File.expand_path("fixture_kit/adapters/minitest_adapter", __dir__)
|
|
26
|
-
autoload :RSpecAdapter,
|
|
26
|
+
autoload :RSpecAdapter, File.expand_path("fixture_kit/adapters/rspec_adapter", __dir__)
|
|
27
27
|
|
|
28
28
|
extend Singleton
|
|
29
29
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fixture_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ngan Pham
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '8.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: benchmark
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: irb
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -138,6 +152,7 @@ files:
|
|
|
138
152
|
- lib/fixture_kit/adapters/minitest_adapter.rb
|
|
139
153
|
- lib/fixture_kit/adapters/rspec_adapter.rb
|
|
140
154
|
- lib/fixture_kit/cache.rb
|
|
155
|
+
- lib/fixture_kit/callbacks.rb
|
|
141
156
|
- lib/fixture_kit/configuration.rb
|
|
142
157
|
- lib/fixture_kit/configuration_helper.rb
|
|
143
158
|
- lib/fixture_kit/definition.rb
|