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 @types = @connection.native_database_types @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 = [ ('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 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| case ignored when String; remove_prefix_and_suffix(table_name) == ignored when Regexp; remove_prefix_and_suffix(table_name) =~ ignored else raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.' end end end end end