fixture_farm 0.3.1 → 1.0.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 +60 -54
- data/Rakefile +5 -3
- data/bin/fixture_farm.rb +8 -2
- data/lib/fixture_farm/active_job_hook.rb +1 -1
- data/lib/fixture_farm/active_record_extension.rb +4 -5
- data/lib/fixture_farm/controller_hook.rb +2 -2
- data/lib/fixture_farm/fixture_recorder.rb +173 -59
- data/lib/fixture_farm/hook.rb +10 -6
- data/lib/fixture_farm/test_helper.rb +3 -3
- data/lib/fixture_farm/version.rb +3 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af9b30c58737a261963da35795445aadfcb38efe2f3ed6d4fc8055f19399b0df
|
4
|
+
data.tar.gz: 865e73ba12d379aba2b3f48a160ce421efe7d49a958db72f19b185d4d1045175
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c1950bc97570624b27f2f928ec82586816429e1f7a34378f5336c83d8d420180ad65093677744a4b6efa8e3cbc2b7a298631837dbb9ffc32d0c11941d372596
|
7
|
+
data.tar.gz: 455b1430edd5fb21874e7cd906688ea55ea3e1753c0e02b13b2bcd051f2e68f17d02174f6b770cac9b15f212cef0923b52c3584f40f7b1d662957cff1a4c0cc3
|
data/README.md
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
# FixtureFarm
|
2
2
|
|
3
3
|
This gem lets you do two things:
|
4
|
-
|
4
|
+
- record fixtures for a block of code (e.g. part of a test).
|
5
5
|
- record fixtures as you browse.
|
6
|
-
- record fixtures for a block of code (e.g. setup part of a test).
|
7
6
|
|
8
|
-
|
7
|
+
A few things to note:
|
8
|
+
- generated fixture names are based on their `belongs_to` fixture names.
|
9
|
+
- generated fixture that `belongs_to` a record from an existing fixture, will reference that fixture by name.
|
10
|
+
- models, destroyed during recording, will be removed from fixtures (if they were originally there).
|
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
|
9
13
|
|
10
14
|
### Limitations
|
11
15
|
|
12
16
|
- doesn't update fixtures
|
13
|
-
-
|
14
|
-
- assumes that all serialized attributes are json (so that at least ActiveStorage::Blob metadata is correctly represented; it really should be Rails serializing attributes according to their respective coders when inserting fixtures into the database, but, alas, this isn't happening)
|
17
|
+
- assumes that all serialized attributes are json (so that at least ActiveStorage::Blob metadata is correctly represented; it really should be Rails serializing attributes according to their respective coders when inserting fixtures into the database, but, alas, this isn't how it works)
|
15
18
|
|
16
19
|
## Installation
|
17
20
|
|
@@ -59,82 +62,85 @@ bundle exec fixture_farm stop
|
|
59
62
|
|
60
63
|
### Record in tests
|
61
64
|
|
62
|
-
To record in tests, wrap some code in `
|
65
|
+
To record in tests, wrap some code in `record_fixtures` block. For example:
|
63
66
|
|
64
67
|
```ruby
|
65
68
|
|
66
69
|
include FixtureFarm::TestHelper
|
67
70
|
|
68
|
-
test '
|
69
|
-
|
70
|
-
user = User.create!(name: 'Bob')
|
71
|
-
post = user.posts.create!(title: 'Stuff')
|
72
|
-
|
73
|
-
stop_recording.call
|
71
|
+
test 'parents fixtures have children' do
|
72
|
+
offending_records = Parent.where.missing(:children)
|
74
73
|
|
75
|
-
|
76
|
-
|
74
|
+
if ENV['GENERATE_FIXTURES']
|
75
|
+
record_fixtures do
|
76
|
+
offending_records.each do |parent|
|
77
|
+
parent.children.create!(name: 'Bob')
|
78
|
+
end
|
77
79
|
end
|
80
|
+
else
|
81
|
+
assert_empty offending_records.map(&:fixture_name),
|
82
|
+
"The following parents don't have children:"
|
78
83
|
end
|
79
84
|
end
|
80
85
|
```
|
81
86
|
|
82
|
-
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
test 'some stuff does the right thing' do
|
86
|
-
user = users('user_1')
|
87
|
+
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.
|
87
88
|
|
88
|
-
|
89
|
-
user.posts.first.publish!
|
90
|
-
end
|
91
|
-
end
|
92
|
-
```
|
89
|
+
`record_fixtures` accepts optional name prefix, that applies to all new fixture names.
|
93
90
|
|
94
|
-
|
91
|
+
### Automatic fixture naming
|
95
92
|
|
96
|
-
|
93
|
+
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`.
|
97
94
|
|
98
|
-
|
95
|
+
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:
|
99
96
|
|
100
97
|
```ruby
|
101
|
-
|
102
|
-
offending_records = Author.where.missing(:posts)
|
103
|
-
|
104
|
-
assert_empty offending_records
|
105
|
-
end
|
98
|
+
FixtureFarm.low_priority_parent_model_for_naming = -> { _1.is_a?(TenantModel) }
|
106
99
|
```
|
107
100
|
|
108
|
-
|
101
|
+
### Attachment fixtures
|
109
102
|
|
110
|
-
|
111
|
-
|
112
|
-
```ruby
|
113
|
-
test 'authors fixtures must have at least one post' do
|
114
|
-
offending_records = Author.where.missing(:posts)
|
103
|
+
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 do the leg work. Not only is this less boring, but it's also going to generate variant fixtures.
|
115
104
|
|
116
|
-
|
117
|
-
record_new_fixtures do
|
118
|
-
offending_records.each do |author|
|
119
|
-
author.posts.create!(text: 'some text')
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
105
|
+
I'd also go as far as suggesting that attachment files for generated blobs should be checked into git just as the fixtures themselves are. To share them with the development environment (e.g. `rails db:fixtures:load`), let's store test attachment files in the same `./storage` directory used in development:
|
123
106
|
|
124
|
-
|
125
|
-
|
107
|
+
```ruby
|
108
|
+
# config/environments/test.rb
|
109
|
+
config.active_storage.service = :local
|
126
110
|
```
|
127
111
|
|
128
|
-
|
129
|
-
|
130
|
-
### Automatic fixture naming
|
112
|
+
Now this test will not only generate attachments and variant fixtures, but also `git add` new attachment files. The old removed ones will show up in `git status`.
|
131
113
|
|
132
|
-
|
114
|
+
```ruby
|
115
|
+
test "product fixtures have images" do
|
116
|
+
offending_records = Product.where.missing(:images_attachments)
|
117
|
+
|
118
|
+
if ENV["GENERATE_FIXTURES"]
|
119
|
+
# Makes generation idempotent
|
120
|
+
`git restore --staged storage`
|
121
|
+
|
122
|
+
record_fixtures do |recorder|
|
123
|
+
ActiveStorage::Attachment.where(record_type: 'Product').destroy_all
|
124
|
+
|
125
|
+
Product.find_each do |product|
|
126
|
+
product.images.attach(
|
127
|
+
io: File.open(file_fixture("products/#{product.fixture_name}.jpg")),
|
128
|
+
filename: "#{product.fixture_name}.jpg",
|
129
|
+
content_type: "image/jpeg"
|
130
|
+
)
|
131
|
+
# This generates variants
|
132
|
+
perform_enqueued_jobs
|
133
|
+
end
|
133
134
|
|
134
|
-
|
135
|
+
recorder.stop!
|
135
136
|
|
136
|
-
|
137
|
-
|
137
|
+
`git add -f #{recorder.new_blob_file_paths.join(' ')}`
|
138
|
+
end
|
139
|
+
else
|
140
|
+
assert_empty offending_records.map(&:fixture_name),
|
141
|
+
"Expected the following product fixtures to have images:"
|
142
|
+
end
|
143
|
+
end
|
138
144
|
```
|
139
145
|
|
140
146
|
## License
|
data/Rakefile
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
4
|
|
5
|
-
require
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
|
7
|
+
require 'rake/testtask'
|
6
8
|
|
7
9
|
Rake::TestTask.new(:test) do |t|
|
8
10
|
t.libs << 'test'
|
data/bin/fixture_farm.rb
CHANGED
@@ -11,9 +11,15 @@ end
|
|
11
11
|
case ARGV[0]
|
12
12
|
when 'record'
|
13
13
|
FixtureFarm::FixtureRecorder.start_recording_session!(ARGV[1])
|
14
|
-
puts
|
14
|
+
puts "Recording fixtures#{" with prefix #{ARGV[1]}" unless ARGV[1].nil?}"
|
15
15
|
when 'status'
|
16
|
-
|
16
|
+
if FixtureFarm::FixtureRecorder.recording_session_in_progress?
|
17
|
+
puts 'Recording is on'
|
18
|
+
elsif (error = FixtureFarm::FixtureRecorder.last_session_error)
|
19
|
+
puts "Recording is off (#{error})"
|
20
|
+
else
|
21
|
+
puts 'Recording is off'
|
22
|
+
end
|
17
23
|
when 'stop'
|
18
24
|
FixtureFarm::FixtureRecorder.stop_recording_session!
|
19
25
|
puts 'Stopped recording'
|
@@ -17,14 +17,12 @@ module FixtureFarm
|
|
17
17
|
existing_fixtures_file_path || candidate_fixtures_file_path
|
18
18
|
end
|
19
19
|
|
20
|
+
private
|
21
|
+
|
20
22
|
def candidate_fixtures_file_path
|
21
23
|
klass = self.class
|
22
|
-
loop do
|
23
|
-
path = Rails.root.join('test', 'fixtures', "#{klass.to_s.underscore.pluralize}.yml")
|
24
|
-
return path if klass >= ActiveRecord::Base || !klass.columns.map(&:name).include?(klass.inheritance_column)
|
25
24
|
|
26
|
-
|
27
|
-
end
|
25
|
+
Rails.root.join('test', 'fixtures', "#{klass.to_s.underscore.pluralize}.yml")
|
28
26
|
end
|
29
27
|
|
30
28
|
def existing_fixtures_file_path
|
@@ -32,6 +30,7 @@ module FixtureFarm
|
|
32
30
|
|
33
31
|
while klass < ActiveRecord::Base
|
34
32
|
path = Rails.root.join('test', 'fixtures', "#{klass.to_s.underscore.pluralize}.yml")
|
33
|
+
|
35
34
|
return path if File.exist?(path)
|
36
35
|
|
37
36
|
klass = klass.superclass
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'fixture_farm/hook'
|
4
4
|
|
5
5
|
module FixtureFarm
|
6
6
|
module ControllerHook
|
@@ -8,7 +8,7 @@ module FixtureFarm
|
|
8
8
|
include Hook
|
9
9
|
|
10
10
|
included do
|
11
|
-
around_action :
|
11
|
+
around_action :record_fixtures, if: :record_fixtures?
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,89 +1,161 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FixtureFarm
|
4
|
-
|
4
|
+
|
5
|
+
mattr_accessor :low_priority_parent_model_for_naming
|
5
6
|
|
6
7
|
class FixtureRecorder
|
7
|
-
|
8
|
+
attr_accessor :new_blob_file_paths
|
9
|
+
|
10
|
+
def self.store_path
|
11
|
+
Rails.root.join('tmp', 'fixture_farm_store.json')
|
12
|
+
end
|
13
|
+
|
14
|
+
def store_path
|
15
|
+
self.class.store_path
|
16
|
+
end
|
8
17
|
|
9
18
|
def initialize(fixture_name_prefix, new_models = [])
|
10
19
|
@fixture_name_prefix = fixture_name_prefix
|
11
20
|
@new_models = new_models
|
21
|
+
@deleted_models = {}
|
12
22
|
@initial_now = Time.zone.now
|
13
|
-
@
|
23
|
+
@named_new_fixtures = {}
|
24
|
+
@existing_fixtures_cache = {}
|
14
25
|
end
|
15
26
|
|
16
27
|
def self.resume_recording_session
|
17
28
|
start_recording_session! unless recording_session_in_progress?
|
18
29
|
|
19
|
-
recording_session =
|
30
|
+
recording_session = load_recording_session
|
20
31
|
|
21
32
|
new_models = recording_session['new_models'].map do |(class_name, id)|
|
22
33
|
class_name.constantize.find(id)
|
23
34
|
end
|
24
35
|
|
25
36
|
new(recording_session['fixture_name_prefix'], new_models)
|
37
|
+
rescue ActiveRecord::RecordNotFound
|
38
|
+
# External interference with database (e.g. fixtures:load)
|
39
|
+
recording_session['error'] = 'database was externally modified/reset'
|
40
|
+
File.write(store_path, recording_session.to_json)
|
41
|
+
nil
|
26
42
|
end
|
27
43
|
|
28
44
|
def self.start_recording_session!(fixture_name_prefix)
|
29
|
-
File.write(
|
45
|
+
File.write(store_path, {
|
30
46
|
fixture_name_prefix: fixture_name_prefix,
|
31
47
|
new_models: []
|
32
48
|
}.to_json)
|
33
49
|
end
|
34
50
|
|
35
51
|
def self.stop_recording_session!
|
36
|
-
FileUtils.rm_f(
|
52
|
+
FileUtils.rm_f(store_path)
|
37
53
|
end
|
38
54
|
|
39
55
|
def self.recording_session_in_progress?
|
40
|
-
|
56
|
+
recording_session = load_recording_session
|
57
|
+
return false unless recording_session
|
58
|
+
|
59
|
+
!recording_session['error']
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.load_recording_session
|
63
|
+
return nil unless File.exist?(store_path)
|
64
|
+
|
65
|
+
JSON.load_file(store_path, permitted_classes: [ActiveSupport::HashWithIndifferentAccess])
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.last_session_error
|
69
|
+
recording_session = load_recording_session
|
70
|
+
return nil unless recording_session
|
71
|
+
|
72
|
+
recording_session['error']
|
41
73
|
end
|
42
74
|
|
43
|
-
def
|
44
|
-
stopped = false
|
75
|
+
def record_fixtures
|
76
|
+
@stopped = false
|
45
77
|
|
46
|
-
subscriber = ActiveSupport::Notifications.subscribe 'sql.active_record' do |event|
|
78
|
+
@subscriber = ActiveSupport::Notifications.subscribe 'sql.active_record' do |event|
|
47
79
|
payload = event.payload
|
48
80
|
|
49
|
-
|
81
|
+
if payload[:name] =~ /([:\w]+) Create/
|
82
|
+
new_fixture_class_name = Regexp.last_match(1)
|
50
83
|
|
51
|
-
|
84
|
+
payload[:connection].transaction_manager.current_transaction.records.reject(&:persisted?).reject(&:destroyed?).each do |model_instance|
|
85
|
+
next if new_fixture_class_name != model_instance.class.name
|
52
86
|
|
53
|
-
|
54
|
-
|
87
|
+
@new_models << model_instance
|
88
|
+
end
|
89
|
+
elsif payload[:name] =~ /([:\w]+) Destroy/
|
90
|
+
payload[:connection].transaction_manager.current_transaction.records.each do |model|
|
91
|
+
fixture_name = existing_fixture_name(model)
|
55
92
|
|
56
|
-
|
93
|
+
@deleted_models[fixture_name] = model if fixture_name
|
94
|
+
end
|
57
95
|
end
|
58
96
|
end
|
59
97
|
|
60
|
-
yield
|
61
|
-
ActiveSupport::Notifications.unsubscribe(subscriber)
|
62
|
-
stopped = true
|
63
|
-
reload_models
|
64
|
-
update_fixture_files(named_new_fixtures)
|
65
|
-
}
|
98
|
+
yield self
|
66
99
|
|
67
|
-
unless stopped
|
68
|
-
reload_models
|
69
|
-
update_fixture_files(named_new_fixtures)
|
70
|
-
end
|
100
|
+
stop! unless @stopped
|
71
101
|
ensure
|
72
|
-
ActiveSupport::Notifications.unsubscribe(subscriber)
|
102
|
+
ActiveSupport::Notifications.unsubscribe(@subscriber)
|
103
|
+
end
|
104
|
+
|
105
|
+
def stop!
|
106
|
+
ActiveSupport::Notifications.unsubscribe(@subscriber)
|
107
|
+
@stopped = true
|
108
|
+
reload_new_models
|
109
|
+
rename_active_storage_blobs_for_idempotency
|
110
|
+
delete_fixtures_for_deleted_models
|
111
|
+
update_fixture_files(named_new_fixtures)
|
73
112
|
end
|
74
113
|
|
75
114
|
def update_recording_session
|
76
115
|
return unless FixtureRecorder.recording_session_in_progress?
|
77
116
|
|
78
|
-
File.write(
|
117
|
+
File.write(store_path, {
|
79
118
|
fixture_name_prefix: @fixture_name_prefix,
|
80
119
|
new_models: @new_models.map { |model| [model.class.name, model.id] }
|
81
120
|
}.to_json)
|
82
121
|
end
|
83
122
|
|
123
|
+
def named_new_fixtures
|
124
|
+
@new_models.uniq.each do |model_instance|
|
125
|
+
ensure_new_fixture_name(model_instance)
|
126
|
+
end
|
127
|
+
|
128
|
+
@named_new_fixtures
|
129
|
+
end
|
130
|
+
|
84
131
|
private
|
85
132
|
|
86
|
-
def
|
133
|
+
def rename_active_storage_blobs_for_idempotency
|
134
|
+
self.new_blob_file_paths = named_new_fixtures.filter_map do |fixture_name, model|
|
135
|
+
next unless model.is_a?(ActiveStorage::Blob)
|
136
|
+
|
137
|
+
rename_blob_file_for_idempotency(fixture_name, model)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def rename_blob_file_for_idempotency(fixture_name, blob)
|
142
|
+
old_key = blob.key
|
143
|
+
new_key = fixture_name
|
144
|
+
|
145
|
+
blob.update!(key: new_key)
|
146
|
+
|
147
|
+
from_path = Rails.root.join('storage', old_key[0..1], old_key[2..3], old_key)
|
148
|
+
to_dir = Rails.root.join('storage', new_key[0..1], new_key[2..3])
|
149
|
+
to_path = to_dir.join(new_key)
|
150
|
+
|
151
|
+
`mkdir -p #{to_dir}`
|
152
|
+
|
153
|
+
`mv #{from_path} #{to_path}`
|
154
|
+
|
155
|
+
to_path
|
156
|
+
end
|
157
|
+
|
158
|
+
def reload_new_models
|
87
159
|
@new_models = @new_models.map do |model_instance|
|
88
160
|
# reload in case model was updated after initial create
|
89
161
|
model_instance.reload
|
@@ -94,28 +166,6 @@ module FixtureFarm
|
|
94
166
|
end.compact
|
95
167
|
end
|
96
168
|
|
97
|
-
def named_new_fixtures
|
98
|
-
@named_new_fixtures ||= begin
|
99
|
-
(@new_models - @ignore_while_tree_walking.to_a).uniq(&:id).each_with_object({}) do |model_instance, named_new_fixtures|
|
100
|
-
@ignore_while_tree_walking.add(model_instance)
|
101
|
-
|
102
|
-
new_fixture_name = [
|
103
|
-
@fixture_name_prefix,
|
104
|
-
first_belongs_to_fixture_name(model_instance),
|
105
|
-
"#{model_instance.class.name.underscore.split('/').last}_1"
|
106
|
-
].select(&:present?).join('_')
|
107
|
-
|
108
|
-
while named_new_fixtures[new_fixture_name]
|
109
|
-
new_fixture_name = new_fixture_name.sub(/_(\d+)$/, "_#{Regexp.last_match(1).to_i + 1}")
|
110
|
-
end
|
111
|
-
|
112
|
-
named_new_fixtures[new_fixture_name] = model_instance
|
113
|
-
|
114
|
-
@ignore_while_tree_walking.delete(model_instance)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
169
|
def first_belongs_to_fixture_name(model_instance)
|
120
170
|
low_priority_name = nil
|
121
171
|
|
@@ -124,13 +174,13 @@ module FixtureFarm
|
|
124
174
|
|
125
175
|
next unless associated_model_instance
|
126
176
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
return associated_model_instance_fixture_name
|
132
|
-
end
|
177
|
+
next unless (associated_model_instance_fixture_name = ensure_new_fixture_name(associated_model_instance))
|
178
|
+
|
179
|
+
unless FixtureFarm.low_priority_parent_model_for_naming&.call(associated_model_instance)
|
180
|
+
return associated_model_instance_fixture_name
|
133
181
|
end
|
182
|
+
|
183
|
+
low_priority_name = associated_model_instance_fixture_name
|
134
184
|
end
|
135
185
|
|
136
186
|
low_priority_name
|
@@ -216,7 +266,7 @@ module FixtureFarm
|
|
216
266
|
if value.to_datetime.minute == 59
|
217
267
|
value += 1.minute
|
218
268
|
value = value.beginning_of_hour
|
219
|
-
elsif
|
269
|
+
elsif [1, 0].include?(value.to_datetime.minute)
|
220
270
|
value = value.beginning_of_hour
|
221
271
|
end
|
222
272
|
value
|
@@ -275,10 +325,74 @@ module FixtureFarm
|
|
275
325
|
end.except(:value_rest)
|
276
326
|
end
|
277
327
|
|
328
|
+
def delete_fixtures_for_deleted_models
|
329
|
+
# TODO: optimize
|
330
|
+
@deleted_models.each do |fixture_name, deleted_model|
|
331
|
+
fixtures_file_path = deleted_model.fixtures_file_path
|
332
|
+
|
333
|
+
fixtures = YAML.load_file(fixtures_file_path, permitted_classes: [ActiveSupport::HashWithIndifferentAccess]) || {}
|
334
|
+
|
335
|
+
if fixtures.delete(fixture_name)
|
336
|
+
if fixtures.empty?
|
337
|
+
File.delete(fixtures_file_path)
|
338
|
+
else
|
339
|
+
File.open(fixtures_file_path, 'w') do |file|
|
340
|
+
yaml = YAML.dump(fixtures).gsub(/\n(?=[^\s])/, "\n\n").delete_prefix("---\n\n")
|
341
|
+
file.write(yaml)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def existing_fixtures_for_model(model_instance)
|
349
|
+
model_class = model_instance.class
|
350
|
+
|
351
|
+
return @existing_fixtures_cache[model_class] if @existing_fixtures_cache.key?(model_class)
|
352
|
+
|
353
|
+
fixtures_file_path = model_instance.fixtures_file_path
|
354
|
+
|
355
|
+
@existing_fixtures_cache[model_class] = if File.exist?(fixtures_file_path)
|
356
|
+
YAML.load_file(
|
357
|
+
fixtures_file_path,
|
358
|
+
permitted_classes: [ActiveSupport::HashWithIndifferentAccess]
|
359
|
+
) || {}
|
360
|
+
else
|
361
|
+
{}
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def ensure_new_fixture_name(model_instance)
|
366
|
+
fixture_name(model_instance) || begin
|
367
|
+
existing_fixtures = existing_fixtures_for_model(model_instance)
|
368
|
+
|
369
|
+
new_fixture_name = [
|
370
|
+
first_belongs_to_fixture_name(model_instance).presence || @fixture_name_prefix,
|
371
|
+
"#{model_instance.class.name.underscore.split('/').last}_1"
|
372
|
+
].select(&:present?).join('_')
|
373
|
+
|
374
|
+
while @named_new_fixtures[new_fixture_name] || existing_fixtures[new_fixture_name] && !@deleted_models[new_fixture_name]
|
375
|
+
new_fixture_name = new_fixture_name.sub(/_(\d+)$/, "_#{Regexp.last_match(1).to_i + 1}")
|
376
|
+
end
|
377
|
+
|
378
|
+
@named_new_fixtures[new_fixture_name] = model_instance
|
379
|
+
|
380
|
+
new_fixture_name
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def existing_fixture_name(model_instance)
|
385
|
+
existing_fixtures = existing_fixtures_for_model(model_instance)
|
386
|
+
|
387
|
+
existing_fixtures.keys.find do |key|
|
388
|
+
ActiveRecord::FixtureSet.identify(key) == model_instance.id
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
278
392
|
def fixture_name(model_instance)
|
279
|
-
named_new_fixtures.find do |_, fixture_model|
|
393
|
+
@named_new_fixtures.find do |_, fixture_model|
|
280
394
|
fixture_model.id == model_instance.id
|
281
|
-
end&.first || model_instance
|
395
|
+
end&.first || existing_fixture_name(model_instance)
|
282
396
|
end
|
283
397
|
end
|
284
398
|
end
|
data/lib/fixture_farm/hook.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
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
|
-
fixture_recorder
|
10
|
-
|
11
|
-
|
9
|
+
return unless fixture_recorder # Bail if session was stopped due to error
|
10
|
+
|
11
|
+
begin
|
12
|
+
fixture_recorder.record_fixtures { block.call }
|
13
|
+
ensure
|
14
|
+
fixture_recorder.update_recording_session
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
15
19
|
|
16
|
-
def
|
20
|
+
def record_fixtures?
|
17
21
|
FixtureRecorder.recording_session_in_progress?
|
18
22
|
end
|
19
23
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
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: 0.
|
4
|
+
version: 1.0.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-07-
|
11
|
+
date: 2025-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -52,6 +52,7 @@ licenses:
|
|
52
52
|
metadata:
|
53
53
|
homepage_uri: https://github.com/featurist/fixture_farm
|
54
54
|
source_code_uri: https://github.com/featurist/fixture_farm.git
|
55
|
+
rubygems_mfa_required: 'true'
|
55
56
|
post_install_message:
|
56
57
|
rdoc_options: []
|
57
58
|
require_paths:
|