From 73f397e35b3f70caeae6dbb554e5e4985f2abb1a Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Thu, 4 Aug 2022 20:06:52 -0400 Subject: [PATCH 1/6] Tell git to ignore all generated fixtures Authored-by: Adam Milligan --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c2860b..7338873 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ pkg/ .idea/* *.db tmp/fixture_builder.yml -test/fixtures/magical_creatures.yml +test/fixtures/**/*.yml Gemfile.lock *.gem From f10531edbb11aae4d7c3f0dd1ce78e754f798d1a Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Thu, 4 Aug 2022 20:09:09 -0400 Subject: [PATCH 2/6] Fix tests to run in Rails 7 Authored-by: Adam Milligan --- test/test_helper.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 778720a..7c002cc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -41,10 +41,19 @@ class MagicalCreature < ActiveRecord::Base end def create_and_blow_away_old_db - ActiveRecord::Base.configurations['test'] = { - 'adapter' => 'sqlite3', - 'database' => 'test.db' - } + if ActiveRecord::VERSION::MAJOR >= 7 + ActiveRecord::Base.configurations = { + test: { + 'adapter' => 'sqlite3', + 'database' => 'test.db' + } + } + else + ActiveRecord::Base.configurations['test'] = { + 'adapter' => 'sqlite3', + 'database' => 'test.db' + } + end ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.create_table(:magical_creatures, :force => true) do |t| From 46afe2e98d514ec8c3842d68a206a39df5965de7 Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Thu, 4 Aug 2022 20:10:11 -0400 Subject: [PATCH 3/6] Handle nested fixture paths Authored-by: Adam Milligan --- lib/fixture_builder/configuration.rb | 7 ++++++- test/fixture_builder_test.rb | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/fixture_builder/configuration.rb b/lib/fixture_builder/configuration.rb index 056d29c..298face 100644 --- a/lib/fixture_builder/configuration.rb +++ b/lib/fixture_builder/configuration.rb @@ -110,7 +110,12 @@ def fixture_directory end def fixtures_dir(path = '') - File.expand_path(File.join(fixture_directory, path)) + subdirs = path.split('/') + path = subdirs.pop || '' + dir = File.expand_path(File.join(fixture_directory, *subdirs)) + + FileUtils.mkdir_p(dir) + File.join(dir, path) end private diff --git a/test/fixture_builder_test.rb b/test/fixture_builder_test.rb index eac005d..f94096b 100644 --- a/test/fixture_builder_test.rb +++ b/test/fixture_builder_test.rb @@ -80,7 +80,13 @@ def test_absolute_rails_fixtures_path end def test_fixtures_dir - assert_match /test\/fixtures$/, FixtureBuilder.configuration.send(:fixtures_dir).to_s + file_path = "wibble.yml" + assert_match(/test\/fixtures\/#{file_path}$/, FixtureBuilder.configuration.send(:fixtures_dir, file_path).to_s) + end + + def test_nested_fixtures_dir + file_path = "foo/bar/wibble.yml" + assert_match(/test\/fixtures\/#{file_path}$/, FixtureBuilder.configuration.send(:fixtures_dir, file_path).to_s) end def test_rebuilding_due_to_differing_file_hashes From f7da2256aee2c0f66c205d9c141dfb03c7859986 Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Thu, 4 Aug 2022 20:10:34 -0400 Subject: [PATCH 4/6] Add per-table configuration options Authored-by: Adam Milligan --- lib/fixture_builder/builder.rb | 5 ++- lib/fixture_builder/configuration.rb | 33 +++++++++++++++ lib/fixture_builder/delegations.rb | 2 +- test/fixture_builder_test.rb | 60 ++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/lib/fixture_builder/builder.rb b/lib/fixture_builder/builder.rb index 83e5d9a..6ae1c0a 100644 --- a/lib/fixture_builder/builder.rb +++ b/lib/fixture_builder/builder.rb @@ -96,7 +96,7 @@ def dump_tables Date::DATE_FORMATS[:default] = Date::DATE_FORMATS[:db] begin fixtures = tables.inject([]) do |files, table_name| - table_klass = table_name.classify.constantize rescue nil + table_klass = fixture_classes[table_name] || table_name.classify.constantize rescue nil if table_klass && table_klass < ActiveRecord::Base rows = table_klass.unscoped do table_klass.order(:id).all.collect do |obj| @@ -154,7 +154,8 @@ def write_fixture_file(fixture_data, table_name) end def fixture_file(table_name) - fixtures_dir("#{table_name}.yml") + file_name = fixture_files[table_name] || table_name + fixtures_dir("#{file_name}.yml") end end end diff --git a/lib/fixture_builder/configuration.rb b/lib/fixture_builder/configuration.rb index 298face..c582230 100644 --- a/lib/fixture_builder/configuration.rb +++ b/lib/fixture_builder/configuration.rb @@ -118,6 +118,39 @@ def fixtures_dir(path = '') File.join(dir, path) end + # This allows for configuring fixtures for a given database table; you may + # set the model class for the table (e.g. for legacy tables) and the name + # of the resulting fixture file for the table (e.g. for nested fixtures). + # + # For example: + # + # FixtureBuilder.configure do |fbuilder| + # fbuilder.configure_tables(wibbles: { + # class: Legacy::Things, + # file: 'legacy/things' + # }) + # end + # + # This will tell the builder to use the `Legacy::Things` class to serialize + # fixtured dumped from the `wibbles` table, and to dump the `wibbles` table + # into the `legacy_things.yml` file. This also works with nested + # directories, which will map to namespaced fixtures. + def configure_tables(configuration) + configuration.each do |table_name, table_config| + table_name = table_name.to_s + fixture_classes.merge!(table_name => table_config[:class]) + fixture_files.merge!(table_name => table_config[:file]) + end + end + + def fixture_classes + @fixture_classes ||= {} + end + + def fixture_files + @fixture_files ||= {} + end + private def file_hashes diff --git a/lib/fixture_builder/delegations.rb b/lib/fixture_builder/delegations.rb index ad39a26..81b2bc9 100644 --- a/lib/fixture_builder/delegations.rb +++ b/lib/fixture_builder/delegations.rb @@ -4,7 +4,7 @@ module FixtureBuilder module Delegations module Configuration def self.included(base) - methods_to_delegate = [:fixtures_dir, :tables, :legacy_fixtures].concat(::FixtureBuilder::Configuration::ACCESSIBLE_ATTRIBUTES).flatten + methods_to_delegate = [:fixtures_dir, :tables, :legacy_fixtures, :fixture_classes, :fixture_files].concat(::FixtureBuilder::Configuration::ACCESSIBLE_ATTRIBUTES).flatten methods_to_delegate.each do |meth| base.delegate(meth, :to => :@configuration) end diff --git a/test/fixture_builder_test.rb b/test/fixture_builder_test.rb index f94096b..328df54 100644 --- a/test/fixture_builder_test.rb +++ b/test/fixture_builder_test.rb @@ -121,4 +121,64 @@ def test_sha1_digests assert_equal first_modified_time, second_modified_time end end + + def test_set_fixture_class + create_and_blow_away_old_db + force_fixture_generation + + old_klass = MagicalCreature + new_klass = Class.new(ActiveRecord::Base) do + self.table_name = "magical_creatures" + serialize :powers, Array + end + Object.instance_eval { remove_const(:MagicalCreature) } + + FixtureBuilder.configure do |fbuilder| + fbuilder.configure_tables(magical_creatures: { class: new_klass }) + + fbuilder.files_to_check += Dir[test_path("*.rb")] + fbuilder.factory do + @enty = new_klass.create(:name => 'Enty', :species => 'ent', + :powers => %w{shading rooting seeding}) + end + end + generated_fixture = YAML.load(File.open(test_path("fixtures/magical_creatures.yml"))) + assert_equal "---\n- shading\n- rooting\n- seeding\n", generated_fixture['enty']['powers'] + ensure + Object.const_set(:MagicalCreature, old_klass) + end + + def test_set_fixture_file + create_and_blow_away_old_db + force_fixture_generation + + FixtureBuilder.configure do |fbuilder| + fbuilder.configure_tables(magical_creatures: { file: "wibbles" }) + + fbuilder.files_to_check += Dir[test_path("*.rb")] + fbuilder.factory do + @enty = MagicalCreature.create(:name => 'Enty', :species => 'ent', + :powers => %w{shading rooting seeding}) + end + end + generated_fixture = YAML.load(File.open(test_path("fixtures/wibbles.yml"))) + assert_equal "---\n- shading\n- rooting\n- seeding\n", generated_fixture['enty']['powers'] + end + + def test_set_fixture_file_with_namespace + create_and_blow_away_old_db + force_fixture_generation + + FixtureBuilder.configure do |fbuilder| + fbuilder.configure_tables(magical_creatures: { file: "legacy/wibbles" }) + + fbuilder.files_to_check += Dir[test_path("*.rb")] + fbuilder.factory do + @enty = MagicalCreature.create(:name => 'Enty', :species => 'ent', + :powers => %w{shading rooting seeding}) + end + end + generated_fixture = YAML.load(File.open(test_path("fixtures/legacy/wibbles.yml"))) + assert_equal "---\n- shading\n- rooting\n- seeding\n", generated_fixture['enty']['powers'] + end end From af745dce3d34f56f657cd5e8c9369fcaee46e721 Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Mon, 29 Aug 2022 12:50:12 -0400 Subject: [PATCH 5/6] Make naming work with string primary keys Authored-by: Adam Milligan --- lib/fixture_builder/namer.rb | 6 +++--- test/fixture_builder_test.rb | 14 ++++++++++++++ test/test_helper.rb | 8 +++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/fixture_builder/namer.rb b/lib/fixture_builder/namer.rb index 953faf8..b611559 100644 --- a/lib/fixture_builder/namer.rb +++ b/lib/fixture_builder/namer.rb @@ -17,7 +17,7 @@ def name(custom_name, *model_objects) raise "Cannot name an object blank" unless custom_name.present? model_objects.each do |model_object| raise "Cannot name a blank object" unless model_object.present? - key = [model_object.class.table_name, model_object.id] + key = [model_object.class.table_name, model_object.id.to_s] raise "Cannot set name for #{key.inspect} object twice" if @custom_names[key] @custom_names[key] = custom_name model_object @@ -34,7 +34,7 @@ def populate_custom_names(created_fixtures) # Rails 3.0 and earlier, create_fixtures returns an array of tuples created_fixtures.each do |fixture| name = fixture[0] - id = fixture[1]['id'].to_i + id = fixture[1]['id'].to_s table_name = fixture[1].model_class.table_name key = [table_name, id] @custom_names[key] = name @@ -42,7 +42,7 @@ def populate_custom_names(created_fixtures) end def record_name(record_hash, table_name, row_index) - key = [table_name, record_hash['id'].to_i] + key = [table_name, record_hash['id'].to_s] name = case when name_proc = @model_name_procs[table_name] name_proc.call(record_hash, row_index.succ!) diff --git a/test/fixture_builder_test.rb b/test/fixture_builder_test.rb index 328df54..87a317c 100644 --- a/test/fixture_builder_test.rb +++ b/test/fixture_builder_test.rb @@ -38,6 +38,20 @@ def test_ivar_naming assert_equal 'king_of_gnomes', generated_fixture.keys.first end + def test_ivar_naming_with_uuid_primary_key + create_and_blow_away_old_db + force_fixture_generation + + FixtureBuilder.configure do |fbuilder| + fbuilder.files_to_check += Dir[test_path("*.rb")] + fbuilder.factory do + @you_know_who = Unnameable.create! + end + end + generated_fixture = YAML.load(File.open(test_path("fixtures/unnameables.yml"))) + assert_equal 'you_know_who', generated_fixture.keys.first + end + def test_serialization create_and_blow_away_old_db force_fixture_generation diff --git a/test/test_helper.rb b/test/test_helper.rb index 7c002cc..be9b698 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -40,6 +40,8 @@ class MagicalCreature < ActiveRecord::Base end end +class Unnameable < ActiveRecord::Base; end + def create_and_blow_away_old_db if ActiveRecord::VERSION::MAJOR >= 7 ActiveRecord::Base.configurations = { @@ -56,12 +58,16 @@ def create_and_blow_away_old_db end ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.create_table(:magical_creatures, :force => true) do |t| + ActiveRecord::Base.connection.create_table(:magical_creatures, force: true) do |t| t.column :name, :string t.column :species, :string t.column :powers, :string t.column :deleted, :boolean, :default => false, :null => false end + + ActiveRecord::Base.connection.create_table(:unnameables, force: true, id: false) do |t| + t.column :id, :uuid, primary: true + end end def force_fixture_generation From 7a042cc6369998e5fef6a400daf96837b3367700 Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Sun, 27 Aug 2023 13:17:14 -0400 Subject: [PATCH 6/6] Remove date format modification ActiveSupport has deprecated passing a format identifier to #to_s for date and time types. Prior to 7.0.7 calling #to_s with no parameters would use the `default` format, and the YAML processor calls #to_s on all fields when dumping content. FixtureBuilder was setting the `default` format to the ISO8601 format that the database expects, which caused the YAML processor to use that content. As of version 7.0.7 ActiveSupport no longer allows calling #to_s without parameters on date/time types if a default option is set. However, the standard default for Date formats is ISO8601, so FixtureBuilder does not need to set the default format. By leaving it unset the YAML dump does not trigger the deprecation warning, and the resulting formatted strings are still valid for Postgres. --- lib/fixture_builder/builder.rb | 45 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/lib/fixture_builder/builder.rb b/lib/fixture_builder/builder.rb index 6ae1c0a..cbe34f8 100644 --- a/lib/fixture_builder/builder.rb +++ b/lib/fixture_builder/builder.rb @@ -92,38 +92,33 @@ def dump_empty_fixtures_for_all_tables end def dump_tables - default_date_format = Date::DATE_FORMATS[:default] - Date::DATE_FORMATS[:default] = Date::DATE_FORMATS[:db] - begin - fixtures = tables.inject([]) do |files, table_name| - table_klass = fixture_classes[table_name] || table_name.classify.constantize rescue nil - if table_klass && table_klass < ActiveRecord::Base - rows = table_klass.unscoped do - table_klass.order(:id).all.collect do |obj| - attrs = obj.attributes.select { |attr_name| table_klass.column_names.include?(attr_name) } - attrs.inject({}) do |hash, (attr_name, value)| - hash[attr_name] = serialized_value_if_needed(table_klass, attr_name, value) - hash - end + fixtures = tables.inject([]) do |files, table_name| + table_klass = fixture_classes[table_name] || table_name.classify.constantize rescue nil + if table_klass && table_klass < ActiveRecord::Base + rows = table_klass.unscoped do + table_klass.order(:id).all.collect do |obj| + attrs = obj.attributes.select { |attr_name| table_klass.column_names.include?(attr_name) } + attrs.inject({}) do |hash, (attr_name, value)| + hash[attr_name] = serialized_value_if_needed(table_klass, attr_name, value) + hash end end - else - rows = ActiveRecord::Base.connection.select_all(select_sql % {table: ActiveRecord::Base.connection.quote_table_name(table_name)}) end - next files if rows.empty? + else + rows = ActiveRecord::Base.connection.select_all(select_sql % {table: ActiveRecord::Base.connection.quote_table_name(table_name)}) + end + next files if rows.empty? - row_index = '000' - fixture_data = rows.inject({}) do |hash, record| - hash.merge(record_name(record, table_name, row_index) => record) - end + row_index = '000' + fixture_data = rows.inject({}) do |hash, record| + hash.merge(record_name(record, table_name, row_index) => record) + end - write_fixture_file fixture_data, table_name + write_fixture_file fixture_data, table_name - files + [File.basename(fixture_file(table_name))] - end - ensure - Date::DATE_FORMATS[:default] = default_date_format + files + [File.basename(fixture_file(table_name))] end + say "Built #{fixtures.to_sentence}" end