【Ruby製のDiscord BOTを作りました】Claude Codeで作ってもらってから学ぶのは楽しい

先日、Claude Codeを使ってDiscord BOTを作りました。

今回は「まず動くものを作ってしまって、コードの書き方はあとから学ぶ」という流れを試してみたのですが、これがとても良かったので、作ったものの紹介と合わせて書いてみます📝

🤖 作ったもの「趣味スレッド発掘BOT」

僕が卒業してからもずっとお世話になっている フィヨルドブートキャンプ (以下、FBC)のDiscordには、趣味などの話をするための趣味チャンネルがあります。かつては趣味の内容ごとにチャンネルが分かれていたのですが、今は1つのフォーラム用チャンネルの中で、多くのスレッドに分かれる形で運用されています。

ただ、スレッド内への投稿は新着情報として気付きにくいという課題がありました。自分がしばらく投稿していないスレッドは非アクティブ化されてしまい、新着投稿がわかりやすい形で表示されません。せっかく誰かが趣味の話を投稿しても、そのスレッドを追っている人にしか届きにくい状態でした。

趣味チャンネル内には沢山スレッドがあるのですが、サイドバーに出るのは一定期間内に自分が投稿したスレッドのみ

「なんか良い方法ないかな〜、うまいこと趣味スレッドを紹介するBOTとかを誰か作ってくれないかな〜」って呑気に考えていたんですが、ふと「いや今なら生成AIを活用してパッと作れるのでは?」と思いました。
そこで、趣味フォーラムのスレッドの中から直近に投稿があったものを毎朝1つランダムでピックアップして、別の「趣味スレッド発掘チャンネル」に紹介するというBOTを作ってみました。

毎朝、趣味チャンネル内の投稿をランダムでピックアップ

リポジトリはこちらです。

github.com

BOTが動く流れは割とシンプルで、以下のようになっています。

  • Ruby製のスクリプトが、Discord APIでフォーラムのスレッド一覧を取得
  • 「直近24時間に投稿があったスレッド」→「7日以内」→「投稿ゼロのスレッド+しばらく紹介していないスレッド」という3段階フォールバックで紹介するスレッドを選択
  • 選んだスレッドの最新投稿スニペット付きで、Webhook経由で発掘チャンネルに投稿
  • GitHub Actionsのcronで毎朝8時に実行。紹介履歴のJSONもActionsが自動でコミットするので、サーバー不要・無料です💰

💡 まず動くものを作り、あとから学ぶ

さて、ここまでは前置きで、ここから本題です。

生成AI時代以前、アプリを作るには、まずコードや仕組みを理解して、試行錯誤しながら少しずつ作っていく必要がありました。作りたいものがあっても「これを実現するにはあれとこれを学んでから…」となって、動くものにたどり着く前にとても時間がかかってしまうのが一般的だったと思います。環境構築やデプロイのハードルもありますし🍣

今回はその順番をひっくり返して、こういう流れで進めてみました。

  1. Claude Codeに要望を伝えて、まず動くMVPを作ってもらう
  2. 実際にアプリを動かしてみて、使いながら直したいところを見つける
  3. 同時に、生成されたRubyコードを読んで「どう書かれているか」を学ぶ

最初のMVPは1時間もかからずに動くところまでいきました。「動くものが目の前にある」というのはとても嬉しいもので、そこからは「スレッド取得の流れはどうなっているんだろう」「選択ロジックはどうやって優先順位をつけているんだろう」と、コードを読んで仕組みを確かめていきました。そして仕組みがわかると、今度は「ランダムの選ばれ方のバランスをもう少し調整したい」と、自分で手を入れられるようになっていきました。

⛏️ BOTの構造と、面白かったポイント

このBOTのコードは、こんな構成になっています。

dig.rb                    # メインエントリーポイント
lib/
  discord_client.rb       # Discord API通信
  history_manager.rb      # 履歴管理
  thread_selector.rb      # スレッド選択ロジック
  message_formatter.rb    # メッセージ装飾

やっていて面白かったのが、初め生成AIがガガっと動くコードを作ってくれた時は1つのファイルに全ての処理が書かれていたのですが、処理ごとにファイルを分割してdig.rbだけ見ればメインの流れをおおよそ追えるようにしたことです。オブジェクト指向を活用して責務を分けた書き方にできて、とてもスッキリしました。

あと今回初めてDiscord BOTを作ってみて面白かった処理は、DiscordのSnowflake IDからタイムスタンプを計算するところです。

# Discord Snowflake: IDの上位ビットにタイムスタンプが埋め込まれている
snowflake = thread["last_message_id"].to_i
Time.at((snowflake >> 22) / 1000.0 + 1420070400)

DiscordのID(メッセージやスレッドに振られている番号)には、実は「作られた時刻」も含まれているようです。だから、わざわざ投稿日をAPIに別途聞きにいかなくても、IDを計算するだけで時刻を割り出すことができます。

やっていることをざっくり言うと、こんな感じです。こういう、知らなかった書き方・仕組みに出会えるのも、生成されたコードを読む学び方の良いところだなと感じました。

  • >> 22 … IDの中から「時刻の部分」だけを取り出す
  • / 1000.0 … ミリ秒を秒に直す
  • + 1420070400 … Discordの基準日(2015年)を、いつもの1970年基準の時刻にそろえる

おまけ

ついでに、「今回作ったリポジトリの中で出てくるRubyの技術を、学習者向けにまとめたドキュメントを作って」とお願いしてまとめてもらいました。こちらは以下ページで公開したのでよろしかったら見てみてください📝
(ひと通り確認して内容に問題は無さそうと踏んでいますが、もしおかしなところがあったらご指摘いただけると幸いです🙏)

hackmd.io

🎊 おわりに

生成AIのおかげで、「まず動くものを作り、あとからその構造やコードの流れを学ぶ」という順番が選べるようになりました。作りたいものがあるなら、エイヤでまず作ってしまえるようになったのはとても良い時代だなと思います。

BOTは、毎朝FBCのDiscordで趣味スレッドを発掘してくれています。また、このBOTはフォークして環境変数を設定するだけで別のDiscordサーバーにも導入できるようにしてあるので、興味のある方はぜひ使ってみてください🚀

関西Ruby会議09に行ってきました

関西Ruby会議09 に行ってきました! 気づいたら2週間経ってしまっていましたが汗、ざっくり参加レポートを書きました📝

会場

会場は大津市の伝統芸能会館というところでした。
普段は能などをやる舞台とのことで、独特な雰囲気でものすごく良かったです!

セッションの感想

いくつか気になったセッションの紹介と感想を書きます。

「照らす技術」をRubyで照らす

