~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/activerecord/lib/active_record/schema_dumper.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'stringio'
 
2
require 'bigdecimal'
 
3
 
 
4
module ActiveRecord
 
5
  # This class is used to dump the database schema for some connection to some
 
6
  # output format (i.e., ActiveRecord::Schema).
 
7
  class SchemaDumper #:nodoc:
 
8
    private_class_method :new
 
9
    
 
10
    ##
 
11
    # :singleton-method:
 
12
    # A list of tables which should not be dumped to the schema. 
 
13
    # Acceptable values are strings as well as regexp.
 
14
    # This setting is only used if ActiveRecord::Base.schema_format == :ruby
 
15
    cattr_accessor :ignore_tables 
 
16
    @@ignore_tables = []
 
17
 
 
18
    def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
 
19
      new(connection).dump(stream)
 
20
      stream
 
21
    end
 
22
 
 
23
    def dump(stream)
 
24
      header(stream)
 
25
      tables(stream)
 
26
      trailer(stream)
 
27
      stream
 
28
    end
 
29
 
 
30
    private
 
31
 
 
32
      def initialize(connection)
 
33
        @connection = connection
 
34
        @types = @connection.native_database_types
 
35
        @version = Migrator::current_version rescue nil
 
36
      end
 
37
 
 
38
      def header(stream)
 
39
        define_params = @version ? ":version => #{@version}" : ""
 
40
 
 
41
        stream.puts <<HEADER
 
42
# This file is auto-generated from the current state of the database. Instead of editing this file, 
 
43
# please use the migrations feature of Active Record to incrementally modify your database, and
 
44
# then regenerate this schema definition.
 
45
#
 
46
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
 
47
# to create the application database on another system, you should be using db:schema:load, not running
 
48
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
 
49
# you'll amass, the slower it'll run and the greater likelihood for issues).
 
50
#
 
51
# It's strongly recommended to check this file into your version control system.
 
52
 
 
53
ActiveRecord::Schema.define(#{define_params}) do
 
54
 
 
55
HEADER
 
56
      end
 
57
 
 
58
      def trailer(stream)
 
59
        stream.puts "end"
 
60
      end
 
61
 
 
62
      def tables(stream)
 
63
        @connection.tables.sort.each do |tbl|
 
64
          next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
 
65
            case ignored
 
66
            when String; tbl == ignored
 
67
            when Regexp; tbl =~ ignored
 
68
            else
 
69
              raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
 
70
            end
 
71
          end 
 
72
          table(tbl, stream)
 
73
        end
 
74
      end
 
75
 
 
76
      def table(table, stream)
 
77
        columns = @connection.columns(table)
 
78
        begin
 
79
          tbl = StringIO.new
 
80
 
 
81
          # first dump primary key column
 
82
          if @connection.respond_to?(:pk_and_sequence_for)
 
83
            pk, pk_seq = @connection.pk_and_sequence_for(table)
 
84
          elsif @connection.respond_to?(:primary_key)
 
85
            pk = @connection.primary_key(table)
 
86
          end
 
87
          
 
88
          tbl.print "  create_table #{table.inspect}"
 
89
          if columns.detect { |c| c.name == pk }
 
90
            if pk != 'id'
 
91
              tbl.print %Q(, :primary_key => "#{pk}")
 
92
            end
 
93
          else
 
94
            tbl.print ", :id => false"
 
95
          end
 
96
          tbl.print ", :force => true"
 
97
          tbl.puts " do |t|"
 
98
 
 
99
          # then dump all non-primary key columns
 
100
          column_specs = columns.map do |column|
 
101
            raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
 
102
            next if column.name == pk
 
103
            spec = {}
 
104
            spec[:name]      = column.name.inspect
 
105
            spec[:type]      = column.type.to_s
 
106
            spec[:limit]     = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
 
107
            spec[:precision] = column.precision.inspect if !column.precision.nil?
 
108
            spec[:scale]     = column.scale.inspect if !column.scale.nil?
 
109
            spec[:null]      = 'false' if !column.null
 
110
            spec[:default]   = default_string(column.default) if column.has_default?
 
111
            (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
 
112
            spec
 
113
          end.compact
 
114
 
 
115
          # find all migration keys used in this table
 
116
          keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
 
117
 
 
118
          # figure out the lengths for each column based on above keys
 
119
          lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
 
120
 
 
121
          # the string we're going to sprintf our values against, with standardized column widths
 
122
          format_string = lengths.map{ |len| "%-#{len}s" }
 
123
 
 
124
          # find the max length for the 'type' column, which is special
 
125
          type_length = column_specs.map{ |column| column[:type].length }.max
 
126
 
 
127
          # add column type definition to our format string
 
128
          format_string.unshift "    t.%-#{type_length}s "
 
129
 
 
130
          format_string *= ''
 
131
 
 
132
          column_specs.each do |colspec|
 
133
            values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
 
134
            values.unshift colspec[:type]
 
135
            tbl.print((format_string % values).gsub(/,\s*$/, ''))
 
136
            tbl.puts
 
137
          end
 
138
 
 
139
          tbl.puts "  end"
 
140
          tbl.puts
 
141
          
 
142
          indexes(table, tbl)
 
143
 
 
144
          tbl.rewind
 
145
          stream.print tbl.read
 
146
        rescue => e
 
147
          stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
 
148
          stream.puts "#   #{e.message}"
 
149
          stream.puts
 
150
        end
 
151
        
 
152
        stream
 
153
      end
 
154
 
 
155
      def default_string(value)
 
156
        case value
 
157
        when BigDecimal
 
158
          value.to_s
 
159
        when Date, DateTime, Time
 
160
          "'" + value.to_s(:db) + "'"
 
161
        else
 
162
          value.inspect
 
163
        end
 
164
      end
 
165
      
 
166
      def indexes(table, stream)
 
167
        if (indexes = @connection.indexes(table)).any?
 
168
          add_index_statements = indexes.map do |index|
 
169
            statment_parts = [ ('add_index ' + index.table.inspect) ]
 
170
            statment_parts << index.columns.inspect
 
171
            statment_parts << (':name => ' + index.name.inspect)
 
172
            statment_parts << ':unique => true' if index.unique
 
173
 
 
174
            '  ' + statment_parts.join(', ')
 
175
          end
 
176
 
 
177
          stream.puts add_index_statements.sort.join("\n")
 
178
          stream.puts
 
179
        end
 
180
      end
 
181
  end
 
182
end