~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: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

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