概要
Rubyの特徴であるmixinをどのようにRailsアプリケーションで活用したときに、どのようなメリットがあるのか考える。
mixinとは
moduleをclassでincludeすることにより、moduleのメソッドをclassで使用できるようにすること。
module TestModule
def test
"test"
end
end
class TestClass
include TestModule
end
Test.new.test
=> "test"
Railsアプリケーションでの利用
業務ロジックをmoduleとして定義し、Controllerでincludeする
module UserClient
extend ActiveSupport::Concern
included do
helper_method :user
end
def user
@_user ||= User.find(params[:user_id])
end
end
module UserProfileClient
extend ActiveSupport::Concern
include UserClient
included do
helper_method :user_profile
end
def user_profile
@_user_profile ||= UserProfile.find(user.id)
end
end
class UsersController < ApplicationController
include UserClient
include UserProfileClient
def show
end
end
<%= user.name %>
<%= user_profile.age %>
mixin利用のメリット
Fat Controllerからの脱却
従来の実装では、Controllerに処理の記述が多くなってしまう。
mixinを利用することで、moduleとして切り出すことができる。
class UsersController < ApplicationController
def show
@user = User.find(params[:user_id])
@user_profile = UserProfile.find(@user.id)
end
end
Fat Modelからの脱却
Fat Controllerから脱却手段としては、Modelに業務ロジックを記載する方法もある。
別モデルの処理の記述が入ったりして、特定モデルの処理の記述が多くなってしまう。
複数のモデルを参照する処理をmoduleに切り出すことで、特定モデルの記述を最小限にする。
class UserModel < ActiveRecord::Base
# relationで取得するのがベストだが、あくまでmixinとの比較のため今回はメソッド定義の形とする。
def user_profile
UserProfile.find(id)
end
end
処理順を気にしなくてよくなる
Fat Controllerの例では、userを生成してから、user_profileを生成するという処理順を気にする必要がある。
処理順を逆にするとエラーになる。
class UsersController < ApplicationController
def show
@user_profile = UserProfile.find(@user.id) # @userはnilなので、idメソッドをコールできずにエラーとなる。
@user = User.find(params[:user_id])
end
end
一方で、mixinではmodule間の依存関係に気をつければよく、処理順を気にする必要はない。
module UserProfileClient
extend ActiveSupport::Concern
# 依存関係を定義
include UserClient
included do
helper_method :user_profile
end
def user_profile
# userメソッドをコールしたときに処理が実行されるので、何らかの事前処理は不要。
@_user_profile ||= UserProfile.find(user.id)
end
end
Controllerのbefore_actionの煩わしさから解放される
特定のUser情報だけ必要なdef showと、すべてのUser情報が必要なdef indexを定義したとき、
それぞれのメソッドで必要な情報を考慮して、before_actionにonly指定をする必要がある。
only指定をしない場合、showですべてのUser情報取得の処理が実行され、無駄な処理をしてしまう。
class UsersController < ApplicationController
before_action :user, only: :show
before_action :users, only: :index
def show
end
def index
end
private
def user
@user = User.find(params[:user_id])
end
def users
@users = User.all
end
end
一方で、mixinを利用したとき、moduleのメソッドがコールされたときに処理が実行されるので、moduleで必要となるメソッドを定義しておいても無駄な処理は発生しない。必要なときに必要な処理量で、必要な情報を取得できる。
module UserClient
extend ActiveSupport::Concern
included do
helper_method :user
helper_method :users
end
def user
@_user ||= User.find(params[:user_id])
end
def users
@_users ||= User.all
end
end
module UserProfileClient
extend ActiveSupport::Concern
include UserClient
def show
end
def index
end
end
<%= user.name %>
<%# usersをコールしない限り、usersの処理は実行されないが、必要な時はコールできる %>
<%= users.size %>
<%= users.name %>
論点
moduleで定義したメソッドをviewで使用する場合、helper_methodとして定義するか、helper側でcontrollerにdelegateするか
paramsはhelper側でdelegateしている
https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/controller_helper.rb#L16