git_friendly_dumper 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +10 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +48 -0
- data/Rakefile +43 -0
- data/features/dump/change_dump_path.feature +26 -0
- data/features/dump/data_dump.feature +27 -0
- data/features/dump/default_dump.feature +39 -0
- data/features/dump/dump_and_raise_error.feature +58 -0
- data/features/dump/dump_deleted_records_and_clobber.feature +98 -0
- data/features/dump/dump_specific_tables.feature +39 -0
- data/features/load/clobber_load.feature +49 -0
- data/features/load/db_dump_path_load.feature +63 -0
- data/features/load/default_load.feature +103 -0
- data/features/load/load_and_raise_error.feature +97 -0
- data/features/load/load_fixtures.feature +159 -0
- data/features/load/load_git_changes.feature +17 -0
- data/features/load/load_specific_tables.feature +119 -0
- data/features/step_definitions/app_steps.rb +25 -0
- data/features/step_definitions/database_steps.rb +172 -0
- data/features/support/announce.rb +3 -0
- data/features/support/env.rb +2 -0
- data/features/support/logger.rb +7 -0
- data/lib/git_friendly_dumper.rb +279 -0
- data/lib/git_friendly_dumper/version.rb +3 -0
- data/lib/tasks/git_friendly_dumper_tasks.rake +51 -0
- data/spec/git_friendly_dumper_spec.rb +319 -0
- data/spec/resources/migrate/20070101000000_create_firsts.rb +11 -0
- data/spec/resources/migrate/20070101010000_create_seconds.rb +11 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/connect.rb +6 -0
- data/spec/support/logger.rb +7 -0
- metadata +196 -0
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            Given /I am in an empty app/ do
         | 
| 2 | 
            +
              steps %q{
         | 
| 3 | 
            +
                Given a directory named "app"
         | 
| 4 | 
            +
                Given I cd to "app"
         | 
| 5 | 
            +
              }
         | 
| 6 | 
            +
              in_current_dir { `ln -s ../../../lib lib` }
         | 
| 7 | 
            +
            end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Given /a Rakefile exists which has an environment task and loads git_friendly_dumper tasks/ do
         | 
| 10 | 
            +
              steps %q{
         | 
| 11 | 
            +
                Given a file named "Rakefile" with:
         | 
| 12 | 
            +
                """
         | 
| 13 | 
            +
                $LOAD_PATH.unshift("lib")
         | 
| 14 | 
            +
                require 'rake'
         | 
| 15 | 
            +
                require 'active_record'
         | 
| 16 | 
            +
                
         | 
| 17 | 
            +
                load "lib/tasks/git_friendly_dumper_tasks.rake"
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                task :environment do
         | 
| 20 | 
            +
                  require 'active_record'
         | 
| 21 | 
            +
                  ActiveRecord::Base.establish_connection(:adapter  => "sqlite3", :database => "test.sqlite3")
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
                """
         | 
| 24 | 
            +
              }
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,172 @@ | |
| 1 | 
            +
            Given /^there is a connection$/ do
         | 
| 2 | 
            +
              ActiveRecord::Base.connection.should_not be_nil
         | 
| 3 | 
            +
            end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Given /^an empty database$/ do
         | 
| 6 | 
            +
              ActiveRecord::Base.establish_connection(:adapter  => "sqlite3", :database => "#{current_dir}/test.sqlite3")
         | 
| 7 | 
            +
              db_table_names.each do |table|
         | 
| 8 | 
            +
                ActiveRecord::Base.connection.drop_table table
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
            end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            When /^I refresh the database tables cache$/ do
         | 
| 13 | 
            +
              # prevents exception SQLite3::SchemaChangedException: no such table: users: SELECT * FROM "users"  (ActiveRecord::StatementInvalid)
         | 
| 14 | 
            +
              # TODO: is there a public API for this?
         | 
| 15 | 
            +
              ActiveRecord::Base.connection.tables 
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            When(/^I execute the schema "([^"]*)"$/) do |schema_path|
         | 
| 19 | 
            +
              schema_definition = File.read(File.join(current_dir, schema_path))
         | 
| 20 | 
            +
              ActiveRecord::Migration.suppress_messages do
         | 
| 21 | 
            +
                ActiveRecord::Schema.define do
         | 
