require 'stringio' require 'active_support/core_ext/big_decimal' module ActiveRecord # = Active Record Schema Dumper # # This class is used to dump the database schema for some connection to some # output format (i.e., ActiveRecord::Schema). class SchemaDumper #:nodoc: private_class_method :new ## # :singleton-method: # A list of tables which should not be dumped to the schema. # Acceptable values are strings as well as regexp. # This setting is only used if ActiveRecord::Base.schema_format == :ruby cattr_accessor :ignore_tables @@ignore_tables = [] class << self def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base) new(connection, generate_options(config)).dump(stream) stream end private def generate_options(config) { table_name_prefix: config.table_name_prefix, table_name_suffix: config.table_name_suffix } end end def dump(stream) header(stream) extensions(stream) tables(stream) trailer(stream) stream end private def initialize(connection, options = {}) @connection = connection @version = Migrator::current_version rescue nil @options = options end 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 <
e stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}" stream.puts "# #{e.message}" stream.puts end stream end def indexes(table, stream) if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| statement_parts = [ "t.index #{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}" if index_lengths.any? index_orders = index.orders || {} statement_parts << "order: #{index.orders.inspect}" if index_orders.any? 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") end end def foreign_keys(table, stream) if (foreign_keys = @connection.foreign_keys(table)).any? add_foreign_key_statements = foreign_keys.map do |foreign_key| parts = [ "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}", remove_prefix_and_suffix(foreign_key.to_table).inspect, ] if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table) parts << "column: #{foreign_key.column.inspect}" end if foreign_key.custom_primary_key? parts << "primary_key: #{foreign_key.primary_key.inspect}" end if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/ parts << "name: #{foreign_key.name.inspect}" end parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete " #{parts.join(', ')}" end stream.puts add_foreign_key_statements.sort.join("\n") end end def remove_prefix_and_suffix(table) table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2") end def ignored?(table_name) ['schema_migrations', ignore_tables].flatten.any? do |ignored| ignored === remove_prefix_and_suffix(table_name) end end end end