Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

« Newer Snippets
Older Snippets »
Showing 1-6 of 6 total  RSS 

Using 64 bit primary keys with migrations

I've got an application which may need more IDs than the standard 32 bit ones typically provide. MySQL has a BIGINT variable type, but it is a little tricky to convince Rails and Migrations to use it. The instructions are for MySQL, but should be easily transferable to other DBs.

1. Monkeypatch ActiveRecord::ConnectionAdapters::MysqlAdapter#native_database_types by appending it to config/environment.rb and add two new types. I've named them int64 and int64_pk

class ActiveRecord::ConnectionAdapters::MysqlAdapter
  def native_database_types #:nodoc:
    {
      :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
      :int64_pk    => "bigint DEFAULT NULL auto_increment PRIMARY KEY",
      :int64       => { :name => "bigint" },
      :string      => { :name => "varchar", :limit => 255 },
      :text        => { :name => "text" },
      :integer     => { :name => "int", :limit => 11 },
      :float       => { :name => "float" },
      :decimal     => { :name => "decimal" },
      :datetime    => { :name => "datetime" },
      :timestamp   => { :name => "datetime" },
      :time        => { :name => "time" },
      :date        => { :name => "date" },
      :binary      => { :name => "blob" },
      :boolean     => { :name => "tinyint", :limit => 1 },
    }
  end
end


2. In the table creation migration, create the table WITHOUT a primary key column, and then add the column manually, referencing int64_pk above:

create_table :slices, :id => false do |t|
       t.column :cheese_id, :integer
       t.column :plant, :string
       t.column :date, :datetime
     end
    add_column :events, :id, :int64_pk
end



3. In tables referencing this one, mark the foreign_id fields as int64:

create_table :fondues do |t|
      t.column :cheese_id, :int64
      t.column :party_id, :integer
      t.column :kirsch_id, :integer
end



4. It turns out that the c-based mysql bindings do NOT do let you set primary keys via the sequence because it coerces the
insert_id into 32 bits at mysql.c:532. There are two way to fix this:

a. Use the pure-ruby gem (ie, remove the c-gem)

b. Patch and rebuild the c-gem by changing line 352 (in version 2.7):

   return INT2NUM(mysql_insert_id(GetHandler(obj)));


Extending this to use LL2NUM solves the issue:

   return LL2NUM(mysql_insert_id(GetHandler(obj)));



5. One last thing, 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


We need to deal with sql, rather than ruby, schemas.

----------------------

Note that I've decided that ONLY the cheese table needs 64bit IDs -- other references are still integers. If you wanted ALL primary keys to be 64 bits, you could have the monkeypatch look like:

class ActiveRecord::ConnectionAdapters::MysqlAdapter
  def native_database_types #:nodoc:
    {
      :primary_key    => "bigint DEFAULT NULL auto_increment PRIMARY KEY",
.
.
.

Numeric colums for latitude / longitude in Rails 1.2 migrations

In Rails 1.1.6, "numeric" datatypes didn't work in migrations. This confused a lot of people who wanted to store geographic data (latitude and longitude) for use in Google Maps and the like. Floats worked, but their precision is limited -- you'll lose three or more decimal places of precision if you store the results of a typical geocoding call in a Float column. And don't even think about using strings to store your latitudes/longitudes.

Fortunately, the numeric datatype problem is fixed in Rails 1.2, and you can now have do this in your migrations:

class CreatePlaces < ActiveRecord::Migration
  def self.up
    create_table :places do |t|
      t.column "lat", :decimal, :precision => 15, :scale => 10
      t.column "lng", :decimal, :precision => 15, :scale => 10
    end
  end

  def self.down
    drop_table :places
  end
end


FYI, if you're stuck on Rails 1.1.6, you can use this approach:
class CreatePlaces < ActiveRecord::Migration
  def self.up
    create_table :places do |t|
      t.column :lat, :float 
      t.column :lng, :float
    end
    execute("ALTER TABLE places MODIFY lat numeric(15,10);")
    execute("ALTER TABLE places MODIFY lng numeric(15,10);")
  end

  def self.down
    drop_table :places
  end
end

... but that's ugly, database-specific (works on MySQL), and not very DRY.

Ruby subversion pre-commit hook to prevent conflicting Rails migrations

This is a subversion pre-commit hook that prevents a Ruby on Rails migration being committed that has the same version as an existing migration. To install you place this in a file called pre-commit in the hooks directory of your subversion repository. You can read about hooks here: http://svnbook.red-bean.com/en/1.0/ch05s02.html

#!/usr/bin/env ruby

repo_path = ARGV[0]
transaction = ARGV[1]
svnlook = '/usr/bin/svnlook'

commit_dirs_changed = `#{svnlook} dirs-changed #{repo_path} -t #{transaction}`
commit_changed = `#{svnlook} changed #{repo_path} -t #{transaction}`
#commit_author = `#{svnlook} author #{repo_path} -t #{transaction}`.chop
commit_log = `#{svnlook} log #{repo_path} -t #{transaction}`
#commit_diff = `#{svnlook} diff #{repo_path} -t #{transaction}`
#commit_date = `#{svnlook} date #{repo_path} -t #{transaction}`

# ******* Migration check ********
# if this is a migration then check that there is not already a migration with the same version number in the repository
files = commit_changed.split(/\n/)
current_migrations = nil
for file in files
  if(file =~ /A\s*(.*?\/migrate\/)(\d+)(.*)/)
    migration_path = $1
    migration_version = $2
    
    if(current_migrations == nil)
      current_migrations = {}
      migration_files = `#{svnlook} tree #{repo_path} #{migration_path}`
      for migration in migration_files
        current_migrations[$1] = true if(migration =~ /\s*(\d+)_(.*)/)
      end
    end
    
    if(current_migrations[migration_version])
     STDERR.puts("The is a pre-existing migration with version #{migration_version} in #{migration_path}")
     exit(1)
    end
  end
end


rake remigrate

From http://errtheblog.com/post/3
Drops your database, recreates it, runs all migrations, then loads fixtures. Heroic.

desc "Drop then recreate the dev database, migrate up, and load fixtures" 
task :remigrate => :environment do
  return unless %w[development test staging].include? RAILS_ENV
  ActiveRecord::Base.connection.tables.each { |t| ActiveRecord::Base.connection.drop_table t }
  Rake::Task[:migrate].invoke
  Rake::Task["db:fixtures:load"].invoke
end

use rake migrations to create schema in production database

// use your migrations to recreate the tables in the testing or production databases

rake migrate RAILS_ENV=production

Rails - Build Test Environment DB from Migrations

This custom rake task builds the test environment database from the migrations rather than the schema dump of the development database. This is especially handy if you have application data inserted via your migrations that you don't want to duplicate in fixtures.

You should be able to stick this in a file called "<whatever>.rake" and put it in your "tasks" directory.

To use as a plugin, create a directory in "vendor/plugins" and call it whatever you want. Inside, create a directory called "tasks". Place this code in a file there and call it whatever you want.

module Rake
  module TaskManager
    def redefine_task(task_class, args, &block)
      task_name, deps = resolve_args(args)
      task_name = task_class.scope_name(@scope, task_name)
      deps = [deps] unless deps.respond_to?(:to_ary)
      deps = deps.collect {|d| d.to_s }
      task = @tasks[task_name.to_s] = task_class.new(task_name, self)
      task.application = self
      task.add_comment(@last_comment)
      @last_comment = nil
      task.enhance(deps, &block)
      task
    end
  end
  class Task
    class << self
      def redefine_task(args, &block)
        Rake.application.redefine_task(self, args, &block)
      end
    end
  end
end

def redefine_task(args, &block)
  Rake::Task.redefine_task(args, &block)
end

namespace :db do
  namespace :test do

    desc 'Prepare the test database and migrate schema'
    redefine_task :prepare => :environment do
      Rake::Task['db:test:migrate_schema'].invoke
    end

    desc 'Use the migrations to create the test database'
    task :migrate_schema => 'db:test:purge' do
      ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
      ActiveRecord::Migrator.migrate("db/migrate/")
    end
  
  end
end
« Newer Snippets
Older Snippets »
Showing 1-6 of 6 total  RSS