| 22 | 
            +
                  eval schema_definition
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            Then(/^a "([^"]*)" table should exist with structure:$/) do |table_name, table|
         | 
| 28 | 
            +
              # table is a Cucumber::Ast::Table
         | 
| 29 | 
            +
              # TODO: make this db independent, currenlty for sqlite3 only
         | 
| 30 | 
            +
              table.diff! ActiveRecord::Base.connection.send :table_structure, table_name
         | 
| 31 | 
            +
            end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            Then /^list the table names$/ do
         | 
| 34 | 
            +
              announce db_table_names.to_sentence
         | 
| 35 | 
            +
            end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            Then(/^the database should have tables:$/) do |table|
         | 
| 38 | 
            +
              table.diff! db_table_names.map {|c| [c]}, :surplus_row => true, :surplus_col => true
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            Then(/^the database should not have table "([^"]*)"$/) do |table_name|
         | 
| 42 | 
            +
              db_table_names.should_not include(table_name)
         | 
| 43 | 
            +
            end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            Then /^show me the tables$/ do
         | 
| 46 | 
            +
              db_table_names.each do |table_name|
         | 
| 47 | 
            +
                pp table_name
         | 
| 48 | 
            +
                pp(table_contents(table_name))
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            Then /^show me the "([^"]*)" table$/ do |table_name|
         | 
| 53 | 
            +
              announce table_contents(table_name).to_yaml
         | 
| 54 | 
            +
            end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            Given /^the database has a "([^"]*)" table( \(with timestamps\))?:$/ do |table_name, timestamps, table|
         | 
| 57 | 
            +
              create_table(table_name, timestamps)
         | 
| 58 | 
            +
              
         | 
| 59 | 
            +
              columns = []
         | 
| 60 | 
            +
              table.headers.each do |column_def|
         | 
| 61 | 
            +
                raise "column_def should look like 'column_name (type)'" unless match = column_def.match(/(\w+) \((\w+)\)/)
         | 
| 62 | 
            +
                add_column_to_table(table_name, match[1], match[2])
         | 
| 63 | 
            +
                columns << match[1]
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
              
         | 
| 66 | 
            +
              table.rows.each do |row|
         | 
| 67 | 
            +
                attrs = {}
         | 
| 68 | 
            +
                columns.each_with_index do |column_name, index|
         | 
| 69 | 
            +
                  attrs[column_name] = row[index]
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
                insert_record_into_table(table_name, attrs)
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            Then /^the "([^"]*)" table should match exactly:$/ do |table_name, table|
         | 
| 76 | 
            +
              table.diff! table_to_strings(table_contents(table_name)), :surplus_col => true
         | 
| 77 | 
            +
            end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            Then /^the "([^"]*)" table should match exactly \(ignoring (ids)?(?: and )?(timestamps)?\):$/ do |table_name, ids, timestamps, table|
         | 
| 80 | 
            +
              table.diff! table_to_strings(table_contents(table_name, :ids => !ids, :timestamps => !timestamps)), :surplus_col => true
         | 
| 81 | 
            +
            end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            When /^I destroy record (\d+) from the "([^"]*)" table$/ do |id, table_name|
         | 
| 84 | 
            +
              class_for_table(table_name).destroy(id)
         | 
| 85 | 
            +
            end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            Then /^the data in the dumped "([^"]*)" yaml files should match the database contents$/ do |table_name|
         | 
| 88 | 
            +
              records = class_for_table(table_name).all
         | 
| 89 | 
            +
              fixtures = fixtures_for_table(table_name)
         | 
| 90 | 
            +
              records.count.should == fixtures.length
         | 
| 91 | 
            +
              records.each {|record| match_fixture_file_against_record(record)}
         | 
| 92 | 
            +
            end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            module FixtureHelpers
         | 
| 95 | 
            +
              def fixture_path_for(record)
         | 
| 96 | 
            +
                fixture_id_path = ("%08d" % record.id).scan(/..../).join('/')
         | 
| 97 | 
            +
                File.join current_dir, "db/dump", record.class.table_name, "#{fixture_id_path}.yml"
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              def replace_record_with_fixture!(record)
         | 
| 101 | 
            +
                require 'active_record/fixtures'
         | 
| 102 | 
            +
                fixture_path =fixture_path_for(record)
         | 
| 103 | 
            +
                fixture = ActiveRecord::Fixture.new(YAML.load(File.read(fixture_path)), record.class)
         | 
