-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
269 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ActiveSupport.on_load :active_record do | ||
require 'ar_mysql_column_charset' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
class SetCharsetForMysql < ActiveRecord::Migration | ||
def all_models | ||
@all_models ||= [ | ||
Agent, | ||
AgentLog, | ||
Contact, | ||
Event, | ||
Link, | ||
Scenario, | ||
ScenarioMembership, | ||
User, | ||
UserCredential, | ||
Delayed::Job, | ||
] | ||
end | ||
|
||
def change | ||
conn = ActiveRecord::Base.connection | ||
|
||
# This is migration is for MySQL only. | ||
return unless conn.is_a?(ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter) | ||
|
||
reversible do |dir| | ||
dir.up do | ||
all_models.each { |model| | ||
table_name = model.table_name | ||
|
||
# `contacts` may not exist | ||
next unless connection.table_exists? table_name | ||
|
||
model.columns.each { |column| | ||
name = column.name | ||
type = column.type | ||
limit = column.limit | ||
options = { | ||
limit: limit, | ||
null: column.null, | ||
default: column.default, | ||
} | ||
|
||
case type | ||
when :string, :text | ||
options.update(charset: 'utf8', collation: 'utf8_general_ci') | ||
case name | ||
when 'username' | ||
options.update(limit: 767 / 4, charset: 'utf8mb4', collation: 'utf8mb4_general_ci') | ||
when 'message', 'options', 'name', 'memory', | ||
'handler', 'last_error', 'payload', 'description' | ||
options.update(charset: 'utf8mb4', collation: 'utf8mb4_bin') | ||
when 'type', 'schedule', 'mode', 'email', | ||
'invitation_code', 'reset_password_token' | ||
options.update(collation: 'utf8_bin') | ||
when 'guid', 'encrypted_password' | ||
options.update(charset: 'ascii', collation: 'ascii_bin') | ||
end | ||
else | ||
next | ||
end | ||
|
||
change_column table_name, name, type, options | ||
} | ||
|
||
execute 'ALTER TABLE %s CHARACTER SET utf8 COLLATE utf8_general_ci' % table_name | ||
} | ||
|
||
execute 'ALTER DATABASE %s CHARACTER SET utf8 COLLATE utf8_general_ci' % conn.current_database | ||
end | ||
|
||
dir.down do | ||
# Do nada; no use to go back | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
require 'active_record' | ||
require 'prepend' unless Module.method_defined?(:prepend) | ||
|
||
module ActiveRecord::ConnectionAdapters | ||
class ColumnDefinition | ||
module CharsetSupport | ||
attr_accessor :charset, :collation | ||
end | ||
|
||
prepend CharsetSupport | ||
end | ||
|
||
class TableDefinition | ||
module CharsetSupport | ||
def new_column_definition(name, type, options) | ||
column = super | ||
column.charset = options[:charset] | ||
column.collation = options[:collation] | ||
column | ||
end | ||
end | ||
|
||
prepend CharsetSupport | ||
end | ||
|
||
class AbstractMysqlAdapter | ||
module CharsetSupport | ||
def prepare_column_options(column, types) | ||
spec = super | ||
conn = ActiveRecord::Base.connection | ||
spec[:charset] = column.charset.inspect if column.charset && column.charset != conn.charset | ||
spec[:collation] = column.collation.inspect if column.collation && column.collation != conn.collation | ||
spec | ||
end | ||
|
||
def migration_keys | ||
super + [:charset, :collation] | ||
end | ||
end | ||
|
||
prepend CharsetSupport | ||
|
||
class SchemaCreation | ||
module CharsetSupport | ||
def column_options(o) | ||
column_options = super | ||
column_options[:charset] = o.charset unless o.charset.nil? | ||
column_options[:collation] = o.collation unless o.collation.nil? | ||
column_options | ||
end | ||
|
||
def add_column_options!(sql, options) | ||
if options[:charset] | ||
sql << " CHARACTER SET #{options[:charset]}" | ||
end | ||
|
||
if options[:collation] | ||
sql << " COLLATE #{options[:collation]}" | ||
end | ||
|
||
super | ||
end | ||
end | ||
|
||
prepend CharsetSupport | ||
end | ||
|
||
class Column | ||
module CharsetSupport | ||
attr_reader :charset | ||
|
||
def initialize(*args) | ||
super | ||
@charset = @collation[/\A[^_]+/] unless @collation.nil? | ||
end | ||
end | ||
|
||
prepend CharsetSupport | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Fake implementation of prepend(), which does not support overriding | ||
# inherited methods nor methods that are formerly overridden by | ||
# another invocation of prepend(). | ||
# | ||
# Here's what <Original>.prepend(<Wrapper>) does: | ||
# | ||
# - Create an anonymous stub module (hereinafter <Stub>) and define | ||
# <Stub>#<method> that calls #<method>_without_<Wrapper> for each | ||
# instance method of <Wrapper>. | ||
# | ||
# - Rename <Original>#<method> to #<method>_without_<Wrapper> for each | ||
# instance method of <Wrapper>. | ||
# | ||
# - Include <Stub> and <Wrapper> into <Original> in that order. | ||
# | ||
# This way, a call of <Original>#<method> is dispatched to | ||
# <Wrapper><method>, which may call super which is dispatched to | ||
# <Stub>#<method>, which finally calls | ||
# <Original>#<method>_without_<Wrapper> which is used to be called | ||
# <Original>#<method>. | ||
# | ||
# Usage: | ||
# | ||
# class Mechanize | ||
# # module with methods that overrides those of X | ||
# module Y | ||
# end | ||
# | ||
# unless X.respond_to?(:prepend, true) | ||
# require 'mechanize/prependable' | ||
# X.extend(Prependable) | ||
# end | ||
# | ||
# class X | ||
# prepend Y | ||
# end | ||
# end | ||
class Module | ||
def prepend(mod) | ||
stub = Module.new | ||
|
||
mod_id = (mod.name || 'Module__%d' % mod.object_id).gsub(/::/, '__') | ||
|
||
mod.instance_methods.each { |name| | ||
method_defined?(name) or next | ||
|
||
original = instance_method(name) | ||
next if original.owner != self | ||
|
||
name = name.to_s | ||
name_without = name.sub(/(?=[?!=]?\z)/) { '_without_%s' % mod_id } | ||
|
||
arity = original.arity | ||
arglist = ( | ||
if arity >= 0 | ||
(1..arity).map { |i| 'x%d' % i } | ||
else | ||
(1..(-arity - 1)).map { |i| 'x%d' % i } << '*a' | ||
end << '&b' | ||
).join(', ') | ||
|
||
if name.end_with?('=') | ||
stub.module_eval %{ | ||
def #{name}(#{arglist}) | ||
__send__(:#{name_without}, #{arglist}) | ||
end | ||
} | ||
else | ||
stub.module_eval %{ | ||
def #{name}(#{arglist}) | ||
#{name_without}(#{arglist}) | ||
end | ||
} | ||
end | ||
module_eval { | ||
alias_method name_without, name | ||
remove_method name | ||
} | ||
} | ||
|
||
include stub | ||
include mod | ||
end | ||
private :prepend | ||
end |