fixture_champagne 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +50 -4
- data/lib/fixture_champagne/migration_context.rb +73 -11
- data/lib/fixture_champagne/migrator.rb +93 -60
- data/lib/fixture_champagne/test_fixtures.rb +5 -4
- data/lib/fixture_champagne/version.rb +1 -1
- data/lib/fixture_champagne.rb +4 -0
- data/lib/tasks/fixture_champagne_tasks.rake +15 -2
- 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: df7d52e3ca9af49027da830311f500968199eedca0af5cf43f37c15d14c323ae
|
4
|
+
data.tar.gz: b0d33d41c4f49d83e2471b5c7c0d3d30e8e98eac7a333b70461d27a462a34a88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d1aaa2fbe30d236f8f7ce8fa06e989b2b20864d5a8cc02d708a037843c3337859c566edc1067da36eb921651654f9943d75e27440b9ffa87a1f7e69e5e96585
|
7
|
+
data.tar.gz: cb33035170df7e2bd01586a2023cb832a3213f534a1ed43677fb44c7d264afa6e9e570b0d63425a06be832d765b5cd5618ab3cd98fa5218fa053ccc2d6898738
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
### Fixture migrations for your Ruby on Rails applications
|
4
4
|
|
5
|
+
[![Build Status](https://github.com/jguecaimburu/fixture_champagne/workflows/Tests/badge.svg)](https://github.com/jguecaimburu/fixture_champagne/actions) [![Gem Version](https://badge.fury.io/rb/fixture_champagne.svg)](https://badge.fury.io/rb/fixture_champagne)
|
6
|
+
|
5
7
|
|
6
8
|
Fixture Champagne is designed to help you keep your fixtures tidy, applying the data migration pattern to create, update or destroy fixtures.
|
7
9
|
|
@@ -92,7 +94,7 @@ Add the `up` and `down` logic to the new migration:
|
|
92
94
|
class CreateInitialEnemy < FixtureChampagne::Migration::Base
|
93
95
|
def up
|
94
96
|
unless Enemy.find_by(name: "Initial Enemy").present?
|
95
|
-
Enemy.create!(name: "Initial Enemy", level: levels(:first_level))
|
97
|
+
Enemy.create!(name: "Initial Enemy", level: levels(:first_level))
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
@@ -114,8 +116,9 @@ enemies_12345678:
|
|
114
116
|
|
115
117
|
If the migration is successful, the migrator will take the max version all available migrations and the current schema version and save both numbers in `test/.fixture_champagne_versions.yml` (or `spec/` if using Rspec) to identify future pending migrations.
|
116
118
|
|
117
|
-
The default label for a new fixture is a unique identifier composed of the table name and the record id. However, this label can be configured
|
119
|
+
The default label for a new fixture is a unique identifier composed of the table name and the record id. However, this label can be [configured](#fixture-labels).
|
118
120
|
|
121
|
+
You can also customize your fixture migrations by overriding the default template. Just place yours in `lib/templates/fixture_champagne/migration/migration.rb.tt`. This can be useful for example if you use [FactoryBot](#extending-your-migrations).
|
119
122
|
|
120
123
|
### Rollback
|
121
124
|
|
@@ -136,6 +139,9 @@ You can better control fixture migrations by creating a config YAML file: `test/
|
|
136
139
|
|
137
140
|
Setting the `overwrite` key to `false` will leave your current fixtures untouched. The generated fixtures will go to `tmp/fixtures`. Default value is set to `true`.
|
138
141
|
|
142
|
+
This feature has partial support for multiple fixture paths (Rails > 7.1). If your fixtures are divided across multiple paths (for example `test/fixtures_a` and `test/fixtues_b`), the fixture migration will fail unless you set `overwrite` to `false`. However, if you set multiple folders but only use one (all your files are in `test/fixtures_a`) then the migration can overwrite that folder.
|
143
|
+
|
144
|
+
|
139
145
|
```yaml
|
140
146
|
# test/fixture_champagne.yml
|
141
147
|
|
@@ -152,7 +158,7 @@ In the previous example, you can configure:
|
|
152
158
|
# test/fixture_champagne.yml
|
153
159
|
|
154
160
|
label:
|
155
|
-
enemy: "%{name}"
|
161
|
+
enemy: "%{name}"
|
156
162
|
```
|
157
163
|
|
158
164
|
To generate:
|
@@ -181,7 +187,7 @@ If `rename` is set to `true`, every time you run `migrate` or `rollback` all fix
|
|
181
187
|
|
182
188
|
Setting the `ignore` key will allow you to control which tables get saved as fixtures. It accepts an array, where items are table names. Any table ignored by this configuration will disappear from the corresponding fixture folder (depending on `overwrite`).
|
183
189
|
|
184
|
-
Let's say for example that each time a new `Enemy` gets created, it creates an associated `Event` in a callback that runs some processing in the background. If that event belongs to a polymorphic `eventable`, for every single one of those, a new event will be added to your fixtures, making the `events.yml` a big but not very useful file. Or maybe events get incinerated a couple of days after execution and it makes no sense to have fixtures for them. In any of those situations, you could ignore them from fixtures like this:
|
190
|
+
Let's say for example that each time a new `Enemy` gets created, it creates an associated `Event` in a callback that runs some processing in the background. If that event belongs to a polymorphic `eventable`, for every single one of those, a new event will be added to your fixtures, making the `events.yml` a big but not very useful file. Or maybe events get incinerated a couple of days after execution and it makes no sense to have fixtures for them. In any of those situations, you could ignore them from fixtures like this:
|
185
191
|
|
186
192
|
```yaml
|
187
193
|
# test/fixture_champagne.yml
|
@@ -207,6 +213,45 @@ If you use single table inheritance, then the file will correspond with the pare
|
|
207
213
|
All fixtures files that correspond to attachments will be copied as they are. Those are the ones located in `fixtures/files`, `fixtures/active_storage` and `fixtures/action_text`.
|
208
214
|
|
209
215
|
|
216
|
+
### Extending your migrations
|
217
|
+
|
218
|
+
You can customize your fixture migrations by overriding the default template. Just place yours in `lib/templates/fixture_champagne/migration/migration.rb.tt`. Let's say you use [FactoryBot](https://github.com/thoughtbot/factory_bot_rails). It could be really helpful to use your existing factories to create new fixtures.
|
219
|
+
|
220
|
+
You could replace the default template with:
|
221
|
+
```ruby
|
222
|
+
require "factory_bot_rails"
|
223
|
+
|
224
|
+
class <%= class_name %> < FixtureChampagne::Migration::Base
|
225
|
+
include FactoryBot::Syntax::Methods
|
226
|
+
|
227
|
+
def up
|
228
|
+
# Create, update or destroy records here
|
229
|
+
end
|
230
|
+
|
231
|
+
def down
|
232
|
+
# Optionally, reverse changes made by the :up method
|
233
|
+
end
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
And all your migrations will now have access to your existing factories. If you have an enemy factory, the new migration could look like:
|
238
|
+
```ruby
|
239
|
+
# 20230126153650_create_initial_enemy.rb
|
240
|
+
|
241
|
+
class CreateInitialEnemy < FixtureChampagne::Migration::Base
|
242
|
+
def up
|
243
|
+
unless Enemy.find_by(name: "Initial Enemy").present?
|
244
|
+
create(:enemy, name: "Initial Enemy", level: levels(:first_level))
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def down
|
249
|
+
Enemy.find_by(name: "Initial Enemy").destroy!
|
250
|
+
end
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
|
210
255
|
## Features currently not supported
|
211
256
|
|
212
257
|
The following fixture features are not supported:
|
@@ -217,6 +262,7 @@ The following fixture features are not supported:
|
|
217
262
|
- Fixture label interpolation (favoured configuration)
|
218
263
|
- HABTM (`have_and_belong_to_many`) associations as inline lists
|
219
264
|
- Support for YAML defaults (this could be nice)
|
265
|
+
- Overwriting multiple fixture paths
|
220
266
|
|
221
267
|
As stated before, at least for now, fixtures files that correspond to attachments will be copied as they are. This means:
|
222
268
|
- This fixtures must be generated manually
|
@@ -4,7 +4,7 @@ module FixtureChampagne
|
|
4
4
|
# MigrationContext sets the context in which a fixture migration is run.
|
5
5
|
#
|
6
6
|
# A migration context checks where the application files are located, which could be
|
7
|
-
# in /test if the app uses Minitest or /spec if the app uses
|
7
|
+
# in /test if the app uses Minitest or /spec if the app uses RSpec, and from that base
|
8
8
|
# it decides where everything else is located:
|
9
9
|
# - Fixture migrations folder
|
10
10
|
# - Configuration YAML file
|
@@ -14,8 +14,13 @@ module FixtureChampagne
|
|
14
14
|
# With all that it decides which migrations are pending, which are executed and the target
|
15
15
|
# versions. Depending on the method called, it also decides the direction the Migrator should execute.
|
16
16
|
class MigrationContext
|
17
|
+
MINITEST_PATH = Rails.root.join("test")
|
18
|
+
RSPEC_PATH = Rails.root.join("spec")
|
19
|
+
|
17
20
|
class << self
|
18
21
|
def migrate
|
22
|
+
raise NotInTestEnvironmentError unless Rails.env.test?
|
23
|
+
|
19
24
|
build_context.migrate
|
20
25
|
end
|
21
26
|
|
@@ -44,18 +49,19 @@ module FixtureChampagne
|
|
44
49
|
end
|
45
50
|
|
46
51
|
def test_suite_folder_path
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
minitest_path
|
52
|
-
elsif rspec_path.exist?
|
53
|
-
rspec_path
|
52
|
+
if test_framework == :rspec && RSPEC_PATH.exist?
|
53
|
+
RSPEC_PATH
|
54
|
+
elsif MINITEST_PATH.exist?
|
55
|
+
MINITEST_PATH
|
54
56
|
else
|
55
|
-
raise "No test nor spec folder found"
|
57
|
+
raise "No test nor spec folder found. Tried: #{[MINITEST_PATH, RSPEC_PATH].to_json}"
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
61
|
+
def test_framework
|
62
|
+
::Rails.application.config.generators.options[:rails][:test_framework]
|
63
|
+
end
|
64
|
+
|
59
65
|
def schema_current_version
|
60
66
|
::ActiveRecord::Migrator.current_version
|
61
67
|
end
|
@@ -84,8 +90,21 @@ module FixtureChampagne
|
|
84
90
|
test_suite_folder_path.join("fixture_champagne.yml")
|
85
91
|
end
|
86
92
|
|
87
|
-
def
|
88
|
-
|
93
|
+
def fixture_paths
|
94
|
+
paths = if test_framework == :rspec
|
95
|
+
rspec_fixture_paths
|
96
|
+
else
|
97
|
+
minitest_fixture_paths
|
98
|
+
end
|
99
|
+
paths.map { |p| Pathname.new(p) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def minitest_fixture_paths
|
103
|
+
MinitestConfig.new(MINITEST_PATH).fixture_paths
|
104
|
+
end
|
105
|
+
|
106
|
+
def rspec_fixture_paths
|
107
|
+
RSpecConfig.new(RSPEC_PATH).fixture_paths
|
89
108
|
end
|
90
109
|
end
|
91
110
|
|
@@ -208,5 +227,48 @@ module FixtureChampagne
|
|
208
227
|
@configuration_data["ignore"].to_a || []
|
209
228
|
end
|
210
229
|
end
|
230
|
+
|
231
|
+
# RSpecConfig loads RSpec configuration
|
232
|
+
class RSpecConfig
|
233
|
+
attr_reader :rspec_path
|
234
|
+
|
235
|
+
def initialize(rspec_path)
|
236
|
+
@rspec_path = rspec_path
|
237
|
+
end
|
238
|
+
|
239
|
+
def fixture_paths
|
240
|
+
$LOAD_PATH.unshift(rspec_path)
|
241
|
+
require "rspec/rails"
|
242
|
+
require_relative rspec_path.join("rails_helper").to_s
|
243
|
+
|
244
|
+
if RSpec.configuration.respond_to?(:fixture_paths=)
|
245
|
+
RSpec.configuration.fixture_paths
|
246
|
+
else
|
247
|
+
Array(RSpec.configuration.fixture_path)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# MinitestConfig loads Minitest configuration
|
253
|
+
class MinitestConfig
|
254
|
+
attr_reader :minitest_path
|
255
|
+
|
256
|
+
def initialize(minitest_path)
|
257
|
+
@minitest_path = minitest_path
|
258
|
+
end
|
259
|
+
|
260
|
+
def fixture_paths
|
261
|
+
# Monkey patching Minitest to avoid autorun
|
262
|
+
Minitest.define_singleton_method(:autorun) { nil }
|
263
|
+
|
264
|
+
require "rails/test_help"
|
265
|
+
|
266
|
+
if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
|
267
|
+
ActiveSupport::TestCase.fixture_paths
|
268
|
+
else
|
269
|
+
Array(ActiveSupport::TestCase.fixture_path)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
211
273
|
end
|
212
274
|
end
|
@@ -14,18 +14,12 @@ module FixtureChampagne
|
|
14
14
|
|
15
15
|
attr_reader :direction, :migrations, :target_migration_version, :target_schema_version, :configuration
|
16
16
|
|
17
|
+
TMP_FIXTURE_PATH = Rails.root.join("tmp", "fixtures")
|
18
|
+
|
17
19
|
class << self
|
18
20
|
def fixture_unique_id(table_name:, id:)
|
19
21
|
"#{table_name}_#{id}"
|
20
22
|
end
|
21
|
-
|
22
|
-
def tmp_fixture_path
|
23
|
-
Rails.root.join("tmp", "fixtures")
|
24
|
-
end
|
25
|
-
|
26
|
-
def fixture_attachment_folders
|
27
|
-
%w[files active_storage action_text].map { |f| fixture_path.join(f) }
|
28
|
-
end
|
29
23
|
end
|
30
24
|
|
31
25
|
def initialize(
|
@@ -86,7 +80,7 @@ module FixtureChampagne
|
|
86
80
|
end
|
87
81
|
|
88
82
|
def build_fixture_data(klasses)
|
89
|
-
data =
|
83
|
+
data = serialize_database_model_instances(klasses)
|
90
84
|
|
91
85
|
data.each_with_object({}) do |(table, table_data), sorted_data|
|
92
86
|
next if table_data.empty?
|
@@ -95,7 +89,7 @@ module FixtureChampagne
|
|
95
89
|
end
|
96
90
|
end
|
97
91
|
|
98
|
-
def
|
92
|
+
def serialize_database_model_instances(klasses)
|
99
93
|
klasses.each_with_object({}) do |klass, hash|
|
100
94
|
table_name = klass.table_name.to_s
|
101
95
|
if hash.key?(table_name)
|
@@ -103,21 +97,21 @@ module FixtureChampagne
|
|
103
97
|
"repeated table key for new fixtures, table #{table_name}, class: #{klass}"
|
104
98
|
end
|
105
99
|
|
106
|
-
hash[table_name] =
|
100
|
+
hash[table_name] = serialize_table_model_instances(klass)
|
107
101
|
end
|
108
102
|
end
|
109
103
|
|
110
|
-
def
|
104
|
+
def serialize_table_model_instances(klass)
|
111
105
|
table_data = {}
|
112
106
|
|
113
|
-
klass.all.each do |
|
114
|
-
label = fixture_label(
|
107
|
+
klass.all.each do |model_instance|
|
108
|
+
label = fixture_label(model_instance)
|
115
109
|
if table_data.key?(label)
|
116
110
|
raise RepeatedFixtureError,
|
117
111
|
"repeated fixture in serialization for label #{label}, class #{klass}"
|
118
112
|
end
|
119
113
|
|
120
|
-
table_data[label] = fixture_serialized_attributes(
|
114
|
+
table_data[label] = fixture_serialized_attributes(model_instance)
|
121
115
|
end
|
122
116
|
|
123
117
|
table_data
|
@@ -128,8 +122,8 @@ module FixtureChampagne
|
|
128
122
|
self.class.fixture_unique_id(table_name: fixture_set.table_name, id: fixture.find.id)
|
129
123
|
end
|
130
124
|
|
131
|
-
def fixture_label(
|
132
|
-
labeler.label_for(
|
125
|
+
def fixture_label(model_instance)
|
126
|
+
labeler.label_for(model_instance: model_instance)
|
133
127
|
end
|
134
128
|
|
135
129
|
def labeler
|
@@ -140,8 +134,8 @@ module FixtureChampagne
|
|
140
134
|
)
|
141
135
|
end
|
142
136
|
|
143
|
-
def fixture_serialized_attributes(
|
144
|
-
serializer.serialized_attributes_for(
|
137
|
+
def fixture_serialized_attributes(model_instance)
|
138
|
+
serializer.serialized_attributes_for(model_instance: model_instance)
|
145
139
|
end
|
146
140
|
|
147
141
|
def serializer
|
@@ -163,20 +157,24 @@ module FixtureChampagne
|
|
163
157
|
end
|
164
158
|
|
165
159
|
def setup_temporary_fixtures_dir
|
166
|
-
FileUtils.rm_r(
|
167
|
-
FileUtils.mkdir(
|
160
|
+
FileUtils.rm_r(TMP_FIXTURE_PATH, secure: true) if TMP_FIXTURE_PATH.exist?
|
161
|
+
FileUtils.mkdir(TMP_FIXTURE_PATH)
|
168
162
|
end
|
169
163
|
|
170
164
|
def copy_fixture_attachments
|
171
|
-
|
172
|
-
FileUtils.cp_r(folder,
|
165
|
+
fixture_attachment_folders.each do |folder|
|
166
|
+
FileUtils.cp_r(folder, TMP_FIXTURE_PATH) if folder.exist?
|
173
167
|
end
|
174
168
|
end
|
175
169
|
|
170
|
+
def fixture_attachment_folders
|
171
|
+
%w[files active_storage action_text].map { |f| effective_fixture_path.join(f) }
|
172
|
+
end
|
173
|
+
|
176
174
|
def temporary_fixture_filename(klass)
|
177
175
|
parts = klass.to_s.split("::").map(&:underscore)
|
178
176
|
parts << parts.pop.pluralize.concat(".yml")
|
179
|
-
|
177
|
+
TMP_FIXTURE_PATH.join(*parts)
|
180
178
|
end
|
181
179
|
|
182
180
|
def create_temporary_fixture_file(data, filename)
|
@@ -188,12 +186,36 @@ module FixtureChampagne
|
|
188
186
|
end
|
189
187
|
|
190
188
|
def overwrite_fixtures
|
191
|
-
removable_fixture_path =
|
192
|
-
FileUtils.mv(
|
193
|
-
FileUtils.mv(
|
189
|
+
removable_fixture_path = effective_fixture_path.dirname.join("old_fixtures")
|
190
|
+
FileUtils.mv(effective_fixture_path, removable_fixture_path)
|
191
|
+
FileUtils.mv(TMP_FIXTURE_PATH, effective_fixture_path)
|
194
192
|
FileUtils.rm_r(removable_fixture_path, secure: true)
|
195
193
|
end
|
196
194
|
|
195
|
+
def effective_fixture_path
|
196
|
+
@effective_fixture_path ||= set_effective_fixture_path
|
197
|
+
end
|
198
|
+
|
199
|
+
def set_effective_fixture_path
|
200
|
+
# From Rails 7.1 users can set multiple fixture paths, the default being test/fixtures.
|
201
|
+
# Most users will use the default path. Others may use a custom path but keep the default in the array,
|
202
|
+
# even if they do not use it. For now the gem supports only these 2 cases.
|
203
|
+
# If a user has fixture files in multiple paths, then it's trickier to decide where to save the new
|
204
|
+
# generated fixtures, so for now the gem will not allow these users to overwrite current files.
|
205
|
+
|
206
|
+
paths_with_files = MigrationContext.fixture_paths.select do |path|
|
207
|
+
Dir[::File.join(path, "{**,*}/*.{yml}")].any?
|
208
|
+
end
|
209
|
+
|
210
|
+
if paths_with_files.size > 1 && configuration.overwrite_fixtures?
|
211
|
+
raise OverwriteNotAllowedError,
|
212
|
+
"can't overwrite fixtures when using multiple folders: #{path_with_files.to_json}. Set overwrite to false"
|
213
|
+
end
|
214
|
+
|
215
|
+
# If all folders are still empty, use the first one
|
216
|
+
paths_with_files.first || MigrationContext.fixture_paths
|
217
|
+
end
|
218
|
+
|
197
219
|
def remember_new_fixture_versions
|
198
220
|
File.open(MigrationContext.fixture_versions_path, "w") do |file|
|
199
221
|
yaml = YAML.dump({ "version" => target_migration_version, "schema_version" => target_schema_version })
|
@@ -211,45 +233,45 @@ module FixtureChampagne
|
|
211
233
|
@rename = rename
|
212
234
|
end
|
213
235
|
|
214
|
-
def label_for(
|
236
|
+
def label_for(model_instance:)
|
215
237
|
if @rename
|
216
|
-
build_label_for(
|
238
|
+
build_label_for(model_instance)
|
217
239
|
else
|
218
|
-
find_label_for(
|
240
|
+
find_label_for(model_instance) || build_label_for(model_instance)
|
219
241
|
end
|
220
242
|
end
|
221
243
|
|
222
244
|
private
|
223
245
|
|
224
|
-
def find_label_for(
|
225
|
-
@pre_existing_fixtures_labels[
|
246
|
+
def find_label_for(model_instance)
|
247
|
+
@pre_existing_fixtures_labels[model_instance_unique_id(model_instance)]
|
226
248
|
end
|
227
249
|
|
228
|
-
def build_label_for(
|
229
|
-
template = @templates[
|
250
|
+
def build_label_for(model_instance)
|
251
|
+
template = @templates[model_instance.class.table_name]
|
230
252
|
|
231
253
|
if template.nil? || template == "DEFAULT"
|
232
|
-
default_label(
|
254
|
+
default_label(model_instance)
|
233
255
|
else
|
234
|
-
interpolate_template(template,
|
256
|
+
interpolate_template(template, model_instance)
|
235
257
|
end
|
236
258
|
end
|
237
259
|
|
238
|
-
def default_label(
|
239
|
-
|
260
|
+
def default_label(model_instance)
|
261
|
+
model_instance_unique_id(model_instance)
|
240
262
|
end
|
241
263
|
|
242
|
-
def
|
243
|
-
Migrator.fixture_unique_id(table_name:
|
264
|
+
def model_instance_unique_id(model_instance)
|
265
|
+
Migrator.fixture_unique_id(table_name: model_instance.class.table_name, id: model_instance.id)
|
244
266
|
end
|
245
267
|
|
246
|
-
def interpolate_template(template,
|
268
|
+
def interpolate_template(template, model_instance)
|
247
269
|
template.gsub(INTERPOLATION_PATTERN) do
|
248
270
|
attribute = ::Regexp.last_match(1).to_sym
|
249
|
-
value = if
|
250
|
-
|
271
|
+
value = if model_instance.respond_to?(attribute)
|
272
|
+
model_instance.send(attribute)
|
251
273
|
else
|
252
|
-
raise WrongFixtureLabelInterpolationError, attribute: attribute, klass:
|
274
|
+
raise WrongFixtureLabelInterpolationError, attribute: attribute, klass: model_instance.class
|
253
275
|
end
|
254
276
|
value
|
255
277
|
end.parameterize(separator: "_")
|
@@ -264,56 +286,63 @@ module FixtureChampagne
|
|
264
286
|
@labeler = labeler
|
265
287
|
end
|
266
288
|
|
267
|
-
def serialized_attributes_for(
|
268
|
-
column_attributes =
|
289
|
+
def serialized_attributes_for(model_instance:)
|
290
|
+
column_attributes = model_instance.attributes.select { |a| model_instance.class.column_names.include?(a) }
|
269
291
|
|
270
292
|
# Favour fixtures autofilled timestamps and autogenerated ids
|
271
293
|
filtered_attributes = %w[id created_at updated_at]
|
272
294
|
|
273
295
|
serialized_attributes = column_attributes.map do |attribute, value|
|
274
|
-
serialize_attribute(
|
296
|
+
serialize_attribute(model_instance, attribute, value, filtered_attributes)
|
275
297
|
end
|
276
298
|
|
277
299
|
serialized_attributes.sort_by(&:first).to_h.except(*filtered_attributes)
|
278
300
|
end
|
279
301
|
|
280
|
-
def serialize_attribute(
|
281
|
-
belongs_to_association =
|
302
|
+
def serialize_attribute(model_instance, attribute, value, filtered_attributes)
|
303
|
+
belongs_to_association = model_instance.class.reflect_on_all_associations.filter(&:belongs_to?).find do |a|
|
282
304
|
a.foreign_key.to_s == attribute
|
283
305
|
end
|
284
|
-
type =
|
306
|
+
type = model_instance.class.type_for_attribute(attribute)
|
285
307
|
|
286
308
|
if belongs_to_association.present?
|
287
309
|
filter_belongs_to_columns(belongs_to_association, filtered_attributes)
|
288
|
-
serialize_belongs_to(
|
310
|
+
serialize_belongs_to(model_instance, belongs_to_association)
|
289
311
|
else
|
290
|
-
serialize_type(
|
312
|
+
serialize_type(model_instance, attribute, value, type)
|
291
313
|
end
|
292
314
|
end
|
293
315
|
|
294
|
-
def serialize_belongs_to(
|
295
|
-
|
316
|
+
def serialize_belongs_to(model_instance, belongs_to_association)
|
317
|
+
associated_model_instance = model_instance.send(belongs_to_association.name)
|
296
318
|
|
297
319
|
reference_label = if belongs_to_association.polymorphic?
|
298
|
-
foreign_type =
|
299
|
-
"#{labeler.label_for(
|
320
|
+
foreign_type = model_instance.send(belongs_to_association.foreign_type)
|
321
|
+
"#{labeler.label_for(model_instance: associated_model_instance)} (#{foreign_type})"
|
300
322
|
else
|
301
|
-
labeler.label_for(
|
323
|
+
labeler.label_for(model_instance: associated_model_instance)
|
302
324
|
end
|
303
325
|
|
304
326
|
[belongs_to_association.name.to_s, reference_label]
|
305
327
|
end
|
306
328
|
|
307
|
-
|
308
|
-
|
329
|
+
# rubocop:disable Metrics/MethodLength
|
330
|
+
def serialize_type(model_instance, attribute, value, type)
|
331
|
+
if type.respond_to?(:scheme) && encrypted_fixtures?
|
332
|
+
[attribute, type.cast_type.serialize(value)]
|
333
|
+
elsif type.type == :datetime
|
309
334
|
# ActiveRecord::Type::DateTime#serialize returns a TimeWithZone object that makes the YAML dump less clear
|
310
|
-
[attribute,
|
335
|
+
[attribute, model_instance.read_attribute_before_type_cast(attribute)]
|
336
|
+
# PostgreSQL jsonb attributes can be saved as hashes or arrays in fixture yaml files
|
337
|
+
elsif type.type == :jsonb
|
338
|
+
[attribute, value]
|
311
339
|
elsif type.respond_to?(:serialize)
|
312
340
|
[attribute, type.serialize(value)]
|
313
341
|
else
|
314
342
|
[attribute, value.to_s]
|
315
343
|
end
|
316
344
|
end
|
345
|
+
# rubocop:enable Metrics/MethodLength
|
317
346
|
|
318
347
|
def filter_belongs_to_columns(belongs_to_association, filtered_attributes)
|
319
348
|
filtered_attributes << belongs_to_association.foreign_key.to_s
|
@@ -321,6 +350,10 @@ module FixtureChampagne
|
|
321
350
|
|
322
351
|
filtered_attributes << belongs_to_association.foreign_type.to_s
|
323
352
|
end
|
353
|
+
|
354
|
+
def encrypted_fixtures?
|
355
|
+
Rails.configuration.active_record.encryption.encrypt_fixtures
|
356
|
+
end
|
324
357
|
end
|
325
358
|
end
|
326
359
|
end
|
@@ -11,7 +11,11 @@ module FixtureChampagne
|
|
11
11
|
include ActiveRecord::TestFixtures
|
12
12
|
|
13
13
|
included do
|
14
|
-
|
14
|
+
if respond_to?(:fixture_paths)
|
15
|
+
self.fixture_paths = MigrationContext.fixture_paths
|
16
|
+
else
|
17
|
+
self.fixture_path = MigrationContext.fixture_paths.first
|
18
|
+
end
|
15
19
|
fixtures :all
|
16
20
|
end
|
17
21
|
|
@@ -20,9 +24,6 @@ module FixtureChampagne
|
|
20
24
|
end
|
21
25
|
|
22
26
|
def before_migrate
|
23
|
-
# Database is already prepared
|
24
|
-
ActiveRecord::TestDatabases.create_and_load_schema(0, env_name: "test") unless Rails.env.test?
|
25
|
-
|
26
27
|
setup_fixtures
|
27
28
|
end
|
28
29
|
|
data/lib/fixture_champagne.rb
CHANGED
@@ -4,12 +4,25 @@ require "fixture_champagne"
|
|
4
4
|
|
5
5
|
namespace :fixture_champagne do
|
6
6
|
desc "Run Fixture Champagne migrations to update fixtures"
|
7
|
-
task migrate: :
|
7
|
+
task migrate: :_load_test_environment do
|
8
8
|
FixtureChampagne::MigrationContext.migrate
|
9
9
|
end
|
10
10
|
|
11
11
|
desc "Rollback Fixture Champagne last executed migration"
|
12
|
-
task rollback: :
|
12
|
+
task rollback: :_load_test_environment do
|
13
13
|
FixtureChampagne::MigrationContext.rollback
|
14
14
|
end
|
15
|
+
|
16
|
+
# HACK: Forces the task to run in the test environment. Rails configs each
|
17
|
+
# environment differently and this can affect how to handle the database.
|
18
|
+
# Keeping the migration with the same environment as the tests makes everything
|
19
|
+
# much simpler.
|
20
|
+
task :_force_test_environment do # rubocop:disable Rake:Desc
|
21
|
+
unless Rails.env.test?
|
22
|
+
tasks = Rake.application.top_level_tasks
|
23
|
+
exec({ "RAILS_ENV" => "test" }, "bin/rails", *tasks)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
task _load_test_environment: %i[_force_test_environment environment]
|
15
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fixture_champagne
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Juan Gueçaimburu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|