ライブステージなどで使われるムービングライトを、発表者さん自作のPicoRubyで動く自作マイコンで制御する話でした。 実際に照明をステージに設置してデモでムービングライトを動かしていたのがめちゃくちゃ面白かったです。

PicoRubyやLチカ(LEDをチカチカさせる)という言葉はよく聞くもののよく分かってなかったのですが、シンプルなコードでライトを動かしたり点滅させたりできるのは面白いなと思いました。

Rails で Remote MCP サーバーを作った話

まず全部自前でコードを書いて、その後にgemで肩代わりできる部分を差し替えていくという構成の発表でした。

ロジックの肝である部分は(当たり前っちゃ当たり前かもですが)gemでは置き換えられていないので、「gemを活用することで、毎回出てきそうなメインロジック以外の部分を楽に準備できる」ことを立証するような内容になっていたというのが面白かったです。

自分だったら便利なgemがあればまず使いたくなっちゃうので、先に実装しきった上でgemに置き換えることができるのがまず凄いなと思いました…!スライド40ページ目の「gemが消すのは行数より抜け漏れ」「0から書いたからどこが危ないかがわかる」という言葉の重みを感じました。

Can you see? I'm GC

ガベージコレクションの話でした。
ガベージコレクションについてはだいぶ前に何かの勉強中に一瞬聞いたことがあって、メモリを解放する何らかのものっていうくらいの認識でした。今回も、途中で紹介されていた「どこからも辿れなくなったオブジェクトは自動でメモリから解放される」という初歩的な概念はわかったのですが、その先は「なるほどわからん」という状態でした…😅

一点わかったのは、C言語では要らなくなったメモリ領域を手動で指定して解放しなければならないところをRubyは自動でやってくれている、その恩恵にめちゃくちゃ預かっているんだなということです。 初めてのプログラミング言語がRubyである自分にとっては、メモリ解放が当たり前のことではないと改めて感じることができて良かったです。

Rubyで未来を照らす 〜占いを実装する技術〜

特定の入力内容から出力結果が一意に決まるなら関数化できるのではということで占いのgemを作った話でした。占いをgemにするなんてすごいなーと思って実際に試してみました。

gemはこちら: https://github.com/ikotome/meiri

irb(main):001> require 'meiri'
irb(main):002> chart = Meiri::ShityuSuimei::Chart.new(year: 1982, month: 10, day: 22, gender: :male)
irb(main):003> chart.year_pillar
=> [8, 10]
irb(main):004> chart.year_tsuhensei
=> :偏財
irb(main):005> chart.day_pillar 
=> [4, 2]

なるほど?この結果がなんなのか全くわからない😇 ここからはClaudeに解説してもらいました。ちょい長いので折りたたんでおきます。

Claudeによる解釈(クリックで開きます)

最初にはっきりさせておくと、これは「四柱推命の教科書的にはこの記号にこういう意味が振られてる」を読み上げてるだけ。しかも流派で解釈が変わる世界だから、当たる当たらないというより「へー」枠で聞いてね。

まず一番大事なのが「日柱の天干=日干(にっかん)」。これが「自分そのもの」を表す軸。day_pillar[4, 2] を変換すると:

s, b = chart.day_pillar
Meiri::Mannenreki::Constants::TENKAN[s]   # => :戊
Meiri::Mannenreki::Constants::TISHI[b]    # => :寅

日干は戊(つちのえ/陽の土)のはず。四柱推命だと戊は「大地・山」のイメージで語られることが多くて、どっしり構えて動じない、包容力がある、面倒を見るのが上手、その反面ちょっと頑固で腰が重い……みたいなキャラ付けをされる。土って派手さより安定の象徴。

次に年柱=壬戌(みずのえ・いぬ)。年柱はよく「その人のルーツ・若い頃・家系まわり」を映すとされる場所。壬は「大きな水・海や大河」の水。山(戊)の人にとって海の要素が土台にある、みたいな配置。

そして年柱についてる通変星が偏財(へんざい)。偏財は占いの世界だと「流動するお金・人付き合いの広さ・気前のよさ・チャンスを見つける嗅覚」あたりのラベル。まじめにコツコツ貯める「正財」に対して、偏財は動かして増やす/人に配るタイプの財、と対比される。それが年柱(ルーツ寄り)に出てる、という読み。

ざっくり物語にすると「山のようにどっしりした本質を持ちつつ、土台には広く流れる水(人・お金・縁)があって、それを動かして回すセンスがある人」みたいなまとめになる。……のだけど、本来はこれを四柱ぜんぶ+五行のバランス+十二運まで見て総合判断するもので、年柱と日干だけ抜き出して語るのは占い的にはかなり乱暴なので、今回はあくまでパーツ紹介です。

結局、占いそのものはかなり主観的な意味づけや解釈が入ってくるのでそこはあえて切り分けてあり、このgemは「占いのための材料を整理するもの」という位置付けのようでした。

その他

「1分間フィードバック」がとても良かった

今回、それぞれの発表が終わった直後に「1分間フィードバック」をする時間が設けられていたのもユニークで良かったです。

https://confvoice.onrender.com/%E9%96%A2%E8%A5%BFruby%E4%BC%9A%E8%AD%B009

この1分間フィードバック用のアプリ自体が実行委員長のydahさんによる自作アプリとのことで、こういう便利ツールを自作されているのも今っぽくてとても面白いなと思いました。

途中でアイスが配られた

午後の休憩時間に、会場でアイスをご提供いただきました。くず餅アイス?というやつで、めちゃくちゃ美味しかったです、ありがとうございました🍨

うっかり上の方を齧ってから写真を撮っています📸

メガネを買おうと決意した

完全に余談ですが、スライドの文字で小さい部分がかなり読みづらく、自分の視力低下を痛感しました(iPhoneで撮影してそれを読んだりしていました😇)。
日常生活で困ることがなかったので油断していたのですが、いよいよメガネを買おうと決意しました👓

おわりに

初めて地方のカンファレンスに参加しましたが、色々な刺激をいただけてとても良い経験になりました。自分はお仕事以外に個人開発でもRubyやRailsに触れていますが、どちらかというと作りたいものを実現するためのツールとして活用させてもらっている感覚が強く、「Rubyで遊ぶ」みたいな観点をあまり持っていなかったので、趣味や好きなことの中からRubyで遊べるものを見つけたいなと思いました。

改めて、素敵な地域Ruby会議を開催してくださったスタッフの皆さん、どうもありがとうございました!!

おまけ:成瀬シリーズ聖地巡礼

大津といえば膳所(ぜぜ)。膳所といえば「成瀬は天下を取りに行く」をはじめとする小説の成瀬シリーズの聖地。
ということで、今回は成瀬シリーズの聖地にもいくつか足を運ぶことができました。成瀬ファンとしてはめちゃくちゃ嬉しかったです😄 できれば近江神宮にも行きたかったけど、暑さで断念しました🌞

JR膳所駅構内通路にドドンと掲示されている特大パネル

ときめき坂の途中にある看板

偶然入ったカフェでカードを配っていた

ミシガンクルーズも乗りました!

作中で、成瀬が巻き込まれないように距離をとっていたミシガンクルーズのパドル(外輪)

クルーズ船内にスタンプラリーのポイントがありました

これは成瀬とは関係ないけど、西川貴教さんのお土産がすごかった

おまけ2

終わってからしばらくこの曲がずっと脳内で流れていました…笑

youtu.be

GitHub Webhook → Discord 通知を飛ばす時に忘れがちなこと(末尾に/githubを付ける)

個人でアプリ開発をしている際、GitHubのIssue・PR作成やコメントなどを自分のDiscordサーバに通知するようにしておくととても便利です。

その手順と、GitHub側で何度かミスった部分をメモしておきます✍️

結論

GitHub WebhookでDiscordに通知する場合は、Webhook URLの末尾に /github を付ける必要があるので注意!

手順

Discord側

  1. 通知を飛ばしたいチャンネルの設定を開く
  2. 「連携サービス」から「ウェブフック」へ
  3. ウェブフックURLをコピー

お名前はご自由に💁

GitHub側

  1. 該当リポジトリのSettingへ 2.サイドバーの「Webhooks」から「Add webhook」へ
  2. 以下の必要項目を入力・設定して下部の「Add webhook」ボタンクリック
    • Payload URL
      • 上でコピーしたウェブフックURLをペーストし、末尾に「/github」を追加
    • Content type
      • 「application/json」に変更する
    • Which events would you like to trigger this webhook?
      • どこまでの範囲で通知した以下の設定。今回は全部通知してほしかったので「Send me everything.」にしました
    • 上記以外はそのままでOK

注意事項

始め、Discord側に通知が来ず、色々調べていたらGitHubのWebhook URLに /github を付けていなかったことが原因でした。

# ⚠️Discord側からコピーしたこのままだとNG
https://discord.com/api/webhooks/{id}/{token}

# ✅末尾に/githubをつければOK
https://discord.com/api/webhooks/{id}/{token}/github

これでちゃんとDiscordに通知されるようになりました🎉

終わりに

これまでも何度か同じことをやっていて、今回も「うーん通知が飛ばないなー」としばらく設定を彷徨っていました😅
将来の自分はまた確実に忘れるので、メモでした📝

Ruby Silverに合格したのでメモなど

先日、「Ruby Silver」こと「Ruby Association Certified Ruby Programmer Silver version 3」を受検し、めでたく合格することができました🙌

www.ruby.or.jp

記録のためにブログを書いていきます📝

受検のきっかけ

Ruby Silverは以前から取りたいと思っていたのですが、なかなか受検することができていませんでした。

昨年11月にフィヨルドブートキャンプを卒業し、年始に「今年こそRuby Silver取らなきゃな〜」と呑気に思っていた矢先、Rubyの基礎力不足を感じる出来事がありました。

やりたいことは多々ありましたがまずこれはRuby Silverを取る機運だろうと思い、勢いで2月7日(土)の受検予約をしたのが1月21日(水)のことでした。

やったこと

予約をしてからは以下の試験対策を中心に行いつつ、気になる部分は公式ドキュメントを確認したり生成AIに類似問題を出してもらうなどして復習していきました。

どれも基本的には「50問やる→採点する→間違えたところを中心にチェック→同じ50問をやり直す」という流れで、1セットで2周解いていました。

あと、特に間違えた問題は必ずirbで実際にコードを実行するようにしました。「ここをこう書き換えたらどうなるかな?」みたいな試行錯誤は楽しかったし、学びも大きかったなと思います。

  • 書籍『最短突破 Ruby技術者認定試験(Silver/Gold対応) 公式テキスト
    • テキスト部分はだいぶ前にひと通り読了済みで、適宜見直していました
    • 基礎力確認問題(30問)と模擬試験(50問)を4セットほどやりました
    • 連続でやり過ぎると回答自体を覚えてしまうので、最後は1週間ほど空けて、前日に最後の1回を解くように逆算して実施しました
  • GitHubに上がっている公式の模擬試験『Ruby技術者認定試験Silver模擬問題
    • 2セットほどやりました
  • Ruby技術者認定試験の対策サイト『REx
    • 7セットほどやりました
    • 一番回数が多いのは、一番手軽にできたからです

受検当日

試験会場は池袋のテストセンターでした。家族も一緒に出かけて、サンシャイン水族館に行ってもらっている間に受検してきました。(僕もサンシャイン水族館行きたかった😇)

試験時間は90分。時間は充分に余裕がありましたが、自信のない問題が10問ほどありました。何度も見直しをして、「よほどの勘違いがない限り自信のないやつが全部不正解でも80点は取れてるはず」という状態で試験を終了、結果は84点でした。
できれば90点以上取りたかったところですが、引き続き精進していきたいと思います。

「合格」の部分、ちょい要素が下にズレているのを修正したい

受検を終えて

試験勉強として繰り返し問題を解いたことで、Rubyの基本的な内容について復習できて良かったです。

時は大AI時代、資格試験の勉強がどこまで役に立つのか?という考え方もあるかもしれませんが、基本的なところから毎回AIに聞いていてはキリが無いですし、AIを使いこなすためにも自分自身の知識をちゃんと定着させていきたいと思っています。なので個人的には受検して良かったですし、勉強の楽しさや達成感を改めて感じることができたなーとも思います。

おわりに

無事合格できて、ホッと胸を撫で下ろしました。
次は基本情報技術者試験を取ろうと思います🚀

www.ipa.go.jp

おまけ1:公式模擬試験にPRを出した

GitHubの模試をやっている途中に問題文の並びに違和感がある箇所があったので、修正のPRを出しました。マージされるといいな✨

github.com

おまけ2(長文):Ruby Silver対策メモ

今回、勉強中に何度も間違えてしまったところやうっかりミスしそうになったことを適宜メモしていました。
せっかくなのでおまけとして載せておきます!

(もしおかしいところがありましたら、そっとご指摘ください🙏)

その他

「その他」から紹介するのも変ですが、カテゴライズしにくかったやつ

  • Stringクラスにto_hメソッドは無い(Arrayクラスにはある)
"hoge".to_h # => エラー
[[:price, 100], [:amount, 5]].to_h # => {price: 100, amount: 5}
  • Stringクラスにbinaryメソッドは無い
"7".binary # => エラー
  • 式展開の省略記法(以下のようにそれぞれ{}を省略可能)
    • "#{$hoge}""#$hoge"
    • "#{@hoge}""#@hoge"
  • メソッド内では定数を定義できない
def hoge
  x = 10
  Y = x < 10 ? "C" : "D"
  puts Y
end
hoge
# => SyntaxErrorが出る

strip, chomp, chop

chomp はAtCoderでよく使っていて無意識に覚えていたので助かりました。

  • strip:文字列の先頭と末尾の空白文字(\t\r\n\f\v)を取り除く
  • chomp:末尾から改行コード("\r\n", "\r", "\n")を取り除く
  • chop:末尾の文字を取り除く。ただし、文字列の末尾が"\r\n"であれば2文字とも取り除く
  • 上記それぞれ、strip! chomp! chop! の破壊的メソッドもある
s = "hello\n\n"

s.chomp  # => "hello\n"
s.chomp  # => "hello\n" # 非破壊的メソッドのため1行上の結果は無かったことになる
s.length # => 7

s.chomp! # => "hello\n"
s.chomp! # => "hello" # 破壊的メソッドのため1行上の結果が保持される
s.length # => 5

pop, push, unshift, shift

⚠️! が無いけど、全部破壊的メソッド

  • popは、末尾の1要素を破壊的に取り出す。引数nがある場合は末尾からn個の要素を取り出して配列で返す
  • pushは、末尾に引数の値を破壊的に追加する
  • shiftは、先頭の1要素を破壊的に取り出す。引数nがある場合は先頭からn個の要素を取り出して配列で返す
  • unshiftは、先頭に引数の値を破壊的に追加する
a = [1, 2, 3, 4, 5] # => [1, 2, 3, 4, 5]
a.pop # => 5
a # => [1, 2, 3, 4]
a.pop(2) # => [3, 4]
a # => [1, 2]
a.push(9) # => [1, 2, 9]
a # => [1, 2, 9]

b = [1, 2, 3, 4, 5] # => [1, 2, 3, 4, 5]
b.shift # => 1
b # => [2, 3, 4, 5]
b.shift(2) # => [2, 3]
b # => [4, 5]
b.unshift(9) # => [9, 4, 5]
b # => [9, 4, 5]

concat, append

  • self.concat(other)self(文字列)にotherを繋げる
  • self.append(other)self(配列)の末尾にotherを追加する
a = "hello"
a.concat(" world") # => "hello world"
a # => "hello world"

b = "hello"
b.concat(" world", "!") # => "hello world!" # 引数は複数渡せる
b # => "hello world!"

c = [1, 2]
c.append(3) # => [1, 2, 3]
c # => [1, 2, 3]

d = [1, 2]
d.append(3, 4) # => [1, 2, 3, 4] # 引数は複数渡せる
d # => [1, 2, 3, 4]

find, detect, select, filter, find_all

  • find, detect:ブロックの値が真になる最初の要素を返す
  • select, filter, find_all:ブロックの値が真になる全ての要素を返す
a = [1, 2, 3, 4, 5, 6]

# 最初の1つだけ返す
a.find { |x| x > 3 }       # => 4
a.detect { |x| x > 3 }     # => 4(findのエイリアス)

# 条件に合う全部を返す
a.select { |x| x > 3 }     # => [4, 5, 6]
a.filter { |x| x > 3 }     # => [4, 5, 6](selectのエイリアス)
a.find_all { |x| x > 3 }   # => [4, 5, 6](selectのエイリアス)

ファイルオープンのモード(r, r+,w, w+, a, a+

これ、覚えるのに苦労しました😇

  • r(readの略):読み込み専用(ファイルが無い場合はエラー)
  • r+:読み書き両方(ファイルが無い場合はエラー)
  • w(writeの略):書き込み専用(新規作成 or 開いた時に中身を消して先頭から書き込む)
  • w+:読み書き両方(新規作成 or 開いた時に中身を消して先頭から書き込む)
  • a(appendの略):追記専用(中身を残して末尾に追記される or 新規作成)
  • a+:読み書き両方(中身を残して末尾に追記される or 新規作成)
  • +は全て「読み書き両方」になる
    • r+は、読む+書く
    • w+は、書く+読む
    • a+は、書く(追記)+読む
    • 特にr+w+は同じ意味に見えるが、 r+は「読む」が起点なので、ファイルがない場合にエラー
  • オープンモードを省略したら、デフォルトのrになる
  • rewindは、「先頭に戻る」(ただし a, a+ では読み込み位置は戻るが、書き込みは常に末尾)
# `test.txt` の中身は `"ABC"`とする
File.open("test.txt", "a+") do |f|
  f.rewind # 読み込み位置が先頭へ
  f.write("Z") # a+モードは、書き込みは常に末尾
  f.rewind # 読み込み位置が先頭へ
  puts f.read
end
# => "ABCZ"

product, zip, push, transpose, combination, permutation

combinationpermutation は問題ではほぼ見かけなかったけど、セットで覚えました。

  • product:全組み合わせ(直積)を作る
  • zip:同じインデックス同士をペアにする(要素数はレシーバに合わせる)
  • push:配列の末尾に引数をそのまま1要素として追加する(展開されない)
  • transpose:配列の行と列を入れ替える(要素数が揃っていないとIndexError)
  • combination:順序を区別しない組み合わせ
  • permutation:順序を区別する順列
# productとzipの基本
[1, 2].product([3, 4]) # [[1, 3], [1, 4], [2, 3], [2, 4]]
[1, 2].zip([3, 4]) # [[1, 3], [2, 4]]

# レシーバと引数の要素数が異なる場合
[1, 2].product([3, 4, 5]) # => [[1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5]]
[1, 2, 3].product([4, 5]) # => [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
[1, 2, 3].zip([4, 5]) # => [[1, 4], [2, 5], [3, nil]]
[1, 2].zip([3, 4, 5]) # => [[1, 3], [2, 4]]

[[1, 2]].push([3, 4]) # => [[1, 2], [3, 4]]
[[1, 2]].push([3, 4, 5]) # => [[1, 2], [3, 4, 5]]
[[1, 2, 3]].push([4, 5]) # => [[1, 2, 3], [4, 5]]

[[1, 2], [3, 4]].transpose # => [[1, 3], [2, 4]]
[1, 2].product([3, 4]).transpose # => [[1, 1, 2, 2], [3, 4, 3, 4]]
[[1, 2], [3, 4, 5]].transpose # element size differs (3 should be 2) (IndexError)

[1, 2, 3].combination(2).to_a  # => [[1, 2], [1, 3], [2, 3]]
[1, 2, 3].permutation(2).to_a  # => [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]

sub, gsub, slice, scan

⚠️ sub! gsub! slice! の破壊的メソッドがあるが、scanだけは無い

  • sub:マッチした最初の箇所のみ置換
  • gsub:マッチした全ての箇所を置換
  • slice:最初にマッチした文字列を返す
  • scan:マッチした文字列を配列で返す
"hello world".sub(/o/, "0")  # => "hell0 world"
"hello world".gsub(/o/, "0") # => "hell0 w0rld"
"hello world".slice(/o/)     # => "o"
"hello world".scan(/o/)      # => ["o", "o"]

IOクラスのクラスメソッド(抜粋)

⚠️ IOFileの親クラス(superclass)なので、IOにあるクラスメソッドはFileでも使える

  • IO.open # ファイルを開く
  • IO.read # ファイルを読む
  • IO.write # ファイルに書く
  • IO.readlines # 全行を配列で読む
  • IO.foreach # 1行ずつ処理
  • IO.select # IO監視

Fileクラスのクラスメソッド(抜粋)

クラスメソッド系はなかなか覚えられなかった…

  • ファイル操作

    • File.open # 開く
    • File.read # 読む
    • File.write # 書く
    • File.delete # 削除 ⚠️Dirクラスにもある
    • File.rename # 名前変更(⚠️Dirクラスメソッドじゃないけどディレクトリにも使える)
    • File.chmod # パーミッション変更
    • File.chown # オーナー変更
    • File.exist? # 存在するか ⚠️Dirクラスにもある
    • File.size # サイズ
    • File.mtime # 更新日時
  • パス文字列の操作

    • File.basename # パスからファイル名を取り出す
    • File.dirname # パスからディレクトリ名を取り出す ⚠️Dirクラスっぽいけど違う
    • File.extname # パスから拡張子を取り出す
    • File.join # パスを結合
    • File.split # パスを分割

Dirクラスのクラスメソッド(抜粋)

覚え方:Linuxコマンドっぽい名前(pwd, cd, mkdir, rmdir)
Linuxコマンドを覚えていないのでこの覚え方はあまり活用できなかった😇)

  • Dir.pwd # 今いるディレクトリ
  • Dir.chdir # ディレクトリを移動
  • Dir.mkdir # ディレクトリを作る
  • Dir.delete # ディレクトリを削除 ⚠️Fileクラスにもある
  • Dir.rmdir # 同上(エイリアス)
  • Dir.unlink # 同上(エイリアス)
  • Dir.entries # 中身の一覧を取得
  • Dir.glob # パターンで検索
  • Dir.exist? # 存在するか ⚠️Fileクラスにもある
  • Dir.home # ホームディレクトリ

