class SchemaComments::SchemaDumper

Public Class Methods

dump(connection=ActiveRecord::Base.connection, stream=STDOUT) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 13
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
  dumper =
    case connection.adapter_name
    when /mysql/i then
      SchemaComments::SchemaDumper::Mysql.new(connection)
    else
      new(connection)
    end
  dumper.dump(stream)
  stream
end

Public Instance Methods

dump(stream) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 25
def dump(stream)
  header(stream)
  tables(stream)
  selectable_attrs(stream)
  trailer(stream)
  stream
end
schema_default(column) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 6
def schema_default(column)
  default_string(column.default)
end

Private Instance Methods

adapter_name() click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 132
def adapter_name
  config = ActiveRecord::Base.configurations[Rails.env]
  config ? config['adapter'] : ActiveRecord::Base.connection.adapter_name
end
selectable_attr(enum, usages, stream) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 160
def selectable_attr(enum, usages, stream)
  buf = StringIO.new

  specs = []
  usages.each do |usage|
    klass = usage[:class]
    method_caption = nil
    if col = usage[:class].columns.detect{|c| c.name == usage[:enum_name]}
      method_caption = SchemaComment.column_comment(klass.table_name, col.name)
    end
    method_caption ||= usage[:enum_name]
    spec = {
      :class_name => klass.name,
      :methos_name => usage[:enum_name],
      :class_caption => SchemaComment.table_comment(klass.table_name),
      :method_caption => method_caption
    }
    specs << spec
  end
  keys = [:class_name, :methos_name, :class_caption, :method_caption]
  lengths = keys.map{ |key| specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
  format_string = lengths.map{ |len| "%-#{len}s" }
  format_string *= ''
  specs.each do |spec|
    values = keys.zip(lengths).map{ |key, len| spec.key?(key) ? spec[key] + " " : " " * len }
    buf.print('  # ')
    buf.print((format_string % values).gsub(/,\s*$/, '').strip)
    buf.puts
  end
  buf.puts "  #"

  selectable_attr_with_default(enum, usages, buf)

  buf.puts "  #"
  buf.puts

  buf.rewind
  stream.print buf.read
end
selectable_attr_with_default(enum, usages, buf) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 200
def selectable_attr_with_default(enum, usages, buf)
  entry_specs = enum.entries.map do |e|
    {:id => "#{e.id.inspect} |", :name => e.name.inspect, :options => e.options ? e.options.inspect : ''}
  end
  entry_specs.unshift({
      :id => "val |", :name => "name and options", :options => ""})
  keys = [:id, :name, :options] & entry_specs.map{ |k| k.keys }.flatten
  lengths = keys.map{ |key| entry_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
  format_string = ["%#{lengths[0]}s" ] + lengths[1..-1].map{ |len| "%-#{len}s" }
  format_string *= ''
  entry_specs.each_with_index do |enum_spec, idx|
    values = keys.zip(lengths).map{ |key, len| enum_spec.key?(key) ? enum_spec[key] + " " : " " * len }
    line = (format_string % values).gsub(/\s+\Z/, '')
    buf.print('  # ')
    buf.print(line)
    buf.puts
    if idx == 0
      buf.print('  # ')
      buf.puts("-" * line.length)
    end
  end
end
selectable_attrs(stream) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 137
def selectable_attrs(stream)
  return unless defined?(SelectableAttr)
  enum_defs = []
  classes = Dir["app/models/*.rb"].map{|f| File.basename(f, ".*").camelize.constantize}.select{|k| k.respond_to?(:single_selectable_attrs)}
  classes.each do |klass|
    (klass.single_selectable_attrs + klass.multi_selectable_attrs).each do |enum_name|
      enum = klass.send(klass.enum_base_name(enum_name) + "_enum")
      enum_def = enum_defs.detect{|enum_def| enum_def[:enum] === enum && enum_def[:enum_name] == enum_name}
      unless enum_def
        enum_def = {:enum => enum, :enum_name => enum_name, :usages => []}
        enum_defs << enum_def
      end
      enum_def[:usages] << {:class => klass, :enum_name => enum_name}
    end
  end

  stream.puts

  enum_defs.each do |enum_def|
    selectable_attr(enum_def[:enum], enum_def[:usages], stream)
  end
end
table(table, stream) click to toggle source
# File lib/schema_comments/schema_dumper.rb, line 35
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 #{table.inspect}"
    if columns.detect { |c| c.name == pk }
      if pk != 'id'
        tbl.print %Q(, :primary_key => "#{pk}")
      end
    else
      tbl.print ", :id => false"
    end
    tbl.print ", :force => true"

    table_comment = @connection.table_comment(table)
    tbl.print ", :comment => '#{table_comment}'" unless table_comment.blank?

    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}'" if @types[column.type].nil?
      next if column.name == pk
      spec = {}
      spec[:name]      = column.name.inspect

      # AR has an optimization which handles zero-scale decimals as integers. This
      # code ensures that the dumper still dumps the column as a decimal.
      spec[:type]      = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
                           'decimal'
                         else
                           column.type.to_s
                         end
      spec[:limit]     = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
      spec[:precision] = column.precision.inspect if column.precision
      spec[:scale]     = column.scale.inspect if column.scale
      spec[:null]      = 'false' unless column.null
      default = schema_default(column) if column.has_default?
      spec[:default]   = default unless default.nil?
      column_comment = SchemaComment.column_comment(table, column.name)
      spec[:comment]   = '"' << (column_comment || '').gsub(/\"/, '\"') << '"' # ここでinspectを使うと最後の文字だけ文字化け(UTF-8のコード)になっちゃう
      (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
      spec
    end.compact

    # find all migration keys used in this table
    keys = [:name, :limit, :precision, :scale, :default, :null, :comment] & column_specs.map{ |k| k.keys }.flatten

    # 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
    if ENV['RAILS_ENV'] == 'test'
      $stderr.puts "[#{e.class.name}] #{e.message}"
      $stderr.puts e.backtrace.join("\n  ")
    end
  end

  stream
end