| 104 | 
            +
                record.destroy
         | 
| 105 | 
            +
                ActiveRecord::Base.connection.insert_fixture fixture, record.class.table_name
         | 
| 106 | 
            +
                record.class.find(record.id)
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
              
         | 
| 109 | 
            +
              def match_fixture_file_against_record(record)
         | 
| 110 | 
            +
                record.attributes.dup.should == replace_record_with_fixture!(record).attributes
         | 
| 111 | 
            +
                announce "#{table_name.singularize} #{record.id} data matches its fixture data #{fixture_path_for(record)}" if @announce
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
              
         | 
| 114 | 
            +
              def fixtures_for_table(table_name)
         | 
| 115 | 
            +
                fixtures = Dir.glob File.join(current_dir, "db/dump", table_name, '**', '*.yml')
         | 
| 116 | 
            +
                announce "Fixtures for #{table_name}:\n#{fixtures.join("\n")}\n" if @announce
         | 
| 117 | 
            +
                fixtures
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
            end
         | 
| 120 | 
            +
            World(FixtureHelpers)
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            module DatabaseHelpers
         | 
| 123 | 
            +
              def create_table(name, timestamps = false)
         | 
| 124 | 
            +
                ActiveRecord::Base.connection.create_table name do |t|
         | 
| 125 | 
            +
                  t.timestamps if timestamps
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
              def add_column_to_table(table_name, column_name, type)
         | 
| 130 | 
            +
                ActiveRecord::Base.connection.change_table table_name do |t|
         | 
| 131 | 
            +
                  t.send type, column_name
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
              end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
              def class_for_table(table_name)
         | 
| 136 | 
            +
                @class_for_table ||= {}
         | 
| 137 | 
            +
                @class_for_table[table_name] ||= begin
         | 
| 138 | 
            +
                  Class.new(ActiveRecord::Base).tap {|klass| klass.table_name = table_name }
         | 
| 139 | 
            +
                end
         | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
              def insert_record_into_table(table_name, attrs)
         | 
| 143 | 
            +
                class_for_table(table_name).create! attrs
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
              #returns ['table', 'names'] with sqlite adapter
         | 
| 147 | 
            +
              def db_table_names
         | 
| 148 | 
            +
                ActiveRecord::Base.connection.tables
         | 
| 149 | 
            +
              end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
              # table_contents 'users' # gives back everything
         | 
| 152 | 
            +
              # table_contents 'users', :timestamps => false # without timestamps
         | 
| 153 | 
            +
              # table_contents 'users', :ids => false # without ids
         | 
| 154 | 
            +
              def table_contents(table_name, opts={:timestamps => true, :ids => true})
         | 
| 155 | 
            +
                contents = class_for_table(table_name).all.map(&:attributes)
         | 
| 156 | 
            +
                contents.tap do |contents|
         | 
| 157 | 
            +
                  contents.map{|c| c.delete('id')} unless opts[:ids]
         | 
| 158 | 
            +
                  contents.map{|c| c.delete('updated_at'); c.delete('created_at')} unless opts[:timestamps]
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
              end
         | 
| 161 | 
            +
              
         | 
| 162 | 
            +
              #because cucumber table#diff! expects types to match and step transforms are silly
         | 
| 163 | 
            +
              def table_to_strings(table)
         | 
| 164 | 
            +
                table.each do |row|
         | 
| 165 | 
            +
                  row.each_pair do |key, value| 
         | 
| 166 | 
            +
                    row[key] = value.is_a?(Time) ? value.utc.to_s(:db) : value.to_s
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
              end
         | 
| 170 | 
            +
            end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
            World(DatabaseHelpers)
         | 
| @@ -0,0 +1,279 @@ | |
| 1 | 
            +
            require 'fileutils'
         | 
| 2 | 
            +
            require 'active_support/all'
         | 
| 3 | 
            +
            require 'active_record/fixtures'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            begin; require 'progressbar'; rescue MissingSourceFile; end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # Database independent and git friendly replacement for mysqldump for rails projects
         | 
| 8 | 
            +
            class GitFriendlyDumper
         | 
| 9 | 
            +
              include FileUtils
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
              attr_accessor :root, :path, :connection, :tables, :force, :include_schema, :show_progress, :clobber_fixtures, :limit, :raise_error, :fixtures
         | 
| 12 | 
            +
              alias_method :include_schema?, :include_schema
         | 
| 13 | 
            +
              alias_method :clobber_fixtures?, :clobber_fixtures
         | 
| 14 | 
            +
              alias_method :show_progress?, :show_progress
         | 
| 15 | 
            +
              alias_method :force?, :force
         | 
| 16 | 
            +
              alias_method :raise_error?, :raise_error
         | 
| 17 | 
            +
              
         | 
| 18 | 
            +
              class << self
         | 
| 19 | 
            +
                def dump(options = {})
         | 
| 20 | 
            +
                  new(options).dump
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              
         | 
| 23 | 
            +
                def load(options = {})
         | 
| 24 | 
            +
                  new(options).load
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
              
         | 
| 28 | 
            +
              def initialize(options = {})
         | 
| 29 | 
            +
                options.assert_valid_keys(:root, :path, :connection, :connection_name, :tables, :force, :include_schema, :show_progress, :clobber_fixtures, :limit, :raise_error, :fixtures, :fixtures_file)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                self.root = options[:root] || (defined?(Rails) && Rails.root) || pwd
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                if options[:fixtures_file]
         | 
| 34 | 
            +
                  raise ArgumentError, "GitFriendlyDumper cannot specify both :fixtures and :fixtures_file" if options[:fixtures].present?
         | 
| 35 | 
            +
                  options[:fixtures] = File.read(options[:fixtures_file]).split("\n").map(&:squish).reject(&:blank?)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                if options[:fixtures] && (options[:include_schema] || options[:clobber_fixtures])
         | 
| 39 | 
            +
                  raise ArgumentError, "GitFriendlyDumper if :fixtures option given, neither :include_schema nor :clobber_fixtures can be given"
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
                
         | 
| 42 | 
            +
                if options[:show_progress] && !defined?(ProgressBar)
         | 
| 43 | 
            +
                  raise RuntimeError, "GitFriendlyDumper requires the progressbar gem for progress option.\n  sudo gem install progressbar"
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                self.path             = File.expand_path(options[:path] || 'db/dump')
         | 
| 47 | 
            +
                self.tables           = options[:tables]
         | 
| 48 | 
            +
                self.fixtures         = options[:fixtures]
         | 
| 49 | 
            +
                self.limit            = options.key?(:limit) ? options[:limit].to_i : 2500
         | 
| 50 | 
            +
                self.raise_error      = options.key?(:raise_error) ? options[:raise_error] : true
         | 
| 51 | 
            +
                self.force            = options.key?(:force) ? options[:force] : false
         | 
| 52 | 
            +
                self.include_schema   = options.key?(:include_schema) ? options[:include_schema] : false
         | 
| 53 | 
            +
                self.show_progress    = options.key?(:show_progress) ? options[:show_progress] : false
         | 
| 54 | 
            +
                self.clobber_fixtures = options.key?(:clobber_fixtures) ? options[:clobber_fixtures] : (options[:tables].blank? ? true : false)
         | 
| 55 | 
            +
                self.connection       = options[:connection] || begin
         | 
| 56 | 
            +
                  if options[:connection_name]
         | 
| 57 | 
            +
                    ActiveRecord::Base.establish_connection(options[:connection_name])
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                  ActiveRecord::Base.connection
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
              
         | 
| 63 | 
            +
              def dump
         | 
| 64 | 
            +
                if fixtures
         | 
| 65 | 
            +
                  raise ArgumentError, "Cannot dump when :fixtures option is given"
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
                self.tables ||= db_tables
         | 
| 68 | 
            +
                tables.delete('schema_migrations') unless include_schema?
         | 
| 69 | 
            +
                if force? || (tables & fixtures_tables).empty? || confirm?(:dump)
         | 
| 70 | 
            +
                  puts "Dumping data#{' and structure' if include_schema?} from #{current_database_name} to #{path.sub("#{root}/",'')}\n"
         | 
| 71 | 
            +
                  clobber_all_fixtures if clobber_fixtures?
         | 
| 72 | 
            +
                  connection.transaction do
         | 
| 73 | 
            +
                    tables.each {|table| dump_table(table) }
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
              
         | 
| 78 | 
            +
              def load
         | 
| 79 | 
            +
                fixtures ? load_fixtures : load_tables
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            private
         | 
| 83 | 
            +
              def current_database_name
         | 
| 84 | 
            +
                @current_database_name ||= (connection.respond_to?(:current_database) && connection.current_database) || 'database'
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
              
         | 
| 87 | 
            +
              def confirm?(type)
         | 
| 88 | 
            +
                dump_path = path.sub("#{root}/", '')
         | 
| 89 | 
            +
                if clobber_fixtures? && type == :dump
         | 
| 90 | 
            +
                  puts "\nWARNING: all fixtures in #{dump_path}"
         | 
| 91 | 
            +
                else
         | 
| 92 | 
            +
                  puts "\nWARNING: the following #{type == :dump ? 'fixtures' : 'tables'} in #{type == :dump ? dump_path : current_database_name}:"
         | 
| 93 | 
            +
                  puts "  " + tables.join("\n  ")
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
                if fixtures
         | 
| 96 | 
            +
                  puts "will have records replaced by the specified #{fixtures.length} fixtures (deleting if fixture file is missing)"
         | 
| 97 | 
            +
                else
         | 
| 98 | 
            +
                  puts "will be replaced with #{type == :dump ? 'records' : 'fixtures'}#{' and table schemas' if include_schema?} from #{type == :dump ? current_database_name : dump_path}."
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
                puts "Do you wish to proceed? (type 'yes' to proceed)"
         | 
| 101 | 
            +
                proceed = ($stdin.gets.downcase.strip == 'yes')
         | 
| 102 | 
            +
                puts "#{type.to_s.capitalize} cancelled at user's request." unless proceed
         | 
| 103 | 
            +
                proceed
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              def fixtures_tables
         | 
| 107 | 
            +
                @fixture_tables ||= Dir[File.join(path, '*')].select{|f| File.directory?(f)}.map{|f| File.basename(f)}
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
              
         | 
| 110 | 
            +
              def db_tables
         | 
| 111 | 
            +
                @db_tables ||= connection.tables
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
              
         | 
| 114 | 
            +
              def load_tables
         | 
| 115 | 
            +
                self.tables ||= fixtures_tables
         | 
| 116 | 
            +
                tables.delete('schema_migrations') unless include_schema?
         | 
| 117 | 
            +
                if force? || (tables & db_tables).empty? || confirm?(:load)
         | 
| 118 | 
            +
                  puts "Loading data#{' and structure' if include_schema?} into #{current_database_name} from #{path.sub("#{root}/",'')}\n"
         | 
| 119 | 
            +
                  connection.transaction do
         | 
| 120 | 
            +
                    tables.each {|table| load_table(table) }
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
              
         | 
| 125 | 
            +
              def load_fixtures
         | 
| 126 | 
            +
                fixtures_tables = []
         | 
| 127 | 
            +
                fixtures.map! do |fixture|
         | 
| 128 | 
            +
                  raise ArgumentError, "Fixture filename error: #{fixture} should be a relative filename e.g. users/0000/0001.yml" unless fixture =~ /^\w+\/\d+\/\d+\.yml$/
         | 
| 129 | 
            +
                  table = fixture.split('/').first
         | 
| 130 | 
            +
                  if (!tables || tables.include?(table))
         | 
| 131 | 
            +
                    unless fixtures_tables.include?(table)
         | 
| 132 | 
            +
                      begin
         | 
| 133 | 
            +
                        "::#{table.classify}".constantize
         | 
| 134 | 
            +
                      rescue NameError
         | 
| 135 | 
            +
                        eval "class ::#{table.classify} < ActiveRecord::Base; end"
         | 
| 136 | 
            +
                      end
         | 
| 137 | 
            +
                      fixtures_tables << table
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
                    fixture
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
                fixtures.compact!
         | 
| 143 | 
            +
              
         | 
| 144 | 
            +
                self.tables = fixtures_tables
         | 
| 145 | 
            +
              
         | 
| 146 | 
            +
                if force? || (tables & db_tables).empty? || confirm?(:load)
         | 
| 147 | 
            +
                  puts "Loading fixtures into #{current_database_name} from #{path.sub("#{root}/",'')}\n"
         | 
| 148 | 
            +
                  show_progress? && (progress_bar = ProgressBar.new("fixtures", fixtures.length))
         | 
| 149 | 
            +
                  connection.transaction do
         | 
| 150 | 
            +
                    fixtures.each do |fixture|
         | 
