RubyKaigi 2025レポート:Rubyで作成したスクリプトをバイナリにして簡単に配布できるか試してみた

こんにちは!Findy Team+開発チームでEMをしているhamです。

今年もRubyKaigi 2025に参加してきました。 私はコロナ後に三重県で開催されたときから4年連続で参加しているのですが、今年も興味深いセッションがたくさんあり、Rubyが着実に進化していることを感じることができました!

本記事では、その中の1つである「The Ruby One-Binary Tool, Enhanced with Kompo」で紹介された「Kompo」について、実際に試してみた結果と所感を紹介します。

Kompoとは

Kompoとは、READMEで「A tool to pack Ruby and Ruby scripts in one binary.」と紹介されている通り、Rubyスクリプトをバイナリにして配布できるツールです。 Rubyのスクリプトをバイナリにすることで、配布が容易になったり、実行環境にRubyのインストールが不要になるため配布先での実行が容易になります。

Kompoは2024年のRubyKaigiでも「It's about time to pack Ruby and Ruby scripts in one binary」のセッションで紹介されており、興味を持っていました。

2024年の時点ではRailsなどの大きなGemは実行できていないとのことだったのですが、2025年の発表ではRailsが動いており進化を感じました!

'Hello, world!'を返すスクリプト

今回のセッションでRailsが動作するようになったと発表されていたので、Webサーバーが動作するバイナリを作ってみることにしました。 とはいえ、最初から大きなものを作ろうとすると詰まる可能性が高いです。何事もスモールスタートが良いですね。

入門といえば 'Hello, world!'ということで、まずは'Hello, world!'を返却するRubyスクリプトでトライしました。

なお、ここからの内容は執筆時点(2025年4月)の情報なので最新版では変更されている可能性があります。

kompo-vfs

Kompoは内部で仮想ファイルシステムを使っているとのことです。

当初は既存のライブラリで実現できないか検討したとのことですが、Kompoのやりたいことにマッチするものがなかったとのことで「kompo-vfs」を自作したそうです。 リポジトリを見ていただければわかりますが、こちらはRustで書かれていました。

Kompoを使うにはまずkompo-vfsをbuildしておく必要があるとのことです。 READMEに沿って作業します。

READMEには次のように記載されています。(※手順はKompoのREADMEに記載されています)

