コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

RailsでのActiveRecordへのfreeze, readonly, strict_loading の違いをざっくり整理する

「引数にActiveRecordを渡したいが、そこから操作されてほしくない」というようなときにどういうアプローチがあるかが気になったので、ざっくりまとめ。

検証した環境

$ bin/rails -v
Rails 7.1.2
$ ruby -v
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [aarch64-linux]

例示に使うモデル

今回は BookAuthor というモデルを例示に使う。定義は以下。

# == Schema Information
#
# Table name: authors
#
#  id         :bigint           not null, primary key
#  name       :string(255)
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class Author < ApplicationRecord
end

# == Schema Information
#
# Table name: books
#
#  id         :bigint           not null, primary key
#  title      :string(255)
#  author_id  :bigint           not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class Book < ApplicationRecord
  belongs_to :author
end

本記事で扱う3つのアプローチ

ActiveRecordオブジェクトの変更を制限する方法として、以下の3つを検証します。

  1. freeze - オブジェクトの変更を完全に禁止
  2. readonly! - データベースへの保存を禁止
  3. strict_loading - 関連レコードの遅延読み込みを禁止

各メソッドごとのざっくりまとめ

freezeとは

freezeはRubyのObjectクラスに定義されているものであり、オブジェクトの変更を禁止するもの。定数などではよく使われるが、ActiveRecordにも適用できる。

例えば以下のように、Bookが持つattributeは更新できなくなる。

irb(main):001> book = Book.last
  Book Load (0.3ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
=> #<Book:0x0000ffffb14fd808 id: 1, title: "hon", author_id: 1, created_at: Sat, 25 Oct 2024 03:44:36.248348000 UTC +00:00, updated_at: Sat, 25 Oct 2024 03:44:36.248348000 UTC +00:00>
irb(main):002> book.title = "test"
=> "test"
irb(main):003> book.freeze
=> #<Book:0x0000ffffb14fd808 id: 1, title: "test", author_id: 1, created_at: Sat, 25 Oct 2024 03:44:36.248348000 UTC +00:00, updated_at: Sat, 25 Oct 2024 03:44:36.248348000 UTC +00:00>
irb(main):004> book.title = "test2"
/usr/local/bundle/gems/activemodel-7.1.2/lib/active_model/attribute_set.rb:59:in `write_from_user': can't modify frozen attributes (FrozenError)
irb(main):005> book.save
/usr/local/bundle/gems/activemodel-7.1.2/lib/active_model/attribute_set.rb:59:in `write_from_user': can't modify frozen attributes (FrozenError)

なお、メソッド自体に関してはObject#freeze (Ruby 3.4 リファレンスマニュアル)を参考のこと。

なお、関連付けられたオブジェクト(例:book.author)まではfreezeされないので、個別にfreezeする必要がある。

readonly!とは

ActiveRecordのメソッドの一つであり、更新作業をできない状態にするもの。

例えば以下のように代入は可能だが保存はできない状態にできる。

irb(main):001> book = Book.last
  Book Load (0.3ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
=> #<Book:0x0000ffff8970d8a0 id: 1, title: "hon", author_id: 1, created_at: Sat, 25 Oct 2024 03:44:36.248348000 UTC +00:00, updated_at: Sat, 25 Oct 2024 03:44:36.248348000 UTC +00:00>
irb(main):002> book.title = "test"
=> "test"
irb(main):003> book.readonly!
=> true
irb(main):004> book.title = "test2"
=> "test2"
irb(main):005> book.save
/usr/local/bundle/gems/activerecord-7.1.2/lib/active_record/persistence.rb:1281:in `_raise_readonly_record_error': Book is marked as readonly (ActiveRecord::ReadOnlyRecord)
irb(main):006> 

こちらも関連オブジェクトはreadonlyにはならないので、気をつける。

strict_loadingとは

Rails6.1から導入された機能で、N+1を抑止するための機能。あらかじめオブジェクトがロードされていない状態で関連を取ろうとするとエラーになる。

例えば以下のように includes などでオブジェクトを取得していない状態だと、関連レコードの情報を取ろうとするとエラーになる。

irb(main):001> book = Book.first
  Book Load (0.3ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
=> #<Book:0x0000ffff7d9ef7f0 id: 1, title: "hon", author_id: 1, created_at: Sat, 25 Oct 2025 03:44:36.248348000 UTC +00:00, updated_at: Sat, 25 Oct 2025 03:44:36.248348000 UTC +00:00>
irb(main):002> book.author
  Author Load (0.4ms)  SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1 LIMIT 1
=> #<Author:0x0000ffff7dd8bb70 id: 1, name: "suzuki", created_at: Sat, 25 Oct 2025 03:43:52.517291000 UTC +00:00, updated_at: Sat, 25 Oct 2025 03:43:52.517291000 UTC +00:00>
irb(main):003> strict_book = Book.strict_loading.first
  Book Load (1.2ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
=> #<Book:0x0000ffff7dca2d08 id: 1, title: "hon", author_id: 1, created_at: Sat, 25 Oct 2025 03:44:36.248348000 UTC +00:00, updated_at: Sat, 25 Oct 2025 03:44:36.248348000 UTC +00:00>
irb(main):004> strict_book.author
/usr/local/bundle/gems/activerecord-7.1.2/lib/active_record/core.rb:230:in `strict_loading_violation!': `Book` is marked for strict_loading. The Author association named `:author` cannot be lazily loaded. (ActiveRecord::StrictLoadingViolationError)

ちゃんと取得していればエラーにならない。

irb(main):001> strict_book = Book.strict_loading.includes(:author).first
  Book Load (0.2ms)  SELECT `books`.* FROM `books` ORDER BY `books`.`id` ASC LIMIT 1
  Author Load (0.2ms)  SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1
=> #<Book:0x0000ffffadc2da50 id: 1, title: "hon", author_id: 1, created_at: Sat, 25 Oct 2025 03:44:36.248348000 UTC +00:00, updated_at: Sat, 25 Oct 2025 03:44:36.248348000 UTC +00:00>
irb(main):002> strict_book.author
=> #<Author:0x0000ffffae99dbb8 id: 1, name: "suzuki", created_at: Sat, 25 Oct 2025 03:43:52.517291000 UTC +00:00, updated_at: Sat, 25 Oct 2025 03:43:52.517291000 UTC +00:00>
irb(main):003> 

なお、追加でのクエリ発行を抑制するだけなので、保存や代入のブロックは行わない

関連リンク

RailsのActiveRecordで主キーの配列を取りたい場合はidsメソッドで取得できる

pluck(:id) を使っていたのでメモ程度に。

動作環境

$ bin/rails -v
Rails 7.1.2

実例

Authorというモデルがあった場合、以下のように取得できる

Author.ids
# => [1, 2, 3]
Author Ids (0.6ms)  SELECT `authors`.`id` FROM `authors`
# => Array

こちらはModel側で主キーを別のものに指定している場合でもよしなに該当カラムを引き当ててくれるので、その点ではpluck(:id)よりも親切

実コード

実際のコードは以下の通り、再帰的なことをやっているのでわかりにくいが primary_key を使って取得している

def ids
  primary_key_array = Array(primary_key)

  if loaded?
    result = records.map do |record|
      if primary_key_array.one?
        record._read_attribute(primary_key_array.first)
      else
        primary_key_array.map { |column| record._read_attribute(column) }
      end
    end
    return @async ? Promise::Complete.new(result) : result
  end

  if has_include?(primary_key)
    relation = apply_join_dependency.group(*primary_key_array)
    return relation.ids
  end

  columns = arel_columns(primary_key_array)
  relation = spawn
  relation.select_values = columns

  result = if relation.where_clause.contradiction?
    ActiveRecord::Result.empty
  else
    skip_query_cache_if_necessary do
      klass.connection.select_all(relation, "#{klass.name} Ids", async: @async)
    end
  end

  result.then { |result| type_cast_pluck_values(result, columns) }
end

参考リンク

『大吉祥寺.pm 2025』に行ってきたよメモ

大吉祥寺.pm 2025 #kichijojipm 』に参加してきたのでそのメモです、

各発表の感想

感想書き残せたものだけですが 🙏


2025年の今になってもMySQLが好き

感想

  • DBAとして見たときの今昔物語と、MySQLMariaDBの最近の話
  • 昔はデータベースとアプリケーションで監視をするものの差があったが、いまはだんだんなくなってきた話は色々調べると出てきそうでなるほどなーと思った
  • MySQLMariaDBは互換性がある、という知識で完全に止まっていたので歴史の授業はたいへんタメにになりました。

関連リンク


カンファレンス登壇者・スタッフにこそ知ってほしいマイクの使い方

speakerdeck.com

感想

  • マイクに関しての取り扱いの話
  • 個人的には学生時代にめちゃくちゃ叩き込まれた話なのでそうだよね感がすごかった
  • 古今東西マイクが入っているかで叩く人が多いので、多くの人に叩いてはならないことを知ってほしい

関連リンク


提案レベルを上げてみたら、私の提案が進捗になっていた県

感想

  • 「質問」になってしまっているものをちゃんと「提案」にして、それがそのままトラッキングできるようにするための話
  • この中の発表はフルリモートのコミュニケーション術にも通じる部分があると思うので自分の考えていることも言語化したい。

関連リンク


今!ソフトウェアエンジニアがハードウェアに手を出すには

感想

  • ソフトウェア開発に軸足を持つ人が電子工作を始めるための足がかりになることをとりあげる話。
  • 発表中でも喋ってくれましたが、AIではキャッチアップしにくそうな話をフィーチャーしてくれたのがよかったです。

関連リンク


機能追加とリーダー業務との類似性

speakerdeck.com

感想

  • リーダー業務と機能開発の類似点を抽象的に分析した話
  • 抽象化して類似性があることを見出すのも一つの能力であるよなと思いながら聞いていました。
  • 質疑応答でアカウンタビリティというフレーズが出てきて、そういえばちゃんとこのあたりのフレーズを理解していなかったので改めて整理してもよいなと思う機会になりました。

関連リンク


ChatGPT、Gemini、Cluadeは、なぜ似たようなUIを採用しているのか?

感想

  • なぜUIが似るのかを分析してみた話
  • 結論に至るまでの切り口が面白く、ELIZA効果などは知らなかったので純粋に学びでした。
  • 今使われているUIが何故その形なのかを分析すると色々見えてくるよなと思いながら話を聞いていました。

関連リンク


コミュニティはいつまでも続くわけではない - PostgreSQLコミュニティの危機

スライドは見つけたら貼ります

感想

  • コミュニティは簡単になくなってしまう話
  • そーだいさんのコミュニティ関係の話を聞くと、私もできることからやろうという気持ちになります。
  • 好きな飲食店でも好きな場所でも、可能な限り維持している人たちにちゃんと還元しないといけないと改めて思いました

関連リンク


地方でエンジニアコミュニティを成功さえる秘訣

感想

  • 地方コミュニティを爆誕させた話
  • そーだいさんの話からの流れでコミュニティを生むにはどうしたらという話で地続き感があってよかったです。
  • 何事も熱量と狂気が大事だなと改めて思いました。

関連リンク


エンジニア採用を引き継いだあなたへ

感想

  • エンジニアリングマネージャーになったひとに向けてのまずは注力する部分の話
  • カジュアル面談に注力したケースのお話をされていて、採用に困っているベンチャーさんなどでは参考になりそうなお話でした。
  • 力のかけ方は各社状況により千差万別なので、こういうN=1の事例でも世に公開してくれるのはありがたい話だなと思いながら聞いていました。

関連リンク


2025年コーディングエージェントの現在地とエンジニアの仕事の変化について

感想

  • 近年のプログラミングにおける生成AIの話と使いこなし方の事例の話
  • おサイフの関係で特定のプロダクトしかつかっていないので、こうやって俯瞰的に色々使ったひとの話は参考になりました。

関連リンク


大「個人開発サービス」時代に僕たちはどう生きるか

感想

  • 生成AIの登場で自分のための道具がつくりやすくなった話
  • 少数のファンでも成立する世界観になりつつあるので、そういうものを作ってもいいなと思いました
    • サウナイキタイとか最たる例なので、事例もある
  • 個人的にはめちゃくちゃ感銘をうけたのと、自分自身の作りかけの個人開発をもっと頑張らねば... 。

関連リンク


定義のない仕事

感想

  • マネージャーを踏まずにCTOになった場合目線での話
  • エンジニアリングマネージャー経験した身としては全然違う目線の話をされていたので、気づきや発見が一番多い発表でした。
  • ここでもバイブコーディングで乗り切る事例が出てきて、時間使いがうまいなと思いながら話を聞いてました。

関連リンク


明日から役にたつLTをダメにする技術をあなたに

スライドはきっと公開されないでしょう

感想

  • 駄目なLTに関して身を挺して教えてくれたLT
  • 「ノイズをいれるな」というのがメインテーマでした。
  • ネットミームって入れたくなりがちだけど、わからん人にはほんとわからんので塩梅難しいですね

関連リンク


カオスエンジニアリングを5分でわかった気になろう

感想

  • 災害的な話とカオスエンジニアリングを絡めた話
  • いつも5分では入り切らないであろう話題を展開されるので今回もそうでした。
  • 実験して未知の未知を減らす取り組みってのがなるほどなーと思って聞いていました。

関連リンク


組織が大きく変わろうとするとき、自分はどうありたいかを考えている

感想

  • 大きなうねりの中での体験談
  • 昨今色んな情報が錯綜するなかでちゃんと自分で情報を取りに行くという姿勢が大事なことを改めて思い知らされました
  • しかしなんだったんだろう中のひとも振り回された怪文書

関連リンク


プロポーザル駆動学習

speakerdeck.com

感想

  • プロポーザルを出すことで学びが得られるよという話
  • かくいう自分もプロポーザル駆動で知識整理をしたことがあるので納得感は大変ありました
  • ただプロポーザル出したあとに開発を始めるなどは後出しじゃんけんなのでそれはよくない
  • プロポーザルを書こう!

関連リンク


2025年のPython環境はここまで簡単になりました! 環境構築ツールパビリオン クイックツアー

感想

  • いまのPythonの環境構築がよくなったことをひたすらに推してくれた話
  • Pythonは門外漢だったので、こんなに大変だったのか感も含めて学びがありました
  • 万博ネタだったのでちょっとした小芝居がいいアクセントになってました

関連リンク


令和でもブログを自宅サーバーで

感想

  • ブログを書こう!!!という話がメイン
  • 直近では感想や試行錯誤の結果こそブログに書き残しておくべきだなと感じています
  • ブログは書きましょう!

関連リンク


なぜスクラムはこうなったのか?歴史が教えてくれたこと

感想

  • スクラムの源流を見ていくと面白いよという話
  • アジャイルソフトウェア開発宣言とかの源流も読み解いていくと人の交差が見えて面白いなと思うので、源流を追うのはいいことだなと常々思っているのでとても共感できた

関連リンク


全体を通しての感想

  • 毎度のことながらバラエティに富んでいてとても良い時間を過ごせました。しかもそれぞれの話でちょっとずつオーバーラップする部分があり、全体をとおして色々相互補完する話が多かったように思えます。
  • クロージングキーノートもさらりとした内容の中に歴史の厚みを感じる内容でした。不安、ファン、funへとつなげていくのはいいことだなと思いました。
  • いろんな兼ね合いで懇親会いけなかったのですが、来年こそは絶対に行きたいな...と思いました。
  • 本当にいろいろな人の思いと熱気で支えられているものなので、来年も微力ながら支援できる範囲で支援しつつ参加したいなと思いました

『EMConf expansion!【EMConf JP 2025サブイベント】』に行ってきたよメモ

『EMConf expansion!【EMConf JP 2025サブイベント】』に参加してきたのでそのメモです、

各発表の感想

※資料スライドは見つけたら貼ります。


Engineering Managementのグローバルトレンド

感想

  • EMの話を基軸にしたエンジニアリングの様々な角度の話
  • 第一部の話もためになるなーと思いつつ聞いていたら、第二部で産業革命と絡めて話が出てきたのがめちゃ濃くてよかったです。
  • 状況に応じて柔軟な役割をこなす人が求められていくのでは?という仮説に乗っかって行きたいなと思いました。
  • あまり普段の生活では得られることのない切り口の話なのでめちゃよかったです(2回目)

関連リンク


「エンジニアマネージャー」の役割を担っている / 担ってみたい方へのキャリアパスガイド

感想

  • EMのキャリアパスとジェネラリスト方面でも良いんだよという話。
  • いろいろなことを経験しているということは希少性としては高いんだよという話に転換するのは色々な人に希望を与えそうな部分が多かった。
  • 自分もジェネラリスト志向が強いタイプなので、刺さる部分が多かった。

関連リンク


あなたの「仮説検証」、ゆがんでいませんか?

感想

  • 仮説検証と認知バイアスを絡めた話
  • 個人的にはあるあるを感じつつも、自分自身もバイアスが常にあることを再認識させられる話だった。
  • 今回の仮説検証の話はそのまま効果測定の文脈でも色々話ができそうでよかった。

関連リンク


全体を通しての感想

  • kyon_mnさんの話は毎度学びがあって個人的にはめちゃくちゃヒットだったのですが、そのあとの話にもうまくつながるところがありよかったです。
  • さいきんエンジニアリングマネージャー業からは遠ざかっていたのですが、いろいろな人の話を聞いていると学びがありました。
  • 自分のEM時代の話もなるべくアウトプットしていけたらなぁと感じました。

ノームカースの最優先条項とは何かをざっくりまとめる

ノースカールとかノールカームとか、最優先事項とか最優先条件とか、覚え間違えが多いのでざっくり整理してまとめる。

原典

書籍「Project Retrospectives: A Handbook for Team Reviews」の中で Kerth’s Prime Directive (カースの最優先司令) として以下の内容が紹介されています。

Regardless of what we discover, we must understand and truly believe that everyone did the best job he or she could, given what was known at the time, his or her skills and abilities, the resources available, and the situation at hand. - Project Retrospectives: A Handbook for Team Reviews

こちらを機械訳すると以下のようになる

何が発見されるかに関係なく、当時の知識、各自のスキルと能力、利用可能なリソース、目の前の状況を考慮して、全員が最善を尽くしたということを理解し、心から信じなければなりません。

この話が書籍 The Art of Agile Development にて Norm Kerth’s Prime Directive と紹介されており、その呼称が世間的に流通している。

どういう文脈で語られたものなのか

ふりかえりをするときに大事な"安全(safe)"を保つために、最初に宣言しておくべき話としてこのフレーズが取り上げられています。もととなった書籍ではこの条項を無視するとふりかえりが成功する確率が大幅に低下するとも書かれています。

2025年現在のフレーズに置き換えると、心理的安全性の話についての話とつながる部分があり、おかしな方向への批判的な意見を出さないためにふりかえり内での心理的安全性をつくるためのフレーズとも考えられる。

参考リンク

GitHubは推奨しているリポジトリサイズがある

知らなかったのでメモがてら

出典

リポジトリは小さく保ち、理想としては 1GB 未満、および 5GB 未満にすることを強くお勧めします。 - GitHub での大きいファイルについて - GitHub Docs

大きなファイルを扱いたい場合はどうするべきか

GitHubとしてはGit Large File Storageを用意しており、そちらを使えば大きなファイルを扱えるようになる。ただし利用できるストレージ容量に関しては制限があるので注意が必要。

関連リンク

Railsではなぜ多対多を表現する中間テーブルは「モデル名が結合されたもの」が使わていたのか

昔から users_teams のようにテーブルに名付けることが多かったが、これは推奨される方法なのかどうかがわからないので整理する。

とりあげる事例

  • 本は著者を持つ
  • 著者はいくつもの本を持つ可能性があるので、N:Nの関係になる

というようなモデルを作るとに、以下のようなER図になるパターンの場合の間を取り持つテーブル名が今回のテーマ。

authorsが著者、booksが本、その場合にN:Nを表現する中間テーブルとしてauthors_booksというテーブルを置く。

この場合に「ふたつのテーブル名を使ってauthors_booksという名前を中間テーブル名にするのがよい」という意見があるが、その意見はどこから来て、現在ではどう捉えるべきなのかを整理する。

なぜこの方法が推奨されていたか

この命名has_and_belongs_to_many を利用するときの考え方に則したものとなっている。たとえば、先程のケースの場合は以下のようなモデルを定義する。

class Author < ApplicationRecord
  has_and_belongs_to_many :books
end

class Book < ApplicationRecord
  has_and_belongs_to_many :authors
end

このようにすることで、AuthorとBookのN:Nの関係を表現できる。

この場合、AuthorBookを関連付けさせるための関係を中間テーブルを使って表現する必要があるのだが、「このテーブルが中間テーブルとして利用します」ということを宣言していない。そのため、テーブル名の命名規則で表現する必要があり、authors_books という命名をする必要がある。

この流れから「中間テーブルには2つのモデル名をスネークケースでつなげる」というような名前になるケースが多かった。

現在ではどうなのか

現在ではRailsガイドにもあるように、has_and_belongs_to_many の利用は推奨はされていない。。また、has_many :through も存在するので、命名もルールに縛られて定義する必要がない。

そのため2025年時点では普通に中間テーブル名のネーミングを考えるアプローチをとり、意識をしないとしたとしてもふさわしい名前を考えてあげるほうがよいと思われる。

参考リンク