記事を書くにあたって
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車)の両方を修正しないといけませんよね?
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
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車)に修正する必要は無くなりました。
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
class AutomaticCar < ApplicationRecord
include CarModel
belongs_to :manufacturer
end
class MissionCar < ApplicationRecord
include CarModel
belongs_to :manufacturer
end
Mergeに関して
共通で書かない場合
例えば、MissionCar(MT車)やAutomaticCar(AT車)がManufacturer(メーカー)のname(名前)に関して検索を行いたいとします。
MissionCar(MT車)やAutomaticCar(AT車)にそれぞれ下記のように書いていませんか?
もし、システムの使用が部分一致ではなく完全一致になった場合対象の処理を全て修正しないといけませんよね?
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
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を使用すれば良いと思います。
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
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
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を変更する時を考えると頭が痛くなります。
def index
@manufacturers = Manufacturer.all.params[:page]).per(20)
end
共通で書かいた場合
Railsのextendingを使用することで一つのファイルにまとめることができました! これで変更する際にも修正箇所はPaginationモジュール一つのみになります。
# 実際にこのファイルを作成した場合はRailsを再起動してください。
# 再起動しない場合はファイルをRailsが読み込みません。
module Pagination
def paginate(page)
page(page).per(10)
end
end
def index
@manufacturers = Manufacturer.all.extending(Pagination)
@manufacturers.paginate(params[:page])
end