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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c999bd5772d62cce811ec251c0a6703a58bd7daf7b31d6d00ed08fe80f9133f
4
- data.tar.gz: 1bb147d5724d90fd9341b480b449218e71827ab00e694bc6c10d1ffaa99bda59
3
+ metadata.gz: df7d52e3ca9af49027da830311f500968199eedca0af5cf43f37c15d14c323ae
4
+ data.tar.gz: b0d33d41c4f49d83e2471b5c7c0d3d30e8e98eac7a333b70461d27a462a34a88
5
5
  SHA512:
6
- metadata.gz: b0a14630348c2e463ca683801cc72be51e9e8b65466a1fb73143072a486979991484dc6d2c80fe081287821d4afa8570c45ec4f33c13f4d2996a22c2202021f4
7
- data.tar.gz: 6c6e4d55bf6654cee801ca4545f052351f4ef81505921240e1e007d362e12e9c050f22cc3f9305c566b8946b7fd81831309dce87f00619266c35bd2263cf3bd5
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 (keep reading to know how).
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 Rspec, and from that base
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
- rspec_path = Rails.root.join("test")
48
- minitest_path = Rails.root.join("spec")
49
-
50
- if minitest_path.exist?
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 fixtures_path
88
- test_suite_folder_path.join("fixtures")
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 = serialize_database_records(klasses)
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 serialize_database_records(klasses)
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] = serialize_table_records(klass)
100
+ hash[table_name] = serialize_table_model_instances(klass)
107
101
  end
108
102
  end
109
103
 
110
- def serialize_table_records(klass)
104
+ def serialize_table_model_instances(klass)
111
105
  table_data = {}
112
106
 
113
- klass.all.each do |record|
114
- label = fixture_label(record)
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(record)
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(record)
132
- labeler.label_for(record: record)
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(record)
144
- serializer.serialized_attributes_for(record: record)
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(self.class.tmp_fixture_path, secure: true) if self.class.tmp_fixture_path.exist?
167
- FileUtils.mkdir(self.class.tmp_fixture_path)
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
- self.class.fixture_attachment_folders.each do |folder|
172
- FileUtils.cp_r(folder, self.class.tmp_fixture_path) if folder.exist?
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
- self.class.tmp_fixture_path.join(*parts)
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 = self.class.fixture_path.dirname.join("old_fixtures")
192
- FileUtils.mv(self.class.fixture_path, removable_fixture_path)
193
- FileUtils.mv(self.class.tmp_fixture_path, self.class.fixture_path)
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(record:)
236
+ def label_for(model_instance:)
215
237
  if @rename
216
- build_label_for(record)
238
+ build_label_for(model_instance)
217
239
  else
218
- find_label_for(record) || build_label_for(record)
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(record)
225
- @pre_existing_fixtures_labels[record_unique_id(record)]
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(record)
229
- template = @templates[record.class.table_name]
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(record)
254
+ default_label(model_instance)
233
255
  else
234
- interpolate_template(template, record)
256
+ interpolate_template(template, model_instance)
235
257
  end
236
258
  end
237
259
 
238
- def default_label(record)
239
- record_unique_id(record)
260
+ def default_label(model_instance)
261
+ model_instance_unique_id(model_instance)
240
262
  end
241
263
 
242
- def record_unique_id(record)
243
- Migrator.fixture_unique_id(table_name: record.class.table_name, id: record.id)
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, record)
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 record.respond_to?(attribute)
250
- record.send(attribute)
271
+ value = if model_instance.respond_to?(attribute)
272
+ model_instance.send(attribute)
251
273
  else
252
- raise WrongFixtureLabelInterpolationError, attribute: attribute, klass: record.class
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(record:)
268
- column_attributes = record.attributes.select { |a| record.class.column_names.include?(a) }
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(record, attribute, value, filtered_attributes)
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(record, attribute, value, filtered_attributes)
281
- belongs_to_association = record.class.reflect_on_all_associations.filter(&:belongs_to?).find do |a|
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 = record.class.type_for_attribute(attribute)
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(record, belongs_to_association)
310
+ serialize_belongs_to(model_instance, belongs_to_association)
289
311
  else
290
- serialize_type(record, attribute, value, type)
312
+ serialize_type(model_instance, attribute, value, type)
291
313
  end
292
314
  end
293
315
 
294
- def serialize_belongs_to(record, belongs_to_association)
295
- associated_record = record.send(belongs_to_association.name)
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 = record.send(belongs_to_association.foreign_type)
299
- "#{labeler.label_for(record: associated_record)} (#{foreign_type})"
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(record: associated_record)
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
- def serialize_type(record, attribute, value, type)
308
- if type.type == :datetime
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, record.read_attribute_before_type_cast(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
- self.fixture_path = MigrationContext.fixtures_path
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureChampagne
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -32,4 +32,8 @@ module FixtureChampagne # :nodoc:
32
32
  end
33
33
  end
34
34
  end
35
+
36
+ class NotInTestEnvironmentError < StandardError; end # :nodoc:
37
+
38
+ class OverwriteNotAllowedError < StandardError; end # :nodoc:
35
39
  end
@@ -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: :environment do
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: :environment do
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.1.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-02-07 00:00:00.000000000 Z
11
+ date: 2023-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails