Skip to content

Commit

Permalink
Merge 2302a59 into d7c5417
Browse files Browse the repository at this point in the history
  • Loading branch information
knu committed Aug 13, 2014
2 parents d7c5417 + 2302a59 commit dff4ad3
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 26 deletions.
3 changes: 3 additions & 0 deletions config/initializers/ar_mysql_column_charset.rb
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
74 changes: 74 additions & 0 deletions db/migrate/20140813110107_set_charset_for_mysql.rb
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
52 changes: 26 additions & 26 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20140605032822) do
ActiveRecord::Schema.define(version: 20140813110107) do

create_table "agent_logs", force: true do |t|
t.integer "agent_id", null: false
t.text "message", limit: 16777215, null: false
t.text "message", limit: 16777215, null: false, charset: "utf8mb4", collation: "utf8mb4_bin"
t.integer "level", default: 3, null: false
t.integer "inbound_event_id"
t.integer "outbound_event_id"
Expand All @@ -25,24 +25,24 @@

create_table "agents", force: true do |t|
t.integer "user_id"
t.text "options", limit: 16777215
t.string "type"
t.string "name"
t.string "schedule"
t.text "options", limit: 16777215, charset: "utf8mb4", collation: "utf8mb4_bin"
t.string "type", collation: "utf8_bin"
t.string "name", charset: "utf8mb4", collation: "utf8mb4_bin"
t.string "schedule", collation: "utf8_bin"
t.integer "events_count"
t.datetime "last_check_at"
t.datetime "last_receive_at"
t.integer "last_checked_event_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "memory", limit: 2147483647
t.text "memory", limit: 2147483647, charset: "utf8mb4", collation: "utf8mb4_bin"
t.datetime "last_web_request_at"
t.datetime "last_event_at"
t.datetime "last_error_log_at"
t.integer "keep_events_for", default: 0, null: false
t.boolean "propagate_immediately", default: false, null: false
t.boolean "disabled", default: false, null: false
t.string "guid", null: false
t.string "guid", null: false, charset: "ascii", collation: "ascii_bin"
end

add_index "agents", ["guid"], name: "index_agents_on_guid", using: :btree
Expand All @@ -53,8 +53,8 @@
create_table "delayed_jobs", force: true do |t|
t.integer "priority", default: 0
t.integer "attempts", default: 0
t.text "handler", limit: 16777215
t.text "last_error", limit: 16777215
t.text "handler", limit: 16777215, charset: "utf8mb4", collation: "utf8mb4_bin"
t.text "last_error", limit: 16777215, charset: "utf8mb4", collation: "utf8mb4_bin"
t.datetime "run_at"
t.datetime "locked_at"
t.datetime "failed_at"
Expand All @@ -71,7 +71,7 @@
t.integer "agent_id"
t.decimal "lat", precision: 15, scale: 10
t.decimal "lng", precision: 15, scale: 10
t.text "payload", limit: 2147483647
t.text "payload", limit: 2147483647, charset: "utf8mb4", collation: "utf8mb4_bin"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "expires_at"
Expand Down Expand Up @@ -103,13 +103,13 @@
add_index "scenario_memberships", ["scenario_id"], name: "index_scenario_memberships_on_scenario_id", using: :btree

create_table "scenarios", force: true do |t|
t.string "name", null: false
t.string "name", null: false, charset: "utf8mb4", collation: "utf8mb4_bin"
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.text "description"
t.text "description", charset: "utf8mb4", collation: "utf8mb4_bin"
t.boolean "public", default: false, null: false
t.string "guid", null: false
t.string "guid", null: false, charset: "ascii", collation: "ascii_bin"
t.string "source_url"
end

Expand All @@ -121,31 +121,31 @@
t.text "credential_value", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "mode", default: "text", null: false
t.string "mode", default: "text", null: false, collation: "utf8_bin"
end

add_index "user_credentials", ["user_id", "credential_name"], name: "index_user_credentials_on_user_id_and_credential_name", unique: true, using: :btree

create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.string "email", default: "", null: false, collation: "utf8_bin"
t.string "encrypted_password", default: "", null: false, charset: "ascii", collation: "ascii_bin"
t.string "reset_password_token", collation: "utf8_bin"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "admin", default: false, null: false
t.integer "failed_attempts", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "admin", default: false, null: false
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at"
t.string "username", null: false
t.string "invitation_code", null: false
t.integer "scenario_count", default: 0, null: false
t.string "username", limit: 191, null: false, charset: "utf8mb4", collation: "utf8mb4_general_ci"
t.string "invitation_code", null: false, collation: "utf8_bin"
t.integer "scenario_count", default: 0, null: false
end

add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
Expand Down
81 changes: 81 additions & 0 deletions lib/ar_mysql_column_charset.rb
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
85 changes: 85 additions & 0 deletions lib/prepend.rb
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

0 comments on commit dff4ad3

Please sign in to comment.