yield @variable if block_given? ってなに?

こんにちは!kossyです!



さて、今回は、トークンベースの認証の仕組みを提供するgemであるdevise_token_authの
コードリーディングをしていた時に書かれていたある構文について、ブログに残してみたいと思います。



yield @variable if block_given?

なんだこれ?と思ったコードは以下です。

    def validate_token
      # @resource will have been set by set_user_by_token concern
      if @resource
        yield @resource if block_given?
        render_validate_token_success
      else
        render_validate_token_error
      end
    end

引用: devise_token_auth/app/controllers/devise_token_auth/token_validations_controller.rb at master · lynndylanhurley/devise_token_auth · GitHub


順番に紐解いてみます。


まず、block_given?は、メソッドを実行する時にブロックが渡されていればtrueを返し、渡されていない時はfalseを返します。
Rubyではどのメソッドでも何の宣言も無しにブロック引数を渡すことができます。
block_given?メソッドの定義元もkarnelモジュールなので、どこでも使うことができます。

参考
www.sejuku.net

次にyield @resource ですが、yieldは渡されたブロックをメソッド内で呼び出すことができます。
また、引数を渡すことが可能で、@resourceを渡されたブロック内で使うこともできます。


もう一度当該コードを確認すると、

    def validate_token
      # @resource will have been set by set_user_by_token concern
      if @resource
        yield @resource if block_given?
        render_validate_token_success
      else
        render_validate_token_error
      end
    end

「もしvalidate_tokenメソッドの呼び出し時に@resourceがtrueの場合でブロックが渡されていれば、渡されたブロック内で@resourceを使って処理をした後に、render_validate_token_successを呼び出す」
「@resourceがnilまたはfalseの場合はrender_validate_token_error」

と書いてあるようです。

validate_tokenメソッドをブロック引数付きで呼び出すユースケースとしては、認証後にユーザーのプロフィール情報に基づいて特定のアクションを実行するケースが考えられます。

例えば、最後に tokenの確認を行なった日時を更新したい場合、以下のような書き方が可能です。

# user = @resource です
validate_token do |user|
  user.update(token_last_validated_at: Time.zone.now)
end

yield @resource if block_given? という記述を行うことで、メソッドに柔軟性を与えることが可能になると思います。

勉強になりました。

参考にさせていただいた記事

Kernel.#block_given? (Ruby 3.3 リファレンスマニュアル)
devise_token_auth/app/controllers/devise_token_auth/token_validations_controller.rb at master · lynndylanhurley/devise_token_auth · GitHub
【Ruby入門】yieldの使い方まとめ | 侍エンジニアブログ
https://qiita.com/tsubasakat/items/0de47f8814b0848f1d16
Rubyのyieldって結局何なの?|よしだ