フックメソッドについて

特定のタイミングで実行されるものです。

意味不明だと思うので、解説します。

included

includeではありません、includedです。

なんとなく、見覚えがあったのですが、違いを全く理解していませんでした。

includedメソッドは、includeメソッドによってモジュールが他のモジュールやクラスにインクルードされたあとに呼び出されます。引数にはモジュールをインクルードするクラスやモジュールが入ります。

ref.xaio.jp

どういうことか、コードで説明します。

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を使いましょう。

railsguides.jp

以上です。

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が見つかりました。

ああ、そういうことかーってなりました。

予約語を使用する場合はバッククォートでエスケープすればいいです。

今思うと、上のrailssqlはきちんとバッククォートでエスケープしています。

今まで気にしなかったですが、予約語対策をしていたんですね。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 9.3 予約語

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って出るようになった

なんじゃこりゃーってなりました。

なんだ昔のmysqlを見に行ってるの?

そんな古い情報見に行かないでくれよ!って感じだと思います。

原因は、gemの情報で古いmysqlの場所を見に行っているのが原因でした。

gem uninstall mysql2

ここで全てのversionを削除しました。

gem install mysql2

これでbundle exec rake db:createで無事に作成出来ました。

参考

stackoverflow.com

railsのmysqlでemojiを使う方法

emojiを使おうと思ったら、色々と問題があるようなので、まとめます。

文字コード問題

まず、emojiはmysqlのutf8では使用できません。 mysqlでemojiを使用するにはutf8mb4にする必要があります。

これは何をしようとしているのかいうと、emojiの文字コードは1文字に4バイト使用します。 通常のutf8では、1文字に対して3バイトまでしか使用できません。 なので、mysqlで保存するためには、文字コードを変更する必要があります。

collation問題

こちらは初期設定のutf8mb4の設定でいくと、寿司ビール(🍣🍺)問題になります。 🍣と🍺が同じものとして扱われます。 こちらを別々に検索できるようにするための設定が必要になります。

これに関連してハハパパ問題というのがあります。

utf8でもきちんと設定しておかないと、はまったりします。

ハハパパ問題(参考)

ハハパパ問題は、「ハハ」「パパ」が同一の値として扱われます。 そんなことがあるのか!って思いましたが、railsmysqlでは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

以上です。

参考

qiita.com

qiita.com

クラスメソッドを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

役割をハッキリ決めて、読みやすくしないとダメだ。