ビールがうまい2023ふりかえり夏

ビールはいつもうまいんですが、最近特にうまいのでふりかえりをします。

定期的な運動

運動をするとビールがうまいです。動かした身体に染み渡るように感じます。

毎日朝晩1時間ずつの愛犬との散歩が習慣になっていて、気持ち良いです。愛犬よ、いつまでも元気でいてくれ。

2023年からはテニススクールに通い始め、毎週1時間半のレッスンを受けています。妻も自分も、学生時代(妻は中学、自分は高校)テニス部に所属していたので経験者ではあるのですが、大学以来ずいぶんとテニスをやっていなかったので、スクールでコーチにあれこれ教えてもらいながらテニスをするのはとても新鮮で楽しいです。もっと上達したいですね。

散歩とテニスで週15時間以上運動をしている(散歩2時間 x 7日 + テニス1.5時間)ので、ビールがうまいです。

体調不良からの回復

5月15日に発熱し、その後検査を実施したらコロナ陽性と診断されました。誕生日に発症するなんて、なかなかのプレゼントですね。 発熱の様子は バースデー発熱2023 - tanaken0515 に記録しました。40度近い発熱が4日連続で出て驚いたしツラかったです。 咳と痰がひどくて、3週間くらいつづきました。

やっと治ったと思ったら、今度は細菌性の肺炎に罹りました。こちらはコロナと違って治療薬(抗菌薬)を処方いただけたので、回復への安心感がありました。とはいえ、発熱症状の改善から咳と息苦しさの解消までは2週間くらい要しました。

そんなこんなでやっと7月になって回復した身体を手に入れたのでした。

健康を噛み締めながら飲むビールはうまいです。

余談

以上、ふりかえりでした。健康な身体で運動して飲むビールはうまいですね。

ここからは完全に余談で、この記事を書くにあたって、iPhoneのアルバムアプリの検索枠に雑に「ビール」と書いてみたら、ちゃんとビールに関連する画像が一覧表示されました。便利。

さらに、今年の写真だけに絞り込みたかったので「ビール 2023」で検索してみると、それっぽい結果が出ました。

左上の写真だけビールじゃないな?と思い、見てみると

原材料名の「ビール酵母」と賞味期限の「2023」を検知してるんですね。

ビールの画像認識と画像内の文字認識の技術が使われてるんだなと知りました。今後も便利に活用できそう。

なにかを漏れなく被りなく検討するときにやっていること

なにかに取り組むときに「漏れなく被りなく思考・検討できているか?」を知りたくなる(あるいは他者から問われる)ことがあります。

そういう時に自分はこの手順で整理することが多いです。

  1. 対象を分解するための「観点」をリストアップする
  2. それぞれの「観点」に対して取り得る「選択肢」をリストアップする
  3. 「観点」と「選択肢」のすべての組み合わせをリストアップした表をつくる
  4. 表の各行に対して深掘りする

実際にやってみる

やったほうがわかりやすいと思うので、ここでは

SUZURIにおけるグッズの注文」

を対象として雑にやることにします。

1. 対象を分解するための「観点」をリストアップする

注文について検討するので「買う人」と「売る人」がいそうですね。

注文時の状況も考えてみましょう。SUZURIが「セール」を開催しているかもしれません。買う人はお得な「クーポン」を使うかもしれません。

これらを踏まえて、ここでは「観点」として次の4つを挙げることにしましょう。

  • 買う人
  • 売る人
  • セール
  • クーポン

2. それぞれの「観点」に対して取り得る「選択肢」をリストアップする

先ほどの4つの観点に対して、どういう選択肢がありそうかを考えてみましょう。

買う人

SUZURIで販売されているグッズたちは、ふらっと訪れて購入することもできるし、会員登録をしたうえで購入することもできます。つまり、次の2つ選択肢があります。

  • (買う人が)ゲストである
  • (買う人が)会員である

売る人

SUZURIでは、様々なクリエイターさんがグッズを販売しています。そういったグッズの中から好きなものを購入できますし、自分自身でデザインしたグッズを購入することもできます。つまり、これも2つ選択肢があります。

  • (売る人が)買う人と異なる
  • (売る人が)買う人と同一である

セール

SUZURIでは、運営によって時々セールが開催されています。開催中に買うとお得です。

  • (セールが)開催中である
  • (セールが)開催していない