## prerequisites
Install [kompo-vfs](https://github.com/ahogappa/kompo-vfs).

#### Homebrew

$ brew tap ahogappa/kompo-vfs https://github.com/ahogappa/kompo-vfs.git
$ brew install ahogappa/kompo-vfs/kompo-vfs

### Building
To build komp-vfs, you need to have cargo installation.

$ git clone https://github.com/ahogappa/kompo-vfs.git
$ cd kompo-vfs
$ cargo build --release

Set environment variables.

$ KOMPO_CLI=/path/to/kompo-vfs/target/release/kompo-cli
$ LIB_KOMPO_DIR=/path/to/kompo-vfs/target/release

MacBookを使っているのでbrewの手順を試しましたがうまくいかなかったので、リポジトリをcloneする方法で実施しました。

mainブランチで試してみましたが、buildがエラーになりました。

% cargo build --release
...
error: could not compile `kompo_storage` (lib) due to 2 previous errors; 1 warning emitted

こちら色々解析したところ、MacBookには対応してなさそうだとわかりました。 そこでDockerを立ち上げてその中でbuildすることにしました。 Kompoの実行もDockerで実施した方が良さそうだったので、Rubyイメージから作成しました。

# Dockerfile
FROM ruby:3.4.3

# Install dependencies
RUN apt-get update && apt-get install -y \
  git \
  build-essential \
  libssl-dev \
  zlib1g-dev \
  libyaml-dev \
  libgmp-dev \
  libreadline-dev \
  pkg-config \
  autoconf \
  bison \
  curl \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

# Install latest Rust using rustup
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

# Set working directory
WORKDIR /app

# Copy project files
COPY . /app/

# Install bundler and dependencies
RUN gem install bundler && bundle install

CMD ["tail", "-f", "/dev/null"]

buildしてbashでコンテナに入り、改めてbuildしたら成功しました🙌

% docker build -t hello-world .
% docker run -it --rm -v .:/app hello-world bash
root:/app# cd kompo-vfs/
root:/app/kompo-vfs# cargo build --release
...
Finished `release` profile [optimized] target(s) in 5.40s

Kompo

次に'Hello, world!'を返すRubyスクリプトを作成します。

# hello.rb
p 'Hello, world!'

Kompoはgemが公開されていないのでリポジトリをcloneしてローカルで生成します。 色々試行錯誤したのちに気づいたのですが、登壇スライドの26ページによるとkompoはfeature/use_rtld_nextの方が最新と思われるのでそちらを利用します。

Demo

Dockerコンテナ内でgem buildを実施して、無事kompo-0.2.0.gemが生成できました。

root:/app/kompo# gem build kompo.gemspec
WARNING:  licenses is empty, but is recommended. Use an license identifier from
https://spdx.org/licenses or 'Nonstandard' for a nonstandard license,
or set it to nil if you don't want to specify a license.
WARNING:  open-ended dependency on mini_portile2 (>= 0) is not recommended
  use a bounded requirement, such as "~> x.y"
WARNING:  open-ended dependency on async (>= 0) is not recommended
  use a bounded requirement, such as "~> x.y"
WARNING:  You have specified the uri:
  https://github.com/ahogappa0613/kompo
for all of the following keys:
  homepage_uri
  changelog_uri
  source_code_uri
Only the first one will be shown on rubygems.org
WARNING:  See https://guides.rubygems.org/specification-reference/ for help
  Successfully built RubyGem
  Name: kompo
  Version: 0.2.0
  File: kompo-0.2.0.gem

次に、Kompoをインストールします。

root:/app# gem install kompo/kompo-0.2.0.gem
...
Successfully installed kompo-0.2.0
10 gems installed

時は来た!あとは梱包(Kompo)するだけです!実行には数分かかるので待ちます。

root:/app# kompo --help
Usage: kompo [options]
    -e, --entrypoint=VAL             File path to use for entry point. (default: './main.rb')
    -g, --use-group=VAL              Group name to use with 'bundle install'. (default: 'default')
        --[no-]gemfile               Use gem in Gemfile. (default: automatically true if Gemfile is present)
        --local-kompo-fs-dir=VAL
        --verbose                    Verbose mode.
        --dest-dir=VAL               Output directry path. (default: current dir)
        --bundle-cache=VAL           Specify the directory created by 'bundle install --standalone'.
        --ruby-version=VAL           Specify Ruby version. (default: current Ruby version)
        --rebuild
        --repack
root:/app# kompo -e hello.rb --local-kompo-fs-dir=kompo-vfs
...
/usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.WD+0x0): multiple definition of `WD'; /tmp/ccNwyWlB.o:(.rodata+0x0): first defined here
/usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.PATHS_SIZE+0x0): multiple definition of `PATHS_SIZE'; /tmp/ccNwyWlB.o:(.rodata+0x28): first defined here
/usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.PATHS+0x0): multiple definition of `PATHS'; /tmp/ccNwyWlB.o:(.rodata+0x30): first defined here
/usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.FILES_SIZE+0x0): multiple definition of `FILES_SIZE'; /tmp/ccNwyWlB.o:(.rodata+0x192c8): first defined here
/usr/bin/ld: /app/kompo-vfs/target/release/libkompo_fs.a(529179467e613863-dummy_fs.o):(.rodata.FILES+0x0): multiple definition of `FILES'; /tmp/ccNwyWlB.o:(.rodata+0x192d0): first defined here
collect2: error: ld returned 1 exit status
/usr/local/bundle/gems/kompo-0.2.0/lib/kompo.rb:193:in 'Kernel#system': Command failed with exit 1: gcc (RuntimeError)

エラーが発生しました。 kompo-vfsで、WDPATHSなどの定義が重複しているようです。

Rustなんもわからんので雰囲気ですが、kompo-vfs/kompo_fs/dummy_fs.cの中でWDPATHSが定義されているので怒られた定義をコメントアウトして再buildしてやり直してみました。

// kompo-vfs/kompo_fs/dummy_fs.c

// const char FILES[] = {};
// const int FILES_SIZE = 0;
// const char PATHS[] = {};
// const int PATHS_SIZE = 0;
// const char WD[] = {47,119,111,114,107,115,112,97,99,101,115,47,114,117,98,121,95,112,97,99,107,97,103,101,114,47, 0};
const char START_FILE_PATH[] = {46,47,109,97,105,110,46,114,98, 0};

再度Kompoを実行。今回は正常終了し、バイナリ(app)が生成されました🎉

root:/app# kompo -e hello.rb --local-kompo-fs-dir=kompo-vfs
...
info: Finish kompo!
root:/app# ls -l app
-rwxr-xr-x 1 root root 76437456 Apr 24 02:15 app
root:/app# ./app
"Hello, world!"

最後に実行します。せっかくなのでRubyが入っていない環境で実行します。

Dockerfileはこちらを使いました。

FROM ubuntu:latest

# Set working directory
WORKDIR /app

# Copy project files
COPY . /app/

CMD ["tail", "-f", "/dev/null"]

コンテナを立ち上げて、バイナリを実行します。 Rubyが入っていない環境で実行できました🎉

% docker build -t ubuntu-app .
% docker run -it --rm -v .:/app ubuntu-app bash
root@62df4e7aa257:/app# ./app
"Hello, world!"

Rails

簡単なRubyスクリプトでは実行できることがわかったので、次はRailsに挑戦です!

ただ、結論は「色々試行錯誤したものの起動できず」でした...

Kompoするところでエラーになったり、Kompoはできたが起動できなかったり、これ以上の解析は難しいので今回は諦めました🙏

最後に

今回は簡単なRubyスクリプトしかできませんでしたが、ワンバイナリで配布してすぐに実行できることはとても便利だと感じました。今後の更なる進化に期待です!!


5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。

LTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!!

https://pixiv.connpass.com/event/352852/pixiv.connpass.com

また、ファインディではこれからも新しいものを取り入れつつ、Rubyを積極的に活用してRubyとともに成長していければと考えております。

一緒に働くメンバーも絶賛募集中なので、興味がある方はぜひこちらから ↓

herp.careers

RubyKaigi 2025レポート:FindyのRailsプロジェクトでSorbetの型チェックを試してみた

こんにちは。ファインディでソフトウェアエンジニアをしているnipe0324です。

先日、愛媛県松山市で開催されていたRubyKaigi 2025 に参加してきました。

様々なセッションに参加し、他社のエンジニアと話す中で多くの刺激をうけました。特に印象深かったのは、Sansanさん、TwoGateさん、Timeeさんなどの企業がRubyの型を導入して運用していたことです。

本記事では、N番煎じですが、Findy転職のRailsプロジェクトにSorbetを入れて型を試してみました。

Rubyの型に興味を持っているけどなかなか試せていないという方の参考になれば嬉しいです。

Sorbetとは?

Sorbet(https://github.com/sorbet/sorbet) はRubyのコードに型注釈をつけて、型エラーを検出できるツールです。

StripeやShopifyなどの企業で利用されています。

Sorbetを利用している企業例

Sorbetのメリット・デメリット

Sorbetの主なメリットとデメリットは次のとおりです。

メリット

  • 型エラーを事前に検出できる
  • コードの可読性と保守性の向上
  • IDEのコード補完がより有効に使える
  • リファクタリングが安全に行える
  • ドキュメントとしての役割

デメリット

  • 導入コストがかかる(コードの修正、型定義の作成)
  • Rubyの動的な特性が一部制限される
  • チーム全体に学習コストが発生する
  • すべてのgemやライブラリが型に対応しているわけではない

Sorbetを使ったRubyのコード例

Sorbetを使うと、型をインラインで定義できます。

このコード例では、sigを使って、greetメソッドがString型の引数を受け取ることを宣言しています。そのため、数値(Integer)を渡すと型エラーが検出されます。

# typed: true

class User
  extend T::Sig

  sig {params(name: String).void}
  def greet(name)
    "Hello #{name}"
  end
end

User.new.greet("Tom")  # OK - 文字列を渡している
User.new.greet(3)      # 型エラー - 数値を渡している
補足:Rubyの型チェッカー

現時点でRubyの主要な型チェッカーとして「Sorbet」と「Steep」があります。また、型定義の書き方として「RBI(Ruby Interface)」と「RBS」があります。
現時点の組み合わせとして、「SorbetとRBI」、「SteepとRBS」というように型チェッカーと型定義を組み合わせ使います。

型導入のモチベーション

Findy転職のRailsアプリケーションでは、「GraphQL API」、「Interactor」、「Model」といったレイヤーで実装をしています。

Findy転職のRailsアプリケーションのレイヤー抜粋

Interactor層では、collectiveidea/interactor という gemを使ってビジネスロジックを実現しています。

1つの操作(ユーザー登録やデータ検索など)を独立した「インタラクション」として実装でき、責務を分割しやすい特徴があります。

課題:Interactorの入出力が不明確

Interactor gemの内部ではOpenStructを使ってデータの受け渡しをしています。つまり、どんな値でも自由に設定できる柔軟性がある反面、入出力として何があるのか不明確になりやすいです。

Interactorのサンプルコードで問題点を確認してみます。

# イメージ実装

# Interactorの実装
class CreateJobDescriptionInteractor
  include Interactor
  
  def call
    job_description = JobDescription.new(create_params)

    if job_description.save
      # contextには何でも入れられるため
      # Interactorの返り値を知るには実装を読む必要がある
      context.job_description = job_description
    else
      context.fail!(error_messages: job_description.errors.full_messages)
    end
  end

  private

  def create_params
    # contextにどんな値が入るかは呼び出し元を見る必要がある
    context.params.slice(:name, :description, :job_type)
  end
end

# 呼び出し元
# Interactorの中身を読まないと何が返されるか分からない
result = CreateJobDescriptionInteractor.call(params: { 
  title: '求人票', 
  description: '求人票の内容', 
  job_type: 'バックエンドエンジニア' 
})

if result.success?
  result.job_description  # job_descriptionが返るのは実装を読まないと分からない
else
  result.error_messages   # error_messagesが返るのも実装を読まないと分からない
end

この問題に対して、型を導入することで入出力を明確にし、コードの可読性と保守性を向上させることを目指しました。

検証した結果

Sorbetの公式ドキュメントを見ながら、GraphQL、Interactor、Modelに対して型を定義してみました。

結果としては、次のとおりです。

  • ActiveRecordのモデルGraphQLのAPItapiocaを使うことで型定義(RBIファイル)をある程度自動生成できるため導入が簡単
  • Interactorは、gemの特性上、型との相性が悪く、PORO(Plain Old Ruby Object)などの設計の変更の実施が必要そう

検証の実施内容

Sorbetのセットアップ

SorbetのGetting Started (https://sorbet.org/docs/adopting) を見ながらセットアップをしました。

まず、Gemfileに必要なgemを追加してbundle installを実行します。

# Gemfile

gem 'sorbet', group: :development
gem 'sorbet-runtime'
gem 'tapioca', require: false, group: [:development, :test]

次に、Tapiocaを使ってSorbetを初期化します。このコマンドでsorbetディレクトリが作成され、プロジェクト内のGemに対して自動的に型定義(RBIファイル)が生成されます。

$ bundle exec tapioca init

Sorbetによる型チェックを実行します。初回は多くの型エラーが出るので、修正していきます。

$ bundle exec srb tc

型エラーを修正して、型チェックが成功したら初期セットアップは完了です 👏

$ bundle exec srb tc
No errors! Great job.

ActiveRecordのモデルに型を追加

tapioca dslコマンドを使うことで、ActiveRecordモデルやGraphQL-RubyのDSL(Domain Specific Language、特定領域向け言語)から自動的に型定義ファイル(RBIファイル)が作成できます。

$ bundle exec tapioca dsl
Loading DSL extension classes... Done
Loading Rails application...

      create  sorbet/rbi/dsl/skill.rbi
      create  sorbet/rbi/dsl/user.rbi
      create  sorbet/rbi/dsl/job_description.rbi
      create  sorbet/rbi/dsl/xxxx.rbi
      ...

例えば、JobDescriptionモデルがある場合、次のようなRBIファイルが自動的に作成されます。これによりモデルのプロパティに型情報が付与されます。

class JobDescription

     # ...

    sig { returns(::String) }
    def title; end

    sig { params(value: ::String).returns(::String) }
    def title=(value); end

    sig { returns(T::Boolean) }
    def title?; end

    sig { returns(T.nilable(::String)) }
    def title_before_last_save; end

では、実際に型チェックが機能するか検証してみます。

# typed: trueをファイルの上部に追加し、型チェックの対象にします。さらに、型エラーの動作確認のためわざと不正な値を設定してみます。

# frozen_string_literal: true
# typed: true

class JobDescription < ApplicationRecord
  def type_check_error_method
    self.title = 1 # String型のプロパティにInteger型を設定(エラーになるはず)
  end
end

Sorbetを実行すると、予想通り型エラーが検出されました。👏

$ bundle exec srb tc
app/models/job_description.rb:6: Assigning a value to value that does not match expected type String https://srb.help/7002
     6 |    self.title = 1
                        ^
  Got Integer(1) originating from:
    app/models/job_description.rb:6:
     6 |    self.title = 1
                        ^
Errors: 1

GraphQLに型を追加

graphql-ruby を利用しているプロジェクトでは、GraphQLの型定義もtapioca dslコマンドで自動生成されます。

# 自動生成されるRBIファイルのイメージ
class CreateJobDescriptionMutation
  sig { params(title: ::String, company_id: ::Integer).returns(T.untyped) }
  def resolve(title:, company_id:); end
end

ActiveRecordモデルと同様に、型定義に違反した実装をすると型エラーが発生します。これによりGraphQLのリゾルバーやミューテーションにも型安全性を導入できます。

ただし、自動生成されたRBIファイルでは戻り値がT.untyped(型のない状態)になることがあるため、具体的な型を指定していく必要があると感じました。

Interactorに型を追加

Interactorでは、型定義をうまく行うことができませんでした。

Interactor gem で定義されているcontextが、OpenStructを使っておりデータが柔軟に設定できるがゆえに型定義がうまくできませんでした。

# frozen_string_literal: true
# typed: true

class CreateFooInteractor
  extend T::Sig

  include Interactor

  # contextのアクセスのための型定義
  # 上手く定義できず、`T.untyped`(型のない状態)になっている
  sig { returns(T.untyped) }
  attr_reader :context

  def call
    foo = Foo.new(create_params)

    if foo.save
      context.foo = foo
    else
      context.fail!(error_messages: foo.errors.full_messages)
    end
  end

  private

  def create_params
    context.params.slice(:title, :company_id)
  end
end

改善案としては、PORO (Plain Old Ruby Object) による実装に変えて、入出力の型を明確にすることで、型定義を記載するという方法が考えられます。

また、https://github.com/maxveldink/sorbet-result にあるような RustのResult型に似た実装を導入するのも効果的です。Result型は「成功」か「失敗」のどちらかの状態を表現するもので、型安全な方法で結果を扱えるようになります。

型でガチガチにするとRubyの良さが失われてしまう懸念もあるため、段階的に導入しつつバランスを見ていく必要があります。

型を試してみた所感

今回既存のRailsプロジェクトでSorbetによる型を試しに導入しました。

感想としては、検討事項は他にもありますが、前向きに型導入を進めていこうと思いました。

  • ActiveRecordやGraphQLにほぼ自動的に型定義を追加できたり、段階的に型チェックを有効化できるので小さく始めやすい
  • ローカル開発やCIで型チェック、GraphQLやテーブルスキーマ変更時の型更新のフローを整備する必要がある
  • SorbetとSteepのどちらが良いかは好みによるので検討は必要がある
  • など

最後に

ファインディでは、一緒にRubyやRailsの開発をしてくれる仲間を募集しています。 興味のある方は、ぜひこちらからチェックしてみてください! herp.careers

また、2025/5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、3社合同でRubyKaigi 2025の振り返りを行います。

オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツが盛りだくさんなのでぜひご参加ください!!

pixiv.connpass.com

RubyKaigi 2025で紹介されたruby.wasmのスライド作成ツールgibier2を使ってみた

はじめに

こんにちは!ファインディでFindy Team+を開発している中嶋(@nakayama__bird)です!

RubyKaigi 2025に参加してきました!

今回のRubyKaigiが初参加で楽しみ半分緊張半分だったのですが最高な3日間でした! Rubyを使う人、Rubyを作る人、そしてRubyで遊ぶ人などたくさんの出会いがあり、日頃の業務でRuby on Railsに触れるのとはまた違った視点でRubyを考えるきっかけとなりました。

複数のセッションに参加した中で特に関心を持ったruby.wasm、そしてruby.wasmを使ったスライド作成ツールgibier2を使ってみての感想をまとめていきたいと思います。

ruby.wasmとは?

ruby.wasmとは、WebAssemblyという技術を使用してRubyのコードをブラウザ上で実行できる仕組みのことです。 Rubyをブラウザ上で実行できることで、環境構築をせずにRubyのコードを試せる環境の提供やフロントエンド実装への可能性を広げるといったメリットがあります。

github.com

わかりやすい例だとruby.wasmのREADMEに掲載されている Try Rubyが挙げられます。ブラウザ上でRubyのコードが動くため、RubyをPCにインストールせずともどんな挙動になるのかを簡単に確認できます。

私自身、約1年半ほど前にコードを初めて書いた駆け出しエンジニアなのですが、ブラウザ上で動きが確認できるのは学習スタートのハードルが下がるという点でとても良いなと思いました。

また、ruby.wasmを活用した事例として、Writing Ruby Scripts with TypeProfのセッションで、TypeProfをブラウザ上で試すことができるTypeProf.wasmが紹介されていました。

気軽に試してみて、もしエラーなどあれば報告してくださいというようなお話をしており、ruby.wasmが新しい技術を手軽に試せる環境として機能している点も大きな魅力だなと感じました。

x.com

gibier2について

gibier2は、ruby.wasmを使ったスライド作成ツールです。 dRuby on Browser Again! でトークセッションをした youchanさんが開発したもので、マークダウンでスライドを作成できます。

x.com

github.com

gibier2の前身としてgibierがあるのですが、こちらはRubyをJavaScriptにコンパイルする Opal を使用しているためスライド作成ツールという点では共通しているものの中身は別物です。

これまで私自身、スライドを作成する際にCanvaやGoogle スライドを使いGUI上で操作して作成していました。しかしながら箇条書きで内容整理をしながら登壇資料が作成できて便利そうということで、マークダウンを使ったスライド作成ツールが気になっていたため早速使ってみました!

セットアップ

開発環境:ruby 3.3.6(RubyのWebAssemblyサポートは3.2.0以降1

READMEを参考に実行しました。具体的なコマンドは、次の通りです。 リポジトリをcloneしてbundle installします。

$ git clone https://github.com/youchan/gibier2
$ cd gibier2
$ bundle install

その後、wasmディレクトリでのセットアップを行います。 README通りにbundle exec rbwasm build -o dist/ruby.wasmをしたところ失敗したため、先にcd wasm && bundle installを実行しました。

$ cd wasm && bundle install

その後、wasmディレクトリで次のコマンドを実行します。

$ bundle exec rbwasm build -o ../dist/ruby.wasm
$ bundle exec rbwasm pack ../dist/ruby.wasm --dir ./src::/src -o ../dist/app.wasm

初回のbuildには時間がかかるのしばらく待ちます。

ルートディレクトリに戻りRackアプリケーションサーバーを立ち上げます。

$ cd ..
$ bundle exec rackup

http://localhost:9161を開くとサンプルのスライドが表示されます。

Ruby WebAssembly

実際に使ってみる

基本的にslide.mdにマークダウンで資料の内容を書いていきます。

<!-- slide.md -->
## RubyKaigi 2025行ってきました
- ruby.wasmを知ることができた
- gibier2を使ってみた
- スピーカーとお話しできた

スライドの背景画像はpublic/images配下におきます。 それをpublic/css/custom.cssで適宜設定します。

/* public/css/custom.css */
.page {
  background-image: url("/images/new_background.png");
  color: #222;
}

Ruby WebAssembly

特にカスタマイズしないと、#だとセンタリングされた状態、##だと見出しとして表示されます。

<!-- slide.md -->
# #が1つの場合

Ruby WebAssembly

<!-- slide.md -->
## #が2つの場合

Ruby WebAssembly

マークダウンのためコードブロックやリンクの追加も可能です。

<!-- slide.md -->
## コードブロックやリンクの挿入も可能

<200b>```ruby
def hello
  puts "Hello, World!"
end
<200b>```

[Findy Tech Blog](https://tech.findy.co.jp/)

Ruby WebAssembly

まとめ

RubyKaigiで関心を持ったruby.wasm、そしてruby.wasmを使ったスライド作成ツールgibier2を使ってみたという内容をまとめました。

発表を聞いてすぐにgibier2を試したのでぜひyouchanさんにお話を聞きたいなと思っていたところ、偶然にも弊社のDrink Upでお会いできました。セッション後、早速使ってみましたと伝えると喜んでもらえて私も嬉しい気持ちになりました。 このように、登壇者とコミュニケーションを取りやすいのもRubyKaigiの良いところです。

gibier2について、今回のRubyKaigiの登壇資料用として作ったため、汎用的なツールとしてはこれからだという話をしていました。 またGUI上でより細かなデザインをできるようにしたりPDFへ書き出せるようにしたりしたいなど、今後の展望についてもお話を聞くことができとても良い経験となりました。

またビルドの流れでREADMEに記載されている通りに実行したところうまく立ち上がらなかったため、Issueを作成してみました。

github.com

OSSへの貢献したいなと考えていたので、RubyKaigiでの出会いをきっかけに実際に行動に移せてよかったです。

最後に

5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。

オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツ盛りだくさんなのでぜひご参加ください!! 今回のブログで取り上げたgibier2を使って登壇資料を作成予定なので、ぜひ見にきていただけると嬉しいです!

pixiv.connpass.com

また、ファインディではこれからも新しいものを取り入れつつ、Rubyを積極的に活用してRubyとともに成長していければと考えております。

一緒に働くメンバーも絶賛募集中なので、興味がある方はぜひこちらから ↓

herp.careers

RubyKaigi 2025 レポート: 早速「RBS::Trace」でRailsプロジェクトの型情報を自動生成してみた!

こんにちは、Findy Freelanceの開発をしているエンジニアの@2boです。

先日、愛媛県で開催されたRubyKaigi 2025に参加してきました。ファインディのブースにお立ち寄りいただいた方、Rubyクイズに答えてくださった方、Drinkupに参加していただいた方、運営やSpeakerの皆様、ありがとうございました! おかげさまでとても楽しく過ごすことができ、興味深いセッションもたくさんありました。

本記事では、その中の1つである@sinsoku_listyさんの「Automatically generating types by running tests」で発表されていた「RBS::Trace」を早速、Findy FreelanceのRailsプロジェクトで試してみた結果と所感を紹介します。

RBS::Traceとは

RBS::Traceは、コード実行時にメソッドの引数と戻り値の型情報を収集し、自動的にInline RBSとしてコメントを挿入したり、RBSファイルを作成してくれるGemです。

実行手順

Findy FreelanceのRailsプロジェクトのテストでRBS::Traceを実行してみました。 執筆時点のバージョンは0.5.1です。 なお、今回は大枠の動作と結果を確認することが目的のため、対象はapp/models/配下に絞っています。

次の手順で実行しました。

1. Gemfileへの追加

gem "rbs-trace"

Gemfile追記後にbundle installを実行します。

$ bundle install

2. RSpecの設定

次に、RSpec用の設定ファイルを作成します。 RBS::Trace.newの引数pathsで対象のファイルを指定しています。

RBSファイルの格納先として、sig/trace/を指定しています。なお、この設定は任意です。

# spec/support/rbs_trace.rb

RSpec.configure do |config|
  # RBSの出力対象とするファイルを指定
  trace = RBS::Trace.new(paths: Dir.glob("#{Dir.pwd}/app/models/**/*"))

  config.before(:suite) { trace.enable }
  config.after(:suite) do
    trace.disable
    trace.save_comments
    # RBSファイルの格納先を指定
    trace.save_files(out_dir: "sig/trace/")
  end
end

3. テストの実行

テストを実行します

$ bundle exec rspec spec/models/

結果の確認

テストを実行すると、Inline RBSが対象のファイルのメソッド定義の上に追記され、RBSファイルが生成されます。 それぞれの結果を次に示します。 なお、掲載しているのは例示用のコードで、実際のFindy Freelanceのコードや処理とはまったく関係ありません。

Inline RBS

app/models/user.rbに次のようなInline RBSが生成されました。

class User < ApplicationRecord
  has_many :projects
  
  # @rbs () -> String
  def full_name
    format_name
  end

  # @rbs () -> Project?
  def main_project
    projects.find_by(active: true)
  end
  
  # @rbs (Date) -> Project::ActiveRecord_AssociationRelation
  def projects_after_date(date)
    projects.where('start_date >= ?', date)
  end
  
  private
  
  # @rbs () -> String
  def format_name
    "#{last_name} #{first_name}"
  end
end

RBSファイル

sig/trace/app/models/user.rbsに次のようなRBSファイルが生成されました。

class User
  def full_name: () -> String

  def main_project: () -> nil
                  | () -> Project

  def projects_after_date: (Date) -> Project::ActiveRecord_AssociationRelation

  def format_name: () -> String
end

参考: テストコード

RBS::TraceによるRBSの自動生成は、テストで実行された内容に依存するため、参考としてテストコードの例を掲載します。

RSpec.describe User do
  describe '#full_name' do
    subject { user.full_name }

    let(:user) { create(:user, first_name: '太郎', last_name: '山田') }

    it 'returns a string combining last_name and first_name' do
      expect(subject).to eq('山田 太郎')
    end
  end

  describe '#main_project' do
    subject { user.main_project }

    let(:user) { create(:user) }

    context 'when there is an active project' do
      let!(:active_project) { create(:project, user: user, active: true) }
      let!(:inactive_project) { create(:project, user: user, active: false) }

      it 'returns the active project' do
        expect(subject).to eq(active_project)
      end
    end

    context 'when there is no active project' do
      let!(:inactive_project) { create(:project, user: user, active: false) }

      it 'returns nil' do
        expect(subject).to be_nil
      end
    end
  end

  describe '#projects_after_date' do
    subject { user.projects_after_date(target_date) }

    let(:user) { create(:user) }
    let(:target_date) { Date.new(2025, 4, 1) }

    let!(:before_project) { create(:project, user: user, start_date: Date.new(2025, 3, 31)) }
    let!(:on_date_project) { create(:project, user: user, start_date: Date.new(2025, 4, 1)) }
    let!(:after_project) { create(:project, user: user, start_date: Date.new(2025, 4, 2)) }

    it 'returns only projects starting on or after the specified date' do
      expect(subject).to include(on_date_project, after_project)
      expect(subject).not_to include(before_project)
    end
  end
end

結果からわかったこと

実行した結果、次のようなことがわかりました。

  • 直接テストしていないが、テスト中に実行されるprivateメソッドの型情報も生成されている
  • ApplicationRecordのサブクラスを返すメソッドは、具体的なクラス名で型情報が生成されている
  • Railsのassociationやscopeメソッドには、型情報が生成されない
    • これらはメソッド定義ではないため当然の結果である
  • ActiveRecord::AssociationRelationのサブクラスのインスタンスを返すメソッドは、[具体クラス名]::ActiveRecord_AssociationRelationのように型情報が記載される
    • これはRailsの仕様によるもので、そのようなクラスを動的生成しているためである
  • 既に記載されているInline RBSは上書きされない
    • RBSファイルの内容はテストを実行するたびに更新される

Steepの導入

せっかくRBSファイルが生成されたので、型チェッカーであるSteepもセットアップして型のあるRailsプロジェクトを疑似体感してみました。 ただし、RBS::TraceだけでRailsプロジェクトの型チェックすべてパスさせることはできないため、VSCodeで型情報を確認できるようにすることを目的としています。

次の手順でセットアップしました。

1. Gemfileへの追加

# Gemfile
gem 'steep'

2. Bundle install

Gemfile追記後にbundle installを実行します。

$ bundle install

3. Steepの設定

設定ファイルとなるSteepfile作成します。

D = Steep::Diagnostic

target :app do
  # RBSファイルの格納先を指定
  signature "sig/trace"
  # Steepで型チェックする対象のファイルを指定
  check "app/models"
  # 型チェック結果を全て抑制(無音)する
  configure_code_diagnostics(D::Ruby.silent)
end

4. VSCodeのSteep拡張のインストール

steep-vscodeをVSCodeにインストールします。

5. RBSファイルのエラーをコメントアウト

RBSファイル内にエラーがあると、VSCodeのSteep拡張が動作しないため、エラーしている箇所をコメントアウトしました。本来は型定義を追加、修正するなどしてエラーを解消する必要がありますが、今回はVSCodeで型情報を確認できることが目的のため、このような対応をしました。

先のRBSファイルの例で言うと、RBS::Traceの実行だけではProject::ActiveRecord_AssociationRelationクラスの型情報が生成されないため、RBS::UnknownTypeNameエラーになります。これをコメントアウトすることでエラーを握りつぶしています。

class User
  def full_name: () -> String

  def main_project: () -> nil
                  | () -> Project

  # RBS::UnknownTypeName エラーになるためコメントアウト
  # def projects_after_date: (Date) -> Project::ActiveRecord_AssociationRelation

  def format_name: () -> String
end

VSCodeでの型情報の表示結果

メソッドの呼び出し箇所をホバーすると、型情報が表示されます。

VSCodeでメソッドをホバーすると型情報が表示される

また、入力の補完時にも型情報が表示されます。

VSCodeで補完時に型情報が表示される

所感

RBS::Trace導入のメリットと可能性

RBS::Traceは、RBSが全くないRailsプロジェクトにとって、型情報導入の最初の一歩として非常に有効だと感じました。テスト実行だけで型情報が自動生成されるため、手動で記載する手間が大幅に省けます。

Inline RBSだけの生成も可能なので、ドキュメント生成ツールとしても活用できそうです。これは人間にとって読みやすいだけでなく、GitHub Copilotなどの生成AIツールにも型情報を提供できるメリットがあります。生成AIがInline RBSから型情報を読み取れば、より正確なコード提案が得られるのではないかと期待しています。

将来的には、RBS::InlineRBS本体に組み込まれる計画もあるようで、Inline RBSだけで型チェックができる日も来るかもしれません。

活用における注意点

型情報の出力結果はテストの実行内容に依存します。例えばStringとnilを返すメソッドがあっても、nilを返すケースのテストがなければ、型情報はStringだけになってしまいます。つまり、RBS::Traceを活用するには、テストの品質確保が前提となります。

また、Railsプロジェクト全体にRBSやSteepを導入した場合、相応のメンテナンス工数がかかると感じました。RBS::Traceだけでは、Rails自体が生成するクラスやメソッドの型情報は提供されないため、rbs_railsなどの併用や、手動での型情報メンテナンスも必要になるでしょう。

RBSのプロジェクトへの導入について

今回の試行から、Findy FreelanceプロジェクトへのRBSやSteep導入を決定したわけではありませんが、これらのツールとエコシステムの現状を実感できました。RBS::Traceのおかげで試すハードルが下がったのは大きな収穫です。開発者の@sinsoku_listyさんには感謝しています。

最後に

RubyKaigi 2025では、Rubyの型に関するセッションがいくつかありました。 すべてを聞けてはいませんが、総じてRubyの型に関するエコシステムは今後もまだまだ進化していきそうだと感じました。 正直、私は今までちゃんとキャッチアップができていなかったのですが、RubyKaigiへの参加をきっかけに興味が強くなり、理解を深めるきっかけとなりました。 今後もRBS, Steep, Sorbetなどの型に関するエコシステムの進化をキャッチアップしつつ、プロジェクトに導入するかどうかを検討していきたいと思います。

5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。 オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツが盛りだくさんなのでぜひご参加ください!!

pixiv.connpass.com

ファインディでは、一緒にRubyやRailsの開発をしてくれる仲間を募集しています。 興味のある方は、ぜひこちらからチェックしてみてください! herp.careers

参考

RubyKaigi 2025 レポート: deprewriter-ruby で非推奨メソッドの置き換えを自動化!

こんにちは!ファインディ株式会社でエンジニアをしているみっきーです。 先日開催されたRubyKaigi 2025に参加しました。 去年はLTの登壇があり、準備で忙しかったので、今年はたくさんのRubyistと話したり、セッションを見られることをとても楽しみにしていました!! 今回は特に印象に残った「On-the-fly Suggestions of Rewriting Method Deprecations」というセッションについて紹介します。

自己紹介

私はFindy Team+でバックエンドエンジニアとして働いており、普段はRubyを使った開発をしています。 また、プライベートでは「omochi gem」というRuby gemを開発・メンテナンスしています。 そのため、今回のRubyKaigi 2025で「On-the-fly Suggestions of Rewriting Method Deprecations」というセッションを見つけたときは、すぐに興味を持ちました。非推奨メソッドの置き換えを自動化できるツールがあれば、ユーザーの移行をスムーズにサポートできると思ったからです。

deprewriter-ruby

deprewriter-ruby gemの紹介

「On-the-fly Suggestions of Rewriting Method Deprecations」セッションでは、ohbaryeさんが開発した「deprewriter-ruby」というgemが紹介されました。このgemは、非推奨になったメソッドの呼び出しを検出し、新しいメソッドへの置き換え方法を自動的に提案してくれるツールです。

deprewriter-rubyの特徴は次の通りです:

  1. 設定ファイルによる柔軟な定義: YAMLファイルで非推奨メソッドとその代替メソッドのマッピングを定義できます。
  2. インラインの提案: コードを実行すると、非推奨メソッドが使われている箇所で、代替メソッドへの置き換え方法が提案されます。
  3. 自動修正機能: 提案された変更を自動的に適用することも可能です。

このgemは、Rubyの標準的な警告メカニズムを拡張して、単に「このメソッドは非推奨です」と警告するだけでなく、「このメソッドは非推奨です。代わりにこのように書き換えてください」という具体的な提案をします。

speakerdeck.com

動かしてみた

セッション後、早速deprewriter-rubyを試してみました。

まず、次のようにインストールします

# Gemfile
gem "deprewriter", github: "ohbarye/deprewriter-ruby"

次に、ライブラリコードで非推奨メソッドとその代替メソッドを定義します

# Library code
require "deprewriter"

class Legacy
  def old_method(arg)
    puts "Using deprecated old_method with #{arg}"
  end

  def new_method(arg)
    puts "Using new_method with #{arg}"
  end

  extend Deprewriter
  deprewrite :old_method, to: 'new_method({{arguments}})'
end

そして、非推奨メソッドを含むコードを実行すると

# User code
legacy = Legacy.new
legacy.old_method("example argument")

環境変数を設定して実行することで、異なるモードで動作させることができます

# ログモード - 非推奨メソッドの警告と提案を表示
$ DEPREWRITER=log ruby your_script.rb

# 差分モード - 変更の差分を表示
$ DEPREWRITER=diff ruby your_script.rb

# 書き換えモード - 自動的にコードを書き換え(注意して使用してください)
$ DEPREWRITER=dangerously_rewrite ruby your_script.rb

ログモードでは次のような警告と提案が表示されます

$ DEPREWRITER=log bundle exec ruby user_code.rb
Calling deprecated method:
W, [2025-04-21T19:49:55.132998 #2137]  WARN -- : DEPREWRITER: Legacy#old_method usage at user_code.rb:9 is deprecated. You can apply the diff below to resolve the deprecation.
--- ./user_code.rb      2025-04-21 19:49:55.123740000 +0900
+++ ./user_code.rb      2025-04-21 19:49:55.123740000 +0900
@@ -6,7 +6,7 @@

 # Call the deprecated method
 puts "Calling deprecated method:"
-legacy.old_method("example argument")
+legacy.new_method("example argument")

 # The deprewriter will detect this call and show a warning
 # suggesting to use new_method instead

Using deprecated old_method with example argument

書き換えモードでは次のような警告と変更がおこなわれます。

$ DEPREWRITER=dangerously_rewrite bundle exec ruby user_code.rb
Calling deprecated method:
W, [2025-04-21T20:11:49.426183 #39447]  WARN -- : DEPREWRITER: Dangerously trying to rewrite. It will rewrite a file to apply the deprecation and load the file
Calling deprecated method:
Using new_method with example argument
Using deprecated old_method with example argument

$ git diff
diff --git a/user_code.rb b/user_code.rb
index d1a2a2b..bfcab60 100644
--- a/user_code.rb
+++ b/user_code.rb
@@ -6,4 +6,4 @@ legacy = Legacy.new

 # Call the deprecated method
 puts "Calling deprecated method:"
-legacy.old_method("example argument")
+legacy.new_method("example argument")

手元の環境でdemoを作成し、試してみました。 驚くほど簡単に非推奨メソッドの検出と提案が行われ、ユーザーがコードを更新する手間を大幅に削減できることを実感しました。

作者のohbaryeさんに聞いてみた

セッション後、ohbaryeさんに直接話を聞く機会がありました。セッションの内容だけでなく、ツール開発のきっかけや普段の情報収集の方法についても興味深いお話を伺うことができました。

ohbaryeさんはHacker Newsを定期的にチェックして、技術トレンドや面白いプロジェクトの情報を集めているそうです。 セッション内で紹介のあったPharoというプログラミング言語もHacker Newsで見かけたそうです。 また、PharoのDeprewriterに触れたきっかけで、deprewriter-rubyを作ったそうです。

「最近は日本語版もあるので、英語が苦手な方にもとっつきやすくなりました」と教えてくれました。さらに、情報収集の効率化について次のようなアドバイスもいただきました。

著名人や有名企業(Shopify、GitHubなど)のブログなど質の高い情報ソースはfeedlyというRSSリーダーを使って集めるようにしています。すべての未読記事を消化するのは大変なので、時間があるときに気になったものだけを読むようにしていますね。

このような効率的な情報収集の方法は、私も取り入れていきたいと思いました。 「必要だから作る」という実践的なアプローチにも共感し、自分のプロジェクトでも同じような姿勢で取り組んでいきたいと感じました。

ohbaryeさんに直接感想を伝えられたことで、オープンソースコミュニティの温かさも実感できた貴重な機会でした!

最後に

deprewriter-rubyは、Rubyの非推奨メソッドの置き換えを自動化するという、一見小さな問題に焦点を当てたgemですが、その影響は大きいと感じました。 非推奨メソッドの変更は避けられないものであり、ユーザーの移行をいかにスムーズにサポートするかは、ライブラリ開発者にとって重要な課題です。 day3のRuby Committers and the World内で、Matzも後方互換を大切にしている話をしていました。

x.com

現在のdeprewriter-rubyには、複雑なメソッド呼び出し(メタプログラミングなど)の書き換えができない、非推奨メソッドが呼び出されるたびに書き換え処理が実行されて効率が悪い、非標準のRubyライブラリに依存しているといった制限があります。 しかし、ohbaryeさんはこれらの課題に対して、より複雑なメソッド呼び出しへの対応、最初の呼び出し後の処理をスキップして最適化する、依存関係を減らしてスタンドアロン化するといった明確な改善計画を持っています。 このような継続的な改善への取り組みを見ると、今後さらに実用的なツールへと進化していくことが楽しみです!!

RubyKaigi 2025では他にも多くの興味深いセッションがありました。 Rubyコミュニティの活発な技術共有やライブラリやツール開発の文化に、改めて感謝の気持ちを抱いた3日間でした。

5/13(火)に、「After RubyKaigi 2025〜ZOZO、ファインディ、ピクシブ〜」として、ピクシブ株式会社、株式会社ZOZO、ファインディ株式会社の3社でRubyKaigi 2025の振り返りを行います。

オンライン・オフラインどちらもありLTやパネルディスカッションなどコンテンツが盛りだくさんなのでぜひご参加ください!!

ファインディでは、一緒に働く仲間を募集しています!! 興味を持っていただいた方はこちらのページからご応募お願いします。

herp.careers

【エンジニアの日常】これが私の推しツール!〜日々の開発を豊かにするおすすめツール〜 Part3

こんにちは。Findy Tech Blog編集長の高橋(@Taka_bow)です。

この記事はこれが私の推しツール!シリーズの第3弾になります。今回も、推しツール紹介と題して、弊社エンジニア達が日々の開発業務で愛用しているツールやOSSを紹介していきます。

トップバッターは奥田さんです!

■ 奥田さん / PdM室 / GenAIイネーブルメント ■ データサイエンティストのだーさん (@Dakuon_Findy) です。2025年の1月よりファインディのプロダクトマネジメント室 GenAIイネーブルメントチームにデータサイエンティストとして参画しております。このチームでは、LLMを活用した各種プロダクトの強化や、社内オペレーションの改善に取り組んでいます。

Polars (Pythonライブラリ)

Polarsの概要

Polarsは、高速かつ明示的なスキーマ定義を特長とするデータフレームライブラリです。大規模データであってもローカル環境でカラムの型の一貫性を保ったまま効率的に処理できるため、信頼性とスピードを両立したデータ分析が可能になります。

Polarsを使ってる様子

Polarsの推しポイント

処理が速い!型がある!の2点です。この2点がデータ分析における推しポイントとなる理由を以下で詳述します。

1. 処理速度

前職で経験したのですが、pandasで2時間かかっていた処理が、Polarsに書き換えた途端に5分少々で完了し、驚愕しました。処理が速すぎて「処理の間に別のコードをのんびり書こう」が成立せず、自分のタイピング速度が開発のボトルネックになっているような感覚になります。ひりつきますね。

次の図は公式が公開している、Parquet形式データ読み込みを含んだ処理速度のベンチマークです。例えば1つめのクエリ (Q1) ではpandasは25秒、Polarsでは約1.5秒と、約16.7倍の速度が出ており、全体を通じてPolarsの処理速度はpandasの約11倍 (Q6) 〜81倍 (Q5) の速度が出ていることが見て取れます。

Updated PDS-H benchmark results より引用

2. カラムごとに明確な型を持てる

Polarsでは、スキーマを用いてカラムごとに明示的な型を定めることができます。たとえば「数値型 (pl.Int64)」とスキーマで決めたカラムに文字列が混ざっていると読み込み処理の段階で型エラーになります(もちろん、Object 型を使えば混在も可能ですが、それを明示しない限りはエラーで気づけます)。これは一見「融通が利かない」と思われがちですが、表記揺れに気づけたり、特定の型を前提とした処理を組んでもバグが起こりづらいというメリットがあります。

正常なスキーマでCSVを読み込んだ例

誤ったスキーマでCSVを読み込んだ場合のエラー

Polarsがここまで快適なのは、単なる実装の工夫ではなく、そもそもの「設計思想」によるところが大きいと考えられます。公式ドキュメントでも次のように述べられています:

Philosophy

The goal of Polars is to provide a lightning fast DataFrame library that:

  • Utilizes all available cores on your machine.
  • Optimizes queries to reduce unneeded work/memory allocations.
  • Handles datasets much larger than your available RAM.
  • A consistent and predictable API.
  • Adheres to a strict schema (data-types should be known before running the query).

Polars is written in Rust which gives it C/C++ performance and allows it to fully control performance-critical parts in a query engine.

これを見てみると、私の推しポイントはまさにこの設計思想の上に成り立っていることがわかります。

  • “provide a lightning fast DataFrame library” → 推しポイントの「処理が速い」につながる話です。まさに "lightning" な速度です。
  • “Strict schema” → 推しポイントの型の話そのものです。明示的な型で処理を行えるという安心感は本当に大きいです。

つまり、「速い・型がある」が推しポイントになるデータフレームライブラリというのはPolarsが最初から目指していたものだった、というわけです。

たとえば長期の時系列データのように、機器の変更などで表記揺れが発生しやすくデータ量も非常に大きいケースでは、Polarsの「型」と「処理速度」の強みが特に活きます。そんな場面に出会ったときにはぜひPolarsのことを思い出してもらえると嬉しいです。

Polarsの設計思想にしっかり踏み込み、自分の手で試したからこその実感が伝わる記事ですね。速度と型、安全と快適さを追い求めるPolars愛が詰まったツール紹介でした!

次は、久木田さんです!

■ 久木田さん / プロダクト開発部 / バックエンド・SRE ■ Freelance開発チームの久木田です。ファインディ入社3年目です。 バックエンド開発からインフラ構築、SRE的業務にも携わっています。

HTTPie

HTTPieの概要

HTTPieとはコマンドラインで使えるHTTPクライアントツールです。curl の代替として使用できるものです。 コマンドが直感的でわかりやすく、レスポンスのJSONは自動的に色付けされて見やすく表示されます。まだβ版ですが、Webアプリやデスクトップアプリも提供されています。

HTTPieを使ってる様子

HTTPieは、レスポンスがJSONであれば自動で整形・色付けしてくれます。

公式サイトのインタラクティブなデモページでCLIコマンドを試すことができるので、ぜひそちらでも試してもらいたいです。

デスクトップアプリも使っており、こちらも使いやすく愛用しています。 デスクトップアプリ版は基本的にはGUIで操作するようになっており、リクエスト履歴の管理などがしやすくAPIテストを効率的にできます。

HTTPieの推しポイント

  • CLIで使う場合

    • JSON出力の見やすさ
      • 自動整形とシンタックスハイライトでAPIレスポンスの確認がしやすい
    • オプションが簡潔
      • 覚えるオプションが少なく、短く済むので直感的に使える
  • デスクトップアプリの場合

    • 使いやすいUI
      • リクエストの作成や管理が簡単
    • コード生成機能
      • 作成したリクエストをcurlコマンドやPython (requests)、JavaScript (fetch)など、他の言語やツールのコードに変換してくれる機能が便利

CLIとデスクトップアプリと場合によって使い分けできるので、API開発やテストでJSONデータを頻繁に扱う人に、HTTPクライアントツールとしておすすめです。

AWS Peacock Management Console

AWS Peacock Management Consoleの概要

Chromeの拡張機能で、AWSコンソールを使いやすくするためのツールです。 AWSアカウントIDによってAWSコンソールのヘッダーの色を変更してくれます。 また、右上のログインユーザーの情報部分にアカウントのエイリアスも表示してくれます。

AWS Peacock Management Consoleを使ってる様子

拡張機能に追加してもらったあとに、オプションで設定をします。 適用したいアカウントIDを入力し、環境ごとに設定したい色を16進数のカラーコードで指定します。

指定することで次のように色が変わります。 アカウントのエイリアスの表示は自動でしてくれます。

Staging環境

Production環境

AWS Peacock Management Consoleの推しポイント

  • 自分がログインしている環境を間違えない。
    • プロダクト・環境ごとにAWSの環境が違うのですが、ヘッダーの色分けによって自分が今どのアカウントにいるかを常に把握できます。

複数のAWSアカウントを使っている開発者にはぜひ導入してもらって、事故を減らしてもらえたらと思います。

直感的な操作感と環境ごとの安全設計にこだわったHTTPieとAWS Peacock Management Console。CLIとGUIを使い分けながら、開発の快適さを追求したツール紹介でした!

最後は金丸さんです!

■ 金丸さん / プロダクト開発部 / バックエンド ■ Findy Freelance バックエンド開発の金丸です。 オペレーション改善などのバックオフィス機能開発を中心として、Findy Freelanceのバックエンド開発に携わっております。

Warp

Warpの概要

Warpは次の特徴を持つターミナルツールです。 - 強力なコマンド補完 - コマンド単位で入出力を管理するブロック - ショートカット、ペイン分割などのカスタマイズ可能

生成AIと連携したAgentモードも特徴の1つです。 (私自身はまだ使いこなせていません。。。)

Warpの推しポイント

推しポイントは2つあります。

サジェストが強力

過去に実施したコマンドの傾向やエラー出力をもとに、次に実施するべきコマンドをサジェストしてくれます。

例: git add -> git commit -> push branch -> open pull request の流れを自動的に補完してくれる

上記のGIFでは git push でエラーが出た際に出力された git push --set-upstream origin hogehoge をサジェストしてくれています。

ブロック機能

Warpはコマンド実行単位でブロックが作成されます。

ブロック機能により、各コマンドに対応する出力結果が明確になり、理解しやすくなるという利点があります。 また、ブロック内の出力だけを選んでコピーしたり、検索したりする操作も簡単に実施できます。

  • 特定コマンドの実行結果をコピーする

  • 出力結果内から該当箇所を検索する

なるほど、Warpの補完とブロック機能は確かに嬉しいですね。日々のターミナル操作がぐっと快適になる感じ、良さそうです。使い込むほどに良さがわかるツールですね。

おわりに

今回ご紹介した3名も、それぞれの開発スタイルに合わせてツールを選び抜き、日々の作業を快適にする工夫を重ねていましたね。スピードや操作性、ミスの防止など、それぞれが注目したポイントからはエンジニアとしてのこだわりが垣間見えます。道具選びひとつ取っても、開発生産性と心地よさを追求する姿勢が伝わってきました!

ファインディでは、さまざまなバックグラウンドを持つエンジニアが活躍しています。技術にこだわり、より良いものを追求する仲間とともに働いてみませんか?

現在、ファインディでは新しいメンバーを募集中です。 興味のある方は、ぜひこちらからチェックしてみてください!

DifyでPdMの問い合わせ対応工数0に!サービスの仕様を理解した社内Botを作った話

こんにちは。ファインディでデータサイエンティストをしています、sasanoshouta(@Edyyyyon)です。

今回は、社内向けに提供している「Findy Team+の使い方について、ざっくばらんに質問できる」社内Botを作成した際の取り組みと、そこから得た学びをご紹介します。

どんなBot?

まず、今回作成したBotの概要を、代表的な2つのユースケースを通じてご紹介します。 1つ目の代表的なユースケースとして、Botの名前の通り、「Team+の使い方」に関する質問ができます。 ユースケース1では、Team+のDevOps分析画面の使い方についての質問に、Botがどのように回答するかを紹介します。

後述しますが、こちらのBotを作る上で基にした情報はサポートページの内容をコンテキストとしている為、URLを付与して返答する事のできる質問に対しては基本的にURL付きで返答を返してくれます。

  • ユースケース1: デプロイしたはずのプルリクがDevOps分析に表示されない原因を教えて下さい
デプロイしたプルリクがDevOps分析に表示されない場合、以下のような点をご確認いただくと原因を特定しやすくなります。
1. スタッツ管理で設定している正規表現が実際のブランチ名やタグ名に合っていない 
   例として、「^(main|master|pri)$」のように設定している場合、ブランチ名が「pri/~」などスラッシュを含む形だとマッチせず、デプロイ対象に含まれません。 
   → 正規表現を見直し、「^(main|master|pri.*)$」など適切な設定に修正いただくと改善されるケースがあります。 
2. デプロイ(プルリク作成/タグ付け)を行ったメンバーが対象チームに所属していない、または表示対象OFFになっている 
   → プロダクト画面上で、チームメンバーの設定や表示設定が正しく行われているかどうかご確認ください。
3. モニタリングのラベル設定が適切でない 
   → 特定の文字列を含んだラベルのみを集計対象にしている場合は、ラベル付けのルールを再度ご確認ください。
上記を確認いただいても解決しない場合は、以下のサポートページや社内向けドキュメントURLもあわせてご参照ください。 
■サポートページ
・正規表現を使ったデプロイ頻度の集計例など 
  https://hogehoge
・DevOps分析の概要 
  https://fugafuga
もしご不明点や追加のご質問があれば、遠慮なくお知らせください。

2つ目の代表的なユースケースとして、単純なTeam+の使い方だけでなく 「特定のシチュエーションを想定した時にTeam+をどのように活用すると良いのか?」 と言うようなやや抽象度が高い質問や、壁打ちのような質問にも対応することができます。

  • ユースケース2: プロダクトオーナー、プロダクトマネージャーの育成観点でどんな活用方法が有効ですか?また重視するといい指標があれば教えてください。
プロジェクトオーナーやプロダクトマネージャーの育成には、以下のようなTeam+活用方法が考えられます。
1. プロジェクト全体を俯瞰できる情報共有 
   - 各担当メンバーのタスクを整理し、進捗と課題を可視化することで、オーナーおよびマネージャーが的確に優先度を判断しながら学習・意思決定できる環境を整えます。
2. 定量的なデータにもとづく振り返り 
   - スプリント終了時やマイルストーンごとに、タスク消化数・リードタイム・タスク完了率などのデータを確認しながら振り返りを実施することで、チームビルディングの視点を持ちつつ育成が行えます。
3. コラボレーションを通じたスキル学習 
   - 例えば、生産性向上やユーザーインタビューなど多面的な業務に関わる際、それぞれ詳しいメンバーからレビューやフィードバックをもらえる仕組みを作り、学習機会を増やすことで、オーナーやマネージャーへ幅広いスキルを身につけさせることができます。
重視するとよい指標の例:
• タスク完了率・計画対実績の差 
• リードタイム(アイデアからリリースまでの期間) 
• タスクごとの担当メンバーのフィードバック量・質 
• プロダクト改善提案数(チームやユーザーからの新たなアイデア) 
• チームメンバー間のコミュニケーションログやレビューの頻度 
これらを定期的にモニタリングし、学習サイクル(学習→実践→振り返り)を短く回すことで、プロジェクトオーナーやプロダクトマネージャーが実践的に成長しやすくなります。
(中略)
これらをプロダクトオーナーやプロダクトマネージャーの活動に取り入れることで、顧客への価値提供や開発組織の生産性向上につながりやすくなります。

Bot作成の背景

このBotを作るに至った背景は、以下のような理由からです。

  • 社内PdMに舞い込んでくるプロダクトに関する問い合わせに対する業務効率化

  • Difyを社内用に解放しており、既にいくつかのBotがSlackワークスペースに産み出されていた

社内PdMに舞い込んでくるプロダクトに関する問い合わせに対する業務効率化

Team+には、日夜プロダクトの進化を支えるPdMが6名在籍しています。Team+はユーザー様に多様な機能を提供している性質上、様々な観点での問い合わせが届きます。 平均すると、1人1日2件程度は問い合わせをいただいており、これに対応する必要があります。

Difyを社内用に解放しており、既にいくつかのBotがSlackワークスペースに産み出されていた

カレー屋さんBotをはじめ、ファインディ内では幅広い職種が使うことのできる多様なBotが稼働しています。これらのBotは社内向けにホスティングされたDifyで作成されており、希望者はDify上で色々なテストをすることができるので、気軽にBotを始めとしたツール作成にチャレンジをすることができます。

こうした背景と、今年よりプロダクトマネジメント室に所属するデータサイエンティストが生成AI活用を推進するミッションを背負うようになった事もあり、「勝手にBot作ってみよう。面白いものができそうだよね」と言うチャレンジから産まれました。

(余談ですが、同じやってみた系の取り組みとして同チームメンバーのだーさん(@Dakuon_Findy)が書いてくれた記事もありますので、是非ご覧になってみてください。)

tech.findy.co.jp

どのように作成したか

作り方自体は至ってシンプルです。以下の情報をコンテキストにしたBotを、簡単なプロンプトを添えてDify上に作成するだけです。

  • Team+のサポートページコンテンツ100ページ分

  • 社内に蓄積された問い合わせFAQ100問分

プロンプトも非常にシンプルで、実際にはたった4行で構成されています。

あなたはTeam+というプロダクトについて知り尽くしているプロフェッショナルです。
ユーザーからの問い合わせに対して相応しいものをコンテキスト内から回答してください。
コンテキストの情報には、プロダクトのサポートページURLまたは社内ドキュメントのURLが添付されているはずです。
コンテキストの情報を元に回答が行える場合は、参照元のURLをユーザーへ提供してください。

たったこれだけで出来てしまいました。

作成した結果どうだったか

Bot作成前、PdMに届いていた問い合わせの件数が新機能に関する問い合わせを除きほぼゼロにすることができました。

また、このBotは社内で職種問わず広く使われているのですが、以下のような声を多数いただくことができました。

社内の声

今後について

このように社内でも好評を得られていることから、プロダクトへの実装を進めようと考えています。

実際にプロダクト上への実装を行う為に、コンテキストの強化・Ops構築やハルシネーション対策など、生成AIを活用した機能ならではの考慮すべき点が論点として残っていますので、これらを解消していく予定です。

最後に

ファインディでは、データサイエンティストを始めとするデータアナリスト、データエンジニアなどのデータ系職種の採用を強化中です。 こちらの記事を読んでいただき、取り組みに興味を持っていただいた方は是非カジュアル面談でお話しましょう。

herp.careers

また、こちらの記事は「プロダクトマネージャーLT Night」と言う弊社主催のイベント内でも登壇させていただいた内容になります。 その際の登壇資料も掲載しておきますので、ぜひご覧ください。

speakerdeck.com