class ActiveRecord::SchemaDumper
:nodoc:
output format (i.e., ActiveRecord::Schema).
This class is used to dump the database schema for some connection to some
= Active Record Schema Dumper
def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base) new(connection, generate_options(config)).dump(stream) stream end
def dump(stream)
def dump(stream) header(stream) extensions(stream) tables(stream) trailer(stream) stream end
def extensions(stream)
def extensions(stream) return unless @connection.supports_extensions? extensions = @connection.extensions if extensions.any? stream.puts " # These are extensions that must be enabled in order to support this database" extensions.each do |extension| stream.puts " enable_extension #{extension.inspect}" end stream.puts end end
def generate_options(config)
def generate_options(config) { table_name_prefix: config.table_name_prefix, table_name_suffix: config.table_name_suffix } end
def header(stream)
def header(stream) define_params = @version ? "version: #{@version}" : "" if stream.respond_to?(:external_encoding) && stream.external_encoding stream.puts "# encoding: #{stream.external_encoding.name}" end stream.puts <<HEADER file is auto-generated from the current state of the database. Instead diting this file, please use the migrations feature of Active Record to ementally modify your database, and then regenerate this schema definition. that this schema.rb definition is the authoritative source for your base schema. If you need to create the application database on another em, you should be using db:schema:load, not running all the migrations scratch. The latter is a flawed and unsustainable approach (the more migrations ll amass, the slower it'll run and the greater likelihood for issues). strongly recommended that you check this file into your version control system. Record::Schema.define(#{define_params}) do end
def indexes(table, stream)
def indexes(table, stream) if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| statement_parts = [ ('add_index ' + remove_prefix_and_suffix(index.table).inspect), index.columns.inspect, ('name: ' + index.name.inspect), ] statement_parts << 'unique: true' if index.unique index_lengths = (index.lengths || []).compact statement_parts << ('length: ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty? index_orders = (index.orders || {}) statement_parts << ('order: ' + index.orders.inspect) unless index_orders.empty? statement_parts << ('where: ' + index.where.inspect) if index.where statement_parts << ('using: ' + index.using.inspect) if index.using statement_parts << ('type: ' + index.type.inspect) if index.type ' ' + statement_parts.join(', ') end stream.puts add_index_statements.sort.join("\n") stream.puts end end
def initialize(connection, options = {})
def initialize(connection, options = {}) @connection = connection @types = @connection.native_database_types @version = Migrator::current_version rescue nil @options = options end
def remove_prefix_and_suffix(table)
def remove_prefix_and_suffix(table) table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2") end
def table(table, stream)
def table(table, stream) columns = @connection.columns(table) begin tbl = StringIO.new # first dump primary key column if @connection.respond_to?(:pk_and_sequence_for) pk, _ = @connection.pk_and_sequence_for(table) elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}" pkcol = columns.detect { |c| c.name == pk } if pkcol if pk != 'id' tbl.print %Q(, primary_key: "#{pk}") elsif pkcol.sql_type == 'uuid' tbl.print ", id: :uuid" tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function end else tbl.print ", id: false" end tbl.print ", force: true" tbl.puts " do |t|" # then dump all non-primary key columns column_specs = columns.map do |column| raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type) next if column.name == pk @connection.column_spec(column, @types) end.compact # find all migration keys used in this table keys = @connection.migration_keys # figure out the lengths for each column based on above keys lengths = keys.map { |key| column_specs.map { |spec| spec[key] ? spec[key].length + 2 : 0 }.max } # the string we're going to sprintf our values against, with standardized column widths format_string = lengths.map{ |len| "%-#{len}s" } # find the max length for the 'type' column, which is special type_length = column_specs.map{ |column| column[:type].length }.max # add column type definition to our format string format_string.unshift " t.%-#{type_length}s " format_string *= '' column_specs.each do |colspec| values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len } values.unshift colspec[:type] tbl.print((format_string % values).gsub(/,\s*$/, '')) tbl.puts end tbl.puts " end" tbl.puts indexes(table, tbl) tbl.rewind stream.print tbl.read rescue => e stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}" stream.puts "# #{e.message}" stream.puts end stream end
def tables(stream)
def tables(stream) @connection.tables.sort.each do |tbl| next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| case ignored when String; remove_prefix_and_suffix(tbl) == ignored when Regexp; remove_prefix_and_suffix(tbl) =~ ignored else raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.' end end table(tbl, stream) end end
def trailer(stream)
def trailer(stream) stream.puts "end" end