クーポン

SUZURIから会員に向けてクーポンが配布されることがあります。クーポンを使うとお得です。

  • (クーポンを)使用する
  • (クーポンを)使用しない

3. 「観点」と「選択肢」のすべての組み合わせをリストアップした表をつくる

手順の1と2を整理すると、このようになります。

4つの観点で2通りずつの選択肢があるので全部で 2 x 2 x 2 x 2 = 16 通りですね。

これをすべて書き出すとこうなります。

4. 表の各行に対して深掘りする

最後に、各行について深掘りをします。 深掘りの内容はケースバイケースですが、ほとんどいつもやるのは次の2つです。

  • 発生し得るかどうか?をYes/Noで記載する
  • 備考にメモを残す

今回の例でやってみた様子がこちらです。

たとえば、SUZURIは会員登録をしないとグッズを販売できない仕様になっているので、「買う人 = ゲスト, 売る人 = 買う人と同一(=ゲスト)」というのはあり得ません。なので CASE 5~8 は「発生する? = No」です。

効能

この手順でやると自分がどこまで理解できていてどこでミスった(いまいちな検討内容になった)のかがわかるので便利です。

「観点」のリストアップ(手順1)で指摘された場合は、向き合う対象のことを理解できていない可能性が高いので、詳しい人に話を聞くことが有効でしょう。

「選択肢」のリストアップ(手順2)で指摘された場合は、「観点」に対する有効な切り口を発見できていないかもしれません。たとえば今回の例では「買う人」という観点に対して「ゲストである」or「会員である」という選択肢を出しましたが、ほかにも "同じメールアドレスを使って何回買っているか” という切り口で「1回だけ買った」or「2回以上買った」という選択肢にすることもできるかもしれません。

(手順3は組み合わせをリストアップするだけの機械作業なので、この手順に指摘はないはず。)

各行に対しての深掘り(手順4)で指摘された場合は、実際どうなるのかに対する認識や想像力が足りていないと考えられます。実状に詳しい人に話を聞くと良いでしょう。

手順4までいくとCASE番号を指しながら会話ができるので、認識の齟齬が発生しにくくなります。これも便利ですね。

おわりに

取り組む対象が複雑になってくると頭のメモリだけでは限界がきます。そういうときに今回の手順は有効だと思います。

加えて、お金や法律に関わる取り組みの場合は小さなミスが大きな問題に発展する可能性があるので、全体像を把握してミスの発生を最小化するためにも有効だと感じています。

このブログを書くにあたってつくったGoogle スプレッドシートを(キャプチャの通り、大したことはしてませんが)一応貼っておきます。

なにかを漏れなく被りなく検討するときにやっていること | tanaken's blog - Google スプレッドシート

RubyKaigi のヘルパーで受付を担当するときに参考になるかもしれないメモ

RubyKaigi 2023 にヘルパーとして参加して、受付を担当しました。 その経験から、知っておくと良いと思ったこと、次回以降こうするとよさそう、などをメモしておきます。

知っておくと良いこと

チケットの種別

RubyKaigi 2023の受付は、参加者から提示される2次元バーコードをスマホで読み込むことでチケットの確認をしていました。

自分が把握している限り、読み込み時にアプリに表示されるチケットの種別は次のとおりです:

  • Attendee
    • 一般参加者のチケット
    • 購入時期によって "Super Early Bird" などの括弧書きがある
  • Sponsor
    • スポンサー企業に配布されるチケット
    • Attendeeと同様にセッションを聴講できる
  • Student
    • 学割チケット
    • 受付時に学生証の提示が必要
  • Guest
    • ゲストチケット
    • (自分は遭遇しなかったのでどういう人がゲストなのかはわからない)
  • Speaker
    • スピーカーのチケット
  • Committer
    • Rubyコミッターのチケット
  • Booth
    • スポンサーブースの運営担当者のチケット
    • セッションを聴講することはできない

受付では、それぞれのチケット種別に応じた対応をする必要があります。

チケットの種別とバッジ(ネームカード)の対応関係

自分が把握している限り、RubyKaigi 2023で用意されていたバッジ(ネームカード)の種類は次のとおりです:

  • Attendee
  • Guest
  • Speaker
  • Committer
  • Exhibitor
  • Staff

