read

For my most recent project I created some kind of "auto-documentation". I thought the most easy way was to simply use MySQLs comments. Turns out, ActiveRecords doesn't support this comments at all. Neither in schema dumping nor in migrations. ( used also for recreating database from a schema.rb ). This ain't bound to Rails; It works wherever you use ActiveRecord (well tested only with activerecord gem 3.1.1).

Insert comments with a migration

(found here: http://goo.gl/7JO5T) (working with activerecord 3.1.1)

module ActiveRecord
  module ConnectionAdapters
    class ColumnDefinition
      attr_accessor :comment

      def to_sql_with_comment
        column_sql = to_sql_without_comment
        return column_sql if comment.nil?
        "#{column_sql} COMMENT '#{base.quote_string(comment)}'"
        end

      alias_method_chain :to_sql, :comment
    end

    class TableDefinition
      def column(name, type, options = {})
        column = self[name] || ColumnDefinition.new(@base, name, type)
        if options[:limit]
          column.limit = options[:limit]
        elsif native[type.to_sym].is_a?(Hash)
          column.limit = native[type.to_sym][:limit]
        end
        column.precision = options[:precision]
        column.scale = options[:scale]
        column.default = options[:default]
        column.null = options[:null]  
        column.comment = options[:comment]
        @columns << column unless @columns.include? column
        self
      end
    end
  end
end

Get comment info for columns collection

module ActiveRecord
  module ConnectionAdapters
    class MysqlColumn
      attr_accessor :comment
    end 

    class MysqlAdapter

      def columns(table_name, name = nil)
        sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
        columns = []
        result = execute(sql, name)
        result.each { |field| 
          c = MysqlColumn.new(field[0], field[5], field[1], field[3] == "YES") 
          c.comment = field[8] || ""
          columns << c
        }
        result.free
        columns
      end         
    end
  end
end

You can use that in your application wherever you need access to the comment (or any other column information)

MyModel.columns_hash.each do |a|
  puts a[1].type.to_s
  puts a[1].name.to_s
  puts a[1].comment if a[1].respond_to?("comment")
end

SchemaDumper

to add comment to schema.rb export

module ActiveRecord
  class SchemaDumper
def table(table, stream) columns = @connection.columns(table) begin tbl = StringIO.new 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" tbl.puts " do |t|" 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 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 spec[:comment] = column.comment.inspect spec[:default] = default_string(column.default) if column.has_default? (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")} spec end.compact keys = [:name, :limit, :precision, :scale, :default, :null, :comment] & column_specs.map{ |k| k.keys }.flatten lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max } format_string = lengths.map{ |len| "%-#{len}s" } type_length = column_specs.map{ |column| column[:type].length }.max 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
end end
(copy this snippets somewhere to your app, everything gets done by monkey patching)

Back to Overview