4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsで共通の処理をどのようにまとめるか、考えてみた

Last updated at Posted at 2021-08-29

記事を書くにあたって

Railsで実装を行なっている際に同じ処理が複数の箇所に記載されてしまうため、モヤモヤして色々と調べた結果です。他にもこういったものがあるよ! とコメント欄で教えていただけると嬉しいです! ポリモーフィック関連付と単一テーブル継承に関しては私自身あまり理解していないので、今回の項目からは除外しています。(個人的にはメリットよりデメリットを大きく感じる……)

Railsで共通の処理はどう実装するの?

concern、merge、extendingを紹介いたします! これらを使用すれば、共通の処理をまとめることができるのではないかと考えています!

concern、merge、extendingはそれぞれ何を共通化できるの?

機能 共通化できるもの
concern モデルのクラスやインスタンスに対しての処理
merge scope(他のモデルのscopeを呼び出すことが可能になる。)
extending モデルの集合のインスタンスに対しての処理

説明の元となるモデルに関して

モデル名称(JP) 列(JP)
MissionCar(MT車) name(名前), manufacturer_id(メーカーID), car_model(車種)
AutomaticCar(AT車) name(名前), manufacturer_id(メーカーID), car_model(車種)
モデル名称(JP) 列(JP)
Manufacturer(メーカー) name(名前)

concernに関して

共通で書かない場合

例えば、Enumerizeを使用する場合に複数のモデルに同じ処理を書いたことはありませんか?
下記の例のような形です。

この場合、car_modelに新しく種類を追加して欲しい! と言われた時にMissionCar(MT車)とAutomaticCar(AT車)の両方を修正しないといけませんよね?

app/models/automatic_car.rb
class AutomaticCar < ApplicationRecord
  belongs_to :manufacturer
  extend Enumerize

  enumerize :car_model, in: %i[sedan suv minivan open] scope: true

  scope :familiesa_car , lambda{ where(car_model: %w[sedan suv minivan]) }
end
app/models/mission_car.rb
class MissionCar < ApplicationRecord
  belongs_to :manufacturer
  extend Enumerize

  enumerize :car_model, in: %i[sedan suv minivan open] scope: true

  scope :familiesa_car , lambda{ where(car_model: %w[sedan suv minivan]) }
end

共通で書いた場合

Concernを使用して共通の処理を抜き出してみます。

下記のようにすることでenumerizeを一つのmoduleに閉じ込めることができます。
今後、car_modelに追加や削除があってもCarModelに対して修正を行い、MissionCar(MT車)やAutomaticCar(AT車)に修正する必要は無くなりました。

app/models/concerns/car_model.rb
module CarModel
  extend Enumerize
  extend ActiveSupport::Concern

  enumerize :car_model, in: %i[sedan suv minivan open] scope: true

  included do
    scope :familiesa_car , lambda{ where(car_model: %w[sedan suv minivan]) }
  end
end
app/models/automatic_car.rb
class AutomaticCar < ApplicationRecord
  include CarModel
  belongs_to :manufacturer
end
app/models/mission_car.rb
class MissionCar < ApplicationRecord
  include CarModel
  belongs_to :manufacturer
end

Mergeに関して

共通で書かない場合

例えば、MissionCar(MT車)やAutomaticCar(AT車)がManufacturer(メーカー)のname(名前)に関して検索を行いたいとします。
MissionCar(MT車)やAutomaticCar(AT車)にそれぞれ下記のように書いていませんか?

もし、システムの使用が部分一致ではなく完全一致になった場合対象の処理を全て修正しないといけませんよね? 

app/models/automatic_car.rb
class AutomaticCar < ApplicationRecord
  include CarModel
  belongs_to :manufacturer

  scope :where_manufacturer_name, -> (search_name) { eager_load(:manufacturer).where('name LIKE ?', "%{ search_name }%") if name.present? }
end
app/models/mission_car.rb
class MissionCar < ApplicationRecord
  include CarModel
  belongs_to :manufacturer

  scope :where_manufacturer_name, -> (search_name) { eager_load(:manufacturer).where('name LIKE ?', "%{ search_name }%") if name.present? }
end

共通で書いた場合

Mergeを使用することで、Mergeで指定したクラスのscopeを使用することが可能になります。
同じscopeを複数の箇所に持たせたいのであれば、Concernを使用すれば良いと思います。

app/models/manufacturer.rb
class Manufacturer < ApplicationRecord
  has_many :mission_cars
  has_many :automatic_cars

   scope :where_name, -> (search_name) { where('name LIKE ?', "%{ search_name }%") if name.present? }

end

app/models/automatic_car.rb
class AutomaticCar < ApplicationRecord
  include CarModel
  belongs_to :manufacturer

 # 或いはscopeで直接merge(Manufacturer.where_name(search_name)と呼び出しても良い
  scope :where_manufacturer_name, -> (search_name) { eager_load(:manufacturer).merge(Manufacturer.where_name(search_name)) }
end
app/models/mission_car.rb
class MissionCar < ApplicationRecord
  include CarModel
  belongs_to :manufacturer

 # 或いはscopeで直接merge(Manufacturer.where_name(search_name)と呼び出しても良い
  scope :where_manufacturer_name, -> (search_name) { eager_load(:manufacturer).merge(Manufacturer.where_name(search_name)) }
end

Extendingに関して

共通で書かない場合

この機能が一番知りたかった機能でした!
例えば、Paginationの処理ですが複数のコントローラーに何度もpage(params[:page]).per(20)と貼り付けていますよね? 20件が30件になった時やページネーションのGEMを変更する時を考えると頭が痛くなります。

app/controllers/manufacturers_controller.rb
  def index
    @manufacturers = Manufacturer.all.params[:page]).per(20)
  end

共通で書かいた場合

Railsのextendingを使用することで一つのファイルにまとめることができました! これで変更する際にも修正箇所はPaginationモジュール一つのみになります。

app/extensions/paginate.rb
# 実際にこのファイルを作成した場合はRailsを再起動してください。
# 再起動しない場合はファイルをRailsが読み込みません。
module Pagination
  def paginate(page)
    page(page).per(10)
  end
end
app/controllers/manufacturers_controller.rb
  def index
    @manufacturers = Manufacturer.all.extending(Pagination)
    @manufacturers.paginate(params[:page])
  end

参考

Railsガイド

4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?