Skip to content

Commit d47b776

Browse files
committed
First implementation (Version)
1 parent c09fa7a commit d47b776

18 files changed

+665
-28
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ capybara-*.html
1111
*.orig
1212
rerun.txt
1313
pickle-email-*.html
14+
*log
1415

1516
# TODO Comment out this rule if you are OK with secrets being uploaded to the repo
1617
config/initializers/secret_token.rb
@@ -42,3 +43,5 @@ bower.json
4243

4344
# Ignore Byebug command history file.
4445
.byebug_history
46+
47+
*~

Gemfile.lock

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
PATH
2+
remote: .
3+
specs:
4+
utf8mb4rails (0.1.0)
5+
departure (~> 4.0, >= 4.0.1)
6+
mysql2 (~> 0.4.0)
7+
8+
GEM
9+
remote: https://rubygems.org/
10+
specs:
11+
actionmailer (4.2.9)
12+
actionpack (= 4.2.9)
13+
actionview (= 4.2.9)
14+
activejob (= 4.2.9)
15+
mail (~> 2.5, >= 2.5.4)
16+
rails-dom-testing (~> 1.0, >= 1.0.5)
17+
actionpack (4.2.9)
18+
actionview (= 4.2.9)
19+
activesupport (= 4.2.9)
20+
rack (~> 1.6)
21+
rack-test (~> 0.6.2)
22+
rails-dom-testing (~> 1.0, >= 1.0.5)
23+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
24+
actionview (4.2.9)
25+
activesupport (= 4.2.9)
26+
builder (~> 3.1)
27+
erubis (~> 2.7.0)
28+
rails-dom-testing (~> 1.0, >= 1.0.5)
29+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
30+
activejob (4.2.9)
31+
activesupport (= 4.2.9)
32+
globalid (>= 0.3.0)
33+
activemodel (4.2.9)
34+
activesupport (= 4.2.9)
35+
builder (~> 3.1)
36+
activerecord (4.2.9)
37+
activemodel (= 4.2.9)
38+
activesupport (= 4.2.9)
39+
arel (~> 6.0)
40+
activesupport (4.2.9)
41+
i18n (~> 0.7)
42+
minitest (~> 5.1)
43+
thread_safe (~> 0.3, >= 0.3.4)
44+
tzinfo (~> 1.1)
45+
arel (6.0.4)
46+
builder (3.2.3)
47+
concurrent-ruby (1.0.5)
48+
departure (4.0.1)
49+
mysql2 (~> 0.4.0)
50+
rails (~> 4.2.0)
51+
diff-lcs (1.3)
52+
erubis (2.7.0)
53+
globalid (0.4.0)
54+
activesupport (>= 4.2.0)
55+
i18n (0.8.6)
56+
loofah (2.0.3)
57+
nokogiri (>= 1.5.9)
58+
mail (2.6.6)
59+
mime-types (>= 1.16, < 4)
60+
mime-types (3.1)
61+
mime-types-data (~> 3.2015)
62+
mime-types-data (3.2016.0521)
63+
mini_portile2 (2.2.0)
64+
minitest (5.10.3)
65+
mysql2 (0.4.9)
66+
nokogiri (1.8.0)
67+
mini_portile2 (~> 2.2.0)
68+
rack (1.6.8)
69+
rack-test (0.6.3)
70+
rack (>= 1.0)
71+
rails (4.2.9)
72+
actionmailer (= 4.2.9)
73+
actionpack (= 4.2.9)
74+
actionview (= 4.2.9)
75+
activejob (= 4.2.9)
76+
activemodel (= 4.2.9)
77+
activerecord (= 4.2.9)
78+
activesupport (= 4.2.9)
79+
bundler (>= 1.3.0, < 2.0)
80+
railties (= 4.2.9)
81+
sprockets-rails
82+
rails-deprecated_sanitizer (1.0.3)
83+
activesupport (>= 4.2.0.alpha)
84+
rails-dom-testing (1.0.8)
85+
activesupport (>= 4.2.0.beta, < 5.0)
86+
nokogiri (~> 1.6)
87+
rails-deprecated_sanitizer (>= 1.0.1)
88+
rails-html-sanitizer (1.0.3)
89+
loofah (~> 2.0)
90+
railties (4.2.9)
91+
actionpack (= 4.2.9)
92+
activesupport (= 4.2.9)
93+
rake (>= 0.8.7)
94+
thor (>= 0.18.1, < 2.0)
95+
rake (10.5.0)
96+
rspec (3.6.0)
97+
rspec-core (~> 3.6.0)
98+
rspec-expectations (~> 3.6.0)
99+
rspec-mocks (~> 3.6.0)
100+
rspec-core (3.6.0)
101+
rspec-support (~> 3.6.0)
102+
rspec-expectations (3.6.0)
103+
diff-lcs (>= 1.2.0, < 2.0)
104+
rspec-support (~> 3.6.0)
105+
rspec-mocks (3.6.0)
106+
diff-lcs (>= 1.2.0, < 2.0)
107+
rspec-support (~> 3.6.0)
108+
rspec-support (3.6.0)
109+
sprockets (3.7.1)
110+
concurrent-ruby (~> 1.0)
111+
rack (> 1, < 3)
112+
sprockets-rails (3.2.1)
113+
actionpack (>= 4.0)
114+
activesupport (>= 4.0)
115+
sprockets (>= 3.0.0)
116+
thor (0.20.0)
117+
thread_safe (0.3.6)
118+
tzinfo (1.2.3)
119+
thread_safe (~> 0.1)
120+
121+
PLATFORMS
122+
ruby
123+
124+
DEPENDENCIES
125+
bundler (~> 1.12)
126+
rake (~> 10.0)
127+
rspec (~> 3.0)
128+
utf8mb4rails!
129+
130+
BUNDLED WITH
131+
1.12.5

