こんにちは、Forkwell 事業部の正徳です。
タイトルにもあるように、Forkwell Jobs の開発では cancancan と action_args の2つの gem を使用しています。この2つの gem を一緒に使う際に問題が起きましたので「問題の紹介」と「解決するコード」を紹介したいと思います。
各 gem の簡単な紹介
知らない方もいると思いますので、各 gem の概要を書いておきます。
cancancan
コードの各所に散らばりがちな権限を Ability
という1つのファイルで管理できるようになります。
# app/models/ability.rb class Ability include CanCan::Ability def initialize(user) user ||= User.new # guest user (not logged in) if user.admin? can :manage, :all else can :read, :all end end end # app/controllers/users_controller.rb class UsersController < ApplicationController load_and_authorize_resource def create if @user.save # do something else render :new end end private def user_params params.require(:user).permit(:name, :age) end end
action_args
コントローラーで使用する値を引数に定義することで、 params
を使わずにコードを書けるようになります。
# app/controllers/users_controller.rb class UsersController < ApplicationController # white-lists User model's attributes permits :name, :age # the given `user` parameter would be automatically permitted by ActionArgs def create(user) @user = User.new(user) end end
2つの gem を使うときの問題点
前述したコードからも分かるように、それぞれの gem で Strong Parameters に対応する方法が異なります。このため、両方のいいとこ取りをするような下記のコードはエラーになってしまいます。
class UsersController < ApplicationController load_and_authorize_resource permits :name, :age def create if @user.save # do something else render :new end end def update(user) if @user.update(user) # do something else render :edit end end end
こんなコードが書けるようにしたいですよね!というわけで、共存させるようなコードを書きました。
コード
# refs # https://github.com/CanCanCommunity/cancancan/blob/v1.13.1/lib/cancan/controller_resource.rb#L79 # https://github.com/asakusarb/action_args/blob/v2.0.0/lib/action_args/params_handler.rb#L45 module CanCan module ActionArgsEx def permits(*attributes) super UsingActionArgs.include_once(self) end end module UsingActionArgs def self.included(klass) klass.singleton_class.prepend(ActionArgsEx) end def self.include_once(klass) method_name = "#{klass.controller_name.singularize}_params".to_sym return if respond_to?(method_name) m = Module.new do define_method method_name do model_name = klass.instance_variable_get(:@permitting_model_name) permitted_attributes = klass.instance_variable_get(:@permitted_attributes) key = (model_name || klass.controller_name).singularize.underscore.to_sym params[key].permit(*permitted_attributes) end end klass.include(m) end end end
Ruby のメタプログラミング力が上がりそうなコードですね!この Module がやっている概要は下記の通りです。
- include したクラスの
self.permits
を上書きする permits
が呼ばれたら、一度だけ 3. 以降の処理を実行する- コントローラーのクラス名から
xxx_params
というメソッド名を作る xxx_params
のメソッドを持つ Module を動的に生成し、コントローラーで include する
これにより、 load_and_authorize_resource
が xxx_params
のメソッドを呼び出すことができるため、エラーが起きなくなります。
ちなみに、使い方はコントローラーで include
するだけです。
class UsersController < ApplicationController include CanCan::UsingActionArgs load_and_authorize_resource permits :name, :age def create if @user.save # do something else render :new end end def update(user) if @user.update(user) # do something else render :edit end end end
cancancan と action_args という2つの gem を組み合わせて使う際の解決方法をご紹介しましたが、いかがだったでしょうか?
このコードが cancancan と action_args を両方とも使っている Rails プロジェクトの役に立てば幸いです。