%記法

  • %!...!: ダブルクォート文字列(式展開:あり、%Qの省略形)
  • %Q(...): ダブルクォート文字列(式展開:あり、由来:Quote)
  • %q(...): シングルクォート文字列(式展開:なし、由来:quote)
  • %W(...): 文字列の配列(式展開:あり、由来:Words)
  • %w(...): 文字列の配列(式展開:なし、由来:words)
  • %I(...): シンボルの配列(式展開:あり、由来:Identifiers)
  • %i(...): シンボルの配列(式展開:なし、由来:identifiers)
  • %s(...): シンボル1つ(式展開:なし、由来:Symbol)
  • %r(...): 正規表現(式展開:あり、由来:Regexp
  • %x(...): コマンド実行(式展開:あり、由来:eXecute)
lang = "Ruby"
%w(#{lang} Python Java) # => ["\#{lang}", "Python", "Java"] # 式展開されない
%W(#{lang} Python Java) # => ["Ruby", "Python", "Java"]     # 式展開される

%i(foo bar baz).class # => Array
%i(foo bar baz).first.class # => Symbol

正規表現

語源から覚えるの大切だなと思いました。

  • 小文字は、マッチするもの
    • \d(digitの略):数字(0-9)にマッチ
    • \w(wordの略):単語構成文字(a-z, A-Z, 0-9, _)にマッチ
    • \s(spaceの略):空白(スペース、タブ、改行)にマッチ
  • 大文字にすると「否定」
    • \D:数字以外
    • \W:単語構成文字以外
    • \S:空白以外
  • 位置を示すもの
    • ^:行の先頭(複数行で各行にマッチ)
    • $:行の末尾(複数行で各行にマッチ)
    • \A:文字列の先頭(文字列全体の最初だけ)
    • \z:文字列の末尾(文字列全体の最後だけ)
  • 量を示すもの
    • *:0回以上(a* → "", "a", "aaa")
    • +:1回以上(a+ → "a", "aaa"(""は×))
    • ?:0回か1回(a? → "", "a")
    • {n}:ちょうどn回(a{3} → "aaa")
    • {n,m}:n回以上m回以下(a{2,4} → "aa", "aaa", "aaaa")
  • その他
    • .:任意の1文字(改行以外)
    • []:文字クラス(どれか1つ)
    • [^]:否定の文字クラス
    • ():グループ化
    • =~:マッチした位置(インデックス)を返す。マッチしなければnilを返す
s = "abc\ndef\nghi"
s.scan(/^\w+/) # => ["abc", "def", "ghi"]
s.scan(/\A\w+/) # => ["abc"]

"Ruby123" =~ /\d+/ # => 4
"Ruby123"[/\d+/] # => "123"

"hello" =~ /x/ # => nil
"hello" =~ /l/ # => 2
"hello".match?(/x/) # => false # match? はtrue/falseを返す

進数

  • 2進数 0b、使える数字:0, 1
    • 0b0001(1)、0b0110(6)、0b1010(10)
  • 8進数 0o または 0、使える数字:0-7
    • 0o10(8)、010(8)、0o25(21)、025(21)
  • 10進数 プレフィックスなし、使える数字:0-9
    • 10(10)、010025は8進数になるので注意
  • 16進数 0x、使える数字:0-9, a-f(A-Fも可)
    • 0x10(16)、0x1F(31)、0x90(144)
  • 関連メソッド
  • to_i(基数) 文字列を指定した基数で整数に変換
    • "10".to_i(2)(2)、"FF".to_i(16)(255)
  • to_i(0) プレフィックスから基数を自動判定
    • "0x10".to_i(0)(16)、"0b10".to_i(0)(2)
  • .hex 16進数文字列を整数に変換
    • "0x90".hex(144)、"FF".hex(255)
  • .oct 8進数文字列を整数に変換
    • "10".oct(8)、"77".oct(63)
0x10 # => 16 # 16進数の10は16
010  # => 8  # 8進数の10は8
0b10 # => 2  # 2進数の10は2

"0xFF".to_i     # => 0   # 引数無しなので10進数として解釈、xの時点で変換が止まり0になる
"0xFF".to_i(16) # => 255 # 16進数として解釈。`0x` プレフィックスも認識 → 255
"0xFF".to_i(0)  # => 255 # プレフィックスから自動判定。`0x` → 16進数 → 255
"0xFF".hex      # => 255 # 16進数文字列として変換 → 255

ヒアドキュメントの開始ラベル

  • "識別子"(ダブルクオート):式展開が有効
  • 識別子(クオート無し):ダブルクオートと同じ
  • '識別子'(シングルクオート):式展開できない
  • `識別子`(バッククオート):コマンド出力
name = "Ruby"

# 基本形:終端ラベルは行頭に置く必要がある
a = <<EOS
Hello #{name}
EOS

# <<- :終端ラベルをインデントできる
b = <<-EOS
  Hello #{name}
  EOS

# <<~ :終端ラベルをインデント可能 + 中身の共通インデントを自動除去
c = <<~EOS
  Hello #{name}
  EOS

# シングルクォート:式展開なし
d = <<'EOS'
Hello #{name}
EOS

# <<- と ' の組み合わせも可能
e = <<-'EOS'
  Hello #{name}
  EOS

p a  # => "Hello Ruby\n"
p b  # => "  Hello Ruby\n"(インデントがそのまま残る)
p c  # => "Hello Ruby\n"(インデントが自動除去される)
p d  # => "Hello #{name}\n"(式展開されない)
p e  # => "  Hello #{name}\n"(式展開されない)

【Ruby】sortメソッドのブロックの役割

調べたことのメモです📝

sortメソッド

ary = [2,4,8,1]

# デフォルトでsortメソッドを使う
p ary.sort # => [1, 2, 4, 8]

# こういう書き方もできる
p ary.sort{ |i,j| i <=> j } # => [1, 2, 4, 8]

# 降順もサッと書ける(以下どちらでも同じ)
p ary.sort{ |i,j| j <=> i } # => [8, 4, 2, 1]
p ary.sort{ |i,j| -i <=> -j } # => [8, 4, 2, 1]

# reverseで降順にもできるけど、sortとreverseで2回処理するので非効率
p ary.sort.reverse # => [8, 4, 2, 1]

ブロックなしの書き方は無意識に使っていたのですが、ブロックありで宇宙船演算子 <=> を使う時に何が起こっているのかイマイチわかっていなかったので確認しました。

公式リファレンス

docs.ruby-lang.org

全ての要素を昇順にソートします。要素同士の比較は <=> 演算子を使って行います。

(中略)

ブロックとともに呼び出された時には、要素同士の比較をブロックを用いて行います。ブロックに2つの要素を引数として与えて評価し、その結果で比較します。ブロックは <=> 演算子と同様に整数を返すことが期待されています。つまり、ブロックは第1引数が大きいなら正の整数、両者が等しいなら0、そして第1引数の方が小さいなら負の整数を返さなければいけません。両者を比較できない時は nil を返します。

うーん、これだけではよくワカラナイ😇

宇宙船演算子 <=> のおさらい

宇宙船演算子 <=> は、左辺と右辺を比較して、以下のように結果を返す。

1 <=> 10  # => -1(左辺が小さい)
10 <=> 10 # => 0(等しい)
10 <=> 1  # => 1(左辺が大きい)

sortのブロックは「比較のルール」を定義している

要は、 sort にブロックを渡すと、「2つの要素を比較してどちらを前にするか」というルールを指定できる、という感じで受け取りました。

ary.sort { |i, j| i <=> j }

ブロックの戻り値(-1, 0, 1)によって、sortが並び順を決めてくれる。

戻り値 意味
-1 i を前に i=2 , j=4 の場合、 i を前にしたまま、
つまり順番はそのまま
0 何もしない i=2, j=2 の場合、
何もしないので順番はそのまま
1 j を前に i=4, j=2 の場合、 j を前にする、
つまり ij を入れ替える

sort は内部でこのブロックを片っ端から何度も呼び出し、全体をどんどん並び替えていってくれる、というイメージ。

※実際のアルゴリズムとしてクイックソートアルゴリズムバブルソートアルゴリズムといった仕組みがある(そして sortクイックソートアルゴリズムが適用されているらしい)ことも知りましたが、難しかったのでここでは割愛します🙏

ただ、Wikipediaに載っているそれぞれのGIFアニメーションがわかりやすかったので、そのリンクは貼っておきます!

ja.wikipedia.org

ja.wikipedia.org

降順にするには

i <=> jj <=> i にするだけで、大小関係が逆転して降順になる。

# 昇順
ary.sort { |i, j| i <=> j } # => [1, 2, 4, 8]

# 降順(i と j を入れ替える)
ary.sort { |i, j| j <=> i } # => [8, 4, 2, 1]

# ちなみにこう書いても降順になる
ary.sort { |i, j| -i <=> -j } # => [8, 4, 2, 1]

おわりに

ary.sort{ |i,j| i <=> j }という1行に隠された動きの片鱗を知ることができて、良かったです。

2025年の雑な振り返り

今年も書きます、毎年恒例年末の雑な振り返り。

法人化して6年が経った

  • 今年も1年間色々なお仕事をさせてもらい感謝です🙏
  • 今年は、後述するフィヨルドブートキャンプ卒業のためにお仕事を少しセーブしていた。

フィヨルドブートキャンプ(FBC)卒業

  • 今年イチのトピックはこれ
  • 2021年秋に入会してずっとお世話になっているプログラミングスクール、フィヨルドブートキャンプ。入会から4年1ヶ月を経て、ようやく卒業できた🎉
  • 夏頃の卒業を目指してたけど結局11月になってしまった。時間はかかったけど楽しかったなぁ。4年だもんなぁ、大学と一緒の期間かかっている。

Webアプリ開発

  • 今年は主に3つのWebアプリ開発に関わっていた。どれも楽しかったな〜

rururu

rururu.app

  • 去年の振り返りでも書いていた、プロジェクトFHのこと。
  • 今年は具体的な開発に全然コミットできていなかったので、来年はもっとやっていきたい🚀

CAMPUS

campus.bz

  • フィヨルドブートキャンプの卒業制作、通常「自作サービス」で作ったアプリ。
  • まだ開発予定の機能が色々あるので、引き続きやっていく🚀

Typnix

typnix.com

  • フィヨルドブートキャンプを卒業したらAgentic Cordingで何か作りたいと思ってて、分割キーボード用のタイピング練習アプリを自作した
  • Agentic Cordingでガッツリ作れて楽しかったし、かなり勉強になった。
  • こっちもコツコツ便利にしていきたい🚀
  • まだリリースしたばかりだけど、Cornix人気の波もありお知らせの投稿は想像以上に見てもらえた。

登壇やカンファレンス参加

登壇

Kaigi on Rails 2025

  • 今年もデザインスタッフとして参加させてもらい楽しかった。3年目。
  • 今年は看板持って入り口で案内したり発表者の目の前で「残り3分」みたいなカンペを出す役とかもやって面白かった
  • 当方RubyRailsの学習をここ数年続けていて年々話の内容が面白くなってきて、今年はそういう意味でも楽しかった

生活

  • 引き続き、早寝早起き生活
  • 早朝もくもく会を始めた
  • ルーティン
    • 体重計測→ラジオ体操は完璧、99%できてたはず
    • スクワットは達成率3割くらい?
    • 筋トレ、全然できなかった😇
      • 筋トレに関しては諦めるという判断をしていたのだけど、2026はやっていきたい
  • 趣味の1人語りポッドキャストも毎週欠かさず配信できて良かった
  • Cosenseで書いている公開日記も毎日継続。丸3年が過ぎていよいよ楽しくなってきた
  • 冬に京都、夏に大阪に行った。楽しかった
    • 太陽の塔を初めて拝めた、めっちゃ良かったな〜
  • ドラム
    • 去年ちょろっと書いてたけど、今年はかつてのバンド仲間とスタジオに入れた。年の前半は月イチくらいで入れてたけど後半は止まってしまった、来年もうちょっとやっていきたいな〜
  • 1番の買い物は分割キーボードかなぁ、まだ全然タイピングができないんだけど涙、おかげでアプリ作れたので。
  • 読書
    • あんまり読めなかった。2026年は、「ちゃんと読まなきゃ」とか「記録しなきゃ」みたいなことは気にせず、雑に乱読したい。
  • 映像作品

2025年の総括

1年前に書いた「2025年やりたいこと」も眺めつつ…

  • 2025年は4年間続いたスクールを卒業した、区切りの1年になったと思う。本当は今年のうちにもう一つ新しい取り組みを始めたかったけどそれは来年に持ち越し、改めて報告できるよう頑張る💪
  • スクールの学習に加えて、登壇や開発もたくさんできてとても充実していたと思う
  • 一方、1年前に書いていたやりたいことのRuby Silver取得や英語の技術書を読むことは未達成
  • ポッドキャストにゲストを呼ぶのは達成できた! junebokuさんとhacktkさん。楽しかったな〜

2026年やりたいこと

  • これまで通り、生活リズムを保って家族で仲良く楽しく過ごしたい
    • 去年と一言一句同じやつ。油断すると崩れていくので、常に一番に意識しておきたい
    • そのために、健康面の基盤を整えたい。2025年はかなり学習に軸足を置いて活動していたので、2026年は適度な運動・筋トレを習慣化したい
  • 上述した「新たな取り組み」をやって報告したい
  • 資格を取る
  • アプリ開発
    • rururu、CAMPUS、Typnix、どれもコツコツやっていきたい
  • 分割キーボードでタイピングできるようになる
    • 毎日Typnixやって投稿する

おまけ:Claude作の2025年まとめページ

毎日書いている日記を全部食わせてSpotify風のまとめページを作ってもらったので紹介します。 なかなか面白いページが一瞬でできた。 来年も作ろうと思うので、Claudeが読み込みやすい形式で日記を書いていこうと思った。

https://claude.ai/public/artifacts/59ebd9cc-7065-4f2c-8406-5732fe64a3c2

TokyuRuby会議16で初めての外部LT登壇しました

この記事は フィヨルドブートキャンプアドベントカレンダー2025 の1日目の記事です。

今年から、卒業生の hagi さんが開発されたフィヨルドブートキャンプ専用のアドベントカレンダーアプリでの開催です🎉

hagi0121.hatenablog.com

そして僕は昨年に続いて初日を担当しています、よろしくお願いします🚀

🔖 目次

👨🏻 自己紹介

@sugiwe (すぎえ)と申します。
先日11月21日、CAMPUSというアプリをリリースしてプログラミングスクールの フィヨルドブートキャンプ (以下、FBC)を卒業しました!よろしければアプリのリリースブログもご覧ください🙏

sugiwe.hatenablog.jp

さて、この記事では先日11/29(土)に参加してきたTokyuRuby会議16の話を書きます。
今回はフィヨルドブートキャンプアドカレンダーなので、FBCに関することを多めに書いていきたいなと思います。

ちなみに僕はポッドキャストもやっておりまして、TokyuRuby会議の帰り道に感想などを収録したエピソードも本日同時公開しましたので、よろしければ併せてお聴きいただけると幸いです👂

listen.style

🍶 TokyuRuby会議とは

TokyuRuby会議は、プログラミング言語RubyにまつわるLT大会で、参加者一人ひとりが食べ物・飲み物を持ち寄り、休憩を挟みつつ4〜5時間ずっとLTが続くという会です。

以下、公式ページ からの引用です。

  • TokyuRuby会議は、東京で開催される Regional RubyKaigi です。
  • Ruby に興味を持つエンジニアが集う Tokyu.rb 主催の LT 大会です。
  • 飲み食いしつつ、みんなでLTをして盛り上がろうというイベントです。
  • 抽選LTを実施予定です! 抽選LT発表者の指名は抽選により、当日会場にて行います!当たるかもしれません!
  • 今回も参加者のみなさんの投票により飯王、酒王、LT王を決定します!

僕は今回のTokyuRuby会議が初の地域Ruby会議参加でした。
何名かの方に「これは一般的な地域Ruby会議とちょっと違うからね」と言われて面白かったです笑。

会場は GMO Yours・フクラス さん。めちゃくちゃ綺麗で広くて良い場所だった…!

🐦 FBC関係者が多い

Rubyコミュニティに顔を出せばほとんどの場合にFBC関係者がいる、という感じかと思いますが、今回のTokyuRuby会議では特にその割合が多いような気がして、僕のように初めてでも知っている人がたくさんいてとても参加しやすかったなと思います。

💁スタッフ

まず今回の実行委員長さんがFBCの現役受講生です。(受講生なのに実行委員長、めちゃくちゃすごい!!!)
また、それ以外のスタッフさんもFBCのメンター・卒業生・受講生が揃っており、安心して参加することができました。スタッフ業お疲れ様でした、ありがとうございました✨

🙋登壇者

登壇者もFBCの卒業生・現役生が多くいらっしゃいました。(そして自分も末席に加えていただきました)

  • 登壇者の中のFBC関係者(sugiwe調べ。違ってたらすみません🙏)
    • 通常LT登壇:6名
    • 抽選LT登壇:1名
    • 飛び込みLT登壇:3名

重複もあるので正味9人です。LTが全体で30くらいだったと思うので、なんと登壇者の3割くらいがFBC関係者でした。

自分が特に感動したのは、「こういった場で喋ったことがないので慣れるために喋ります」と飛び込み登壇されていた方がいたことです。その行動力を真似したいと思ったし、そう思って行動した瞬間を見られてかなりグッと来てしまいました。

🙎参加者

参加者の中にもFBC関係者さんがたくさんいらっしゃいました!
(参加者に限らずスタッフ・登壇者まとめての話になりますが)オフラインでお話できる貴重な機会だったので、色々な方にご挨拶できて良かったです😄

💬 LT登壇しました

TokyuRuby会議はエントリーのハードルが低めのLT会ということで、FBC内のDiscordでも「はじめてのLT会(FBC内で不定期開催されるオンラインLTイベント)の次にLTデビューに適している」と紹介されていたので、エイヤー の気持ちで登壇を希望しました。

先日リリースした自作サービスCAMPUSの開発時にやっていたことと絡めて、以下の内容で登壇させていただきました。

speakerdeck.com

緊張しましたが楽しかったです!

LT王には全然届きませんでしたが、なんと自分の発表に1票入っていました!!!コメント欄には一言、「いい」のお言葉。めちゃ嬉しいです、どなたか存じませんがありがとうございます🙏🙏🙏

額縁に飾っておきたい、この画面

とはいえ内容的には色々反省があるので、今回発表した考え方をさらに実践してもっと深めてみたいなと思っています。

✨ LT・ご飯の感想

飲み食いしてたり発表前に緊張していたりでちゃんと聞けなかったものもあるのですが、どれも素敵な発表でした。 ほとんどメモできてなかったので、撮っていた写真などから思い出したものを挙げていきます。

  • Linuxディストリビューションの良さをまだ何も知らないので知りたい
  • H×Hの念能力系統になぞらえてバックエンド系・インフラ系とか分けた系統図、面白かった
  • O'Reilly Learning Platformで前書きの多読してみたい(いいお値段するんだよなぁ涙)
  • 暦に技術負債が溜まっている話、面白かった
  • 「毎日Rubyで開発してるのにgemを作ったことがない」からgemをリリースしたの凄い
  • 息子に勝つために素因数分解ゲーム作ったの凄い(というか息子さんも凄すぎる)
  • 抽選LTで突然ブルパップ銃の話が始まるの凄い
  • minitestの結果で魚を泳がせるの超面白かった

ご飯も、どれも美味しくて最高でした!

  • 牛すじ煮めちゃくちゃ美味しかった
  • 豚の角煮もめちゃくちゃ美味しかった
  • チキン春巻き、梅大葉のやつ美味しかった
  • 酢豚ならぬ酢鶏、美味しかった
  • チーズケーキ美味しかった
  • コーヒー美味しかった
  • ポンデリング.split、食べやすくて良かった
  • 納豆チップス、意外にもとても美味しかった
  • 海苔が巻かれた団子、とても美味しかった
  • ねこねこレモンケーキ、可愛くて美味しかった
  • 柿甘味という和菓子、感動的に美味しかった
  • 転職ドラフトさんのビール 、美味しかった!DAYDREAMをいただきました🍺

ご飯の感想のほうが多くなってしまった…🍖🍡🍗

反省

皆さんのLTを振り返ってみると自分の発表は何かちょっと真面目すぎたというか、中身もさることながらですが構成も発表のお作法に沿いすぎていたというか、もっとこう「自分の興味あること一点突破」みたいな発表をしてみたいなーと思いました。

「時間が決まっているからにはその時間内に収まるようにしなきゃ」と思って準備後半は内容というより発表のことに気持ちが向いていたんですが、なんかそんなお行儀の良いものではなく、「とにかく言いたいことを詰め込む、そして銅鑼がなるまで喋り続ける」みたいなLTはとてもカッコいいなと思いました。

🚀 紹介されていたgemを確認・試してみました

LTで紹介されていた以下のgemを確認・試してみました!
ちなみにこの2つのgemをリリースされたのもFBCの卒業生・現役生です👏
自分もgemをリリースしたい気持ちになりました🔥

inline_partial

rubygems.org

パーシャルに別ファイルに分けずに同一ファイル内に置いておくためのgemのようです。 上でも紹介したCAMPUSで言うと、まさに発表内で紹介したマイページの「参加イベント」「主催イベント」のところは別ファイルのパーシャルに分けていますが、これを単一ファイル内に置いておける、ということになります。

inline_partialの特徴を伝えるために実際のCAMPUSのコードをすごく省略化した上で書いてみました。

# 本当は以下の部分をパーシャルで別ファイルに分けるけど、同一ファイル内に置いておける
<% inline_partial(:event_item) do |event| %>
  <div>
    <h4><%= event.title %></h4>
    <p><%= event.start_date %></p>
    <!-- その他、各種イベント情報 -->
  </div>
<% end %>

<div>
  <h3>参加イベント</h3>
  <%= render_inline_partial :event_item, collection: participated_events %>
</div>

<div>
  <h3>主催イベント</h3>
  <%= render_inline_partial :event_item, collection: created_events %>
</div>

今回は既にパーシャルで別ファイルに分割済みなのとコード量の関係から実際にinline_partialの導入はできていませんが、紹介されていたように「パーシャル化したいけど別ファイルに分けるほどでもない」ときにとても重宝しそうです。

というか、「1つのerb内にまとめて書く」という方針が自分の発表した「Viewファーストな開発」とめちゃくちゃ相性が良いのではということに気が付きました。チャンスがあればぜひ試してみたいです!

METs-advisor

rubygems.org

METsとは、身体活動の強さを表す単位で、安静に座っている状態を1メッツというそうです。知らなかった!

# まずインストール
% gem install mets_advisor

# 実行、あとは質問に答えていくだけ
% mets_advisor

Please select your language / 言語を選択してください: 日本語

1週間の歩数を1日平均で教えてください: 7000
1週間に何日間運動をしていますか?: 5

運動の強度を選んでください: 軽度(徒歩・ピラティスなど)

以下から該当する活動を選んでください: 少し速めに歩く
その運動は1日合計で何分ほどですか?: 20

🎉あなたの身体活動量は理想値を上回っています!🎉
---------------------------------------------------
あなたの身体活動量は【24 METs】です。
素晴らしいですね!これからもこの身体活動量を続け、健康を維持してください!

意外と悪くない結果が出ました🙌
とはいえ自覚としてはちょっと運動不足気味なので、健康に気をつけてもう少し運動しなきゃと思っています🏃

🙏 終わりに

初めてのTokyuRuby会議参加、そして初めてのLT登壇でしたが、とても楽しかったです。
運営チームの皆さま、スポンサーの皆さま、そして参加者の皆さま、どうもありがとうございました!!!

📅 明日のアドカレ

ここまでお読みいただき、ありがとうございました😄

明日のフィヨルドブートキャンプアドベントカレンダー2025は、 kyokucho1989 さんの予定です。

フィヨルドブートキャンプアドベントカレンダー2025 は始まったばかりです、25日までぜひお楽しみください〜✨