チケットの種別との対応関係を表にするとこうなります。

チケットの種別 バッジ
Attendee / Sponsor / Student Attendee
Guest Guest
Speaker Speaker
Committer Committer
Booth Exhibitor
(チケットの種別に依らず名前で判断) Staff

バッジと配布物の対応関係

RubyKaigi 2023で受付に用意されていた配布物は次のとおりです:

  • バッジ(ネームカード)
  • ネームストラップ
  • バウチャーチケット
  • スタンプラリーシート
  • Tシャツ(Speaker / Committer / Staff)
  • スポンサーブース関連の注意事項が記載された用紙

バッジと配布物の対応関係を整理すると次のようになります。

バッジ ネームストラップ バウチャーチケット スタンプラリーシート Tシャツ ブース注意事項
Attendee o o o Tシャツコーナーを案内 x
Guest o o o Tシャツコーナーを案内 x
Speaker o o o o x
Committer o o o o x
Exhibitor o o x x o
Staff o o o o x

ここまででわかるように、受付でのチェックイン業務は

  1. 2次元バーコードを読み込んでチケットの種別を知る
  2. チケットの種別に応じてバッジを判断する
  3. バッジに応じて配布物を判断して案内する

という感じです。

業務の時系列と傾向

次の3つの期間でそれぞれ傾向がありそうでした。

  • day0
  • day1
  • day2以降

day0

RubyKaigi 2023では、スポンサーブースの各企業の担当者が会期の前日(day0)に来てブースの設営をしました。

なのでこの日の業務は

  • チケットの受付
    • 関係者以外の立ち入りを避けるために実施
    • 主にBoothチケットだが、Booth以外(SponsorやAttendee)の方が設営の手伝いに来るケースもあった
    • day1以降でつかう参加者リストを複製したday0専用のリストをつくってチェックインを実施
  • ブース関連の問い合わせの対応
    • 配送したダンボールが届いてない、ゴミはどうすればいいか、備品(養生テープなど)の貸出はあるか、など

を行ないました。

day1

会期初日です。とにかくたくさんの方のチェックインを行ないます。 RubyKaigi 2023では、参加者1,200名のうち800〜900名が初日にチェックインしていただいたと記憶しています。 ご参加いただいたみなさま、スムーズな受付にご協力いただきありがとうございました!

day2以降

チェックイン業務は落ち着く一方で、忘れものの報告や探しものの依頼が増えてきます。

また、RubyKaigi 2023ではday2からスポンサーブースでのスタンプラリーが実施されました。すべてのスタンプを集めた方が景品のピンバッジを受け取りにくるので、その対応も行ないました。

スポンサーへの事前案内

スポンサーブースの設営および撤収を行なうday0と最終日は、スポンサーブース担当者からの問い合わせが増えます。

問い合わせの中の一部は、RubyKaigi運営チームからスポンサー企業への事前案内に書かれているものがありました。そういった場合に「事前案内のここ読んでください」で対応できると効率が良いと思います。

RubyKaigi 2023では、受付担当のヘルパーに@tamaclawさん(スポンサー企業としても参加されている)がいらっしゃったので、そのあたりの情報に詳しくとても助かりました。

スポンサーブース担当者からの問い合わせの対応方法

まずは企業名を聞く

問い合わせをする側はその内容だけをお話になることが多いので、問い合わせを受ける側はどうしても忘れがちですが、まずは企業名を聞くことが大切です。

どの企業からの問い合わせなのかを記録する意味でも重要ですし、企業のスポンサー種別(Ruby, Platinum, Goldなど)によって回答に必要となる条件が異なる場合もあります。

わかっていることはその場で案内し、わからないことはオーガナイザーにエスカレする

事前案内に書かれていることやすでに対応が決まっていることは、その場で案内します。 わからないことはトランシーバーでオーガナイザーにエスカレしましょう。その際に企業名も合わせて伝えましょう。

「荷物が届いてない」→まずは着荷を確認してもらう

届くべき荷物が会場に届いていない、という問い合わせがたくさん来ます(実際たくさん来ました)。

まずは荷物が会場に届いてるかどうかを確認してもらいましょう。配送時の伝票番号を使えばすぐに調べることができるはずです。(スポンサーブース担当者のみなさまにおかれましては、配送時の伝票番号は必ず控えておいていただきたいです)