| 151 | 
            +
                      match_data = fixture.match(/(\w+)\/(.+)\.yml/)
         | 
| 152 | 
            +
                      table, id, file = match_data[1], match_data[2].sub('/','').to_i, File.join(path, fixture)
         | 
| 153 | 
            +
                      
         | 
| 154 | 
            +
                      raise "Couldn't determine id from #{fixture} (id was #{id})" if id < 1
         | 
| 155 | 
            +
                      connection.delete("DELETE FROM #{table} WHERE id=#{id};")
         | 
| 156 | 
            +
                      load_fixture(table.classify.constantize, table, file) if File.exist?(file)
         | 
| 157 | 
            +
                      show_progress? && progress_bar.inc
         | 
| 158 | 
            +
                    end
         | 
| 159 | 
            +
                  end
         | 
| 160 | 
            +
                  show_progress && progress_bar.finish
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
              end
         | 
| 163 | 
            +
              
         | 
| 164 | 
            +
              def dump_table(table)
         | 
| 165 | 
            +
                clobber_fixtures_for_table(table)
         | 
| 166 | 
            +
                count = connection.select_value("SELECT COUNT(*) FROM %s" % table).to_i
         | 
| 167 | 
            +
                show_progress? && (progress_bar = ProgressBar.new(table, count))
         | 
| 168 | 
            +
                
         | 
| 169 | 
            +
                offset = 0
         | 
| 170 | 
            +
                while (records = select_records(table, offset)).any?
         | 
| 171 | 
            +
                  dump_records(table, records, show_progress? && progress_bar)
         | 
| 172 | 
            +
                  offset += limit
         | 
| 173 | 
            +
                end
         | 
| 174 | 
            +
                
         | 
| 175 | 
            +
                show_progress? && progress_bar.finish
         | 
| 176 | 
            +
                dump_table_schema(table) if include_schema?
         | 
| 177 | 
            +
              rescue ActiveRecord::ActiveRecordError => e
         | 
| 178 | 
            +
                puts "dumping #{table} failed: #{e.message}"
         | 
| 179 | 
            +
                puts "Partial dump files have been left behind and you should clean up before continuing (e.g. git status, git checkout, git clean)."
         | 
| 180 | 
            +
                raise e if raise_error?
         | 
| 181 | 
            +
              end
         | 
| 182 | 
            +
              
         | 
| 183 | 
            +
              def select_records(table, offset)
         | 
| 184 | 
            +
                connection.select_all("SELECT * FROM %s LIMIT #{limit} OFFSET #{offset}" % table)
         | 
| 185 | 
            +
              end
         | 
| 186 | 
            +
              
         | 
| 187 | 
            +
              def dump_records(table, records, progress_bar)
         | 
| 188 | 
            +
                records.each_with_index do |record, index|
         | 
| 189 | 
            +
                  id = record['id'] ? record['id'].to_i : index + 1
         | 
| 190 | 
            +
                  fixture_file = File.join(path, table, *id_path(id)) + ".yml"
         | 
| 191 | 
            +
                  `mkdir -p #{File.dirname(fixture_file)}`
         | 
| 192 | 
            +
                  File.open(fixture_file, "w") do |record_file|
         | 
| 193 | 
            +
                    record_file.write record.to_yaml
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
                  show_progress? && progress_bar.inc
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
              end
         | 
| 198 | 
            +
              
         | 
| 199 | 
            +
              def load_table(table)
         | 
| 200 | 
            +
                # create a placeholder AR class for the table without loading anything from the app.
         | 
| 201 | 
            +
                klass = eval "class #{table.classify} < ActiveRecord::Base; end"
         | 
| 202 | 
            +
                include_schema? ? load_table_schema(table) : clobber_records(table)
         | 
| 203 | 
            +
                files = Dir[File.join(path, table, '**', '*.yml')]
         | 
| 204 | 
            +
                show_progress? && (progress_bar = ProgressBar.new(table, files.length))
         | 
| 205 | 
            +
                files.each do |file|
         | 
| 206 | 
            +
                  load_fixture(klass, table, file)
         | 
| 207 | 
            +
                  show_progress? && progress_bar.inc
         | 
| 208 | 
            +
                end
         | 
| 209 | 
            +
                show_progress? && progress_bar.finish
         | 
| 210 | 
            +
              rescue ActiveRecord::ActiveRecordError => e
         | 