README.md

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,53 @@
1-
# utf8mb4rails
2-
Adds a couple of rake tasks to deal with utf8 to utf8mb4 migrations for mysql.
1+
# Utf8mb4rails
2+
3+
A simple gem that adds a rake task to deal with utf8 to utf8mb4 migrations for mysql.
4+
5+
## Installation
6+
7+
Add this line to your application's Gemfile:
8+
9+
```ruby
10+
gem 'utf8mb4rails'
11+
```
12+
13+
And then execute:
14+
15+
$ bundle
16+
17+
Or install it yourself as:
18+
19+
$ gem install utf8mb4rails
20+
21+
## Usage
22+
23+
This gem will add in your rails project a new task called utf8mb4. This tast is aimed to easily convert tables in utf8 into
24+
utf8mb4 without truncating column contents and maintaining its original schema (without changing max sizes).
25+
26+
**USE AT YOUR OWN RISK!!!**
27+
28+
$ rake -T
29+
30+
```
31+
...
32+
rake db:utf8mb4 # migrates a table (ENV["TABLE"]) to utf8mb encoding
33+
...
34+
35+
```
36+
37+
You can modify its behavior using env vars.
38+
39+
- **TABLE**: The table you want to migrate, if no column is specified every single column will be migrated, also the default charset
40+
of the table is altered.
41+
- **COLUMN**: The column to be migrated (needs TABLE defined). It will only migrate that column definition.
42+
- **COLLATION**: The collation (utf8mb4_unicode_520_ci by default)
43+
44+
45+
## Contributing
46+
47+
Bug reports and pull requests are welcome on GitHub at https://github.com/magec/utf8mb4rails.
48+
49+
50+
## License
51+
52+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
53+

lib/utf8mb4rails.rb

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
1-
require "utf8mb4rails/version"
1+
require 'utf8mb4rails/version'
2+
require 'utf8mb4rails/migrator'
3+
require 'departure'
4+
5+
Departure.configure do |_config|; end
6+
def usage
7+
puts
8+
puts 'Usage: '
9+
puts
10+
puts 'For migrating an entire table use:'
11+
puts '$ TABLE=table_name rake db:utf8mb4'
12+
puts
13+
puts 'For migrating an specified column table:'
14+
puts '$ TABLE=table_name COLUMN=column_name rake db:utf8mb4'
15+
puts
16+
puts 'You can also specify the COLLATION (utf8mb4_unicode_520_ci)'
17+
end
218

319
module Utf8mb4rails
4-
# Your code goes here...
20+
extend Rake::DSL
21+
22+
namespace :db do
23+
desc 'migrates a table (ENV["TABLE"]) to utf8mb encoding'
24+
task utf8mb4: :environment do
25+
table = ENV['TABLE']
26+
unless table
27+
puts 'Please specify a table with TABLE='
28+
usage
29+
exit 1
30+
end
31+
32+
column = ENV['COLUMN']
33+
runner = Migrator::Runner.new
34+
if column
35+
puts 'Migrating #{table}.#{column}'
36+
runner.migrate_column!(table, column)
37+
else
38+
puts 'No column specified, will migrate the entire table'
39+
runner.migrate_table!(table)
40+
end
41+
end
42+
end
543
end

