fixture_farm 1.0.0 → 1.1.1
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 +84 -45
- data/bin/fixture_farm.rb +13 -3
- data/lib/fixture_farm/active_job_hook.rb +1 -1
- data/lib/fixture_farm/controller_hook.rb +1 -1
- data/lib/fixture_farm/fixture_recorder.rb +30 -8
- data/lib/fixture_farm/hook.rb +3 -3
- data/lib/fixture_farm/test_helper.rb +2 -2
- data/lib/fixture_farm/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3a39068e7447367066d1947cf1f14d16366d10e88e91ae922ff3953b7667ff5
|
4
|
+
data.tar.gz: 7f28f26923b51368511ed5ef8c55531f2d405ba36d11ea802caedbc554fbcf0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84a8649ace5e14d8dd09b94cdde3d15f02ebd8bd602f706b48ec48f9bff9ab4ea0170fd26827886ee8b0595909d930ce16e61d21e422df9b9e9a7c545df248b7
|
7
|
+
data.tar.gz: d25f84374a79d33ee8ee117656c59e7523d4087dd3dc9065de7a540b882b5cd2fd9897e4aee3aa23af3a8f0eb43ee5168efb2d390dc12ecbde8b1a3b0fd22656
|
data/README.md
CHANGED
@@ -9,6 +9,7 @@ A few things to note:
|
|
9
9
|
- generated fixture that `belongs_to` a record from an existing fixture, will reference that fixture by name.
|
10
10
|
- models, destroyed during recording, will be removed from fixtures (if they were originally there).
|
11
11
|
- generated `ActiveStorage::Blob` fixtures file names, will be the same as fixture names (so they can be generated multiple times, without generating new file each time).
|
12
|
+
- AR models gain `#fixture_name` method
|
12
13
|
|
13
14
|
### Limitations
|
14
15
|
|
@@ -54,89 +55,127 @@ include FixtureFarm::ActiveJobHook if defined?(FixtureFarm)
|
|
54
55
|
Then start/stop recording using tasks:
|
55
56
|
|
56
57
|
```bash
|
57
|
-
bundle exec fixture_farm record
|
58
|
+
bundle exec fixture_farm record
|
59
|
+
# OR
|
60
|
+
bundle exec fixture_farm record name_prefix
|
61
|
+
# OR
|
62
|
+
bundle exec fixture_farm record name_prefix:replaces_name
|
63
|
+
|
58
64
|
bundle exec fixture_farm status
|
59
65
|
bundle exec fixture_farm stop
|
60
66
|
```
|
61
67
|
|
62
68
|
### Record in tests
|
63
69
|
|
64
|
-
To record in tests, wrap some code in `
|
70
|
+
To record in tests, wrap some code in `record_fixtures` block. For example:
|
65
71
|
|
66
72
|
```ruby
|
67
73
|
|
68
74
|
include FixtureFarm::TestHelper
|
69
75
|
|
70
|
-
test '
|
71
|
-
|
72
|
-
user = User.create!(name: 'Bob')
|
73
|
-
post = user.posts.create!(title: 'Stuff')
|
74
|
-
|
75
|
-
recorder.stop!
|
76
|
+
test 'parents fixtures have children' do
|
77
|
+
offending_records = Parent.where.missing(:children)
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
+
if ENV['GENERATE_FIXTURES']
|
80
|
+
record_fixtures do
|
81
|
+
offending_records.each do |parent|
|
82
|
+
parent.children.create!(name: 'Bob')
|
83
|
+
end
|
79
84
|
end
|
85
|
+
else
|
86
|
+
assert_empty offending_records.map(&:fixture_name),
|
87
|
+
"The following parents don't have children:"
|
80
88
|
end
|
81
89
|
end
|
82
90
|
```
|
83
91
|
|
84
|
-
|
92
|
+
Assuming there was a parent fixture `dave` that didn't have any children, this test will fail. Now, running the same test with `GENERATE_FIXTURES=1` will generate one child fixture named `dave_child_1`. The test is now passing.
|
85
93
|
|
86
|
-
|
87
|
-
test 'some stuff does the right thing' do
|
88
|
-
user = users('user_1')
|
94
|
+
`record_fixtures` accepts optional name prefix, that applies to all new fixture names.
|
89
95
|
|
90
|
-
|
91
|
-
|
92
|
-
|
96
|
+
#### Fixture Name Replacement
|
97
|
+
|
98
|
+
`record_fixtures` also supports hash arguments for advanced fixture naming control:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
# Replace 'client_1' with 'new_client' in fixture names, or use 'new_client' as prefix if not found
|
102
|
+
record_fixtures(new_client: :client_1) do
|
103
|
+
User.create!(name: 'Test User', email: 'test@example.com')
|
93
104
|
end
|
94
105
|
```
|
95
106
|
|
96
|
-
|
107
|
+
This works in two ways:
|
108
|
+
- **Replacement**: If a generated fixture name contains `client_1`, it gets replaced with `new_client`
|
109
|
+
- **Prefixing**: If a generated fixture name doesn't contain `client_1`, it gets prefixed with `new_client_`
|
110
|
+
|
111
|
+
For example:
|
112
|
+
- A user fixture that would be named `client_1_user_1` becomes `new_client_user_1` (replacement)
|
113
|
+
- A user fixture that would be named `user_1` becomes `new_client_user_1` (prefixing)
|
97
114
|
|
98
|
-
|
115
|
+
### Automatic fixture naming
|
99
116
|
|
100
|
-
|
117
|
+
Generated fixture names are based on the first `belongs_to` association of the model. E.g., if a new post fixtures belongs_to to a user fixture `bob`, the name is going to be `bob_post_1`.
|
101
118
|
|
102
|
-
|
103
|
-
test 'authors fixtures must have at least one post' do
|
104
|
-
offending_records = Author.where.missing(:posts)
|
119
|
+
It's possible to lower the priority of given parent assiciations when it comes to naming, so that certain names are only picked when there are no other suitable parent associations. This is useful, for example, to exclude `acts_as_tenant` association:
|
105
120
|
|
106
|
-
|
107
|
-
|
121
|
+
```ruby
|
122
|
+
FixtureFarm.low_priority_parent_model_for_naming = -> { _1.is_a?(TenantModel) }
|
108
123
|
```
|
109
124
|
|
110
|
-
|
125
|
+
### Attachment fixtures
|
111
126
|
|
112
|
-
|
127
|
+
Rather than [manually crafting attachment fixtures](https://guides.rubyonrails.org/v8.0/active_storage_overview.html#adding-attachments-to-fixtures), we can get the gem to do the work. Not only is this less boring, but it's also going to generate variant fixtures.
|
113
128
|
|
114
|
-
|
115
|
-
test 'authors fixtures must have at least one post' do
|
116
|
-
offending_records = Author.where.missing(:posts)
|
129
|
+
If we then check the generated blob files into git (along with the fixture files themselves), no attachment processing will be happening in tests or after `rails db:fixtures:load`.
|
117
130
|
|
118
|
-
|
119
|
-
record_new_fixtures do
|
120
|
-
offending_records.each do |author|
|
121
|
-
author.posts.create!(text: 'some text')
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
131
|
+
We'll need a special storage service for the fixture blobs we want to keep versioned. For example:
|
125
132
|
|
126
|
-
|
127
|
-
|
133
|
+
```yml
|
134
|
+
# config/storage.yml
|
135
|
+
test_fixtures:
|
136
|
+
service: Disk
|
137
|
+
root: <%= Rails.root.join("test/fixtures/files/active_storage_blobs") %>
|
128
138
|
```
|
129
139
|
|
130
|
-
|
140
|
+
Now a test like the one below is either going to fail if some product fixtures have no attachments, or, if run with `GENERATE_FIXTURES=1`, is going to generate those attachment fixtures, their variant fixtures if needed, along with all the blob files tucked away in a separate (from regular throw away storage) folder that can be checked in:
|
131
141
|
|
132
|
-
|
142
|
+
```ruby
|
143
|
+
if ENV["GENERATE_FIXTURES"]
|
144
|
+
setup do
|
145
|
+
@original_queue_adapter = Rails.configuration.active_job.queue_adapter
|
146
|
+
# This is so that variants get generated and blobs analyzed
|
147
|
+
Rails.configuration.active_job.queue_adapter = :inline
|
148
|
+
|
149
|
+
@original_storage_service = ActiveStorage::Blob.service
|
150
|
+
ActiveStorage::Blob.service = ActiveStorage::Blob.services.fetch(:test_fixtures)
|
151
|
+
end
|
133
152
|
|
134
|
-
|
153
|
+
teardown do
|
154
|
+
Rails.configuration.active_job.queue_adapter = @original_queue_adapter
|
155
|
+
ActiveStorage::Blob.service = @original_storage_service
|
156
|
+
end
|
157
|
+
end
|
135
158
|
|
136
|
-
|
159
|
+
test "product fixtures have images" do
|
160
|
+
offending_records = Product.where.missing(:images_attachments)
|
137
161
|
|
138
|
-
|
139
|
-
|
162
|
+
if ENV["GENERATE_FIXTURES"]
|
163
|
+
record_fixtures do |recorder|
|
164
|
+
ActiveStorage::Attachment.where(record_type: 'Product').destroy_all
|
165
|
+
|
166
|
+
Product.find_each do |product|
|
167
|
+
product.images.attach(
|
168
|
+
io: File.open(file_fixture("products/#{product.fixture_name}.jpg")),
|
169
|
+
filename: "#{product.fixture_name}.jpg",
|
170
|
+
content_type: "image/jpeg"
|
171
|
+
)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
else
|
175
|
+
assert_empty offending_records.map(&:fixture_name),
|
176
|
+
"Expected the following product fixtures to have images:"
|
177
|
+
end
|
178
|
+
end
|
140
179
|
```
|
141
180
|
|
142
181
|
## License
|
data/bin/fixture_farm.rb
CHANGED
@@ -4,14 +4,24 @@
|
|
4
4
|
require_relative '../lib/fixture_farm/fixture_recorder'
|
5
5
|
|
6
6
|
def usage
|
7
|
-
puts 'Usage: bundle exec fixture_farm <record|status|stop> [
|
7
|
+
puts 'Usage: bundle exec fixture_farm <record|status|stop> [name_prefix|name_prefix:replaces_name]'
|
8
8
|
exit 1
|
9
9
|
end
|
10
10
|
|
11
11
|
case ARGV[0]
|
12
12
|
when 'record'
|
13
|
-
|
14
|
-
|
13
|
+
prefix_arg = ARGV[1]
|
14
|
+
|
15
|
+
# Parse hash syntax like "new_user:user_1" into {new_user: :user_1}
|
16
|
+
if prefix_arg&.include?(':')
|
17
|
+
parts = prefix_arg.split(':', 2)
|
18
|
+
parsed_prefix = { parts[0].to_sym => parts[1].to_sym }
|
19
|
+
else
|
20
|
+
parsed_prefix = prefix_arg
|
21
|
+
end
|
22
|
+
|
23
|
+
FixtureFarm::FixtureRecorder.start_recording_session!(parsed_prefix)
|
24
|
+
puts "Recording fixtures#{" with prefix #{prefix_arg}" unless prefix_arg.nil?}"
|
15
25
|
when 'status'
|
16
26
|
if FixtureFarm::FixtureRecorder.recording_session_in_progress?
|
17
27
|
puts 'Recording is on'
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FixtureFarm
|
4
|
-
|
5
4
|
mattr_accessor :low_priority_parent_model_for_naming
|
6
5
|
|
7
6
|
class FixtureRecorder
|
@@ -16,7 +15,13 @@ module FixtureFarm
|
|
16
15
|
end
|
17
16
|
|
18
17
|
def initialize(fixture_name_prefix, new_models = [])
|
19
|
-
|
18
|
+
if fixture_name_prefix.is_a?(Hash)
|
19
|
+
@fixture_name_replacements = fixture_name_prefix
|
20
|
+
@fixture_name_prefix = nil
|
21
|
+
else
|
22
|
+
@fixture_name_prefix = fixture_name_prefix
|
23
|
+
@fixture_name_replacements = {}
|
24
|
+
end
|
20
25
|
@new_models = new_models
|
21
26
|
@deleted_models = {}
|
22
27
|
@initial_now = Time.zone.now
|
@@ -72,7 +77,7 @@ module FixtureFarm
|
|
72
77
|
recording_session['error']
|
73
78
|
end
|
74
79
|
|
75
|
-
def
|
80
|
+
def record_fixtures
|
76
81
|
@stopped = false
|
77
82
|
|
78
83
|
@subscriber = ActiveSupport::Notifications.subscribe 'sql.active_record' do |event|
|
@@ -95,9 +100,11 @@ module FixtureFarm
|
|
95
100
|
end
|
96
101
|
end
|
97
102
|
|
98
|
-
yield self
|
103
|
+
result = yield self
|
99
104
|
|
100
105
|
stop! unless @stopped
|
106
|
+
|
107
|
+
result
|
101
108
|
ensure
|
102
109
|
ActiveSupport::Notifications.unsubscribe(@subscriber)
|
103
110
|
end
|
@@ -144,8 +151,10 @@ module FixtureFarm
|
|
144
151
|
|
145
152
|
blob.update!(key: new_key)
|
146
153
|
|
147
|
-
|
148
|
-
|
154
|
+
blobs_root_path = Pathname.new(ActiveStorage::Blob.service.root)
|
155
|
+
|
156
|
+
from_path = blobs_root_path.join(old_key[0..1], old_key[2..3], old_key)
|
157
|
+
to_dir = blobs_root_path.join(new_key[0..1], new_key[2..3])
|
149
158
|
to_path = to_dir.join(new_key)
|
150
159
|
|
151
160
|
`mkdir -p #{to_dir}`
|
@@ -366,12 +375,25 @@ module FixtureFarm
|
|
366
375
|
fixture_name(model_instance) || begin
|
367
376
|
existing_fixtures = existing_fixtures_for_model(model_instance)
|
368
377
|
|
369
|
-
|
378
|
+
base_name = [
|
370
379
|
first_belongs_to_fixture_name(model_instance).presence || @fixture_name_prefix,
|
371
380
|
"#{model_instance.class.name.underscore.split('/').last}_1"
|
372
381
|
].select(&:present?).join('_')
|
373
382
|
|
374
|
-
|
383
|
+
@fixture_name_replacements.each do |new_name, old_name|
|
384
|
+
# Only apply replacement if the base_name doesn't already start with new_name
|
385
|
+
# This prevents double-application of replacements
|
386
|
+
next if base_name.start_with?("#{new_name}_")
|
387
|
+
|
388
|
+
original_name = base_name
|
389
|
+
base_name = base_name.gsub(/\b#{Regexp.escape(old_name.to_s)}\b/, new_name.to_s)
|
390
|
+
|
391
|
+
# If no replacement occurred, use new_name as prefix
|
392
|
+
base_name = "#{new_name}_#{base_name}" if base_name == original_name
|
393
|
+
end
|
394
|
+
|
395
|
+
new_fixture_name = base_name
|
396
|
+
while @named_new_fixtures[new_fixture_name] || (existing_fixtures[new_fixture_name] && !@deleted_models[new_fixture_name])
|
375
397
|
new_fixture_name = new_fixture_name.sub(/_(\d+)$/, "_#{Regexp.last_match(1).to_i + 1}")
|
376
398
|
end
|
377
399
|
|
data/lib/fixture_farm/hook.rb
CHANGED
@@ -4,12 +4,12 @@ require 'fixture_farm/fixture_recorder'
|
|
4
4
|
|
5
5
|
module FixtureFarm
|
6
6
|
module Hook
|
7
|
-
def
|
7
|
+
def record_fixtures(&block)
|
8
8
|
fixture_recorder = FixtureRecorder.resume_recording_session
|
9
9
|
return unless fixture_recorder # Bail if session was stopped due to error
|
10
10
|
|
11
11
|
begin
|
12
|
-
fixture_recorder.
|
12
|
+
fixture_recorder.record_fixtures { block.call }
|
13
13
|
ensure
|
14
14
|
fixture_recorder.update_recording_session
|
15
15
|
end
|
@@ -17,7 +17,7 @@ module FixtureFarm
|
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
|
-
def
|
20
|
+
def record_fixtures?
|
21
21
|
FixtureRecorder.recording_session_in_progress?
|
22
22
|
end
|
23
23
|
end
|
@@ -4,8 +4,8 @@ require 'fixture_farm/fixture_recorder'
|
|
4
4
|
|
5
5
|
module FixtureFarm
|
6
6
|
module TestHelper
|
7
|
-
def
|
8
|
-
FixtureRecorder.new(fixture_name_prefix).
|
7
|
+
def record_fixtures(fixture_name_prefix = nil, &block)
|
8
|
+
FixtureRecorder.new(fixture_name_prefix).record_fixtures(&block)
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/fixture_farm/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fixture_farm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- artemave
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|