Skip to content

Instantly share code, notes, and snippets.

@awilliams
Last active December 19, 2015 00:18
Show Gist options
  • Save awilliams/5867330 to your computer and use it in GitHub Desktop.
Save awilliams/5867330 to your computer and use it in GitHub Desktop.
ActiveRecord Mysql2 boolean casting error
# Must have mysql installed with user 'rails' and 'root', no password for either
# Tested on the rails dev box virtual machine - https://github.com/rails/rails-dev-box
#
# See https://github.com/rails/rails/issues/11119
TEST_DATABASE = 'ar_mysql2_boolean_quoting'
unless File.exists?('Gemfile')
File.write('Gemfile', <<-GEMFILE
source 'https://rubygems.org'
gem 'rails', github: 'rails/rails'
gem 'mysql'
gem 'mysql2'
GEMFILE
)
system 'bundle'
end
require 'bundler'
Bundler.setup
require 'mysql'
require 'mysql2'
require 'active_record'
require 'minitest/autorun'
require 'logger'
###########################
class Telephone < ActiveRecord::Base
validates_uniqueness_of :active
end
class ARMysqlTest < MiniTest::Unit::TestCase
def setup
root_query("DROP DATABASE IF EXISTS #{TEST_DATABASE}")
root_query("CREATE DATABASE #{TEST_DATABASE}")
end
def teardown
root_query("DROP DATABASE #{TEST_DATABASE}")
end
#
# The following tests use Mysql column type tinyint(1)
#
def test_mysql_tinyint_emulate
# This is the default case using mysql adapter
# `t.boolean :active` in the migration
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = true (default value)
_test_boolean_casting(:mysql, :tinyint, true)
end
def test_mysql2_tinyint_emulate
# This is the default case using mysql2 adapter
# `t.boolean :active` in the migration
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = true (default value)
_test_boolean_casting(:mysql2, :tinyint, true)
end
def test_mysql_tinyint_no_emulate
_test_boolean_casting(:mysql, :tinyint, false)
end
def test_mysql2_tinyint_no_emulate
_test_boolean_casting(:mysql2, :tinyint, false)
end
#
# The following use Mysql column type varchar(1)
#
def test_mysql_string_emulate
_test_boolean_casting(:mysql, :string, true)
end
def test_mysql2_string_emulate
_test_boolean_casting(:mysql2, :string, true)
end
def test_mysql_string_no_emulate
_test_boolean_casting(:mysql, :string, false)
end
def test_mysql2_string_no_emulate
_test_boolean_casting(:mysql2, :string, false)
end
def _test_boolean_casting(adapter, db_column_type, emulate_booleans)
setup_connection(adapter, emulate_booleans, db_column_type)
setup_telephones_table(db_column_type)
Telephone.reset_column_information
Telephone.create!(name: 'Nokia', active: true)
assert !Telephone.new(name: 'Samsung', active: true).valid?, 'Record should not be valid since another record with active=true exists'
end
# Creates table with 'boolean' column
def setup_telephones_table(db_column_type)
::ActiveRecord::Schema.define do
create_table :telephones do |t|
t.string :name
case db_column_type
when :string
t.string :active, limit: 1
when :tinyint
t.boolean :active
else
raise ArgumentError
end
end
end
end
# Setup ActiveRecord to use either mysql or mysql2 adapter
def setup_connection(adapter, emulate_booleans, db_column_type = nil)
::ActiveRecord::Base.clear_all_connections!
::ActiveRecord::Base.establish_connection(adapter: adapter, user: 'rails', database: TEST_DATABASE)
case adapter
when :mysql2
::ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = emulate_booleans
when :mysql
::ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = emulate_booleans
else
raise ArgumentError
end
log_tag = "#{adapter}|#{db_column_type}|emulate_booleans(#{emulate_booleans ? 'T' : 'F'})"
::ActiveRecord::Base.logger = Logger.new(STDOUT)
::ActiveRecord::Base.logger.formatter = proc do |_, _, _, msg|
"%s:%s\n\n" % ["\e[1m\e[33m#{log_tag}\e[0m", msg]
end
end
def root_query(sql)
@client ||= Mysql2::Client.new(username: 'root', host: 'localhost', password: nil)
@client.query(sql)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment