Home Software Libraries Ruby activerecord Active Recordのallが持つ条件を引き継ぐ性質と動的クエリ構築への応用 @wakairo 22 Feb, 2026 06:32 +00:00 RailsのActive Recordのallメソッドは、 その名前から「テーブルに存在する全レコードを取得するメソッド」と理解されがちです。 しかし実際には「その時点までに積み上げられた条件に該当するものを全て取得する」という実務上とても重要な性質を持っています。 本記事では、このallの性質の基本と、動的にデータベースクエリを組み立てる際の実践的な応用テクニックについて解説します。 allは「構築済みの条件」を引き継ぐ データベースにPenモデルのレコードが3件保存されているとします。 以下のようにPenクラスに対して直接allを呼び出すと、テーブルに存在する全てのレコードが取得されます。 test(dev):000> Pen.all Pen Load (1.2ms) SELECT "pens".* FROM "pens" /* loading for pp */ LIMIT 11 => [#<Pen:0x00007cdf641ba6f0 id: 1, color: "red", price: 80, created_at: "2026-02-20 05:52:53.460900000 +0000", updated_at: "2026-02-20 05:52:53.460900000 +0000">, #<Pen:0x00007cdf66b23208 id: 2, color: "blue", price: 80, created_at: "2026-02-20 05:53:03.457025000 +0000", updated_at: "2026-02-20 05:53:03.457025000 +0000">, #<Pen:0x00007cdf66b230c8 id: 3, color: "red", price: 150, created_at: "2026-02-20 05:53:10.752331000 +0000", updated_at: "2026-02-20 05:53:10.752331000 +0000">] では、別の条件に続けてallを呼び出すとどうなるでしょうか。 以下の実行例を見ると分かるように、allは「全て」ではなく、「それまでの条件を満たすもの全て」を返します。 ここではcolor: "red"を満たすレコードだけが取得されています。 test(dev):000> Pen.where(color: "red").all Pen Load (0.1ms) SELECT "pens".* FROM "pens" WHERE "pens"."color" = 'red' /* loading for pp */ LIMIT 11 => [#<Pen:0x00007cdf66b20648 id: 1, color: "red", price: 80, created_at: "2026-02-20 05:52:53.460900000 +0000", updated_at: "2026-02-20 05:52:53.460900000 +0000">, #<Pen:0x00007cdf66b20508 id: 3, color: "red", price: 150, created_at: "2026-02-20 05:53:10.752331000 +0000", updated_at: "2026-02-20 05:53:10.752331000 +0000">] このように、allは単に全件取得する機能だけでなく、 手前に条件がある場合には「そこまでに構築されたクエリ条件(Relationオブジェクト)を引き継ぎ、そのまま返す」という機能を担う側面があります。 応用例:動的クエリ構築においてallを起点にしてメソッドチェーン可能にする この「そこまでに構築された条件を引き継ぐ」というallの性質は、 「URLのクエリパラメータが存在する場合だけ条件を追加する」といった動的なデータベースクエリ構築において役立ちます。 例えば、クエリパラメータを処理するfilter_byというスコープを定義することを考えてみましょう。 このスコープは、以下のように「1年以内に作成されたレコード」という前提条件(where)のあとにメソッドチェーンで呼び出される想定です。 test(dev):000> Pen.where(created_at: 1.year.ago..).filter_by(color: "red", max_price: 100) Pen Load (0.4ms) SELECT "pens".* FROM "pens" WHERE "pens"."created_at" >= '2025-02-21 05:50:22.982591' AND "pens"."color" = 'red' AND "pens"."price" <= 100 /* loading for pp */ LIMIT 11 => [#<Pen:0x000072d2c03393d8 id: 1, color: "red", price: 80, created_at: "2026-02-20 05:52:53.460900000 +0000", updated_at: "2026-02-20 05:52:53.460900000 +0000">] この挙動を実現するfilter_byスコープの実装は以下のようになります。 最初にallを呼び出して現在のRelation(既に構築済みのwhere条件など)を変数pensに格納し、 そのpensに対してパラメータの有無をチェックしながら条件を継ぎ足しています。 class Pen < ApplicationRecord scope :filter_by, ->(params) do pens = all pens = pens.where(color: params[:color]) if params[:color].present? pens = pens.where(price: ..params[:max_price]) if params[:max_price].present? pens end end ここで重要になるのが、filter_byの中で最初にallを呼んで変数に入れている点です。 allは「何も条件がない状態」ではなく、 「このスコープが呼ばれた時点のRelationをそのまま受け取る」役割を果たしています。 そのため、Pen.where(created_at: …)のように手前で条件を付けていても、 それをリセットすることなくさらに条件を積み上げられます。 つまり、allを変数に入れることで、 手前の条件を維持した上でさらに条件を積み上げていくための「起点」を作ることができるのです。 なお、クエリパラメータが全て空で条件追加がない場合でも、allが返した元のRelationがpensに入っているため、 メソッドチェーンをそのまま継続できます。 (参考)個別スコープへの切り出しとそのタイミング 先ほどの例ではfilter_byの内部で変数に再代入しながら条件を追加していましたが、 もしcolorやpriceによる絞り込みをアプリケーション内の他の場所で単独で利用したい場合は、 それぞれを個別のスコープとして切り出すことでコードをよりキレイに書くことができます。 Active Recordのスコープは、ブロックの評価結果がnilまたはfalseになった場合、 自動的に元のRelationをそのまま返す(条件を追加せずにスルーする)という便利な仕様になっています。 そのため、以下のようにメソッドチェーンでそのままつなぐだけで、条件分岐をスコープの中に自然にカプセル化できます。 class Pen < ApplicationRecord scope :with_color, ->(color) { where(color: color) if color.present? } scope :with_max_price, ->(max_price) { where(price: ..max_price) if max_price.present? } scope :filter_by, ->(params) do with_color(params[:color]).with_max_price(params[:max_price]) end end なお、YAGNI原則の観点からは、 これらの個別スコープ(with_colorやwith_max_price)が他の場所で「本当に必要になるまで」は、 無理に切り出さない方が良いでしょう。 つまり、最初から再利用性を過剰に意識してスコープを量産するよりは、 まずは前述のpens = allを使うアプローチで1箇所にまとめておき、 個別スコープが本当に必要になったタイミングで切り出す方が、 結果的には開発コストを減らせるはずです。
Home Software Unix Unix commands bash UNIX環境向け簡易通知システムの例 @wakairo 20 Feb, 2026 02:25 +00:00 UNIXシステムの既存機構を活用したミニマルな通知システムの実装例です。 複数の通知元から任意の通知メッセージを受信し、それらをシェルプロンプトに表示できます。 個人利用の環境(趣味用途や開発用途など)への導入を想定しています。 コードの短さと処理の軽さが特徴です。 カスタマイズや環境依存部分の調整を前提としています。利用前にコードに一通り目を通してください。 通知先(メッセージ受信側)のコードはBash用です。Bash以外のシェルではそのシェルに合わせた変更が必要です。 設計思想 本システムの設計の核は「付け加えるものは最小限に、UNIXそのままの仕様と挙動を活かす」ことです。 本システムは、「ファイル」と「シェルのプロンプト更新」という既存機構のみを用いた通知機構です。そのため、常駐プロセスは不要です。 通知状態を「ファイルの存在」に還元することで、通知の生成・可視化・永続化・消費を、仕様と実装の両面で、OSに委ねます。そのため、同期や状態管理のための複雑な機構は不要です。 プロンプト表示時にのみ通知を消費します。そのため、常時監視のためのリソース消費と実装複雑性は不要です。 以上の基本思想に合わせ、厳密な順序保証やマルチユーザ間の公平性よりも、単純性・可搬性・挙動の自明さを優先する設計とします。 システム仕様(外部仕様)1. システム概要 ファイルシステムを介してユーザのシェル(Bash)のプロンプトに通知を表示する軽量な仕組みです。例えば、systemdユニットの実行失敗通知に利用できます。 2. 基本仕様 通知メッセージ: 通知したい内容そのものを「ファイル名」とします(ファイルの中身は空で構いません)。 通信路: /var/lib/notify_inbox/ ディレクトリを使用します。 通知発行: 前述のディレクトリに、touchコマンドなどで、メッセージをファイル名とした空ファイルを作成します。 永続性: メモリ(RAMディスク)ではなくディスクを使用するため、OS再起動後も通知は保持されます。 表示タイミング: シェルプロンプトがユーザに表示される直前のタイミングでチェックされ、プロンプトと同時に表示されます。 ライフサイクル: 通知は一度だけ表示され、表示直後に自動削除されます。 重複排除: 同じファイル名(同じメッセージ)はシステム上に1つしか存在できません。 順序: 複数のメッセージがあるときは、アルファベット順(ロケール依存の辞書順)で表示されます。 3. 注意事項・制約 ファイル名(通知メッセージ)の制限: メッセージの最大長はファイルシステムのファイル名の最大長(最大バイト数)に依存します。 /(スラッシュ)はファイル名に使えません。 .(ドット)から始まるファイル名については、通知と削除が行われるかどうかは、Bashのdotglobの設定に依存します。 ファイル名にスペースを含めても動作しますが、見やすさのため _ や - の使用を推奨します。 特殊文字やマルチバイト文字を含む通知メッセージ: 通知メッセージ(ファイル名)に記号やスペースを含む場合、適切なエスケープやクォートが必要です。通知元での記述仕様を確認してください。 通知メッセージに非ASCII文字を使うときには、systemdのユニットファイルなど、メッセージの文字列を含むファイルの文字コードとUNIXシステムのロケールが整合するようにしてください。 通知元と通知先でロケール(言語設定)が異なるせいで文字化けが発生するなど、ロケールによる不具合が起こる場合があります。必要に応じて、メッセージの文字種を問題が起きない文字(ASCII文字など)に限ったり、適切にロケールを設定したり(例: env LC_ALL=ja_JP.UTF-8 touch "/var/lib/notify_inbox/01_メッセージ")してください。 複数のユーザで利用している環境での利用: 通知ディレクトリは全ユーザ読み書き可能(0777)です。システム上の全ユーザが通知を見ることができます。 マルチユーザ環境の場合、このプロンプト表示システムを導入したユーザの中で「最初にシェルを操作した人」だけが通知を受け取り、ファイルが消去されます(早い者勝ち)。 セキュリティ: この簡易システムは詳細なセキュリティの検討を経たものではありません。セキュリティの確保が必要な環境では使用しないでください。 実装コード一式Step 1. 共有ディレクトリの設定(管理者権限) systemdの機能を使って、起動時にディレクトリを作成し、適切な権限を与えます。 作成ファイル: /etc/tmpfiles.d/notify_inbox.conf # Type Path Mode UID GID Age Argument d /var/lib/notify_inbox 0777 root root - - 解説: 0777 に設定し、スティッキービット(t)を意図的に付けていません。これにより、rootが作成した通知ファイルを一般ユーザが削除できるようになります。 設定の反映: sudo systemd-tmpfiles --create Step 2. 通知先(受信側)の設定(一般ユーザ) ユーザの .bashrc に、プロンプト表示時に通知をチェックし、 通知があれば表示する処理を追加します。 編集ファイル: ~/.bashrc # --- 簡易通知システム 受信関数 --- notify_in_prompt() { NOTIFY_PS1_PREFIX='' local inbox_dir="/var/lib/notify_inbox" # 1. ファイル一覧を取得 local files=("$inbox_dir"/*) # 2. ディレクトリがない、または、ディレクトリがあっても # ファイルがない場合は以後の処理をしない (このことが軽量化のポイント) # 該当する場合は、nullglobがオンでもオフでも早期リターンする # こちらの方が、shopt -p nullglobで前の状態を保存してevalで戻すよりも高速 # nullglobがオンで、マッチするファイルがない場合、 # 配列の要素数が0になるのでここでreturnして以後の処理はしない。 if [ ${#files[@]} -eq 0 ]; then return fi # nullglobがオフで、マッチするファイルがない場合、 # 配列の先頭要素に パターン文字列 "/var/lib/notify_inbox/*" がそのまま入る # 「そのパスにファイルが存在しない」かつ「文字列がパターンと一致する」ならファイル無し if [ ! -e "${files[0]}" ] && [ "${files[0]}" = "$inbox_dir/*" ]; then return fi # パスを取り除き、ファイル名のみの配列を作成 local names=("${files[@]##*/}") # 複数の通知がある場合、スペース区切りで連結される # # 区切りをスペースから変更したい場合は、デフォルトの IFS を、 # local 変数として上書きすることで、結合文字を変更できる # ※ "${names[*]}" の展開時に、IFSの先頭の1文字が使われる # # 例1: 縦棒(パイプ)区切りにしたい場合 "Msg1|Msg2" # local IFS='|' # 例2: 区切り文字なしにしたい場合 "Msg1Msg2" # local IFS='' # # ここではスペース区切りで連結した上で #「赤背景・白文字」で目立たせて表示する # 色コード(\e[...m)は \001 と \002 で必ず囲む。 # (\001 及び \002 は Readline に「非表示文字列」を知らせるマーカー) # これを忘れると、Bashがプロンプトの長さを計算できず、 # 長いコマンド入力時や履歴を遡った時に表示崩れが発生する。 # またANSI-C Quoting(シングルクォーテーション囲みの前に$を付ける)で # バックスラッシュエスケープを解釈させ実バイトを埋め込む。 NOTIFY_PS1_PREFIX=$'\001\e[41;37m\002 '"${names[*]}"$' \001\e[0m\002' # シンプルにそのまま表示する場合 # NOTIFY_PS1_PREFIX="${names[*]}" # 3. ファイルを一括削除 (表示済みとして消費) # ユーザがrmをエイリアスで定義していても、そのエイリアスは無視 # -- を挟むことで - で始まるファイル名があったとしてもオプションとして解釈されない command rm -f -- "${files[@]}" } # 1. PROMPT_COMMAND に登録 # プロンプトが表示される「直前」に毎回実行され、変数 NOTIFY_PS1_PREFIX を更新する # # 注意: コードの複雑化を避け、可読性を優先するため、.bashrcの複数回読み込み(リロード)に対する # ガード処理は省略しています。必要があれば環境に合わせて書き換えてください。 if [ -z ${PROMPT_COMMAND:+x} ]; then # PROMPT_COMMANDが定義されていない、または、空文字列のときは単純にセット PROMPT_COMMAND="notify_in_prompt" elif declare -p PROMPT_COMMAND 2>/dev/null | grep -q '^declare \-[a-z]*a'; then # PROMPT_COMMANDが配列の時は先頭要素として追加 PROMPT_COMMAND=("notify_in_prompt" "${PROMPT_COMMAND[@]}") else # PROMPT_COMMANDが空でない文字列の時はその前に追加 PROMPT_COMMAND="notify_in_prompt; $PROMPT_COMMAND" fi # 2. PS1 の先頭に変数を埋め込み # 必ず 'シングルクォート' で囲む。 # ダブルクォートだと「読み込み時」に展開されてしまうが、 # シングルクォートなら「表示のたび」にその時点の変数が参照される。 PS1='${NOTIFY_PS1_PREFIX}'"$PS1" 設定の反映: exec $SHELL -l Step 3. 通知元(送信側)の設定例 systemdユニットファイルに通知コマンドを仕込みます。 例: バックアップサービスのユニット (/etc/systemd/system/backup.service) [Unit] Description=Daily Backup Service [Service] Type=oneshot ExecStart=/usr/local/bin/backup_script.sh # --- 通知機能の追加 --- # 処理が失敗した場合のみ通知ファイルを置く # ファイル名: "[!Backup_Failed]" ExecStopPost=/bin/bash -c 'if [ "$EXIT_STATUS" != "0" ]; then touch "/var/lib/notify_inbox/[!Backup_Failed]"; fi' 解説: ExecStopPost: サービス終了時(成功・失敗問わず)に実行されます。 $EXIT_STATUS: systemdが終了時に設定する環境変数の一つです。0 以外ならエラーと判断しています。確実な失敗通知には OnFailure= の利用も検討してください。 touch "...": ファイルを作るだけです。このファイル名がそのまま通知メッセージになります。 Step 4. 動作確認 実際に通知が表示されるかテストします。 テスト用通知を作成(手動): # 任意の場所で実行 touch "/var/lib/notify_inbox/[!Test_Message]" プロンプトを表示(Enterキーを押す): ターミナルで Enter を押したタイミングで、プロンプトの前に以下のように表示されます。 [!Test_Message] user@hostname:~$ 消去確認: もう一度 Enter を押し、通知が消えることを確認します。 systemd経由のテスト(失敗シミュレーション): ExecStart=/bin/false と書いたダミーのユニットを作成して systemctl start し、プロンプトにエラーが出るか確認してください。
Home Software Windows Windows Subsystem for Linux (WSL) WSLとVS Codeの組み合わせで快適に開発するためのセットアップ @wakairo 14 Feb, 2026 07:06 +00:00 WSLはVS Codeと連携させることが可能です。 具体的には、VS CodeからWSL内のOSにアクセスできるようにセットアップを行うと、 VS Codeの便利機能がいろいろと使えたりして良いです。 連携のためには、単純にインストールするのではなく、そのためのセットアップが必要です。 具体的な手順などについては、VS Codeの開発元であるMicrosoftの以下のページをご覧ください。 https://learn.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-vscode
Home Software Unix Others systemd 【systemd Unitファイル】設定項目の説明が見つからない理由と公式manページの階層構造 @wakairo 13 Feb, 2026 13:12 +00:00 systemdのUnitファイルを読んだり書いたりしていると、 特定の設定項目について、どこに正確な説明があるのか分からず困ることがあります。 実はUnitファイルの設定項目は、ユニット種別ごとに1ページにまとまっているわけではなく、 複数のmanページに階層的に分散して記述されています。 本記事では、この階層構造を踏まえて公式マニュアルの読み方を整理します。 なお、この記事はsystemdのUnitファイルを編集した経験がある人を想定読者としています。 systemd Unitファイルの公式仕様はmanページにある systemdのUnitファイルの仕様や設定項目は、公式にはmanコマンドで表示されるマニュアルに集約されています。 例えば、Unitファイル全体の共通仕様は次のコマンドで確認できます。 man systemd.unit Web版は、systemdの開発主体が以下のサイトで公開しています。 https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html 注意:systemdのバージョンによって、ユニット種別や設定項目に違いがあります。 このリンク先は最新バージョンのマニュアルを指しているため、お手元の環境と違いがある場合はmanコマンドを優先してください。 マニュアルの読み方のポイント systemdのmanページは情報量が多く、設定項目がどのページにあるのか分かりにくい構成になっています。 そこで「設定項目がどの層に属するか」を意識して読むと見通しが良くなります。 マニュアルの「3層構造」 Unitファイルの設定項目などは、概念的には次の3層に分けられます。 第一層:全Unit共通の仕様(systemd.unit)と記法(systemd.syntax) 第二層:複数Unit種別で共有される設定(systemd.exec、systemd.kill、systemd.resource-control) 第三層:Unit種別固有の設定(systemd.service、systemd.timer、systemd.socketなど) マニュアルの索引:systemd.directives 「設定項目名は分かるが、どのマニュアルに載っているか分からない」ときは、索引を活用します。 man systemd.directivesコマンドで表示される systemd.directivesは索引ページに相当し、 全ての設定項目名と、それが記載されているページが一覧になっています。 このページを開いて項目名で検索するとどのページに説明があるかが分かります。 (Tips)「Additional options…」の誘導を鍵にページをたどる systemdのマニュアルでは、ページ冒頭付近に Additional options are listed in … という文が書かれていることがあります。 これは「このページに載っていない設定項目は、別のどのページに書かれているか」を示す案内です。 設定が見つからない場合は、ページ内でこの一文を検索し、リンクされているページを確認すると効率的です。 各層の代表的なページの概略第一層:全Unit共通の仕様(systemd.unit)と記法(systemd.syntax)systemd.unit(共通の仕様) 対象:全てのUnit 主な内容:ユニットの説明文、依存関係、起動・有効化の条件 設定項目の例:Description=、Requires=、After=、ConditionPathExists= 主なセクション:[Unit]、[Install] 参考:Specifiers(%iや%h)についても、systemd.unitのページの当該項目に説明があります。 systemd.syntax(共通の記法) 対象:全てのUnit 主な内容:基本的な構文、エスケープ処理、コメントの書き方 参考:systemd.timerで使う時間関連(48hrやWed *-1)の書き方はsystemd.timeのページです。 第二層:複数Unit種別で共有される設定 第二層の設定の特徴は、「記述するセクション」と「説明が書かれているmanページ」が一致しない点です。 例えばUser=やEnvironment=は[Service]セクションに記述しますが、 説明はsystemd.serviceではなくsystemd.execのページにあります。 対して、第三層の設定(例:ExecStart=)は、 「記述するセクション([Service])」と「説明ページ(systemd.service)」が一致しています。 注意:以下の第二層の説明において、対象として例示しているユニットはsystemdのバージョンによっては違いがあります。 systemd.exec(実行環境全般) 対象:プロセスの実行に関わるユニット。例:service、socket、swap、mount 主な内容:実行ユーザ、環境変数、標準出力、作業ディレクトリ 設定項目の例:User=、Environment=、StandardOutput=、WorkingDirectory= systemd.kill(停止時の制御) 対象:プロセスを持つユニット。例:scope、service、socket、swap、mount 主な内容:kill方法、タイムアウト時の挙動 設定項目の例:KillMode=、KillSignal= systemd.resource-control(リソース制御) 対象:リソースを使用するユニット。例:slice、scope、service、socket、swap、mount 主な内容:CPU、メモリ、I/Oなどの制限 設定項目の例:CPUQuota=、MemoryMax=、IOWeight=、TasksMax= 第三層:Unit種別固有の設定systemd.service(serviceユニット固有) 主な内容:起動方式、再起動、プロセス種別 設定項目の例:ExecStart=、Restart=、Type=、PIDFile= 主なセクション:[Service] systemd.socket(socketユニット固有) 主な内容:待ち受けストリーム、モード、ユーザ・グループ 設定項目の例:ListenStream=、SocketMode=、SocketUser= 主なセクション:[Socket] など 例:serviceユニットの書き方を調べる場合 .serviceファイルについて読み書きする際は、以下の手順でマニュアルを参照します。 全体像を把握するとき 第一層(systemd.unit、systemd.syntax)で全Unitの共通事項を把握する 第二層(systemd.exec、systemd.kill、systemd.resource-control)で利用可能な共通設定を確認する 第三層(systemd.service)でserviceユニット固有の設定を確認する 設定項目名だけ分かっているとき 既存の.serviceを読む場合などで、特定の設定項目について調べたいときは、 systemd.directivesを開いてその設定項目名で検索します。 該当ページが分かれば、そこから詳細な仕様を確認できます。
Home Software Unix Unix commands man man pageが読むのに適したWebサイト @wakairo 13 Feb, 2026 01:41 +00:00 https://www.man7.org/linux/man-pages/ : 正確性や公式性が高く評価されているらしい https://ja.manpages.org/ : 日本語を含む複数言語に強い https://man.archlinux.org/ : 日々更新が行われるため最新の情報に強いArch Linuxのmanページ。デザインもモダンでスマホなどでも読みやすそう。
Home Software Libraries Ruby rails RailsのAction MailerとAction Mailboxはmail gemを利用している @wakairo 07 Feb, 2026 03:07 +00:00 RailsでMailクラスに遭遇したら、それはmail gemのものです。ですので、そのAPI情報などは、Railsの情報源ではなく、以下のようなmail gemの情報源を見ると得られます。 GitHub: https://github.com/mikel/mail APIドキュメント: https://www.rubydoc.info/gems/mail/
Home Software Libraries Ruby activerecord Active Recordマイグレーション:t.referencesとadd_referenceで外部キー制約を詳細設定する方法 @wakairo 05 Feb, 2026 04:18 +00:00 要点 Active Recordのマイグレーションファイルにてt.referencesまたはadd_referenceを使う際、 foreign_key:オプションに対してtrueの代わりに ハッシュを渡すことで外部キー制約の詳細を設定 できます。 このハッシュで指定できるオプションはadd_foreign_keyのAPIドキュメントに記載されているオプションです。 具体例t.references を使う場合(新規テーブル作成時)create_table :books do |t| t.references :author, foreign_key: { on_delete: :cascade } end add_reference を使う場合(既存テーブルへの追加)add_reference :books, :author, foreign_key: { on_delete: :cascade } 説明 Active Recordのマイグレーションファイルにてテーブル間に関連付けをする際には、 t.referencesまたはadd_referenceを使うと便利です。 この2つのメソッドは、呼び出すときにforeign_key:オプションを付けると、 外部キー制約も同時に付与することが出来ます。例えば、foreign_key: trueという記述をよく見かけます。 foreign_key: trueは外部キー制約をかけることだけの指定となりますが、 ここでのtrueの代わりにハッシュを渡すことで、外部キー制約の詳細が設定可能になります。 このハッシュの中に入れられるオプション、つまり外部キー制約の詳細設定項目は、add_foreign_keyに渡せるオプションと同じです。 このadd_foreign_keyに渡せるオプションの具体的な種類とそれぞれの意味については、 add_foreign_keyのAPIドキュメントをご覧ください。
Home Software Libraries Ruby activerecord Active Recordマイグレーションでのadd_foreign_keyとadd_referenceの違い @wakairo 30 Jan, 2026 12:14 +00:00 Railsガイドを一読しただけでは、 add_foreign_keyとadd_referenceが、それぞれどのようなもので、どう違うのかがいまいちよく分かりませんでした。 そこで、add_foreign_keyとadd_referenceによってdb/schema.rbがどのように変化するかを基に、 それぞれの機能について確認してみました。 add_foreign_key add_foreign_keyは外部キー制約の追加だけを行います。よって、カラムやインデックスの追加は行いません。 外部キー制約とは、子テーブルが参照している親のIDが親テーブルに存在することをデータベースレベルで保証する制約です。 例えば、以下の記述をマイグレーションファイルに行ったとします。 add_foreign_key :products, :users すると以下の記述がdb/schema.rbに追加されます。 add_foreign_key "products", "users" この記述は、products(子テーブル)のuser_idカラムに登場するIDが、users(親テーブル)のidカラムに必ず存在するように制約をかけています。 add_reference add_referenceは、テーブル間の関連付けに関する複数の設定を一度に行える便利メソッドです。 add_referenceの基本機能は、カラムとインデックスの追加です。 例えば、以下の記述をマイグレーションファイルに行ったとします。 add_reference :products, :user すると以下のように、users(親テーブル)に関連付ける2つの記述、具体的にはuser_idのカラムとインデックスをproducts(子テーブル)に追加する2つの記述が、db/schema.rbに追加されます。 t.integer "user_id" t.index ["user_id"], name: "index_products_on_user_id" またadd_referenceは、foreign_key:trueオプションを追加することで、前述の2つに加えて外部キー制約も同時に設定できます。 例えば、以下の記述をマイグレーションファイルに行ったとします。 add_reference :products, :user, foreign_key:true すると以下のように、前述の2つの記述に加えて、add_foreign_keyの記述がdb/schema.rbに追加されます。 t.integer "user_id" t.index ["user_id"], name: "index_products_on_user_id" add_foreign_key "products", "users" (参考)add_referenceとt.referencesの機能は基本的に同じ add_referenceとcreate_tableのブロックの中で呼び出すt.referencesは基本的に同じ機能を提供します(オプション以外の引数の部分で違いはありますが。) 実際にt.referencesへ渡せるオプションはadd_referenceと同じです。 また、t.referencesの実装とadd_referenceの実装のどちらもReferenceDefinition.newを内部で呼ぶ形になっています(v8.1.2で確認)。
Home Software Libraries Ruby activerecord ActiveRecord::Rollbackで例外を伝播させずにロールバック後の処理を継続する @wakairo 18 Jan, 2026 13:49 +00:00 Active RecordのTransactionブロック内で例外が投げられた場合、ActiveRecord::Rollback以外の例外はロールバックの後に再度投げられます。 したがって、処理失敗で例外を投げるメソッド(save!等)を使えば、基本的にその例外はロールバックの後Transactionブロックの外へ伝播します。 これをキャッチしなければ、RailsはHTTPのエラーレスポンスをブラウザ等のクライアントに返します。 つまり、「処理に失敗したら、ロールバックして、あとの処理は切り上げて、エラーレスポンスを返す」という挙動は簡単に実装できます。 一方で、「ロールバック後に例外を出さずに処理を継続する」場合には、ActiveRecord::Rollbackを活用できます。 以下は、Transactionの成否でリダイレクト先を変えるコード例です。 completed = false ActiveRecord::Base.transaction do unless obj1.save && obj2.save raise ActiveRecord::Rollback end completed = true end if completed redirect_to completed_path else redirect_to uncompleted_path end
Home Software Programming languages Ruby 【小ネタ】Hash#mergeはキーワード引数でも動作する SatoKen @kenicode 17 Jan, 2026 10:05 +00:00 Last edited 17 Jan, 2026 10:37 +00:00 RubyのHash#mergeのマニュアルでは、 その引数はハッシュとしか書かれていません。 例えば、以下のような形です。 irb(main):001> {f1: 1, f2: 2}.merge({b3: 3}) => {:f1=>1, :f2=>2, :b3=>3} では、キーワード引数を渡すとどうなるのかと言うと、以下のように動きはしますよ、という小ネタです。 irb(main):002> {f1: 1, f2: 2}.merge(b3: 3) => {:f1=>1, :f2=>2, :b3=>3} Rubyの言語仕様上OKなのかは不明ですので、自分から積極的に使うというよりは、 他人のコードを読むときに役立つかもしれない小ネタ知識でした。
Active Recordのallが持つ条件を引き継ぐ性質と動的クエリ構築への応用
RailsのActive Recordの
allメソッドは、 その名前から「テーブルに存在する全レコードを取得するメソッド」と理解されがちです。 しかし実際には「その時点までに積み上げられた条件に該当するものを全て取得する」という実務上とても重要な性質を持っています。本記事では、この
allの性質の基本と、動的にデータベースクエリを組み立てる際の実践的な応用テクニックについて解説します。allは「構築済みの条件」を引き継ぐデータベースに
Penモデルのレコードが3件保存されているとします。 以下のようにPenクラスに対して直接allを呼び出すと、テーブルに存在する全てのレコードが取得されます。では、別の条件に続けて
allを呼び出すとどうなるでしょうか。 以下の実行例を見ると分かるように、allは「全て」ではなく、「それまでの条件を満たすもの全て」を返します。 ここではcolor: "red"を満たすレコードだけが取得されています。このように、
allは単に全件取得する機能だけでなく、 手前に条件がある場合には「そこまでに構築されたクエリ条件(Relationオブジェクト)を引き継ぎ、そのまま返す」という機能を担う側面があります。応用例:動的クエリ構築において
allを起点にしてメソッドチェーン可能にするこの「そこまでに構築された条件を引き継ぐ」という
allの性質は、 「URLのクエリパラメータが存在する場合だけ条件を追加する」といった動的なデータベースクエリ構築において役立ちます。例えば、クエリパラメータを処理する
filter_byというスコープを定義することを考えてみましょう。 このスコープは、以下のように「1年以内に作成されたレコード」という前提条件(where)のあとにメソッドチェーンで呼び出される想定です。この挙動を実現する
filter_byスコープの実装は以下のようになります。 最初にallを呼び出して現在のRelation(既に構築済みのwhere条件など)を変数pensに格納し、 そのpensに対してパラメータの有無をチェックしながら条件を継ぎ足しています。ここで重要になるのが、
filter_byの中で最初にallを呼んで変数に入れている点です。allは「何も条件がない状態」ではなく、 「このスコープが呼ばれた時点のRelationをそのまま受け取る」役割を果たしています。 そのため、Pen.where(created_at: …)のように手前で条件を付けていても、 それをリセットすることなくさらに条件を積み上げられます。 つまり、allを変数に入れることで、 手前の条件を維持した上でさらに条件を積み上げていくための「起点」を作ることができるのです。 なお、クエリパラメータが全て空で条件追加がない場合でも、allが返した元のRelationがpensに入っているため、 メソッドチェーンをそのまま継続できます。(参考)個別スコープへの切り出しとそのタイミング
先ほどの例では
filter_byの内部で変数に再代入しながら条件を追加していましたが、 もしcolorやpriceによる絞り込みをアプリケーション内の他の場所で単独で利用したい場合は、 それぞれを個別のスコープとして切り出すことでコードをよりキレイに書くことができます。Active Recordのスコープは、ブロックの評価結果が
nilまたはfalseになった場合、 自動的に元のRelationをそのまま返す(条件を追加せずにスルーする)という便利な仕様になっています。 そのため、以下のようにメソッドチェーンでそのままつなぐだけで、条件分岐をスコープの中に自然にカプセル化できます。なお、YAGNI原則の観点からは、 これらの個別スコープ(
with_colorやwith_max_price)が他の場所で「本当に必要になるまで」は、 無理に切り出さない方が良いでしょう。 つまり、最初から再利用性を過剰に意識してスコープを量産するよりは、 まずは前述のpens = allを使うアプローチで1箇所にまとめておき、 個別スコープが本当に必要になったタイミングで切り出す方が、 結果的には開発コストを減らせるはずです。UNIX環境向け簡易通知システムの例
UNIXシステムの既存機構を活用したミニマルな通知システムの実装例です。 複数の通知元から任意の通知メッセージを受信し、それらをシェルプロンプトに表示できます。 個人利用の環境(趣味用途や開発用途など)への導入を想定しています。
設計思想
本システムの設計の核は「付け加えるものは最小限に、UNIXそのままの仕様と挙動を活かす」ことです。
以上の基本思想に合わせ、厳密な順序保証やマルチユーザ間の公平性よりも、単純性・可搬性・挙動の自明さを優先する設計とします。
システム仕様(外部仕様)
1. システム概要
ファイルシステムを介してユーザのシェル(Bash)のプロンプトに通知を表示する軽量な仕組みです。例えば、systemdユニットの実行失敗通知に利用できます。
2. 基本仕様
/var/lib/notify_inbox/ディレクトリを使用します。touchコマンドなどで、メッセージをファイル名とした空ファイルを作成します。3. 注意事項・制約
/(スラッシュ)はファイル名に使えません。.(ドット)から始まるファイル名については、通知と削除が行われるかどうかは、Bashのdotglobの設定に依存します。_や-の使用を推奨します。env LC_ALL=ja_JP.UTF-8 touch "/var/lib/notify_inbox/01_メッセージ")してください。0777)です。システム上の全ユーザが通知を見ることができます。実装コード一式
Step 1. 共有ディレクトリの設定(管理者権限)
systemdの機能を使って、起動時にディレクトリを作成し、適切な権限を与えます。
作成ファイル:
/etc/tmpfiles.d/notify_inbox.conf0777に設定し、スティッキービット(t)を意図的に付けていません。これにより、rootが作成した通知ファイルを一般ユーザが削除できるようになります。設定の反映:
Step 2. 通知先(受信側)の設定(一般ユーザ)
ユーザの
.bashrcに、プロンプト表示時に通知をチェックし、 通知があれば表示する処理を追加します。編集ファイル:
~/.bashrc設定の反映:
Step 3. 通知元(送信側)の設定例
systemdユニットファイルに通知コマンドを仕込みます。
例: バックアップサービスのユニット (
/etc/systemd/system/backup.service)解説:
ExecStopPost: サービス終了時(成功・失敗問わず)に実行されます。$EXIT_STATUS: systemdが終了時に設定する環境変数の一つです。0以外ならエラーと判断しています。確実な失敗通知にはOnFailure=の利用も検討してください。touch "...": ファイルを作るだけです。このファイル名がそのまま通知メッセージになります。Step 4. 動作確認
実際に通知が表示されるかテストします。
テスト用通知を作成(手動):
プロンプトを表示(Enterキーを押す): ターミナルで
Enterを押したタイミングで、プロンプトの前に以下のように表示されます。消去確認: もう一度
Enterを押し、通知が消えることを確認します。systemd経由のテスト(失敗シミュレーション):
ExecStart=/bin/falseと書いたダミーのユニットを作成してsystemctl startし、プロンプトにエラーが出るか確認してください。WSLとVS Codeの組み合わせで快適に開発するためのセットアップ
WSLはVS Codeと連携させることが可能です。 具体的には、VS CodeからWSL内のOSにアクセスできるようにセットアップを行うと、 VS Codeの便利機能がいろいろと使えたりして良いです。
連携のためには、単純にインストールするのではなく、そのためのセットアップが必要です。
具体的な手順などについては、VS Codeの開発元であるMicrosoftの以下のページをご覧ください。
https://learn.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-vscode
【systemd Unitファイル】設定項目の説明が見つからない理由と公式manページの階層構造
systemdのUnitファイルを読んだり書いたりしていると、 特定の設定項目について、どこに正確な説明があるのか分からず困ることがあります。
実はUnitファイルの設定項目は、ユニット種別ごとに1ページにまとまっているわけではなく、 複数のmanページに階層的に分散して記述されています。
本記事では、この階層構造を踏まえて公式マニュアルの読み方を整理します。
なお、この記事はsystemdのUnitファイルを編集した経験がある人を想定読者としています。
systemd Unitファイルの公式仕様はmanページにある
systemdのUnitファイルの仕様や設定項目は、公式には
manコマンドで表示されるマニュアルに集約されています。例えば、Unitファイル全体の共通仕様は次のコマンドで確認できます。
Web版は、systemdの開発主体が以下のサイトで公開しています。
https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html
マニュアルの読み方のポイント
systemdのmanページは情報量が多く、設定項目がどのページにあるのか分かりにくい構成になっています。 そこで「設定項目がどの層に属するか」を意識して読むと見通しが良くなります。
マニュアルの「3層構造」
Unitファイルの設定項目などは、概念的には次の3層に分けられます。
マニュアルの索引:systemd.directives
「設定項目名は分かるが、どのマニュアルに載っているか分からない」ときは、索引を活用します。
man systemd.directivesコマンドで表示される systemd.directivesは索引ページに相当し、 全ての設定項目名と、それが記載されているページが一覧になっています。このページを開いて項目名で検索するとどのページに説明があるかが分かります。
(Tips)「Additional options…」の誘導を鍵にページをたどる
systemdのマニュアルでは、ページ冒頭付近に
という文が書かれていることがあります。
これは「このページに載っていない設定項目は、別のどのページに書かれているか」を示す案内です。
設定が見つからない場合は、ページ内でこの一文を検索し、リンクされているページを確認すると効率的です。
各層の代表的なページの概略
第一層:全Unit共通の仕様(systemd.unit)と記法(systemd.syntax)
systemd.unit(共通の仕様)
Description=、Requires=、After=、ConditionPathExists=[Unit]、[Install]systemd.syntax(共通の記法)
第二層:複数Unit種別で共有される設定
第二層の設定の特徴は、「記述するセクション」と「説明が書かれているmanページ」が一致しない点です。
例えば
User=やEnvironment=は[Service]セクションに記述しますが、 説明はsystemd.serviceではなくsystemd.execのページにあります。対して、第三層の設定(例:
ExecStart=)は、 「記述するセクション([Service])」と「説明ページ(systemd.service)」が一致しています。systemd.exec(実行環境全般)
User=、Environment=、StandardOutput=、WorkingDirectory=systemd.kill(停止時の制御)
KillMode=、KillSignal=systemd.resource-control(リソース制御)
CPUQuota=、MemoryMax=、IOWeight=、TasksMax=第三層:Unit種別固有の設定
systemd.service(serviceユニット固有)
ExecStart=、Restart=、Type=、PIDFile=[Service]systemd.socket(socketユニット固有)
ListenStream=、SocketMode=、SocketUser=[Socket]など
例:serviceユニットの書き方を調べる場合
.serviceファイルについて読み書きする際は、以下の手順でマニュアルを参照します。全体像を把握するとき
設定項目名だけ分かっているとき
既存の
.serviceを読む場合などで、特定の設定項目について調べたいときは、 systemd.directivesを開いてその設定項目名で検索します。 該当ページが分かれば、そこから詳細な仕様を確認できます。man pageが読むのに適したWebサイト
RailsのAction MailerとAction Mailboxはmail gemを利用している
RailsでMailクラスに遭遇したら、それはmail gemのものです。ですので、そのAPI情報などは、Railsの情報源ではなく、以下のようなmail gemの情報源を見ると得られます。
Active Recordマイグレーション:t.referencesとadd_referenceで外部キー制約を詳細設定する方法
要点
Active Recordのマイグレーションファイルにて
t.referencesまたはadd_referenceを使う際、foreign_key:オプションに対してtrueの代わりに ハッシュを渡すことで外部キー制約の詳細を設定 できます。このハッシュで指定できるオプションはadd_foreign_keyのAPIドキュメントに記載されているオプションです。
具体例
t.referencesを使う場合(新規テーブル作成時)add_referenceを使う場合(既存テーブルへの追加)説明
Active Recordのマイグレーションファイルにてテーブル間に関連付けをする際には、
t.referencesまたはadd_referenceを使うと便利です。この2つのメソッドは、呼び出すときに
foreign_key:オプションを付けると、 外部キー制約も同時に付与することが出来ます。例えば、foreign_key: trueという記述をよく見かけます。foreign_key: trueは外部キー制約をかけることだけの指定となりますが、 ここでのtrueの代わりにハッシュを渡すことで、外部キー制約の詳細が設定可能になります。このハッシュの中に入れられるオプション、つまり外部キー制約の詳細設定項目は、
add_foreign_keyに渡せるオプションと同じです。 このadd_foreign_keyに渡せるオプションの具体的な種類とそれぞれの意味については、 add_foreign_keyのAPIドキュメントをご覧ください。Active Recordマイグレーションでのadd_foreign_keyとadd_referenceの違い
Railsガイドを一読しただけでは、 add_foreign_keyとadd_referenceが、それぞれどのようなもので、どう違うのかがいまいちよく分かりませんでした。 そこで、add_foreign_keyとadd_referenceによって
db/schema.rbがどのように変化するかを基に、 それぞれの機能について確認してみました。add_foreign_key
add_foreign_keyは外部キー制約の追加だけを行います。よって、カラムやインデックスの追加は行いません。
外部キー制約とは、子テーブルが参照している親のIDが親テーブルに存在することをデータベースレベルで保証する制約です。
例えば、以下の記述をマイグレーションファイルに行ったとします。
すると以下の記述が
db/schema.rbに追加されます。この記述は、products(子テーブル)のuser_idカラムに登場するIDが、users(親テーブル)のidカラムに必ず存在するように制約をかけています。
add_reference
add_referenceは、テーブル間の関連付けに関する複数の設定を一度に行える便利メソッドです。
add_referenceの基本機能は、カラムとインデックスの追加です。 例えば、以下の記述をマイグレーションファイルに行ったとします。
すると以下のように、users(親テーブル)に関連付ける2つの記述、具体的にはuser_idのカラムとインデックスをproducts(子テーブル)に追加する2つの記述が、
db/schema.rbに追加されます。またadd_referenceは、
foreign_key:trueオプションを追加することで、前述の2つに加えて外部キー制約も同時に設定できます。 例えば、以下の記述をマイグレーションファイルに行ったとします。すると以下のように、前述の2つの記述に加えて、add_foreign_keyの記述が
db/schema.rbに追加されます。(参考)add_referenceとt.referencesの機能は基本的に同じ
add_referenceとcreate_tableのブロックの中で呼び出すt.referencesは基本的に同じ機能を提供します(オプション以外の引数の部分で違いはありますが。) 実際にt.referencesへ渡せるオプションはadd_referenceと同じです。 また、t.referencesの実装とadd_referenceの実装のどちらも
ReferenceDefinition.newを内部で呼ぶ形になっています(v8.1.2で確認)。ActiveRecord::Rollbackで例外を伝播させずにロールバック後の処理を継続する
Active RecordのTransactionブロック内で例外が投げられた場合、ActiveRecord::Rollback以外の例外はロールバックの後に再度投げられます。
したがって、処理失敗で例外を投げるメソッド(
save!等)を使えば、基本的にその例外はロールバックの後Transactionブロックの外へ伝播します。 これをキャッチしなければ、RailsはHTTPのエラーレスポンスをブラウザ等のクライアントに返します。 つまり、「処理に失敗したら、ロールバックして、あとの処理は切り上げて、エラーレスポンスを返す」という挙動は簡単に実装できます。一方で、「ロールバック後に例外を出さずに処理を継続する」場合には、ActiveRecord::Rollbackを活用できます。 以下は、Transactionの成否でリダイレクト先を変えるコード例です。
【小ネタ】Hash#mergeはキーワード引数でも動作する
RubyのHash#mergeのマニュアルでは、 その引数はハッシュとしか書かれていません。 例えば、以下のような形です。
では、キーワード引数を渡すとどうなるのかと言うと、以下のように動きはしますよ、という小ネタです。
Rubyの言語仕様上OKなのかは不明ですので、自分から積極的に使うというよりは、 他人のコードを読むときに役立つかもしれない小ネタ知識でした。