From 0f2211516d63f88758b9ca9bb101cca2296b6919 Mon Sep 17 00:00:00 2001 From: Jordan Brough Date: Wed, 18 Nov 2015 21:11:57 -0700 Subject: [PATCH] Simplify fixture generation and add customization hooks - Always use ActiveRecord instead of only when we happen to be able to constantize the name. - Add select_scope_proc for custom record selection - Add hashize_record_proc for custom serialization - Use attributes_before_type_cast --- lib/fixture_builder/builder.rb | 47 +++++++++------------------- lib/fixture_builder/configuration.rb | 24 +++++++++++++- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/lib/fixture_builder/builder.rb b/lib/fixture_builder/builder.rb index 4dfaa0d..8807f88 100644 --- a/lib/fixture_builder/builder.rb +++ b/lib/fixture_builder/builder.rb @@ -96,26 +96,25 @@ 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 - if table_klass && table_klass < ActiveRecord::Base - rows = table_klass.unscoped do - table_klass.all.collect do |obj| - attrs = obj.attributes - attrs.inject({}) do |hash, (attr_name, value)| - hash[attr_name] = serialized_value_if_needed(table_klass, attr_name, value) - hash - end - end - end - else - rows = ActiveRecord::Base.connection.select_all(select_sql % {table: ActiveRecord::Base.connection.quote_table_name(table_name)}) + # Always create our own Class (inheriting from ActiveRecord) so that: + # 1) We can always use ActiveRecord, even if the app doesn't have an + # ActiveRecord model defined (e.g. some join tables) + # 2) We don't have to worry about default scopes and other things that + # may be present on the application's class. + table_class = Class.new(ActiveRecord::Base) { self.table_name = table_name } + + records = select_scope_proc.call(table_class).to_a + + rows = records.map do |record| + hashize_record_proc.call(record) 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 + fixture_data = rows.map do |row| + [record_name(row, table_name, row_index), row] + end.to_h write_fixture_file fixture_data, table_name @@ -127,22 +126,6 @@ def dump_tables say "Built #{fixtures.to_sentence}" end - def serialized_value_if_needed(table_klass, attr_name, value) - if table_klass.respond_to?(:type_for_attribute) - if table_klass.type_for_attribute(attr_name).respond_to?(:serialize) - table_klass.type_for_attribute(attr_name).serialize(value) - else - table_klass.type_for_attribute(attr_name).type_cast_for_database(value) - end - else - if table_klass.serialized_attributes.has_key? attr_name - table_klass.serialized_attributes[attr_name].dump(value) - else - value - end - end - end - def write_fixture_file(fixture_data, table_name) File.open(fixture_file(table_name), 'w') do |file| file.write fixture_data.to_yaml diff --git a/lib/fixture_builder/configuration.rb b/lib/fixture_builder/configuration.rb index c56584a..d0fece5 100644 --- a/lib/fixture_builder/configuration.rb +++ b/lib/fixture_builder/configuration.rb @@ -9,7 +9,7 @@ class Configuration ACCESSIBLE_ATTRIBUTES = [:select_sql, :delete_sql, :skip_tables, :files_to_check, :record_name_fields, :fixture_builder_file, :fixture_directory, :after_build, :legacy_fixtures, :model_name_procs, - :write_empty_files] + :write_empty_files, :select_scope_proc, :hashize_record_proc] attr_accessor(*ACCESSIBLE_ATTRIBUTES) SCHEMA_FILES = ['db/schema.rb', 'db/development_structure.sql', 'db/test_structure.sql', 'db/production_structure.sql'] @@ -35,6 +35,28 @@ def factory(&block) write_config end + # this gets called when selecting records from the database to dump into + # fixtures. you can use it to customize things like the order in which + # records are selected. + def select_scope_proc + @select_scope_proc ||= ->(table_class) do + scope = table_class.unscoped + if table_class.primary_key + scope = scope.order(table_class.primary_key => :asc) + end + scope + end + end + + # this gets called to turn each record into a hash before dumping to yaml. + # you can customize it if you want to do things like leave out some fields + # (e.g. created_at & updated_at, which are automatically populated by Rails) + def hashize_record_proc + @hashize_record_proc ||= ->(record) do + record.attributes_before_type_cast + end + end + def select_sql @select_sql ||= "SELECT * FROM %{table}" end