2
0

More than 1 year has passed since last update.

ActiveSupport::Concernでモジュールを作る

Last updated at Posted at 2022-04-09

Concernとは

Concernとは「関心事」という意味で、Railsではコントローラやモデルの一部の概念や機能を実装するモジュールのことを指します。Concernを用いることで、ある特定の概念や機能をコントローラやモデルから切り出すことができ、複数のコントローラやモデル間で使い回すことができます。クラス、モジュール間のincludeやextendの説明はここでは省きます。

スコープ

以下のようにPhotoモデルとVideoモデルに共通のスコープが定義されていたとします。

photo.rb
class Photo < ApplicationRecord
    scope :display_member_only, -> { where(member_only: true) }
end
video.rb
class Video < ApplicationRecord
    scope :display_member_only, -> { where(member_only: true) }
end

このとき、以下のようにすることでスコープを共通処理として切り出すことができます。

common_module.rb
module CommonModule
  def self.included(base)
    base.class_eval do
      scope :display_member_only, -> { where(member_only: true) }
    end
  end
end

そしてCommonModuleをincludeすることでこのdisplay_member_onlyを使うことができます。

photo.rb
class Photo < ApplicationRecord
    include CommonModule
end
video.rb
class Video < ApplicationRecord
    include CommonModule
end
irb(main):002:0> Video.display_member_only
...(省略)
irb(main):002:0> Photo.display_member_only
...(省略)
# display_member_onlyスコープが使える

そして、ActiveSupport::Concernを使うと、先ほどのモジュール内を以下のように書くことができます。

common_module.rb
module CommonModule
  extend ActiveSupport::Concern
    
  included do
    scope :display_member_only, -> { where(member_only: true) }
  end
end

ActiveSupport::Concernを使うことで「def self.included(base) ... end」としていた部分が「included do ... end」に置き換わりました。こちらの方が簡潔に記述できそうですね。

クラスメソッド、インスタンスメソッド

以下のようにクラスメソッドとインスタンスメソッドがモデルに追加されたとしましょう。

photo.rb
class Photo < ApplicationRecord
  class << self
    def order_by_latest
      order(released_at: :desc)
    end
  end
    
  def title_with_released_at
    "#{title}#{released_at}】"
  end
end
video.rb
class Video < ApplicationRecord
  class << self
    def order_by_latest
      order(released_at: :desc)
    end
  end
    
  def title_with_released_at
    "#{title}#{released_at}】"
  end
end

これらの同じ処理をmoduleに切り出すと以下のように書くことができます。

common_module.rb
module CommonModule
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def order_by_latest
      order(released_at: :desc)
    end
  end

  def title_with_released_at
    "#{title}#{released_at}】"
  end
end

スコープのときと同様、各モデルからCommonModuleをincludeすることによってこれらのメソッドが使えます。
base.extendあたりの書き方は以下の記事が参考になるかと思います。

irb(main):002:0> Video.order_by_latest
...(省略)
irb(main):002:0> Photo.first.title_with_released_at
=> "photo1【2022-04-01】"

そして、ActiveSupport::Concernを使うと以下のように書けます。

common_module.rb
module CommonModule
  extend ActiveSupport::Concern

  module ClassMethods
    def order_by_latest
      order(released_at: :desc)
    end
  end

  def title_with_released_at
    "#{title}#{released_at}】"
  end
end

ActiveSupport::Concernをextendすることで「def self.included(base) ... end」を剥がすことができます。ちなみにクラスメソッドのorder_by_latestは以下のようにも書くことができます。

common_module.rb
module CommonModule
  extend ActiveSupport::Concern

  class_methods do
    def order_by_latest
      order(released_at: :desc)
    end
  end

  def title_with_released_at
    "#{title}#{released_at}】"
  end
end

「module ClassMethods ... end」を「class_methods do ... end」として書く方法です。
こちらの方が直感的に読めるイメージです。

まとめ

スコープとクラスメソッド、インスタンスメソッドをActiveSupport::Concernを使って共通モジュールとしてまとめたものが以下になります。

common_module.rb
module CommonModule
  extend ActiveSupport::Concern

  # スコープ
  included do
    scope :display_member_only, -> { where(member_only: true) }
  end

  # クラスメソッド
  class_methods do
    def order_by_latest
      order(released_at: :desc)
    end
  end
    
  # インスタンスメソッド
  def title_with_released_at
    "#{title}#{released_at}】"
  end
end

モデル間での共通処理がある場合は、処理の肥大化や重複を避けるためにも積極的に使っていきたいです。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0