Skip to content

Commit

Permalink
Merge pull request #681 from airblade/fix_651_mysql_charset
Browse files Browse the repository at this point in the history
MySQL: Specify UTF-8 charset for versions table
  • Loading branch information
jaredbeck committed Dec 27, 2015
2 parents 402a78e + d138b95 commit eef918b
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 6 deletions.
35 changes: 34 additions & 1 deletion lib/generators/paper_trail/templates/create_versions.rb
@@ -1,13 +1,21 @@
class CreateVersions < ActiveRecord::Migration

# Class names of MySQL adapters.
# - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
# - `Mysql2Adapter` - Used by `mysql2` gem.
MYSQL_ADAPTERS = [
"ActiveRecord::ConnectionAdapters::MysqlAdapter",
"ActiveRecord::ConnectionAdapters::Mysql2Adapter"
]

# The largest text column available in all supported RDBMS is
# 1024^3 - 1 bytes, roughly one gibibyte. We specify a size
# so that MySQL will use `longtext` instead of `text`. Otherwise,
# when serializing very large objects, `text` might not be big enough.
TEXT_BYTES = 1_073_741_823

def change
create_table :versions do |t|
create_table :versions, versions_table_options do |t|
t.string :item_type, :null => false
t.integer :item_id, :null => false
t.string :event, :null => false
Expand All @@ -31,4 +39,29 @@ def change
end
add_index :versions, [:item_type, :item_id]
end

private

# Even modern versions of MySQL still use `latin1` as the default character
# encoding. Many users are not aware of this, and run into trouble when they
# try to use PaperTrail in apps that otherwise tend to use UTF-8. Postgres, by
# comparison, uses UTF-8 except in the unusual case where the OS is configured
# with a custom locale.
#
# - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
# - http://www.postgresql.org/docs/9.4/static/multibyte.html
#
# Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
# to be fixed later by introducing a new charset, `utf8mb4`.
#
# - https://mathiasbynens.be/notes/mysql-utf8mb4
# - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
#
def versions_table_options
if MYSQL_ADAPTERS.include?(connection.class.name)
{ options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_col' }
else
{}
end
end
end
7 changes: 3 additions & 4 deletions spec/generators/install_generator_spec.rb
Expand Up @@ -13,15 +13,15 @@
prepare_destination
run_generator
end

it "generates a migration for creating the 'versions' table" do
expect(destination_root).to have_structure {
directory 'db' do
directory 'migrate' do
migration 'create_versions' do
contains 'class CreateVersions'
contains 'def change'
contains 'create_table :versions do |t|'
contains 'create_table :versions'
end
end
end
Expand All @@ -42,7 +42,7 @@
migration 'create_versions' do
contains 'class CreateVersions'
contains 'def change'
contains 'create_table :versions do |t|'
contains 'create_table :versions'
end
end
end
Expand All @@ -63,5 +63,4 @@
}
end
end

end
51 changes: 51 additions & 0 deletions spec/generators/paper_trail/templates/create_versions_spec.rb
@@ -0,0 +1,51 @@
require "rails_helper"
require "generators/paper_trail/templates/create_versions"

RSpec.describe CreateVersions do
describe "#change", verify_stubs: false do
let(:migration) { described_class.new }

before do
allow(migration).to receive(:add_index)
allow(migration).to receive(:create_table)
end

it "creates the versions table" do
migration.change
expect(migration).to have_received(:create_table) do |arg1|
expect(arg1).to eq(:versions)
end
end

case ENV["DB"]
when "mysql"
it "uses InnoDB engine" do
migration.change
expect(migration).to have_received(:create_table) do |_, arg2|
expect(arg2[:options]).to match(/ENGINE=InnoDB/)
end
end

it "uses utf8mb4 character set" do
migration.change
expect(migration).to have_received(:create_table) do |_, arg2|
expect(arg2[:options]).to match(/DEFAULT CHARSET=utf8mb4/)
end
end

it "uses utf8mb4_col collation" do
migration.change
expect(migration).to have_received(:create_table) do |_, arg2|
expect(arg2[:options]).to match(/COLLATE=utf8mb4_col/)
end
end
else
it "passes an empty options hash to create_table" do
migration.change
expect(migration).to have_received(:create_table) do |_, arg2|
expect(arg2).to eq({})
end
end
end
end
end
11 changes: 10 additions & 1 deletion spec/spec_helper.rb
Expand Up @@ -44,7 +44,16 @@
mocks.verify_partial_doubles = true
end

# The settings below are suggested to provide a good initial experience
# Support for disabling `verify_partial_doubles` on specific examples.
config.around(:each, verify_stubs: false) do |ex|
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = false
ex.run
mocks.verify_partial_doubles = true
end
end

# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# These two settings work together to allow you to limit a spec run
Expand Down

0 comments on commit eef918b

Please sign in to comment.