2015年1月29日木曜日

Rails 4.2からはmodule ClassMethodsではなくConcern#class_methodsを使おう

もう1月も終盤ですが、あけましておめでとうございます。

先日、と言ってももう1カ月くらいは前だった気がしますが、Ruby on Rails 4.2.0がリリースされました。
⇒ リリースノート
 リリースノート(日本語)

主要な修正は以下の5つのようです。
  • ActiveJob
  • メールの非同期処理
  • Adequate Record
  • Web Console
  • 外部キーのサポート
どれも気になるところではありますが、外部キーのサポートとか特に気になりますが、個人的に一番気になったのは「Concern#class_methodsとKernel#concernの導入」です。
以下のコミットで導入されました。
⇒ Introduce Concern#class_methods and Kernel#concern

いままでconcernはこんな感じのモジュールを用意していました。
# 4.1まで
module Hoge
  extend ActiveSupport::Concern

  included do
  end

  module ClassMethods
  end
end

class Fuga < ActiveRecord::Base
  include Hoge
end

それが4.2以降はConcern#class_methodsを使用して以下のように書けるようになります。
# 4.2以降(Concern#class_methods)
module Hoge
  extend ActiveSupport::Concern

  included do
  end

  class_methods do
  end
end

class Fuga < ActiveRecord::Base
  include Hoge
end

さらにKernel#concernを使えば以下のようにmoduleである必要すらなくり、ActiveSupport#Concernをextendする必要がなくなります。
# 4.2以降(Kernel#class_methods)
concern :Hoge
  included do
  end

  class_methods do
  end
end

class Fuga < ActiveRecord::Base
  include Hoge
end


module ClassMethodsとConcern#class_methodsではクラス変数の扱いが微妙に異なるようです。
# app/models/concerns/hoge_old.rb
module HogeOld
  extend ActiveSupport::Concern

  included do
  end

  module ClassMethods
    def func_old
      @@val_old ||= 'old style'
    end
  end
end

# app/models/concerns/hoge_new.rb
module HogeNew
  extend ActiveSupport::Concern

  included do
  end

  class_methods do
    def func_new
      @@val_new ||= 'new style'
    end
  end
end

# app/models/concerns/user.rb
class User < ActiveRecord::Base
  include HogeOld
  include HogeNew
end
上記のようなコードがあるとして、実行すると以下のような結果になります。
[1] pry(main)> User.class_variable_defined?(:@@val_old)
=> false
[2] pry(main)> HogeOld::ClassMethods.class_variable_defined?(:@@val_old)
=> false
[3] pry(main)> User.func_old
=> "old style"
[4] pry(main)> User.class_variable_defined?(:@@val_old)
=> false
[5] pry(main)> HogeOld::ClassMethods.class_variable_defined?(:@@val_old)
=> true
[6] pry(main)> User.class_variable_defined?(:@@val_new)
=> false
[7] pry(main)> HogeNew::ClassMethods.class_variable_defined?(:@@val_new)
=> false
[8] pry(main)> User.func_new
=> "new style"
[9] pry(main)> User.class_variable_defined?(:@@val_new)
=> true
[10] pry(main)> HogeNew::ClassMethods.class_variable_defined?(:@@val_new)
=> false
[11] pry(main)>
module ClassMethodsでクラス変数を定義するとincludeしたクラスのクラス変数とはならず、ClassMethodsのクラス変数(モジュール変数)になってしまします。しかし、Concern#class_methodsを利用するとincludeしたクラスのクラス変数になります。
通常、concernでクラス変数を利用する際は後者の挙動を期待するように思います。

ちなみにKernel#concernでclass_methodsを使った時はクラス変数を使用するべきではありません。
「warning: class variable access from toplevel」みたいな警告が出ます。
これが意図した挙動かまでは追ってませんが、どうなんでしょうね?

いずれにしてもmodule ClassMethodsはやめてclass_methodsを使用したほうが見通しは良くなりそうです。