lib/utf8mb4rails/migrator.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Use percona
2+
require_relative 'migrator/db_inspector'
3+
require_relative 'migrator/runner'
4+
require_relative 'migrator/column_info'
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module Utf8mb4rails
2+
module Migrator
3+
4+
# Abstraction for dealing with column information
5+
# received from the database
6+
class ColumnInfo
7+
TEXT_TYPES = %w(CHAR VARCHAR TINYTEXT TEXT MEDIUMTEXT LONGTEXT).freeze
8+
attr_accessor :info
9+
10+
# Receives a hash with (:type, :default, :max_length, :charset)
11+
def initialize(info)
12+
@info = info
13+
end
14+
15+
# @return Bool : True if the column is in utf8mb4
16+
def utf8mb4?
17+
info[:charset] =~ /utf8mb4/
18+
end
19+
20+
# @return String : the sql part of the new type of the column definition
21+
def new_type_for_sql
22+
return "#{info[:type]}(#{info[:max_length]})" if info[:type] =~ /CHAR/
23+
info[:type]
24+
end
25+
26+
# @return String : the sql part of the default value of the column definition
27+
def default_value_for_sql
28+
return nil unless info[:default]
29+
"default '#{info[:default]}'"
30+
end
31+
32+
# @return Bool : True if the column is of a text type
33+
def text_column?
34+
TEXT_TYPES.include?(info[:type])
35+
end
36+
end
37+
end
38+
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
module Utf8mb4rails
2+
module Migrator
3+
class DBInspector
4+
# Initializes AR
5+
def initialize
6+
ActiveRecord::Base.establish_connection(
7+
ActiveRecord::Base.connection_config.merge(adapter: 'percona')
8+
)
9+
end
10+
11+
##
12+
# @returns [Array<String>] An array with column names
13+
def columns(table)
14+
ActiveRecord::Base.connection.columns(table).map(&:name)
15+
end
16+
17+
# Returns a hash with information about the column
18+
#
19+
# @param [String] table
20+
# @param [String] column
21+
#
22+
# @returns [Hash] { type: String, default: String or nil, charset: String or nil }
23+
def column_info(table, column)
24+
sql = "SELECT DATA_TYPE, COLUMN_DEFAULT, CHARACTER_SET_NAME, CHARACTER_MAXIMUM_LENGTH
25+
FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = '#{table}'
26+
AND COLUMN_NAME = '#{column}'
27+
AND TABLE_SCHEMA='#{database_name}'"
28+
result = ActiveRecord::Base.connection.execute(sql).first
29+
ColumnInfo.new(
30+
type: result[0].upcase,
31+
default: result[1],
32+
charset: result[2],
33+
max_length: result[3]
34+
)
35+
end
36+
37+
private
38+
39+
def database_name
40+
ActiveRecord::Base.connection.current_database
41+
end
42+
end
43+
end
44+
end

lib/utf8mb4rails/migrator/runner.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module Utf8mb4rails
2+
module Migrator
3+
class Runner
4+
NEW_COLLATION = ENV.fetch('COLLATION', 'utf8mb4_unicode_520_ci').freeze
5+
6+
attr_accessor :inspector
7+
8+
def initialize
9+
@inspector = DBInspector.new
10+
end
11+
12+
def migrate_column!(table, column)
13+
column_info = inspector.column_info(table, column)
14+
if column_info.utf8mb4?
15+
puts " Skipping column #{column} (already in utf8mb4)'"
16+
return
17+
end
18+
return unless column_info.text_column?
19+
sql = "ALTER TABLE `#{table}` DROP COLUMN \`#{column}`\ ,
20+
ADD COLUMN \`#{column}`\ #{column_info.new_type_for_sql} CHARACTER SET utf8mb4
21+
COLLATE #{NEW_COLLATION} #{column_info.default_value_for_sql}"
22+
ActiveRecord::Base.connection.execute(sql)
23+
end
24+
25+
def migrate_table_schema!(table)
26+
sql = "ALTER TABLE `#{table}` CONVERT TO CHARACTER SET utf8mb4 COLLATE #{NEW_COLLATION}"
27+
ActiveRecord::Base.connection.execute(sql)
28+
end
29+
30+
def migrate_table!(table)
31+
inspector.columns(table).each do |column|
32+
puts " migrating #{table}:#{column}"
33+
migrate_column!(table, column)
34+
end
35+
migrate_table_schema!(table)
36+
rescue
37+
puts "Problems accesing the table #{table}"
38+
puts $!
39+
exit 1
40+
end
41+
end
42+
end
43+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class CreateActivities < ActiveRecord::Migration
2+
def change
3+
create_table :activities do |t|
4+
t.string :name
5+
t.text :description
6+
t.timestamps null: false
7+
end
8+
end
9+
end

0 commit comments

Comments
 (0)