devise_token_authの「devise_token_auth_group」のコードリーディング

こんにちは!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の内部挙動を把握してみたいと思います。


コードリーディング

github.com

上記が定義元です。

早速読んでいきます。

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の柔軟さがよくわかるコードでした。