Skip to content

Commit

Permalink
db:create for MySQL now much cleaner.
Browse files Browse the repository at this point in the history
  • Loading branch information
pat committed Jun 17, 2012
1 parent 8aaeaf6 commit d297272
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 37 deletions.
2 changes: 2 additions & 0 deletions activerecord/lib/active_record.rb
Expand Up @@ -141,6 +141,8 @@ module Tasks
extend ActiveSupport::Autoload

autoload :DatabaseTasks
autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
end

autoload :TestCase
Expand Down
31 changes: 1 addition & 30 deletions activerecord/lib/active_record/railties/databases.rake
Expand Up @@ -59,36 +59,7 @@ db_namespace = namespace :db do
rescue
case config['adapter']
when /mysql/
if config['adapter'] =~ /jdbc/
#FIXME After Jdbcmysql gives this class
require 'active_record/railties/jdbcmysql_error'
error_class = ArJdbcMySQL::Error
else
error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
end
access_denied_error = 1045
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
ActiveRecord::Base.establish_connection(config)
rescue error_class => sqlerr
if sqlerr.errno == access_denied_error
print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
root_password = $stdin.gets.strip
grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
"TO '#{config['username']}'@'localhost' " \
"IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
ActiveRecord::Base.establish_connection(config.merge(
'database' => nil, 'username' => 'root', 'password' => root_password))
ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
ActiveRecord::Base.connection.execute grant_statement
ActiveRecord::Base.establish_connection(config)
else
$stderr.puts sqlerr.error
$stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}"
$stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
end
end
ActiveRecord::Tasks::DatabaseTasks.create config
when /postgresql/
@encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
begin
Expand Down
19 changes: 12 additions & 7 deletions activerecord/lib/active_record/tasks/database_tasks.rb
@@ -1,14 +1,19 @@
class ActiveRecord::Tasks::DatabaseTasks
def self.create(configuration)
if File.exist?(configuration['database'])
$stderr.puts "#{configuration['database']} already exists"
return
end
TASKS_PATTERNS = {
/mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks,
# /postgresql/ => ActiveRecord::Tasks::PostgreSQLTasker,
/sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks
}

ActiveRecord::Base.establish_connection(configuration)
ActiveRecord::Base.connection
def self.create(configuration)
class_for_adapter(configuration['adapter']).new(configuration).create
rescue Exception => e
$stderr.puts e, *(e.backtrace)
$stderr.puts "Couldn't create database for #{configuration.inspect}"
end

def self.class_for_adapter(adapter)
key = TASKS_PATTERNS.keys.detect { |key| adapter[key] }
TASKS_PATTERNS[key]
end
end
76 changes: 76 additions & 0 deletions activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -0,0 +1,76 @@
class ActiveRecord::Tasks::MySQLDatabaseTasks
DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
ACCESS_DENIED_ERROR = 1045

delegate :connection, :establish_connection, :to => ActiveRecord::Base

def initialize(configuration)
@configuration = configuration
end

def create
establish_connection configuration_without_database
connection.create_database configuration['database'], creation_options
establish_connection configuration
rescue error_class => error
raise error unless error.errno == ACCESS_DENIED_ERROR

$stdout.print error.error
establish_connection root_configuration_without_database
connection.create_database configuration['database'], creation_options
connection.execute grant_statement.gsub(/\s+/, ' ').strip
establish_connection configuration
rescue error_class => error
$stderr.puts error.error
$stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
$stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset']
end

private

attr_reader :configuration

def configuration_without_database
configuration.merge('database' => nil)
end

def creation_options
{
:charset => (configuration['charset'] || DEFAULT_CHARSET),
:collation => (configuration['collation'] || DEFAULT_COLLATION)
}
end

def error_class
case configuration['adapter']
when /jdbc/
require 'active_record/railties/jdbcmysql_error'
error_class = ArJdbcMySQL::Error
when /mysql2/
Mysql2::Error
else
Mysql::Error
end
end

def grant_statement
<<-SQL
GRANT ALL PRIVILEGES ON #{configuration['database']}.*
TO '#{configuration['username']}'@'localhost'
IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
SQL
end

def root_configuration_without_database
configuration_without_database.merge(
'username' => 'root',
'password' => root_password
)
end

def root_password
$stdout.print "Please provide the root password for your mysql installation\n>"
$stdin.gets.strip
end
end
19 changes: 19 additions & 0 deletions activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -0,0 +1,19 @@
class ActiveRecord::Tasks::SQLiteDatabaseTasks
def initialize(configuration)
@configuration = configuration
end

def create
if File.exist?(configuration['database'])
$stderr.puts "#{configuration['database']} already exists"
return
end

ActiveRecord::Base.establish_connection(configuration)
ActiveRecord::Base.connection
end

private

attr_reader :configuration
end
120 changes: 120 additions & 0 deletions activerecord/test/cases/mysql_rake_test.rb
@@ -0,0 +1,120 @@
require 'cases/helper'

module ActiveRecord
class MysqlDBCreateTest < ActiveRecord::TestCase
def setup
@connection = stub(:create_database => true)
@configuration = {
'adapter' => 'mysql',
'database' => 'my-app-db'
}

ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
end

def test_establishes_connection_without_database
ActiveRecord::Base.expects(:establish_connection).
with('adapter' => 'mysql', 'database' => nil)

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_creates_database_with_default_options
@connection.expects(:create_database).
with('my-app-db', {:charset => 'utf8', :collation => 'utf8_unicode_ci'})

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_creates_database_with_given_options
@connection.expects(:create_database).
with('my-app-db', {:charset => 'latin', :collation => 'latin_ci'})

ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge(
'charset' => 'latin', 'collation' => 'latin_ci'
)
end

def test_establishes_connection_to_database
ActiveRecord::Base.expects(:establish_connection).with(@configuration)

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
end

class MysqlDBCreateAsRootTest < ActiveRecord::TestCase
def setup
require 'mysql'

@connection = stub(:create_database => true, :execute => true)
@error = Mysql::Error.new "Invalid permissions"
@configuration = {
'adapter' => 'mysql',
'database' => 'my-app-db',
'username' => 'pat',
'password' => 'wossname'
}

$stdin.stubs(:gets).returns("secret\n")
$stdout.stubs(:print).returns(nil)
@error.stubs(:errno).returns(1045)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).raises(@error).then.
returns(true)
end

def test_root_password_is_requested
$stdin.expects(:gets).returns("secret\n")

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_connection_established_as_root
ActiveRecord::Base.expects(:establish_connection).with({
'adapter' => 'mysql',
'database' => nil,
'username' => 'root',
'password' => 'secret'
})

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_database_created_by_root
@connection.expects(:create_database).
with('my-app-db', :charset => 'utf8', :collation => 'utf8_unicode_ci')

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_grant_privileges_for_normal_user
@connection.expects(:execute).with("GRANT ALL PRIVILEGES ON my-app-db.* TO 'pat'@'localhost' IDENTIFIED BY 'wossname' WITH GRANT OPTION;")

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_connection_established_as_normal_user
ActiveRecord::Base.expects(:establish_connection).returns do
ActiveRecord::Base.expects(:establish_connection).with({
'adapter' => 'mysql',
'database' => 'my-app-db',
'username' => 'pat',
'password' => 'secret'
})

raise @error
end

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end

def test_sends_output_to_stderr_when_other_errors
@error.stubs(:errno).returns(42)

$stderr.expects(:puts).at_least_once.returns(nil)

ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
end
end

0 comments on commit d297272

Please sign in to comment.