Speaker / Committer の顔と名前

チェックインの際に、SpeakerとCommitterにTシャツを配布するので、顔と名前を事前に把握しておくと対応がスムーズになります。

とはいえ、GitHubSNSのアイコンは知っていても対面したことがない方だと顔の特徴で判断するのは難しいでしょう。ヘルパーのメンバーの中に詳しい方(たとえば、古くからコミュニティに参加している方など)がいれば、そういった方に対応をお願いしても良いと思います。

受付で使いそうな英語のフレーズ

海外の方も多くいらっしゃるので、英語での簡単な対応は用意しておくと良いです。

一応今回自分も用意していたんですが、英会話が苦手なのでもたもたしているうちに周囲のヘルパーの方がフォローして対応してくださったので助かりました。

会場内のマップと会場周辺のマップ

参加者から会場(ホールA,B,C、トイレ、喫煙所、スポンサー控室など)について質問を受けることがあります。会場内のマップは把握しておきましょう。

また、RubyKaigiの参加者ではない方が受付にいらっしゃる場合もあります。その際に会場周辺についても把握しておくとなお良いでしょう。

RubyKaigi 2023の会場となった「まつもと市民芸術館」の近くには「松本市美術館」というよく似た名前の施設があり、海外からの観光客と思しき方が間違えてこちらの会場(まつもと市民芸術館)に来てしまった、という事例がありました。

落としもの / 探しものの対応方法

落としもの: いつどこで拾ったのかを聞く

落としものを拾って受付に届けてくださる方がいらっしゃいます。 受け取る際に、拾った時間と場所を確認しましょう。探しにきた人に本人確認をする際の参考情報になります。

探しもの: 特徴を聞く、本人確認をする

落としものを探している人が受付にいらっしゃいます。 実物を見せる前に、落としものの特徴(大きさ、重さ、色、材質など)を聞きましょう。 高価なものや個人情報に関わるものは、可能な限り本人確認をしましょう。

RubyKaigi 2023ではスマートフォンの落としものが多数ありました。特徴を聞いた上で、生体認証(指紋認証や顔認証)でロックが解除できることを確認してからお渡しする、という手続きを取っていました。(実際に対応した参加者の方から「どんなスマホか確認されてロック解除の確認もあり、脆弱性のない返却フローですごく安心した!」というフィードバックをいただけて嬉しかったです)

次回以降こうするとよさそう

問い合わせカードみたいなものを用意する

今回は各種問い合わせを付箋にメモして管理していたんですが、地味に管理がしにくいなと思いました。必要事項を聞き忘れていてメモが不十分になったりもします。 ある程度定型化された問い合わせ内容であれば、専用のカードみたいなものを用意して、問い合わせ者自身に書いてもらう、という運用が良いかなと思いました。

たとえば、スポンサーからの問い合わせであれば、企業名、担当者名、問い合わせ内容をカードに書いてもらって提出してもらうようにしたら、企業名のヒアリング漏れがなくなりそうです。また、カードごとに対応状況を管理(ウラ面にメモするなど)できそうなので、対応漏れも防げるかもしれません。

スポンサーへの事前案内をヘルパーにも共有しておく

今回はたまたまヘルパーの中にスポンサーを兼ねている方がいらっしゃったので対応できましたが、次回以降もそうなるとは限らないので、事前に共有しておくと良さそうです。 (自分が見落としていただけ(実は今回も共有されていた)だったら申し訳ないです)


以上、メモでした。楽しく受付できましたし、いろいろと学びもありました。次回以降の参考になれば嬉しいです。

自分とpyamaさんの差分

この記事 Rubyのsetterの返り値 (2) - tanaken’s blog について、@pyama86さんが調べて記事を書いてくれました。

ten-snapon.com

この記事を読んで気づきというか感じたことがいくつかありました。

記事の内容は

  • 調べてもすぐには分からなくて悔しかった
  • ChatGPTを活用して調査範囲を絞り込んだ
  • gdbGDB: The GNU Project Debugger)をつかって該当の実装を発見した

という感じ。

まずすぐに思ったのは、こういうふうに「自分の知識の外にある情報を自分の土俵に引っ張ってくる力」でかなり差が出るな、ってことです。令和5年になって特に。

