Rails Schema creation problem

2019-03-09 18:08发布

问题:

I am using Jruby and rails 2.2.2. My problem is I have a migration that is not being correctly written to the database schema.

Here is my migration:

class CreateNotes < ActiveRecord::Migration
  def self.up
    create_table(:notes, :options => 'ENGINE=MyISAM') do |t|
      t.string :title
      t.text :body

      t.timestamps
    end

    execute "alter table notes ADD FULLTEXT(title, body)"

end

Here is what it produces on in schema.rb

create_table "notes", :force => true do |t|
  t.string   "title"
  t.text     "body"
  t.datetime "created_at"
  t.datetime "updated_at"
end

add_index "notes", ["title", "body"], :name => "title"

I have two question:

  • How do I get 'ENGINE=MyISAM' into the schema?
  • Why did my execute statement become add_index "notes", ["title", "body"], :name => "title"? and how do I force migrations to leave it as an execute statement?

Thanks to Christian Lescuyer for the answer. However, when I tried this nothing changed. I uncommented the config.active_record... line but, my schema has not changed. I have tried this in jruby and on ruby 1.8.6 with rails 2.2.2 and edge rails and there is not changes in the schema. Can anybody tell me what I am doing wrong?

回答1:

As I use foreign key constraints, I use the SQL format for migrations. In environment.rb:

# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
config.active_record.schema_format = :sql


回答2:

I too expected to see a new .sql file appear after a "rake db:migrate", once I set

config.active_record.schema_format = :sql

in config/environment.rb.

Apparently that's not how it works, however. I have to do this explicitly to get a db/[development|test|production]_structure.sql file:

rake db:structure:dump


回答3:

Just an update for those on Rails 3 (beta 4, currently) - Christian's solution is still correct, only the correct place to put the line is in config/application.rb, under the scope of the Application class which should be defined in a module named after your Rails project.



回答4:

christian is right.

do

config.active_record.schema_format = :sql

in environment.rb

but then you have to use a different schema dump format and file location. try doing your migration and looking for "schema.sql" instead of scehema.rb

the reason for all of this is that the point of the scheme file is a database unspecific (works for all types of databases) file. so when you use features which only work on mysql through an unsupoorted execute statement, they can't be shoehorned in to schema.rb



回答5:

To use the SQL variant for testing (instead of schema.rb), you'll need to use

rake db:test:clone_structure

Our schema uses UUIDs (uuid gem) and also Red Hill on Rails (RHoR) nice FK plug-in. Unfortunately, the FKs require PKs that can only be added using EXECUTES in the migrations.

It's well known that these executes do not make it to the schema.rb; however, it was harder to find the rake alternative to db:test:prepare for apps that cannot use schema.rb.



回答6:

The following monkeypatch solves both the FULLTEXT index issue and DB engine option for your schema dumper (Rails 3.2). You can put it in config/initializers/ (e.g. schema_dumper_monkeypatch.rb):

module ActiveRecord
  class SchemaDumper
    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 #{remove_prefix_and_suffix(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"

        # Add table engine
        res = @connection.execute "SHOW TABLE STATUS LIKE '#{table}'"
        engine = res.first[res.fields.index("Engine")] rescue nil
        tbl.print ", :options => 'ENGINE=#{engine}'" if engine
        res = nil # Free the result        

        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
          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

        # find all migration keys used in this table
        keys = [:name, :limit, :precision, :scale, :default, :null] & 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
      end

      stream
    end    

    def indexes(table, stream)
      if (indexes = @connection.indexes(table)).any?
        add_index_statements = indexes.map do |index|

          if index.name =~ /fulltext/i
            "  execute \"CREATE FULLTEXT INDEX #{index.name} ON #{index.table} (#{index.columns.join(',')})\""
          elsif index.name =~ /spatial/i
            "  execute \"CREATE SPATIAL INDEX #{index.name} ON #{index.table} (#{index.columns.join(',')})\""
          else
            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.join(', ')
          end
        end

        stream.puts add_index_statements.sort.join("\n")
        stream.puts
      end
    end
  end
end