フックメソッドについて
特定のタイミングで実行されるものです。
意味不明だと思うので、解説します。
included
includeではありません、included
です。
なんとなく、見覚えがあったのですが、違いを全く理解していませんでした。
includedメソッドは、includeメソッドによってモジュールが他のモジュールやクラスにインクルードされたあとに呼び出されます。引数にはモジュールをインクルードするクラスやモジュールが入ります。
どういうことか、コードで説明します。
module Foo def self.included(klass) puts "#{klass}にincludeされました" end end class Bar include Foo end =>Barにincludeされました
includeしたタイミングで上のputsが反応しています。
これを利用して、特異メソッド化する手法がよく使われます。
module Foo def self.included(klass) puts "#{klass}にincludeされました" klass.extend Methods end module Methods def foo 'foo' end end end class Bar include Foo end Bar.foo => "foo"
extendでMethodsをBarのみに使用できるようにしています。 これでincludeした場合に特異メソッド化ができます。
included
は単なるフックメソッドとして、使用されており、inlucdeとは全く違うものでした。
ここの理解を全くしていなかったな。
特異メソッドでincludeする方法
何個か方法あるので、少しづつ紹介します。
クラス内でincludeする
module Foo def hello 'hello' end end class Bar class << self include Foo end end Bar.hello => "hello"
特定のオブジェクトだけincludeする
x = Bar.new class << x include Foo end x.hello => "hello"
Object#extend
これが一番一般的ですね。
y = Bar.new y.extend Foo y.hello => "hello"
方法としては、この3つあります。
意外にextendの用法を忘れてしまう・・・orz
クラス変数の注意点
クラス変数なんて、めったに使うことなんてないし、rails
で使うならclass_attribute
があるんで、普通は必要ないです。
しかし、よくクラス変数やめろ!なんて言われることが多いと思うので、ここでおさらいしておきます。
クラス変数とは
そもそもクラス変数ってなんぞや?
クラス間で共有出来る変数ですね。
例えば、下記のようなものです。
class Foo @@x = 1 def x @@x end def x=value @@x = value end end pry(main)> a = Foo.new => #<Foo:0x007f89982500e8> pry(main)> a.x = 3 => 3 pry(main)> b = Foo.new => #<Foo:0x007f8998343978> pry(main)> b.x = 5 => 5 pry(main)> a.x => 5
確かに共有されますね。
ただし、厄介なことがあります。
- サブクラスにも継承される
- クラス階層に属している
前者はわかりやすいと思うので、後者について説明します。
先程の続きです。
pry(main)> @@x = 6 (pry):17: warning: class variable access from toplevel => 6 pry(main)> b.x => 6
上記のように、mainクラスの時に変更したものも値が変更されてしまいます。
こういう罠が多いので、クラス変数は使うなってことになります。
けど、どうしてもクラス間で共有したいって時があります。
そういう場合はどうすればいいのかってことです。
あくまでもrails側の方法です。
class_attribute
railsではclass_attributeがあります。
これは、先程のサブクラスの継承を無しにして、なおかつトップレベルのものは参照されなくなります。
なので、railsでクラス間の値を共有したい場合は、class_attribute
を使いましょう。
以上です。
mysqlの予約語に気をつけた方がいい
やられました...orz
直接sqlをいじることがなかったのですが、いじった時に気付きました。
mysql> show tables; +-----------------------------------+ | Tables_in_foo | +-----------------------------------+ | categories | | comments | | rankings | | reads | | schema_migrations | | supplementals | | taggings | | tags | | users | +-----------------------------------+
こんな感じで表示されています。
ここでreads
テーブルのデータを取得しようとします。
mysql> select * from reads; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'reads' at line 1
なぜ、syntaxエラーになる???
おかしいだろ!ってなりました。
テーブルはきちんと作成されているのか?
reads.frm reads.ibd
二つともきちんとある。
rails側では値が取得できている。
[1] pry(main)> Read.count (0.3ms) SELECT COUNT(*) FROM `reads` => 0
わからん・・・。
試しにデータを投入しても、無事に投入できる。
ここで予約語っていう概念か?と思い調べました。
そうしたらREAD/READS
が見つかりました。
ああ、そういうことかーってなりました。
予約語を使用する場合はバッククォート
でエスケープすればいいです。
今思うと、上のrailsのsqlはきちんとバッククォート
でエスケープしています。
今まで気にしなかったですが、予約語対策をしていたんですね。
cpuの情報を見る方法(mac/linux)
lscpu
でいけるんじゃない?って思っていたのでしたが、macは違いました。
mac
system_profiler SPHardwareDataType
Hardware Overview: Model Name: MacBook Pro Model Identifier: MacBookPro11,1 Processor Name: Intel Core i5 Processor Speed: 2.4 GHz Number of Processors: 1 Total Number of Cores: 2 L2 Cache (per Core): 256 KB L3 Cache: 3 MB Memory: 8 GB ....
linux
lscpu
mikami@ik1-324-22232:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 2 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 45 Model name: Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz Stepping: 7 CPU MHz: 2499.998 BogoMIPS: 4999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32K L1i cache: 32K L2 cache: 4096K NUMA node0 CPU(s): 0,1 mikami@ik1-324-22232:~$ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 2 NUMA node(s): 1 Vendor ID: GenuineIntel CPU family: 6 Model: 45 Model name: Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz Stepping: 7 CPU MHz: 2499.998 BogoMIPS: 4999.99 Hypervisor vendor: KVM Virtualization type: full L1d cache: 32K L1i cache: 32K L2 cache: 4096K NUMA node0 CPU(s): 0,1
linuxの方がいっぱい情報あるんで、こっちの方がいいな・・・って思いました。
ちなみにsystem_profiler
は他の引数もあるので、それを渡すことで色々見れそうな感じがしました。
macのmysqlをupdateしたら、Library not loaded: /usr/local/opt/mysql56/lib/libmysqlclient.18.dylibって出るようになった
railsのmysqlでemojiを使う方法
emojiを使おうと思ったら、色々と問題があるようなので、まとめます。
文字コード問題
まず、emojiはmysqlのutf8では使用できません。 mysqlでemojiを使用するにはutf8mb4にする必要があります。
これは何をしようとしているのかいうと、emojiの文字コードは1文字に4バイト使用します。 通常のutf8では、1文字に対して3バイトまでしか使用できません。 なので、mysqlで保存するためには、文字コードを変更する必要があります。
collation問題
こちらは初期設定のutf8mb4の設定でいくと、寿司ビール(🍣🍺)問題になります。 🍣と🍺が同じものとして扱われます。 こちらを別々に検索できるようにするための設定が必要になります。
これに関連してハハパパ問題というのがあります。
utf8でもきちんと設定しておかないと、はまったりします。
ハハパパ問題(参考)
ハハパパ問題は、「ハハ」「パパ」が同一の値として扱われます。 そんなことがあるのか!って思いましたが、railsのmysqlではdefaultでこの設定になっています。 何も知らないと検索結果おかしい・・・みたいな罠にはまります。 では、どのようなcollationがあるかを見ていきます。
- utf8_bin
- utf8_general_ci
- utf8_unicode_ci
utf8_bin 文字コードが完全に一致するもののみ返す 一番厳しいですね
utf8_general_ci アルファベットの大文字・小文字は区別しない 他は全て区別する
utf8_unicode_ci 大文字小文字/全角半角を区別しない。
「あああ」は「アアア」「ァァァ」「ぁぁぁ」にマッチする。
railsはdefaultでutf8_unicode_ciになっています。 なので、この設定は変更しましょう。
show full columns from topics;
でcollationの確認をすることができます。
mysql> show full columns from topics; +------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+ | Field | Type | Collation | Null | Key | Default | Extra | Privileges | Comment | +------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+ | id | int(11) | NULL | NO | PRI | NULL | auto_increment | select,insert,update,references | | | title | varchar(255) | utf8_unicode_ci | NO | | NULL | | select,insert,update,references | | | body | text | utf8_unicode_ci | NO | | NULL | | select,insert,update,references | | | name | varchar(255) | utf8_unicode_ci | NO | | NULL | | select,insert,update,references | | | category_id | int(11) | NULL | NO | MUL | NULL | | select,insert,update,references | | | thumbnail_updated_at | datetime | NULL | YES | | NULL | | select,insert,update,references | | | thumbnail_file_size | int(11) | NULL | YES | | NULL | | select,insert,update,references | | | thumbnail_content_type | varchar(255) | utf8_unicode_ci | YES | | NULL | | select,insert,update,references | | | thumbnail_file_name | varchar(255) | utf8_unicode_ci | YES | | NULL | | select,insert,update,references | | | created_at | datetime | NULL | NO | | NULL | | select,insert,update,references | | | updated_at | datetime | NULL | NO | | NULL | | select,insert,update,references | | +------------------------+--------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+ 11 rows in set (0.00 sec)
何もしていないと、無事にutf8_unicode_ci
になって、ウワって結果になると思います。
default: &default adapter: mysql2 collation: utf8_general_ci
これは初期設定でやるべきことですね。
もし、運用フェーズで気づいたなら、mysql側で変更しましょう。
index問題
最大のインデックス長が757バイトになっています。 utf8では255文字で、utf8mb4では191文字になります。
解決方法としては、二つあります。
- インデックス長を増やす
- インデックス長を減らす
減らす方法はデフォルトの動きではならなくなるので、インデックス長を増やす方が良いです。
mysql側の設定
my.cnfを設定します。
[mysqld]
innodb_file_per_table
innodb_file_format=barracuda
innodb_large_prefix = 1
rails側の設定
database.ymlの設定
default: &default adapter: mysql2 charset: utf8mb4 encoding: utf8mb4 collation: utf8mb4_bin pool: 5 timeout: 5000
絵文字を区別したいのであれば、collationの設定をutf8mb4_bin
にしないといけません。
区別する必要がないのであれば、utf8mb4_general_ci
で大丈夫です。
ただし、このままだとmigrationの設定がうまくいかないので、挙動を変更するようにします。
config/initializers/ar_innodb_row_format.rb
ActiveSupport.on_load :active_record do module ActiveRecord::ConnectionAdapters class AbstractMysqlAdapter def create_table_with_innodb_row_format(table_name, options = {}) table_options = options.merge(:options => 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC') create_table_without_innodb_row_format(table_name, table_options) do |td| yield td if block_given? end end alias_method_chain :create_table, :innodb_row_format end end end
以上です。
参考
クラスメソッドをprivateメソッドにする方法
単純にprivate
にするだけはダメです。
class Foo def self.bar puts 'bar' end private def self.baz puts 'baz' end end Foo.baz =>baz
という風に実は普通に呼べています。
方法は二種類あります。
- self内でprivateを呼ぶ
- private_class_methodを定義する
self内privateを呼ぶ
class Foo def self.bar puts 'bar' end class << self private def baz puts 'baz' end end end Foo.baz NoMethodError: private method `baz' called for Foo:Class Did you mean? bar
きちんとprivate化されていますね。
private_class_methodを定義する
class Foo def self.bar puts 'bar' end def self.baz puts 'baz' end private_class_method :baz end Foo.baz NoMethodError: private method `baz' called for Foo:Class Did you mean? bar
同じ結果になっています。 ただし、後者はメソッドが定義された後に書かないといけなません。
そこは注意が必要ですね。
以上です。
文字列からクラス名にアクセスする方法
constantize
を使用します。
"Topic".constantize => Topic(id: integer, title: string, body: text, category_id: integer, thumbnail_updated_at: datetime, thumbnail_file_size: integer, thumbnail_content_type: string, thumbnail_file_name: string, created_at: datetime, updated_at: datetime, name: string)
定義ファイル activesupport-4.2.7.1/lib/active_support/core_ext/string/inflections.rb @ line 65:
def constantize ActiveSupport::Inflector.constantize(self) end
自分自身を返してアクセスしている。
以上です。
nestしたcontent_tagを書く方法と注意点
nestしたcontent_tagを書く場合です。
concatで連結して書くことができます。
content_tag(:div, class: "c-grid__quotation-image") do concat(image_tag(url)) concat(link_to("出典:#{host_name}", url)) end
ただし、こんだけ連結させる場合は、別途renderで書いた方がマシです。
render "foo", url: urlm host_name: host_name
役割をハッキリ決めて、読みやすくしないとダメだ。