これまでも、たとえば検索エンジンで調べるキーワードの選び方とか確からしいドキュメントの選球眼とか書いてある文章を読み解く力とか、そういったもので自分の知識を拡張する力みたいなものは重要であったし、引き続きそれらは重要だとは思うんですが、令和5年の現在はLLMを利用した対話型のやりとりで知識を拡張することが手軽にできるようになっていて、それを活用するスキルの有無でめっちゃ差が出るじゃんってことを痛感したのでした。

もうみんな言ってることだし自分も頭ではわかってるつもりでしたが、実際に体験して「ああこういうことか〜」と響きました。

次点で、「分からないことを悔しがる力」の差です。性格の差もあるとは思うものの、エンジニアとしての素養というか、分からないことを放置したくないという気持ちの差を感じました。

一晩寝て起きても分からなくて悔しい、みたいな感覚、最近忘れちまってないかい?俺?

最後は純粋に知識と経験の差ですね。今回の例でいえばRubyの内部実装をデバッグするための手段としてgdbを使う、というルートにたどり着けなそう、という点です。

いや、ここまで書いて気づいたんですが、それこそChatGPTをつかえばそれ引き出せたんじゃね?という話ですよね。

やってみたらできました。(最初はByebugとかを提示されたけど)

そんなこんなで、もっとやっていかねばならんなと思ったのでした。

自分がわからん〜と放置しそうになっていたことについて、pyamaさんにスッと解明されてしまったのは(ありがたかったけど)それなりに悔しかったっす。やるぞ

Rubyのsetterの返り値 (2)

前回書いた記事 Rubyのsetterの返り値 - tanaken’s blog にコメントをいただきました。(ありがとうございます!!)

https://ruby-doc.org/3.2.1/syntax/methods_rdoc.html#label-Return+Values にどういう挙動をするのかが記載されているとのことで、読んでみましょう。

Note that for assignment methods the return value will be ignored when using the assignment syntax. Instead, the argument will be returned:

"assignment syntax"*1 をつかった "assignment methods"*2 では返り値が無視されるよ、代わりに引数を返すよ。ということですね。

サンプルコードもわかりやすいです。

def a=(value)
  return 1 + value
end

p(self.a = 5) # prints 5

また、sendメソッドを使って該当のメソッドを直接呼び出す場合は返り値が無視されないそうです。

p send(:a=, 5) # prints 6

へぇ〜。

前回の記事で使ったコードでも確認しておくとこんな感じです。

class Bar
  def x=(val)
    @x = val
    'うおぉぉぉおぉぉぉぅぉ'
  end
end
irb(main):001:1* class Bar
irb(main):002:2*   def x=(val)
irb(main):003:2*     @x = val
irb(main):004:2*     'うおぉぉぉおぉぉぉぅぉ'
irb(main):005:1*   end
irb(main):006:0> end
=> :x=
irb(main):007:0> bar = Bar.new
=> #<Bar:0x00000001030f2130>
irb(main):008:0> bar.x = 10
=> 10
irb(main):009:0> bar.send(:x=, 10)
=> "うおぉぉぉおぉぉぉぅぉ"

なるほどねぇ〜。

どういう実装になってるの?

わからんです。

https://github.com/ruby/ruby を "assignment syntax" や "assignment methods" で検索してますが、まだ実装にはたどり着いていません。

parser gemをつかって雑に構文解析をしてみましたが、不慣れでさっぱりです。

text = <<~EOS
def a=(value)
  return 1 + value
end
self.a = 5
EOS
# => "def a=(value)\n  return 1 + value\nend\n\nself.a = 5\n"

Parser::CurrentRuby.parse(text)
# => 
# s(:begin,
#   s(:def, :a=,
#     s(:args,
#       s(:arg, :value)),
#     s(:return,
#       s(:send,                                   
#         s(:int, 1), :+,                          
#         s(:lvar, :value)))),                     
#   s(:send,                                       
#     s(:self), :a=,                               
#     s(:int, 5)))         

ruby の内部実装についての知識をつけないと、どこを探せばいいか見当がつかなそうです。

ここ読むと良いよ〜などあればアドバイスお待ちしてます!

*1:"=" のことかな?

*2:引数を変数に割り当てることを目的としたメソッドのこと?

Rubyのsetterの返り値

Rubyのsetterとは

オブジェクトのインスタンス変数に値を代入するためのメソッド。インスタンス変数名から「@」を削除し、末尾に「=」を付けたメソッド名とする習慣がある。

Ruby用語集 (Ruby 3.2 リファレンスマニュアル) より引用

例えばこういうクラスがあった場合、

class Foo
  def x=(val)
    @x = val
  end
end

こういうことができますよね。

irb(main):001:1* class Foo
irb(main):002:2*   def x=(val)
irb(main):003:2*     @x = val
irb(main):004:1*   end
irb(main):005:0> end
=> :x=
irb(main):006:0> foo = Foo.new
=> #<Foo:0x0000000102f817d8>
irb(main):007:0> foo.x = 10
=> 10

ここでの x= メソッドの話です。

返り値

さっきの例だと

irb(main):007:0> foo.x = 10
=> 10

返り値は 10 です。実装を見てもそりゃそうだよなという感じです。

さて、ではこんなクラスだとどうでしょう?

class Bar
  def x=(val)
    @x = val
    'うおぉぉぉおぉぉぉぅぉ'
  end
end

この場合、x= メソッドの返り値は 'うおぉぉぉおぉぉぉぅぉ' になりそうに見えませんか?僕はそう思ってました。

試してみると

irb(main):001:1* class Bar
irb(main):002:2*   def x=(val)
irb(main):003:2*     @x = val
irb(main):004:2*     'うおぉぉぉおぉぉぉぅぉ'
irb(main):005:1*   end
irb(main):006:0> end
=> :x=
irb(main):007:0> bar = Bar.new
=> #<Bar:0x0000000104ae5998>
irb(main):008:0> bar.x = 10
=> 10

この場合も返り値は 10 なんです。へぇ〜〜。

ちなみに、書き方の問題じゃないよね?と思って一応これも試しましたが

irb(main):009:0> bar.x=(10)
=> 10

返り値は変わらず 10 でした。そうなんだ〜

= で終わらないメソッドだったら?(念のため)

class Baz
  def set_x(val)
    @x = val
    'うおぉぉぉおぉぉぉぅぉ'
  end
end
irb(main):001:1* class Baz
irb(main):002:2*   def set_x(val)
irb(main):003:2*     @x = val
irb(main):004:2*     'うおぉぉぉおぉぉぉぅぉ'
irb(main):005:1*   end
irb(main):006:0> end
=> :set_x
irb(main):007:0> baz = Baz.new
=> #<Baz:0x00000001048a6da8>
irb(main):008:0> baz.set_x(10)
=> "うおぉぉぉおぉぉぉぅぉ"

ですよね、"うおぉぉぉおぉぉぉぅぉ" ですよね。

ってことは = で終わるメソッドとそうでないメソッドでは返り値が異なる ってことですね。

なぜ?

まだ調べてないです(ごめんなさい)

というか、調査の方針が立ってないんですよねぇ。

Rubyが式をどうやって評価してるか?みたいな話題かなって思ってはいます。

などあればアドバイスお待ちしてます。

Rails における BASIC 認証

Rails における BASIC 認証について調べたのでその記録です。

シンプルなケース

https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic.html にあるように http_basic_authenticate_with を使うのが最もシンプルな実装でしょう。

 class PostsController < ApplicationController
   http_basic_authenticate_with name: "dhh", password: "secret", except: :index

   def index
     render plain: "Everyone can see me!"
   end

   def edit
     render plain: "I'm only accessible if you know the password"
   end
end

ただし、これだと一組の name, password しか設定できません。

発展的なケース

業務上の理由で、複数の name, password の組に対してBASIC認証を設定したいシーンがありました。

改めて https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic.html を読んでみると、"Advanced Basic example" として次のコードが紹介されていました。

class ApplicationController < ActionController::Base
  before_action :set_account, :authenticate

  private
    def set_account
      @account = Account.find_by(url_name: request.subdomains.first)
    end

    def authenticate
      case request.format
      when Mime[:xml], Mime[:atom]
        if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
          @current_user = user
        else
          request_http_basic_authentication
        end
      else
        if session_authenticated?
          @current_user = @account.users.find(session[:authenticated][:user_id])
        else
          redirect_to(login_url) and return false
        end
      end
    end
end

リクエストの format が xmlatom のときに BASIC 認証を要求しているようですね。

