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 diff --git a/lib/fixture_builder/builder.rb b/lib/fixture_builder/builder.rb index 83e5d9a..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 = 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 @@ -154,7 +149,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 056d29c..c582230 100644 --- a/lib/fixture_builder/configuration.rb +++ b/lib/fixture_builder/configuration.rb @@ -110,7 +110,45 @@ 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 + + # 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 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/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 eac005d..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 @@ -80,7 +94,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 @@ -115,4 +135,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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 778720a..be9b698 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -40,19 +40,34 @@ class MagicalCreature < ActiveRecord::Base end end +class Unnameable < 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| + 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