今回はRailsネタ
検証環境は次の通りです
$ ruby -v ruby 2.5.0p0 $ bundle exec rails -v Rails 5.2.0
例えば今回次のようなコマンドでstringのnameとbooleanのdisabledをもったUserモデルを作ったとしましょう
$ bundle exec rails g model User name:string disabled:boolean
migrationなどを済ませるとconsoleなどでcreateとかが出来るようになります
> User.create(name: "hoge", disabled: true) #=> #<User id: 1, name: "hoge", disabled: true, created_at: "2018-05-05 05:51:58", updated_at: "2018-05-05 05:51:58"> > User.create(name: 'fuga', disabled: false) # => #<User id: 2, name: "fuga", disabled: false, created_at: "2018-05-05 06:01:04", updated_at: "2018-05-05 06:01:04"> > user = User.find(1) > user.name #=> "hoge" > user.disabled #=> true
ここまでがRailsの世界となっていて一度dbを見てみます
今回はDBはmysql上で構築しています
先程作成したレコードが追加されているかを確認するためにSELECTしてみます
mysql> SELECT * FROM users; +----+------+----------+---------------------+---------------------+ | id | name | disabled | created_at | updated_at | +----+------+----------+---------------------+---------------------+ | 1 | hoge | 1 | 2018-05-05 06:00:56 | 2018-05-05 06:00:56 | | 2 | fuga | 0 | 2018-05-05 06:01:04 | 2018-05-05 06:01:04 | +----+------+----------+---------------------+---------------------+ 2 rows in set (0.00 sec)
disabledはRails上でtrueだったものが1, falseだったものが0として格納されています
このdisabledはMySQL上のどの型で作成されているかでいうとtinyint(1)です
mysql> DESC users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | name | varchar(255) | YES | | NULL | | | disabled | tinyint(1) | YES | | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+
ここまではよく知った世界だと思います
ただこのtinyint(1)は0, 1以外も入って-128~127までいれることが出来ます
ここで一つ疑問として0, 1以外が入るとどうなるのでしょうか
Rails上で更新してみる
> User.find(1).update(disabled: 100) > User.find(1).update(disabled: -100)
どちらもエラーは出ないですが、disabledはtrueになります
MySQL上でも1です
MySQL上で更新してみる
mysql> UPDATE users SET disabled = 100 where id = 1; mysql> UPDATE users SET disabled = -100 where id = 1;
RailsでUser.find(1).disabled
してみるとどちらもtrueがかえってきます
裏側どうなってるの?
RailsというよりはActiveRecordなのでActiveRecord側のコードを見てみます
※ ここからはコードをざっと読んで見てなので間違ってたらコメントください
まずtinyint(1)がRails上でbooleanとなるのはactive_record/connection_adapters/abstract_mysql_adapter.rbに定義されている
emulate_booleans
がtrueの場合にtinyint(1)にType::Boolean.new
が割り当てられる
でこのType::Booleanとは何ものかというとこちら
このclass内のcast_valueに注目する
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set ~~~ def cast_value(value) if value == "" nil else !FALSE_VALUES.include?(value) end end
valueとしてわたってきた空文字の場合はnil, それ以外で値がFALUSE_VALUES
に該当するものがfalse, 該当しないものがtrueとなるのである
余談だけどFALSE_VALUESに該当すれば良いのでUser.create(name: "test", disabled: 'off')
とすればfalseになりdb上は0が入る
動作的な部分はこれで理解は出来た!はず
Railsだけでシステムを構築していない場合
今回挙動を見たようにRails上でデータを扱っているとbooleanで1, 0が入ることはなさそう
ただRails以外もDBにアクセスしており、それはtinyint(1)に0, 1以外をいれている場合はどうするのか?
答えは先程コードを見た時のactive_record/connection_adapters/abstract_mysql_adapter.rbのemulate_booleans
でこれをfalseにしてあげれば良い
コード内にもコメンドで記載されている
config/application.rbに下記を追加する
require 'active_record/connection_adapters/mysql2_adapter' ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
ただこれ一つ問題があってアプリケーション全体にこの設定が適用されてしまうので、このカラムはbooleanで扱って、このカラムは数値で返すことが出来なくなる
この時はモデルに次のように指定してあげると個別カラムで数値で扱うことが出来る
class User < ApplicationRecord attribute :disabled, ActiveModel::Type::Integer.new end
まとめ
今回はtinyint(1)のRails上での扱われ方を見ていった 今回のType moduleはstringとかdatetimeもあるので見てみるとどういった時にどのよにcastされるかわかるので面白い github.com