authenticate_with_http_basicrequest_http_basic_authentication が初見でした。

authenticate_with_http_basic について

https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#method-i-authenticate_with_http_basic を読んでみると

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 96
def authenticate_with_http_basic(&login_procedure)
  HttpAuthentication::Basic.authenticate(request, &login_procedure)
end

とありました。なるほど HttpAuthentication::Basic.authenticate ってなんですか。

https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic.html#method-i-authenticate ですね。

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 105
def authenticate(request, &login_procedure)
  if has_basic_credentials?(request)
    login_procedure.call(*user_name_and_password(request))
  end
end

「BASIC 認証の認証情報があるなら login_procedurecall するぜ (ブロックとして渡された認証手続きを実行するぜ)」と言ってますね。

「そのブロックに渡す引数は *user_name_and_password な!」とも言ってます。

user_name_and_password はその名の通り BASIC 認証で入力された user_name と password ですね。slat演算子 * がついてることからも配列だと分かります。コードはこちらの通り https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic.html#method-i-user_name_and_password

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 115
def user_name_and_password(request)
  decode_credentials(request).split(":", 2)
end

ここまでの情報をもとに、サンプルコードのこれ↓がなにをやっているのかを整理すると

if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
  @current_user = user
else
  # ...
end
  • authenticate_with_http_basic { |u, p| ... }
    • リクエストにBASIC認証情報があるならば、その user_name と password (それぞれ変数 u, p )をつかってブロック内のなんらかの処理をする
    • リクエストにBASIC認証情報がないならば nil を返す
  • if user = ...
    • 上記の処理が真ならば(user に何らかの真な値が入るならば)
  • @current_user = user
    • current_user に user を入れる

という感じですね。

request_http_basic_authentication について

https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#method-i-request_http_basic_authentication を読んでみると

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 100
def request_http_basic_authentication(realm = "Application", message = nil)
  HttpAuthentication::Basic.authentication_request(self, realm, message)
end

とのこと。はーい、 HttpAuthentication::Basic.authentication_request ってなんですか

https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic.html#method-i-authentication_request

def authentication_request(controller, realm, message)
  message ||= "HTTP Basic: Access denied.\n"
  controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"', "")}")
  controller.status = 401
  controller.response_body = message
end

401 を返して BASIC 認証を要求しているってことですね。

BASIC 認証自体については HTTP 認証 - HTTP | MDN を参照のこと。

さて、改めてサンプルコードに戻ると

if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) }
  @current_user = user
else
  request_http_basic_authentication
end

authenticate_with_http_basic が偽だったら BASIC 認証を要求する、ということですね。

これらを使って複数の user_name, password の BASIC 認証を実装してみる

class ApplicationController < ActionController::Base
  before_action :authenticate

  private
    def authenticate
      credentials = [%w[name1 password1], %w[name2 password2]]

      unless authenticate_with_http_basic { |u, p| credentials.include?([u, p]) }
        request_http_basic_authentication
      end
    end
end

こんな感じですかねぇ

さらに便利メソッドがある

この記事を書きながら調べてたら authenticate_or_request_with_http_basic とかいうメソッドを見つけました。 名前からしてそうって感じなんですが https://api.rubyonrails.org/v7.0.4.2/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#method-i-authenticate_or_request_with_http_basic を読むと

# File actionpack/lib/action_controller/metal/http_authentication.rb, line 92
def authenticate_or_request_with_http_basic(realm = nil, message = nil, &login_procedure)
  authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm || "Application", message)
end

ということで、さっきまで初見だった2つのメソッドを組み合わせたメソッドなんですね。

なので、↑で実装したコードを書き換えると

class ApplicationController < ActionController::Base
  before_action :authenticate

  private
    def authenticate
      credentials = [%w[name1 password1], %w[name2 password2]]

      authenticate_or_request_with_http_basic { |u, p| credentials.include?([u, p]) }
    end
end

と書けそうですね。動作確認してないので動かなかったら教えて下さい。


24時を過ぎて数分経ってしまいましたが、これは1月の記事です。さっきお茶をこぼさなければ絶対に24時前に書き終えられたはずなんです、だれですかあんなところにお茶を置いたのは!

おやすみなさい 🍵


追記: 投稿日過去にできました。 https://i-am-an-easy-going.hatenablog.com/entry/2020/02/16/225916