こんにちは!kossyです!
今回はRailsでToken認証機能を提供するGem「devise_token_auth」を導入すると使えるようになる、
「devise_token_auth_group」メソッドのコードを読みながら、ブログに残してみたいと思います。
環境
Ruby 2.6.6
Rails 6.0.3.6
devise_token_auth 1.1.5
devise_token_auth_groupメソッド
devise_token_authを使って、複数のモデルに認証機能を付与している場合に使うメソッドになります。
例えば、User権限とAdmin権限がある場合、
class HomeController < ApplicationController devise_token_auth_group :member, contains: [:user, :admin] def index if current_member.is_a?(Admin) @posts = Post.all else @posts = Post.where.not(status: [:draft, :archive]) end end
上記のように :memberというシンボルを渡すことで、そのcontroller内でcurrent_memberというメソッドが使えるようになり、
userかadminかで処理を分岐させるように実装することができます。
では、devise_token_auth_groupの内部挙動を把握してみたいと思います。
コードリーディング
上記が定義元です。
早速読んでいきます。
mappings = "[#{opts[:contains].map { |m| ":#{m}" }.join(',')}]"
opts[:contains]には:userと:adminが格納されていると仮定して、コンソールで試してみます。
mappings = "[#{opts[:contains].map { |m| ":#{m}" }.join(',')}]" => "[:user,:admin]"
シンボルが入った配列を文字列に変換していますね。
class_evalの構文は、group_nameが member だと仮定して、よしなに変換して読んでみます。
def authenticate_member!(favourite=nil, opts={}) unless member_signed_in? unless current_member render_authenticate_error end end end def member_signed_in? !!current_member end def current_member(favourite=nil) @current_member ||= set_group_user_by_token(favourite) end def set_group_user_by_token(favourite) mappings = #{mappings} mappings.unshift mappings.delete(favourite.to_sym) if favourite mappings.each do |mapping| current = set_user_by_token(mapping) return current if current end nil end def current_members #{mappings}.map do |mapping| set_user_by_token(mapping) end.compact end def render_authenticate_error return render json: { errors: [I18n.t('devise.failure.unauthenticated')] }, status: 401 end if respond_to?(:helper_method) helper_method( "current_member", "current_members", "member_signed_in?", "render_authenticate_error" ) end
authenticate_member?やcurrent_memberなど、deviseを使っているとよく目にするメソッドが出てきましたね。
内部の処理についてはもはや説明不要でしょう。
おわりに
噛み砕いて表現すると、devise_token_auth_groupメソッドは引数に渡したシンボルを基に、deviseでよく使う認証周りのメソッドをよしなに定義してくれるメソッドでした。
黒魔術なメソッドを使って動的に定義ができるRubyの柔軟さがよくわかるコードでした。