From d138b955afe615375afdc9b053af94b8dded372d Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Thu, 24 Dec 2015 01:50:14 -0500 Subject: [PATCH] MySQL: Specify utf8mb4 charset for versions table Fixes #651 --- .../paper_trail/templates/create_versions.rb | 35 ++++++++++++- spec/generators/install_generator_spec.rb | 7 ++- .../templates/create_versions_spec.rb | 51 +++++++++++++++++++ spec/spec_helper.rb | 11 +++- 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 spec/generators/paper_trail/templates/create_versions_spec.rb diff --git a/lib/generators/paper_trail/templates/create_versions.rb b/lib/generators/paper_trail/templates/create_versions.rb index ff794f9e..2ce2e090 100644 --- a/lib/generators/paper_trail/templates/create_versions.rb +++ b/lib/generators/paper_trail/templates/create_versions.rb @@ -1,5 +1,13 @@ 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, @@ -7,7 +15,7 @@ class CreateVersions < ActiveRecord::Migration 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 @@ -31,4 +39,29 @@ class CreateVersions < ActiveRecord::Migration 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 diff --git a/spec/generators/install_generator_spec.rb b/spec/generators/install_generator_spec.rb index 3933c1a8..61e62d6e 100644 --- a/spec/generators/install_generator_spec.rb +++ b/spec/generators/install_generator_spec.rb @@ -13,7 +13,7 @@ describe PaperTrail::InstallGenerator, :type => :generator do prepare_destination run_generator end - + it "generates a migration for creating the 'versions' table" do expect(destination_root).to have_structure { directory 'db' do @@ -21,7 +21,7 @@ describe PaperTrail::InstallGenerator, :type => :generator do migration 'create_versions' do contains 'class CreateVersions' contains 'def change' - contains 'create_table :versions do |t|' + contains 'create_table :versions' end end end @@ -42,7 +42,7 @@ describe PaperTrail::InstallGenerator, :type => :generator do migration 'create_versions' do contains 'class CreateVersions' contains 'def change' - contains 'create_table :versions do |t|' + contains 'create_table :versions' end end end @@ -63,5 +63,4 @@ describe PaperTrail::InstallGenerator, :type => :generator do } end end - end diff --git a/spec/generators/paper_trail/templates/create_versions_spec.rb b/spec/generators/paper_trail/templates/create_versions_spec.rb new file mode 100644 index 00000000..2de17496 --- /dev/null +++ b/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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b8b6b399..0dca3a47 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -44,7 +44,16 @@ RSpec.configure do |config| 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