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

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

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

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

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

このとき、以下のようにすることでスコープを共通処理として切り出すことができます。
```ruby: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を使うことができます。
```ruby:photo.rb
class Photo < ApplicationRecord
    include CommonModule
end
```

```ruby: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を使うと、先ほどのモジュール内を以下のように書くことができます。

```ruby: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」に置き換わりました。こちらの方が簡潔に記述できそうですね。

## クラスメソッド、インスタンスメソッド
以下のようにクラスメソッドとインスタンスメソッドがモデルに追加されたとしましょう。
```ruby: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
```

```ruby: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に切り出すと以下のように書くことができます。

```ruby: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あたりの書き方は以下の記事が参考になるかと思います。

https://pikawaka.com/ruby/include#%E3%82%AF%E3%83%A9%E3%82%B9%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%82%E5%90%8C%E6%99%82%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88

```
irb(main):002:0> Video.order_by_latest
...(省略)
irb(main):002:0> Photo.first.title_with_released_at
=> "photo1【2022-04-01】"
```
そして、ActiveSupport::Concernを使うと以下のように書けます。

```ruby: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は以下のようにも書くことができます。

```ruby: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を使って共通モジュールとしてまとめたものが以下になります。
```ruby: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
```

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