| 211 | 
            +
                puts "loading #{table} failed - check log for details"
         | 
| 212 | 
            +
                raise e if raise_error?
         | 
| 213 | 
            +
              end
         | 
| 214 | 
            +
              
         | 
| 215 | 
            +
              def load_fixture(klass, table, file)
         | 
| 216 | 
            +
                fixture = ActiveRecord::Fixture.new(YAML.load(File.read(file)), klass)
         | 
| 217 | 
            +
                begin
         | 
| 218 | 
            +
                  connection.insert_fixture fixture, table
         | 
| 219 | 
            +
                rescue ActiveRecord::ActiveRecordError => e
         | 
| 220 | 
            +
                  puts "loading fixture #{file} failed - check log for details"
         | 
| 221 | 
            +
                  raise e if raise_error?
         | 
| 222 | 
            +
                end
         | 
| 223 | 
            +
              end
         | 
| 224 | 
            +
              
         | 
| 225 | 
            +
              def dump_table_schema(table)
         | 
| 226 | 
            +
                File.open(File.join(path, table, 'schema.rb'), "w") do |schema_file|
         | 
| 227 | 
            +
                  if table == 'schema_migrations'
         | 
| 228 | 
            +
                    schema_file.write schema_migrations_schema
         | 
| 229 | 
            +
                  else
         | 
| 230 | 
            +
                    schema_dumper.send :table, table, schema_file
         | 
| 231 | 
            +
                  end
         | 
| 232 | 
            +
                end
         | 
| 233 | 
            +
              end
         | 
| 234 | 
            +
             | 
| 235 | 
            +
              def schema_migrations_schema
         | 
| 236 | 
            +
                <<-end_eval
         | 
| 237 | 
            +
              create_table "schema_migrations", :force => true, :id => false do |t|
         | 
| 238 | 
            +
                t.string "version", :null => false
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
              add_index :schema_migrations, :version, :unique => true, :name => 'unique_schema_migrations'
         | 
| 241 | 
            +
                end_eval
         | 
| 242 | 
            +
              end
         | 
| 243 | 
            +
              
         | 
| 244 | 
            +
              def schema_dumper
         | 
| 245 | 
            +
                @schema_dumper ||= ActiveRecord::SchemaDumper.send :new, @connection
         | 
| 246 | 
            +
              end
         | 
| 247 | 
            +
              
         | 
| 248 | 
            +
              def load_table_schema(table)
         | 
| 249 | 
            +
                schema_definition = File.read(File.join(path, table, 'schema.rb'))
         | 
| 250 | 
            +
                ActiveRecord::Migration.suppress_messages do 
         | 
| 251 | 
            +
                  ActiveRecord::Schema.define do
         | 
| 252 | 
            +
                    eval schema_definition
         | 
| 253 | 
            +
                  end
         | 
| 254 | 
            +
                end
         | 
| 255 | 
            +
              end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
              def clobber_fixtures_for_table(table)
         | 
| 258 | 
            +
                `rm -rf #{File.join(path, table)}`
         | 
| 259 | 
            +
                `mkdir -p #{File.join(path, table)}`
         | 
| 260 | 
            +
              end
         | 
| 261 | 
            +
              
         | 
| 262 | 
            +
              def clobber_all_fixtures
         | 
| 263 | 
            +
                fixtures_tables.each {|table| clobber_fixtures_for_table(table)}
         | 
| 264 | 
            +
              end
         | 
| 265 | 
            +
              
         | 
| 266 | 
            +
              def clobber_records(table)
         | 
| 267 | 
            +
                connection.delete "DELETE FROM #{table}"
         | 
| 268 | 
            +
              end
         | 
| 269 | 
            +
              
         | 
| 270 | 
            +
              # Partitions the given id into an array of path components.
         | 
| 271 | 
            +
              #
         | 
| 272 | 
            +
              # For example, given an id of 1
         | 
| 273 | 
            +
              # <tt>["0000", "0001"]</tt>
         | 
| 274 | 
            +
              #
         | 
| 275 | 
            +
              # Currently only integer ids are supported
         | 
| 276 | 
            +
              def id_path(id)
         | 
| 277 | 
            +
                ("%08d" % id).scan(/..../)
         | 
| 278 | 
            +
              end
         | 
| 279 | 
            +
            end
         |