rails

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars101
mentions55
036912Oct 4Oct 18Nov 2
UTC days · repositories rails owns13 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-17

npm145M
036.2M72.5M108.7M145MOct 4Oct 18Nov 2
Oct 27npm: 1/6 packages observed

Across 6 known packages in repositories rails owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete.

Top repositories

rails’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside rails’s account where rails opened or reviewed pull requests in the past 365 days.

Who noticed rails

Everyone who starred or forked rails’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

55posts
Nov 30 posts
  1. No tricks, just treats Hi, it’s Claudio Baccigalupo. Don’t be scared 🎃 this week’s changes to the Rails codebase are all treats. Let’s explore them. New guide to multiple databases in Active Record As part of the Rails Foundation effort, the refreshed guide is live, tackling the three-tier production configuration that new apps get by default. Tuning performance for deployment guide open for review A PR that updates the guide to performance tuning is open. Your feedback is helpful, especially if you have experience tuning Rails apps in production. Don’t load Capybara and Selenium on boot in new applications The generated Gemfile now declares capybara and selenium-webdriver with require: false. ActionDispatch::SystemTestCase already requires both when a system test is loaded, so test processes that don’t run system tests boot faster. Don’t render authenticity tokens under the :header_only CSRF strategy :header_only verifies requests by their Sec-Fetch-Site header and never reads a token, yet forms, button_to and csrf_meta_tags were still generating one on every render. Now they render none, so a page’s bytes no longer change from one request to the next. Process each Action Cable connection’s commands in order Commands from the same connection could previously run at the same time or out of order. Scope immutable_strings_by_default to string columns The setting applied to every column whose type subclassed ActiveModel::Type::String, so text columns and PostgreSQL’s xml, citext, ltree, tsvector and macaddr columns were inferred as :string, losing their type. Render hidden="until-found" as is A string or symbol value of the hidden attribute is no longer replaced by "hidden": tag.div(hidden: "until-found") # Before: <div hidden="hidden"></div> # After: <div hidden="until-found"></div> Fix preloading has_many :through associations with source_type and conditions There were four PRs this week fixing preloading of :through associations: Post.preload(:tags, :inactive_tags).first.inactive_tags # => no longer includes active tags Fix left_joins and eager_load dropping an intermediate join When the same through association was joined from different parent paths, the second path reused the join of the first, silently returning values from the wrong records. Exclude default_order from exists? and calculations exists? and calculations such as count already removed explicit ordering. Now they also remove default_order, which avoids invalid SQL on PostgreSQL. Keep query block scoped optimizer hints on the subquery for MySQL For count with limit, offset or distinct, all optimizer hints used to move to the outer query, while table-level hints like INDEX(posts idx) were silently ignored. Deprecate ActiveRecord::Validations::NumericalityValidator Active Record now uses ActiveModel::Validations::NumericalityValidator directly. The old constant still works with a deprecation warning. Deprecate the option accessors on ColumnDefinition Use options instead, for example column.options[:limit] instead of column.limit. Fix CurrentAttributes defaults for attribute names given as strings class Current < ActiveSupport::CurrentAttributes attribute "currency", default: "USD" end Current.currency # Before: nil # After: "USD" Match URL-decoded query parameter names when filtering logs A parameter such as %70assword is now matched by a :password filter in request and redirect logs. Add Northwest Territories to ActiveSupport::TimeZone::MAPPING They no longer shares winter clocks with US Mountain time. Prefer Northwest Territories (or America/Inuvik) for users in that region. Use Markdown as the default API docs markup The README files of Railties, Active Support, Action View and Action Pack have all been converted from RDoc to Markdown. Decode and encode PostgreSQL integer arrays in C Reading or writing an integer[] or bigint[] column no longer allocates one String per element. Other performance PRs that landed this week: cache the keyed HMAC in MessageVerifier, speed up Time.zone.today and Date, unroll the form builder helpers, call render_in optimistically, reduce allocations when building URLs, and make Symbol#to_param return the frozen Symbol#name. You can view the whole list of changes here. We had 33 contributors to the Rails codebase this past week! Until next time! View this post online You’re subscribed to This Week in Rails on HEY World New guide to multiple databases in Active Record: rails#57747 Tuning performance for deployment guide open for review: rails#59387 Don’t load Capybara and Selenium on boot in new applications: rails#59408 Don’t render authenticity tokens under the :header_only CSRF strategy: rails#59370 Process each Action Cable connection’s commands in order: rails#59371 Scope immutable_strings_by_default to string columns: rails#59351 Render hidden="until-found" as is: rails#59340 Fix preloading has_many :through associations with source_type and conditions: rails#59448 PRs: rails#59446 this: rails#59444 week: rails#59443 Fix left_joins and eager_load dropping an intermediate join: rails#59318 Exclude default_order from exists? and calculations: rails#59415 calculations: rails#59436 Keep query block scoped optimizer hints on the subquery for MySQL: rails#59386 Deprecate ActiveRecord::Validations::NumericalityValidator: rails#59305 Deprecate the option accessors on ColumnDefinition: rails#59299 Fix CurrentAttributes defaults for attribute names given as strings: rails#59347 Match URL-decoded query parameter names when filtering logs: rails#59314 Add Northwest Territories to ActiveSupport::TimeZone::MAPPING: rails#59221 Use Markdown as the default API docs markup: rails#59339 Railties: rails#59345 Active Support: rails#59344 Action View: rails#59343 Action Pack: rails#59342 Decode and encode PostgreSQL integer arrays in C: rails#59399 cache the keyed HMAC in MessageVerifier: rails#59430 Time.zone.today: rails#59421 Date: rails#59437 unroll the form builder helpers: rails#59407 render_in optimistically: rails#59403 reduce allocations when building URLs: rails#59316 Symbol#to_param: rails#59356 here: github.com/rails/rails/compare/@%7B2026-10-23%7D.…

  2. 新卒エンジニアが書き溜めた技術メモ 4選 はじめに こんにちは、namonaki です。新卒で入社して半年、分からなかったことを個人用のメモに書き留めてきました。その中からいくつか選りすぐって、改めて調べつつ記事にしてみました。 ■ 対象 Rails や React でコードは書けるが、「なぜこう書くと動くのか」はまだ説明できない人 普段なんとなく使っている仕組みの裏側を、ソースコードや DevTools で確かめてみたい人 ■ 扱うこと ブラウザ・Web標準 React と Islands Architecture Rails テスト 各章は独立しているので、気になるところから読んでください。参考にした資料は、本文中の番号から末尾の脚注に飛べます。 ! 前提のバージョン(2026年10月時点) Ruby 4.0 / Rails 8.1 React 17 / Node.js 24 factory_bot 6.5 1. ブラウザ・Web標準 JSON-LD(検索エンジン向けの構造化データ)について調べていたとき、JS を書くわけでもないのに、なぜ <script> タグを使うのか疑問に思ったのがきっかけです。 1.1 <script> の属性なし / async / defer の違い 調べてみると、HTML の要素を探して操作する JS には、defer を付けるのが基本でした。ダウンロードは早く始めつつ、実行は HTML の解析が終わるまで待ってくれるからです。 <script src="a.js"></script> <!-- ① 属性なし --> <script src="a.js" async></script> <!-- ② async --> <script src="a.js" defer></script> <!-- ③ defer --> HTMLの解析を止めるか 実行タイミング 実行順 属性なし 止める 読み込んだらすぐ 書いた順 async ダウンロード中は止めない ダウンロードが終わりしだい 保証なし defer 止めない HTMLの解析が終わった後 書いた順 defer は「ダウンロードは早く、実行は最後」という良いとこ取り1 念のため、コード側でも DOMContentLoaded を待つと二重の保険になる querySelectorAll のように要素が見つからなくてもエラーにならない書き方では、何も起きないだけなので気づきにくい。defer を付けておけば、この「要素がまだ無い」事故を防げる 1.2 Webのルールは誰が決めているのか 調べてみると、Web のルールは1つの団体が決めているわけではなく、部分ごとに別の団体が決めていました。 パンくずリストを構造化データで書いたときの例で見てみます。 <script type="application/ld+json"> { "@context": "schema.org", "@type": "BreadcrumbList", "itemListElement": [ ... ] } </script> この数行でも、部分ごとにルールを決めている団体が違いました。 部分 ルール 決めている団体 <script> タグ HTML WHATWG application/ld+json MIMEタイプの仕組み / 登録 IETF / IANA { ... } の書き方 JSON IETF・Ecma International @context などの書き方 JSON-LD W3C BreadcrumbList という単語 語彙 schema.org WHATWG:HTML と DOM。ブラウザを作る企業が中心。2019年に W3C と合意し、HTML は「HTML Living Standard」に一本化。版の区切りはなく更新され続ける2 W3C:CSS、アクセシビリティ(WAI-ARIA)、JSON-LD など3 IETF:HTTP や JSON など、通信とデータ形式の基礎(RFC)4 IANA:MIMEタイプの一覧、ドメイン名、IPアドレスの登録・管理5 Ecma International:JavaScript の文法(ECMAScript)6 schema.org:Google・Microsoft・Yahoo! などによる共同プロジェクト。標準化団体ではなく、構造化データ用の単語帳を作っている7 ■ 補足 +json は「中身は JSON 形式」という意味の接尾辞8 <script> の type に JS 以外を指定すると、ブラウザは実行せずデータとして置いておく。検索エンジンはこれを読む 2. React と Islands Architecture Wikipedia のような辞書的なページは、全体を SPA にするとかえって重くなるので、一部分だけを React で動くようにすることがあると思います。こうした構成にも何か名前が付いているはずだと思い、調べてみたのがきっかけです。 2.1 「一部だけReact」すなわち Islands Architecture …ではなかった 最初は、一部だけ React を使う構成ならすべて Islands Architecture だと思っていましたが、サーバーが空の div を返し、ブラウザで React が中身を一から作る構成は、Islands Architecture ではありませんでした。Islands では島の HTML もサーバーが作り、ブラウザはそれを hydrate するからです。 ■ Islands Architecture の条件9 島(インタラクティブな部分)の初期 HTML もサーバーが描画する ブラウザはその HTML に hydrate(既存の要素を再利用して、イベントや状態を結び付ける)する 島の外には JS を送らない ■ 空の div にマウントする構成(React 17 の例) // サーバーが返すのは空の div だけ。ブラウザ側で中身を一から作る(render) document.querySelectorAll('.search-widget').forEach((el) => { // サーバーからの値は data-params 属性で受け取る ReactDOM.render(<SearchWidget params={JSON.parse(el.dataset.params)} />, el); }); サーバーが返す HTML と、JS 実行後の HTML を並べるとこうなります。 【render(空の div にマウントする構成)】 サーバーが返したHTML JS 実行後 <div class="search-widget"> <div class="search-widget"> (空っぽ) → <form>...</form> ← JS が一から作る </div> </div> 【hydrate(Islands Architecture)】 サーバーが返したHTML JS 実行後 <div> <div> <form>...</form> → <form>...</form> ← 要素は作り直さず、イベントや状態を結び付ける </div> </div> 見分け方:JS を切ったときに島の中身が見えるか React 17 なら ReactDOM.render が render、ReactDOM.hydrate が hydrate10 React 18 以降は、createRoot().render() と hydrateRoot() に置き換わっている11 「島の外には JS を送らない」点だけは Islands と共通 2.2 hydrate で起きていること 調べてみると、hydrate は、サーバーが作った要素をそのまま使い、クリック時の処理や状態を後から結び付ける処理でした。 サーバーから届いた HTML は「見た目」だけで、ボタンを押しても何も起きません。素の JS で、後から動くようにするとこうなります12。 <button id="like">いいね 0</button> // 既にあるボタンを探して、処理を後付けする(要素は作り直さない) const button = document.getElementById('like'); let count = 0; button.addEventListener('click', () => { count += 1; button.textContent = いいね ${count}; }); これが「既存の HTML にイベントを結び付ける」いちばん素朴な形 React の hydrate も考え方は同じで、中では次のことをしている サーバーが返したHTML(見た目だけ。押しても動かない) <form><input><button>検索</button></form> ↓ ReactDOM.hydrate(<SearchForm />, el) ① コンポーネントを実行して、「本来こうなるはず」の設計図をメモリ上に作る ② 設計図と既存の要素を先頭から照らし合わせ、一致した要素を「この部品の担当」として紐付ける ③ onClick などのイベント処理を受け付けられるようにする ④ useState の状態や useEffect を動かし始める ↓ 見た目は同じまま、押せば動く状態になる 設計図と HTML が食い違うと、React 17 は開発モードで警告を出す。テキストの違いは書き換えて合わせるが、属性の違いは直らないことがあるので、食い違いはバグとして直す10:1 ②で作る「設計図 ↔ 実際の要素」の対応表は、正確には Fiber というオブジェクトで、React はこれを使って書き換える要素や、押されたボタンの担当を見つけている13 ■ ブラウザで確かめる Fiber が実際の DOM 要素に付いている様子は、React で作られたサイトなら DevTools で見られます。ここでは React 公式サイトの react.dev を使います。 Chrome で react.dev を開き、DevTools を開く(Mac なら ⌘ + Option + I) DevTools の左上にある矢印のアイコン(要素を選ぶボタン)を押してから、「Learn React」ボタンをクリックする。矢印を押さずにクリックすると、リンク先へページが移動してしまう Console タブで $0(Elements タブで選択中の要素)を実行し、<a … aria-label="Learn React" …>Learn React</a> が表示されることを確かめる 次を実行する Object.keys($0) // => ['reactFiber$9s34k04vvrf', 'reactProps$9s34k04vvrf'] // (2026年10月時点。$ の後ろの文字列はページを開くたびに変わる) $0[Object.keys($0).find(k => k.startsWith('__reactProps$'))] // => {className: 'w-full sm:w-auto …', aria-label: 'Learn React', target: '_self', ref: ƒ, onClick: ƒ, …} reactFiber$… が Fiber への参照、reactProps$… が props。props の中に onClick の関数が入っているのが見える react.dev は React 18 以降で作られているが、キーの名前は React 17 と同じ これらは React 内部の仕組みで、公開された機能ではない。観察だけにとどめ、自分のコードから読み書きしない 2.3 render と hydrate の違いのまとめ 違うのは「要素を一から作るか、サーバーが作ったものを再利用するか」だけでした。Fiber との紐付け(2.2 の②)、イベントの受け付け(2.2 の③)、状態や副作用の開始(2.2 の④)は、どちらも同じです。 ここに「ページの一部か全体か」という範囲の軸を加えると、よく聞く構成は次の4つに整理できます。 render(要素を一から作る) hydrate(サーバーが作った要素を再利用) ページの一部 空の div にマウントする構成 Islands Architecture ページ全体 SPA サーバーで描画して hydrate(Next.js など) どのマスでも、マウントした要素より内側は Fiber と紐付く。違うのは「どこまでを React に任せるか」と「要素を作るか、再利用するか」だけ SPA もページ全体が Fiber と紐付くが、サーバーが返すのは空の div なので、hydrate ではなく render Islands との違いは hydrate する範囲で、Islands は島ごとに別々の React として hydrate し、島の外は React と無関係の HTML のまま。Next.js はページ全体(document)を1つの React として hydrate する 3. Rails Rails のチュートリアル系のサイトや動画で「コントローラーで @ を付けた変数は、ビューでも @ を付けて参照できる」と聞いたものの、裏で何が起きているのか分からず、Rails を学び始めてすぐに疑問に思ったのがきっかけで調べました。 3.1 コントローラーのインスタンス変数がビューに渡る仕組み コントローラーのインスタンス変数がビューで使えるのは、Rails がビュー用のオブジェクトに書き写しているからでした。さらに、テンプレートはそのオブジェクトのメソッドに変換されて実行されていました(Rails 8.1 のソースより)14。 class UsersController < ApplicationController def show @user = User.find(params[:id]) # @ 付きならビューで使える posts = @user.posts # @ なしはこのメソッドの中だけ end end 3.2 そもそも、なぜ書き写す必要があるのか インスタンス変数は、それを持っているオブジェクトの中からしか見えません15。 @user を持っているのは、UsersController のインスタンス テンプレートを実行するのは、それとは別のビュー用オブジェクト 別々のオブジェクトなので、ビューで @user を使うには、ビュー用オブジェクト側にも同じインスタンス変数が必要 そこで Rails が、コントローラーの変数を1つずつビュー用オブジェクトに書き写している 3.3 全体の流れ ブラウザから GET /users/1(ユーザーの詳細画面)のリクエストが届く ↓ ルーティング(resources :users)が users#show に振り分け、params[:id] = "1" にする ① コントローラーを new して、show アクションを実行する ↓ @user = User.find(1) ↓ render(書かなくても自動で呼ばれる) ② 集める view_assigns … コントローラーのインスタンス変数をハッシュにする ↓ { "user" => #<User id: 1> } ③ 作る view_context … ビュー用オブジェクトを new する(ハッシュとコントローラー自身を渡す) (new するクラスの正体は ④ で補足) ↓ ⑤ 書き込む assign … ハッシュの中身を、ビュー用オブジェクトのインスタンス変数にする ↓ ⑥ テンプレート実行 テンプレートを「ビュー用オブジェクトのメソッド」に変換して呼ぶ → テンプレートの @user は、ビュー用オブジェクトの @user 以下、順に見ていきます。 ① コントローラーはリクエストごとに new される ソースを読むと、Rails はリクエストが届くたびにコントローラーを new していました。ルーティングが users#show と判断すると、UsersController.dispatch というクラスメソッドが呼ばれ、その中で new されます(actionpack action_controller/metal.rb)。 def self.dispatch(name, req, res) ... new.dispatch(name, req, res) # UsersController.new して、そのインスタンスで処理する end Web サーバー(Puma など)がリクエストを受け取る ↓ Rails.application.call(env) Rack ミドルウェア(Rails の中で順番に呼ばれる前処理の部品。セッション・Cookie など) ↓ ActionDispatch::Routing::RouteSet#call … ルーティング ↓ Journey::Router#serve … GET /users/1 に合うルートを探す ↓ RouteSet::Dispatcher#serve … "users" → UsersController クラスに変換 UsersController.dispatch(:show, req, res) … Metal に定義されたクラスメソッド ↓ new.dispatch(:show, req, res) … self.new = UsersController.new UsersController のインスタンス#dispatch … 以降はこのインスタンスの上で動く Rack は、Ruby の Web サーバー(Puma など)とアプリ(Rails など)をつなぐ共通ルール。「call(env) を受け取って [ステータス, ヘッダー, 本文] を返す」という約束だけを決めている env は、リクエストの情報(URL、HTTP メソッド、ヘッダーなど)を詰めたハッシュ Puma・Rack ミドルウェア・Rails は別々のサーバーではなく、同じ1つのプロセスの中で動いている リクエストごとに別のインスタンスなので、別のユーザーのリクエストと @user が混ざることはない ② 集める:view_assigns actionpack abstract_controller/rendering.rb のメソッドです。 show の実行後に呼ばれたときの値を、コメントで書き込んでいます。 def view_assigns variables = instance_variables - _protected_ivars instance_variables => [:@_request, :@_response, :@_params, :@_routes, ..., :@user] ← Rails 内部用も混ざっている 配列の引き算で内部用を外す => [:@user] variables.each_with_object({}) do |name, hash| # name = :@user、hash は最初は空の {} hash[name.slice(1, name.length)] = instance_variable_get(name) # "@user" の2文字目以降 "user" をキーに、@user の値を詰める end => { "user" => #<User id: 1> } end 使っているのは、Ruby の標準メソッド2つだけでした16。 メソッド 何をするか instance_variables そのオブジェクトが持つインスタンス変数の名前を、シンボルの配列で返す instance_variable_get(名前) 名前を指定して、その変数の値を取り出す instance_variables はレシーバーを省略しているので、self.instance_variables と同じ。self は①で new されたコントローラーのインスタンス 除外する _protected_ivars は、@_params や @_request など、Rails 内部用の変数を並べた一覧 each_with_object({}) は、空のハッシュを用意して1件ずつ詰め、最後にそのハッシュを返すメソッド つまり、コントローラーの中身を一覧して、@ 付きの変数を「名前 → 値」のハッシュに変換しているだけでした。 ③ 作る:view_context actionview action_view/rendering.rb のメソッドです。 def view_context view_context_class.new(lookup_context, view_assigns, self) end 第1引数:どのテンプレートファイルを使うかを決める条件(探すフォルダ、言語、html / json などの形式) 第2引数:②で作ったハッシュ 第3引数:コントローラー自身(self) ■ self はなぜコントローラーなのか view_context は actionview の ActionView::Rendering モジュールに書かれていますが、それでも self はコントローラーでした。実行時の self は、「そのメソッドがどのファイルやモジュールに書かれているか」ではなく、「ドットの左側にいたオブジェクト(レシーバー)」で決まるからです17。 module Greeting def who = self end class UsersController include Greeting # Greeting のメソッドを、UsersController のインスタンスから呼べるようにする end c = UsersController.new c.who.equal?(c) # => true(レシーバーの c が self になる) UsersController.ancestors.first(2) # => [UsersController, Greeting] include は、メソッドをコピーするのではなく、メソッドを探す経路(ancestors)にモジュールを追加する c.who と呼ぶと、Ruby は UsersController → Greeting の順に who を探し、見つけたメソッドを c の上で実行する Rails の view_context も同じで、コントローラーに include されたモジュールのメソッドです。①で new したコントローラーのインスタンスから、レシーバーを省略して(self.〜 として)呼ばれ続けるので、self はずっとそのインスタンスのままです。そのため第3引数の self には、リクエストを処理しているコントローラー自身が渡されます。 ④ ビュー用のクラスは「無名クラス」 new しているクラス view_context_class は、ActionView::Base を継承した無名クラス(名前のないクラス)でした。 ■ 無名クラスとは Ruby では Class.new でクラスを作れます18。それを定数(大文字で始まる名前)に入れると名前が付き、入れなければ無名クラスになります。 Dog = Class.new { def hi = "hi" } dog = Class.new { def hi = "hi" } Dog.name # => "Dog" dog.name # => nil(中身は同じでも、ローカル変数に入れただけでは名前が付かない) dog.new.hi # => "hi"(変数 dog を通して、普通のクラスと同じように使える) 違うのは名前の有無だけで、継承・include・メソッド定義・new は普通のクラスと同じようにできる 無名クラスでも、使い回すには変数などに入れておく必要がある。ただし変数はクラスを入れておく箱にすぎず、変数名はクラスの名前にならない。箱は何個あってもよく入れ替えもできるので、クラス側には「自分がどの名前で呼ばれているか」が分からない。名前として記録されるのは、最初に入れた定数だけ Rails はビュー用の無名クラスを、コントローラーのクラス側に @view_context_class として入れてキャッシュし、③の view_context_class.new(...) で取り出して new している ■ ヘルパーが呼べる理由 Rails は、この無名クラスにルーティングのヘルパーとアプリのヘルパーを include しています(action_view/rendering.rb)。 Class.new(klass) do include routes.url_helpers(supports_path) # users_path など include helpers # app/helpers/ のメソッド end ビュー用オブジェクトはこのクラスのインスタンスなので、ビューで users_path や自作のヘルパーが呼べる ancestors をたどると、無名クラス → ヘルパーのモジュール → … → ActionView::Base の順にメソッドを探している ⑤ 書き込む:initialize と assign ③の new を呼ぶと、Ruby は次の2つを順に行います。 メモリ上に空のオブジェクトを作る そのオブジェクトの initialize を、new と同じ引数で自動的に呼ぶ action_view/rendering.rb view_context_class.new(lookup_context, view_assigns, self) ↓ ↓ ↓ action_view/base.rb ↓ ↓ ↓ def initialize(lookup_context, assigns, controller) @lookup_context = lookup_context ... assign_controller(controller) # @_controller にコントローラーを入れる ... assign(assigns) # 最後に、②のハッシュを書き込む end def assign(new_assigns) @_assigns = new_assigns new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } end assign は、②の view_assigns のちょうど逆の操作でした。 ② コントローラー側 ⑤ ビュー側 使うメソッド instance_variable_get instance_variable_set 名前の変換 :@user → "user" "user" → "@user" 結果 変数 → ハッシュ ハッシュ → 変数 instance_variable_set("@user", value) は、@user = value を、変数名を文字列で指定して書いたもの19 @user = value と書くと変数名はコードを書いた時点で決まるが、instance_variable_set なら実行中に決められる Rails は開発者がどんな名前の変数を作るか知らないので、ハッシュのキーから名前を組み立てて代入している 2つのオブジェクトの中身の移り変わりを図にすると、こうなります。 【コントローラー】 @_params, @user=#<User name: "山田"> (show を実行した直後) │ │ ② view_assigns:内部用を除いて、名前と値のハッシュにする ▼ { "user" => #<User name: "山田"> } │ │ ③ ビュー用のクラス.new(ハッシュ, self) ▼ 【ビュー用オブジェクト】 (まだ空っぽ) │ │ ⑤ initialize の中で │ assign_controller(self) … @_controller にコントローラーを入れる │ assign(ハッシュ) … "user" を "@user" にして instance_variable_set ▼ 【ビュー用オブジェクト】 @_controller, @user=#<User name: "山田"> │ │ ⑥ テンプレートを実行 ▼ <p>山田さん</p> ⑥ テンプレートは「ビュー用オブジェクトのメソッド」になる ここでいうテンプレートは、app/views/ 配下のファイル全部のことです。 種類 ファイル名の例 アクションのテンプレート users/show.html.erb partial(部品のテンプレート) users/_form.html.erb レイアウト layouts/application.html.erb _ で始まるのは partial だけで、どれも同じ仕組みで動きます。ここでは show.html.erb を例にします。 <p><%= @user.name %>さん</p> Rails はこのファイルを、次の Ruby のメソッドに変換していました。rails runner で ActionView::Template を作り、compiled_source(action_view/template.rb)を表示させた結果です。実際の出力は1行にまとまっているので、見やすく改行してコメントを足しています。 def _app_views_users_show_html_erb__1476774381686002440_15872(local_assigns, output_buffer, &_) @virtual_path = "users/show"; @output_buffer.safe_append='<p>'.freeze; # 固定の HTML はそのまま足す @output_buffer.append=( @user.name ); # <%= @user.name %> は、式の結果を(エスケープして)足す @output_buffer.safe_append='さん</p>'.freeze; @output_buffer # 組み立てた HTML を返す end 実行したコード rails runner で、テンプレートの中身を直接渡して変換させました。ファイルを作ったり DB を書き換えたりはしないので、どの Rails アプリでも試せます。 bundle exec rails runner ' template = ActionView::Template.new( "<p><%= @user.name %>さん</p>", # テンプレートの中身 "app/views/users/show.html.erb", # ファイルのパス(実在しなくてよい) ActionView::Template.registered_template_handler(:erb), locals: [], format: :html, virtual_path: "users/show" ) puts template.send(:compiled_source) # Ruby のメソッドに変換した結果を表示 ' compiled_source は外から呼べない private メソッドなので、send で呼んでいる メソッド名の末尾の数字は、実行するたびに変わる テンプレートは、ファイルのまま実行されるのではなく、1つの def に変換される メソッド名は、ファイルのパスと番号から自動で作られる。先頭の _ は他のメソッドと名前がぶつからないための接頭辞で、partial かどうかとは関係ない Haml でも、生成されるコードの中身が違うだけで仕組みは同じ ■ 変換から実行までの3段階 show.html.erb(ただのテキストファイル) │ 1. 変換:テンプレートエンジン(ERB / Haml)が "def ... end" という Ruby の「文字列」にする ▼ "def _app_views_users_show_html_erb...(local_assigns, output_buffer, &_) ... @user ... end" │ 2. 定義:module_eval で読み込ませる → ビュー用のクラスで使える本物のメソッドになる ▼ ビュー用のクラスのメソッド _app_views_users_show_html_erb... │ 3. 実行:ビュー用オブジェクト.public_send(:_app_views_users_show_html_erb__...) ▼ メソッドの中の self = ビュー用オブジェクト ├ @user → self の @user(⑤で assign した値) └ users_path → self のメソッド(④で include されたヘルパー) module_eval(文字列) は、文字列のコードをクラスの中に書いたものとして実行する。"def ... end" を渡せば、そのクラスにメソッドが追加される public_send(:foo) は、メソッド名をシンボルで指定して呼ぶメソッド。obj.public_send(:foo) は obj.foo と同じ テンプレートのメソッド名は実行中に自動で作られるので、obj._app_views_... のようにコードへ直接書いておけない。そのため、名前を値として受け取れる public_send で呼んでいる klass = Class.new klass.module_eval("def greet = 'こんにちは'") # 文字列のコードから greet メソッドを定義する obj = klass.new name = :greet # 呼びたいメソッドの名前を、値として持っている obj.public_send(name) # => "こんにちは"(obj.greet と同じ) 1と2は最初の1回だけ。2回目以降のリクエストは、定義済みのメソッドを呼ぶだけなので速い テンプレートの @user が読めるのは、Ruby に「メソッドの中の @xxx は、そのメソッドを呼ばれたオブジェクト(self)の変数を指す」というルールがあるから(③の self の話と同じ考え方で、つまり今回はビュー用オブジェクトの @user を指す) 3.4 この仕組みから分かること 書き写されるのは、オブジェクトへの参照でした。中身を丸ごと複製(ディープコピー)しているわけではありませんでした。 Ruby の変数に入っているのは、オブジェクトそのものではなく「そのオブジェクトがどこにあるか」を指す参照です。②⑤の書き写しは、この参照を受け渡しているだけで、オブジェクトを複製する処理(dup など)はどこにもありません。 ref = instance_variable_get(:@user) # ② コントローラーの @user から参照を取り出す hash["user"] = ref # ハッシュに入れる(複製しない) instance_variable_set("@user", ref) # ⑤ ビュー用オブジェクトの @user に入れる(複製しない) そのため、2つの @user は同じオブジェクトを指します。 [コントローラー] @user ──┐ ├──→ #<User name: "山田">(メモリ上に1つだけ) [ビュー用オブジェクト] @user ──┘ 現象 理由 ビューで @user = 別のもの と代入し直しても、コントローラー側は変わらない 変数は別々で、ビュー側の矢印だけが別のものを指し直すから ビューで @user.name = "x" と中身を変えると、コントローラー側から見ても変わる 2つの変数が同じオブジェクトを指しているから partial でも @user が使える partial も同じビュー用オブジェクトのメソッドとして実行されるから ビューでは params も使える ビュー用オブジェクトは params のデータを持たず、⑤で @_controller に入れたコントローラーの params を呼んでいるから(delegate による委譲) @usr と書き間違えてもエラーにならず、nil になる Ruby では、作っていないインスタンス変数を読むと nil が返るから 4. テスト いろいろなテストコードを読むうちに、FactoryBot には create だけでなく build / build_stubbed もあり、データの作り方が3つあると知り、なぜ使い分ける必要があるのか疑問に思ったのがきっかけで調べました。 4.1 FactoryBot の create / build / build_stubbed 調べてみると、DB から読み直す必要がないテストなら、create ではなく build_stubbed を使うと速くなると分かりました。テストデータを作る FactoryBot には、オブジェクトの作り方が3種類ありました20。 user = create(:user) # DB に保存する user = build(:user) # 保存しない(User.new と同じ状態) user = build_stubbed(:user) # 保存しないが、保存済みのふりをする DB への保存 id 向いているテスト 速さ create する 本物 画面表示や検索など、DB から読み直す処理 遅い build しない nil 保存前のバリデーションなど 速い build_stubbed しない 偽の id DB を使わないメソッド 最速 DB に触らない分だけ速いので、create が要らない場面では build / build_stubbed を選ぶ 関連も、既定では親と同じ作り方になる(build なら関連も保存されない) build_stubbed は、DB に保存しないまま、保存済みのように振る舞います21。 user = build_stubbed(:user) user.id # => 1001(偽の id) user.persisted? # => true(「DB に保存済みか」を聞くと、保存済みと答える) user.save # => RuntimeError(本当に DB に触ろうとするとエラー) persisted? の persist は「残り続ける」という意味。「DB に保存されて、プログラムが終わっても残る状態になったか(永続化されたか)」を聞くメソッド build_stubbed はこれに true を返すので、user_path(user) のように保存済みを前提にした URL の生成なども、DB なしでテストできる おわりに 最後まで読んでいただき、ありがとうございました。 脚注 MDN Web Docs「<script>: スクリプト要素」 ↩︎ WHATWG「HTML Living Standard」 ↩︎ W3C「JSON-LD 1.1」 ↩︎ IETF「RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format」 ↩︎ IANA「Media Types」 ↩︎ Ecma International「ECMAScript Language Specification(ECMA-262)」 ↩︎ schema.org「About Schema.org」 ↩︎ IETF「RFC 6839: Additional Media Type Structured Syntax Suffixes」 ↩︎ Jason Miller「Islands Architecture」 ↩︎ React(旧ドキュメント)「ReactDOM」 ↩︎ ↩︎ React「hydrateRoot」「createRoot」 ↩︎ MDN Web Docs「EventTarget: addEventListener() メソッド」 ↩︎ facebook/react v17.0.2「ReactDOMComponentTree.js」「ReactDOMHostConfig.js」 ↩︎ Rails ソースコード(8-1-stable)「actionpack/lib/abstract_controller/rendering.rb」「actionview/lib/action_view/rendering.rb」「actionview/lib/action_view/base.rb」「actionpack/lib/action_controller/metal.rb」「actionview/lib/action_view/helpers/controller_helper.rb」「actionview/lib/action_view/template.rb」 ↩︎ Ruby リファレンスマニュアル「変数と定数(インスタンス変数)」 ↩︎ Ruby リファレンスマニュアル「Object#instance_variables」「Object#instance_variable_get」 ↩︎ Ruby リファレンスマニュアル「変数と定数(擬似変数)」 ↩︎ Ruby リファレンスマニュアル「Class.new」 ↩︎ Ruby リファレンスマニュアル「Object#instance_variable_set」 ↩︎ factory_bot「Build strategies」 ↩︎ thoughtbot/factory_bot v6.5.5「lib/factory_bot/strategy/stub.rb」 ↩︎

  3. We have discussed doing this for a long time, finally! rails#59390

    22 likes · 1 repost · 1,858 views
  4. AIに効かせるガードレールとして、RuboCop設定をRails本体と比較して見直した話 こんにちは、ツクリンク株式会社でソフトウェアエンジニアをしている千葉です。 今回は、AI開発のガードレールとして正しく機能させることを目的に、ツクリンクの.rubocop.ymlをRails本体(rails/rails)のRuboCop設定と比較し、設定が異なるCopを一つずつ見直しました。 本記事では、差分のあった12Copの一覧と、「寄せる・寄せない」をどう判断したのかを代表的なCopを例に整理してご紹介します。 ✅ はじめに ツクリンクではRuboCopを運用しており、以前はClaude Code Routinesで違反を自動で解消する仕組みもご紹介しました。ただ、.rubocop.ymlの設定自体は過去の経緯で決まったものも多く、「なぜこのルールを無効にしているのか」「世の中の標準からどのくらいズレているのか」をあらためて確認する機会がありませんでした。 一方で、ツクリンクではエンジニア全員がClaude Codeを使っています。AIはプロジェクトの文脈を読み取ってコードを書くので、.rubocop.ymlは人間向けの規約というより、AIが書くコードの質を決める文法のガードレールになります。思考の方向性を示すCLAUDE.md、インターフェース定義のopenapi.ymlとあわせて、このガードレールがしっかり整備されていれば、多少ラフなプロンプトでもAIが迷わずプロジェクトに馴染むコードを書けるようになるはずだと考えています。 CIでRuboCopを実行する仕組みは既にあるため、ガードレールの仕組み自体は整っています。ただし、仕組みがあっても、ルール側が「正しい」と言える状態になっていなければ機能しません。過去の経緯で無効化されたままのCopや、pending(RuboCopに追加されたが、まだ有効化されていない状態)のまま放置されたCopが残った設定では、AIがそれに従って書いたとしても正直あまり意味がないと思っています。 そこで今回は、ガードレールの中身を正しくするために、Railsアプリとして最も参照されるであろうRails本体のRuboCop設定を基準にして、自社設定との差分を一つずつ見直して、ルール側を先に確定させることにしました。 「自社のRuboCop設定が妥当なのか分からない」「AI開発に向けてコード規約を整備したい」といった方の参考になれば幸いです。 ✅ 比較方法 今回の比較は以下の条件で行いました。 比較対象: rails/railsが有効化している99Cop 方法: 両者の.rubocop.ymlを比較して設定が異なるCopを洗い出し、rails/railsの設定をツクリンクのリポジトリに適用してrubocopを実行し、Copごとの違反件数を集計 rails/railsが有効化している99Copのうち、ツクリンクと設定が異なっていたのは12Copでした。 ✅ Rails本体と設定が異なる12Cop 比較した結果を一覧にすると以下のようになりました。Cop名は公式ドキュメントへのリンクになっています。 Cop rails/rails tsukulink 判定 Style/StringLiterals double_quotes 無効 対応不要と判断 Style/FrozenStringLiteralComment always 無効 影響が大きいため今回は見送る Layout/IndentationConsistency indented_internal_methods normal 対応不要と判断 Layout/EmptyLinesAroundAccessModifier only_before around 対応不要と判断 Layout/SpaceInsideBlockBraces 有効 無効 適用済み Layout/EndAlignment variable keyword 対応不要と判断 Style/RedundantReturn 有効 無効 適用済み Performance/SelectMap 有効 pending 適用済み Style/KeywordArgumentsMerging 有効 pending 適用済み Style/ArrayIntersect 有効 pending 適用済み Style/Semicolon AllowAsExpressionSeparator: true false 対応不要と判断 Lint/Debugger DebuggerRequires: debugのみ デフォルト 対応不要と判断 ✅ 判断の仕方(代表的な3Cop) 12Cop全てを同じ観点で見ていきましたが、ここでは判断の仕方が分かりやすい3つを取り上げます。 各Copについて、何をチェックするルールか、両者の設定、寄せた場合に何が起きるか、メリット・デメリットの順に見て、判定を出しています。 採らなかった例: Style/StringLiterals 文字列リテラルのクォートをシングル・ダブルのどちらかに統一するCopです。 rails/rails: ダブルクォートで統一します(double_quotes)。式展開やエスケープが無くても"..."を使います tsukulink: 現状、無効。シングルとダブルが混在しています(RuboCop本体のデフォルトはシングル統一) 寄せると: 違反は60221箇所あり、これを全て'...'から"..."に置換することになります。挙動は変わりませんが、ほぼ全ファイルの差分になってしまいます rails/rails(ダブル統一): 上がNG、下がOK シングル統一(RuboCopデフォルト): 上がOK、下がNG name = 'tsukulink' name = "tsukulink" シングル統一でも式展開・エスケープが必要なときはダブルを使う(どちらの設定でもOK) greeting = "こんにちは、#{name} さん" メリット Rails本体・omakase・Standard1といったRails周辺の主流と同じ見た目になること 後から式展開を入れるときにクォートを替える手間がなくなり、レビューでクォートの選び方を議論する必要が消えること デメリット ほぼ全ファイルに及ぶ差分でgit blameが壊れること 進行中のブランチと広範囲にコンフリクトする可能性があること Rubyコミュニティの標準(RuboCopデフォルトはシングル)とは逆で、挙動上の利点がないこと 判定 挙動上の利点がない見た目の差に対して、ほぼ全ファイルの差分とgit blameが壊れるコストが見合わないため、対応不要と判断しました。 採った例: Style/RedundantReturn メソッドの最後の式に付いた不要なreturnをチェックするCopです。Rubyは最後の式の値を自動的に返すので書かなくてもよいものです。 rails/rails: 有効。ただしreturn a, bのように複数値を返す場合は許容します(AllowMultipleReturnValues: true) tsukulink: 無効(「明示的にreturnを書くことを許容」として外している)。RuboCopデフォルトは有効です 寄せると: 違反は61箇所で、末尾のreturnを削除するだけです。挙動は変わりません。途中で抜けるガード節のreturnは対象外です NG def display_name return name.presence || "名称未設定" end OK def display_name name.presence || "名称未設定" end メリット Rubyの慣用に沿い、RuboCopデフォルト・Rails本体・omakaseのすべてと一致すること 件数が少なく、Safe autocorrectで完了すること デメリット 「末尾でもreturnを書いて返り値であることを明示したい」という書き方ができなくなること(ツクリンクが無効にしている理由)。ただし実害はありません 判定 RuboCopデフォルトとも一致しており、件数が少なくSafe autocorrectだけで完了するため、適用しました。 保留した例: Style/FrozenStringLiteralComment ファイル先頭に# frozen_string_literal: trueのマジックコメントがあるかをチェックするCopです。このコメントがあると、そのファイル内の文字列リテラルは全て凍結(変更不可)になります。 rails/rails: 全ファイルに必須です(always) tsukulink: 無効。コメントがあるのは117ファイルのみです 寄せると: 違反は2944ファイルあり、それぞれ先頭に1行追加することになります。自動修正はUnsafeで、文字列を破壊的に変更しているコード(<<やgsub!など)があるとFrozenErrorで落ちるようになります NG(ファイル先頭にコメントがない) class Company < ApplicationRecord OK frozen_string_literal: true class Company < ApplicationRecord コメントを付けると壊れるコードの例 message = "こんにちは" message << "世界" # FrozenError message = +"こんにちは" # 先頭に + を付けるか String.new で回避 メリット 同じリテラルが評価されるたびに新しいStringオブジェクトを作らなくなり、メモリ負荷が下がること 意図しない文字列の破壊的変更(定数やデフォルト引数の汚染)をエラーで捕まえられること Ruby 3.4は凍結されていないリテラルの変更に警告を出すようになっており、将来のデフォルトに先回りできること デメリット 実行時になって初めてFrozenErrorが出る可能性があり、テストで網羅されていない経路では本番障害になり得ること 全テストの実行とRuby 3.4の警告(-W:deprecated)による事前確認が必要なこと 判定 対応したいものの、自動修正がUnsafeで本番障害になり得ます。全テストの実行とRuby 3.4の警告確認を含めて別途取り組む必要があるため、今回は見送りました。 ✅ 対応方針まとめ 12Copの判定を整理すると以下になります。 判定 Cop 適用済み(Safe autocorrect or 設定変更のみ) Layout/SpaceInsideBlockBraces、Style/RedundantReturn、Style/KeywordArgumentsMerging、Style/ArrayIntersect 適用済み(手動修正) Performance/SelectMap 影響が大きいため今回は見送る Style/FrozenStringLiteralComment 対応不要と判断 Style/StringLiterals、Layout/IndentationConsistency、Layout/EmptyLinesAroundAccessModifier、Layout/EndAlignment、Style/Semicolon、Lint/Debugger 「対応不要」と判断したCopに共通するのは、Rails本体と揃えても自社にとっての実利がないか、差分のコストに見合わないという点です。Rails本体はフレームワークのソースコードであり、アプリケーションコードと同じスタイルが最適とは限らないため、「Rails本体がこうしているから」だけを理由に寄せるのは避けました。 一方で適用したCopは、RuboCopデフォルトで有効なもの(Layout/SpaceInsideBlockBraces、Style/RedundantReturn)か、pendingとして次期デフォルトになることが決まっているもの(Performance/SelectMap、Style/KeywordArgumentsMerging、Style/ArrayIntersect)で、いずれも件数が少なくSafe autocorrectで完了する、あるいはパフォーマンス上の実利があるものです。 ✅ まとめ Rails本体のRuboCop設定を基準に自社設定を比較してみたところ、99Copのうち差分があったのは12Copで、そのうち実際に寄せるべきと判断したのは6Copでした(うち5Copを適用し、1Copは対応を見送り)。 12Copを一つずつ見ていくと、採るべきものと採らなくてよいものはかなりはっきり分かれました。有名なプロジェクトの設定であっても、自社に合うかどうかは一つずつ確かめる必要があると改めて認識しました。 RuboCopの設定を見直したいと考えている方や、AI開発に向けてコード規約を整備したいと考えている方の判断材料になれば幸いです。 脚注 omakaseはRailsチームが提供するアプリケーション向けのRuboCop設定、Standardはコミュニティ主導のRubyスタイルガイドです。いずれもRails本体の設定とは一部異なるルールを採用しており、本記事では「Rails周辺で広く使われる標準」の参考として言及しています。 ↩︎

  5. Safer secrets, ZJIT, and more! New apps stop falling back to Marshal for signed messages, the local secret file is no longer world-readable, and SQLite finally renames columns the way the database intended. Here’s what’s new in Rails: Default config.active_support.message_serializer to :json for 8.2 With config.load_defaults 8.2, messages from MessageVerifier and MessageEncryptor no longer fall back to deserializing Marshal payloads. That covers signed IDs, signed GlobalIDs, Active Storage keys, and the rest. Apps on 7.1 through 8.1 keep :json_allow_marshal. Confirm nothing still arrives as Marshal via the message_serializer_fallback.active_support notification before you switch. Create tmp/local_secret.txt with owner-only permissions The development and test secret_key_base file was created with the process umask, often 022, so it was world-readable. It is now written as 0600, and an existing file is tightened on read on non-Windows platforms, matching config/master.key. Keep route redirects from a string template on the request host redirect("/%{path}") could turn GET /old/%2Finjected.example into //injected.example, which browsers follow to another host. When interpolation would give the target a scheme or host the template does not name, the delimiter is now percent-encoded. Templates that already name a host, such as redirect("//cdn.example.com/%{path}"), are unchanged. Add config.zjit Like config.yjit, this enables the JIT after boot so code that only runs while starting the app is not compiled. When ZJIT is available it takes precedence over YJIT, since only one JIT can be on. config.zjit = true Use native RENAME COLUMN for rename_column on SQLite rename_column used to rebuild the table and copy index, check constraint, and virtual column SQL as written, so it failed when that SQL used the old column name. The rebuild also dropped triggers, broke foreign keys from other tables, and lost a string primary key. SQLite’s own ALTER TABLE ... RENAME COLUMN updates all of those. Fix in_order_of returning no records after a second serialize Types whose serialized value changes when serialized again, such as deterministic encryption, json columns, and serialize coders, made in_order_of match nothing. Values are serialized once now. Fix numericality on a decimal attribute with implicit scale A database-backed decimal column without an explicit scale stores integers, but the validator treated the original float as if no rounding happened. Validation now uses the value the attribute actually holds. # t.decimal :unscaled_bank_balance, precision: 10 subject = NumericData.new(unscaled_bank_balance: 10_000_000.121) subject.unscaled_bank_balance # => 10000000 subject.valid? # => true Allow Active Record objects in the tuple where syntax The single-column form already accepted records. A record in a column tuple is now expanded to its primary key values. The same change fixes arrays of objects that delegate #id: only a one-element array was expanded before, so a longer list compared against NULL. Cpk::Book.where([:author_id, :id] => book) Author.where(id: [decorator.new(one), decorator.new(two)]) Raise when deliver_all_later is called with an already accessed message deliver_later already raised in that situation. ActionMailer.deliver_all_later used to enqueue anyway and silently drop local changes. Truncate tables shadowed on the PostgreSQL search path db:truncate_all and the reset of parallel test databases truncated the table a bare name resolves to twice and left the shadowed one untouched. They also failed on a table whose name contains a dot. Truncation now uses schema-qualified names from the catalog. Bump the minimum supported MySQL version to 5.7.9 That is the version where innodb_default_row_format defaults to DYNAMIC, so the old ROW_FORMAT=DYNAMIC workaround for indexing utf8mb4 varchar(255) columns is gone. Tables are created without an explicit ROW_FORMAT. Add a before_ractorize load hook Rails::Application#ractorize! now runs ActiveSupport.on_load(:before_ractorize) so frameworks, engines, and gems can prepare their own state before the app is made shareable. Framework ractorizing moved into each railtie instead of living inside ractorize!. ActiveSupport.on_load(:before_ractorize) do MyGem.make_shareable! end Base deprecation proxies on BasicObject DeprecatedObjectProxy and DeprecatedInstanceVariableProxy now inherit from BasicObject. Methods added to Object after Active Support loads, such as to_json, to_param, blank?, or in?, are forwarded to the deprecated object with a warning instead of running on the proxy. ParameterFilter#filter_param passes a one-pair hash to Proc filters The third argument is now { key => value }, the same hash filter would pass for that pair, instead of nil. Check ActiveModel::Attributes methods without building the attributes hash respond_to? for a method that is not defined no longer calls attributes. A key that an attributes override adds, without an attribute or a method, no longer makes the model respond to it. Active Record already worked this way. Deprecate ssl_default_redirect_status, raise_on_assign_to_attr_readonly, and drop two PostgreSQL adapter checks A few cleanups landed this week: - ActionDispatch::SSL’s ssl_default_redirect_status: is deprecated. The default has been 308 since Rails 6.1, and using the middleware without the argument now uses 308 instead of 307. - config.active_record.raise_on_assign_to_attr_readonly has been a framework default since 7.1. It will be removed in 9.0 so attribute writers can be optimized further. - supports_identity_columns? and supports_native_partitioning? always returned true since PostgreSQL 10. They are gone. You can view the whole list of changes here. We had 40 contributors to the Rails codebase this past week! Until next time! View this post online You’re subscribed to This Week in Rails on HEY World Default config.active_support.message_serializer to :json for 8.2: rails#59156 Create tmp/local_secret.txt with owner-only permissions: rails#59247 Keep route redirects from a string template on the request host: rails#59218 Add config.zjit: rails#59183 Use native RENAME COLUMN for rename_column on SQLite: rails#59204 Fix in_order_of returning no records after a second serialize: rails#59207 Fix numericality on a decimal attribute with implicit scale: rails#59246 Allow Active Record objects in the tuple where syntax: rails#59248 Raise when deliver_all_later is called with an already accessed message: rails#59192 Truncate tables shadowed on the PostgreSQL search path: rails#59101 Bump the minimum supported MySQL version to 5.7.9: rails#59175 Add a before_ractorize load hook: rails#59026 Base deprecation proxies on BasicObject: rails#59325 ParameterFilter#filter_param passes a one-pair hash to Proc filters: rails#59190 Check ActiveModel::Attributes methods without building the attributes hash: rails#59206 Deprecate ssl_default_redirect_status: rails#59181 raise_on_assign_to_attr_readonly: rails#59191 drop two PostgreSQL adapter checks: rails#59232 here: github.com/rails/rails/compare/@%7B2026-10-16%7D.…

  6. The Rails API documentation has been converted to Markdown: rails#59339

    36 likes · 5 reposts · 924 views
  7. Sucks that they don't have some kind of basic respect for accesibility or localization. Maybe editing this line to add an SVG <title> or <desc> tag would do it. I think screenreaders unpack SVGs to get at those. github.com/rails/rails/blob/2f2c00ba683ec7845d8fb…

    1 like
  8. Yup, I said it in the past: rails#56779

    2 likes
  9. The Rails Initialization Process: From bin/rails to a Running Server When you run bin/rails server, Rails doesn't immediately start accepting HTTP requests. It first loads dependencies, resolves the command, prepares the server, loads your application configuration, runs initializers, and builds the Rack application. Let's walk through it. 1. Launching Rails bin/rails The journey starts with the bin/rails executable: #!/usr/bin/env ruby APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../config/boot" require "rails/commands" The first line tells the operating system to execute the script using Ruby. Next, Rails defines APP_PATH, which points to config/application.rb. This constant will be used later when Rails loads the application. Then it requires two files: - config/boot.rb, to prepare the Ruby environment and dependencies. - rails/commands, to resolve and execute the requested Rails command. Let's look at them one by one. config/boot.rb ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) require "bundler/setup" require "bootsnap/setup" This file prepares the application's dependency environment. First, BUNDLE_GEMFILE tells Bundler which Gemfile to use, unless it has already been configured. Next, bundler/setup configures Ruby's load path so the application can load the gems declared in the Gemfile. Finally, bootsnap/setup enables Bootsnap's caching mechanisms to help reduce startup time. At this point, the dependencies are ready to be loaded. 2. Resolving the Rails command rails/commands.rb After booting the dependency environment, Rails loads rails/commands.rb. A simplified version looks like this: require "rails/command" aliases = { "g" => "generate", "d" => "destroy", "c" => "console", "s" => "server", "db" => "dbconsole", "r" => "runner", "t" => "test" } command = ARGV.shift command = aliases[command] || command Rails::Command.invoke command, ARGV ARGV contains the command-line arguments passed to the script. For example, when you execute: bin/rails server -p 3001 Rails extracts server as the command and leaves -p 3001 as the remaining arguments. If you use the shorthand s, Rails expands it to server. It then calls Rails::Command.invoke to find and execute the appropriate command. rails/command.rb The Rails::Command.invoke method looks up the requested command and executes it when a matching command is found. If Rails cannot resolve the command, it can fall back to invoking a Rake task with the corresponding name. For our example, Rails resolves server to Rails::Command::ServerCommand. You can explore the implementation in the Rails command source code. 3. Preparing the server The server command is responsible for preparing the environment and starting the server. A simplified version of its perform method looks like this: def perform extract_environment_option_from_argument set_application_directory! prepare_restart Rails::Server.new(server_options).tap do |server| require APP_PATH Dir.chdir(Rails.application.root) if server.serveable? print_boot_information(server.server, server.served_url) server.start end end end The exact implementation can vary between Rails versions, but the important steps are the same. First, Rails processes the environment option and prepares the application directory and restart behavior. Then it creates a Rails::Server instance using the configured server options. These can include the host, port, environment, PID file, and server adapter. Notice the order: the server object is created before APP_PATH is required. This ordering matters because the server setup needs to establish the selected environment before the application is loaded. What is Rails::Server? In modern Rails, Rails::Server inherits from Rackup::Server. Rackup provides the interface for running Rack applications, while Rails adds application-specific behavior around it. The server object prepares the server configuration, but this doesn't mean that the Rails application has already completed initialization. That happens in the next part of the process. 4. Loading the application configuration When Rails executes: require APP_PATH it loads your application's config/application.rb. A typical Rails application contains something similar to: require_relative "boot" require "rails/all" Bundler.require(*Rails.groups) module MyApp class Application < Rails::Application # Application configuration end end The actual file depends on your application and Rails version. Let's break down the important parts. Loading rails/all require "rails/all" This loads the standard Rails frameworks, including Active Record, Action Controller, Action View, and other framework components. Applications can choose a more selective set of frameworks, so the exact set depends on how the application was created and configured. Defining Rails::Application module MyApp class Application < Rails::Application # Application configuration end end This defines your application class and its configuration. For example, this is where you might configure time zones, autoload paths, middleware, generators, or other application settings. However, defining the application class is not the same as fully initializing the application. Rails still needs to execute the initialization process. 5. Building the Rack application Now we reach Rails::Server#start. The server startup process prepares runtime resources, handles development caching where applicable, configures logging, and delegates to Rackup to load and run the application. For a standard Rails application, the Rack configuration file is config.ru: require_relative "config/environment" run Rails.application This file is the bridge between Rack and Rails. Rackup uses Rack::Builder to evaluate the configuration file and build the Rack application. The first line loads config/environment.rb. Let's follow that call. 6. Initializing Rails config/environment.rb A standard Rails environment file looks like this: require_relative "application" Rails.application.initialize! The first line ensures the application configuration has been loaded. The second line triggers the actual initialization process. This is an important distinction: config/application.rb defines the application; Rails.application.initialize! initializes it. The initialization process is implemented through Rails' initializer infrastructure. rails/all.rb and the Railties When config/application.rb requires rails/all, Rails loads the framework components and their Railties. A Railtie is an integration point that allows a framework or library to participate in Rails initialization and configuration. Rails uses these integrations to register initializers, configure middleware, prepare framework components, and coordinate startup. Running initializers The initialize! method calls run_initializers, which executes the registered initializers in dependency order. The initializers are organized into three broad groups: 1. Bootstrap initializers: prepare the application and its environment. 2. Railtie initializers: configure and integrate Rails frameworks, engines, and application-level behavior. 3. Finisher initializers: perform later initialization work and finalize the application setup. The ordering matters because some initializers depend on work completed by earlier ones. For example, a framework may need its configuration to be loaded before another initializer uses it. Application-specific initializers in config/initializers also participate in this process. Once initialization finishes, Rails.application is ready to serve as the application object passed to Rack. 7. From Rails.application to the HTTP server Remember this line in config.ru? run Rails.application It tells Rack to use the Rails application as the Rack endpoint. Rack builds the middleware application, and incoming requests pass through the configured middleware stack before reaching Rails' request-handling components. The request eventually reaches the router and the relevant controller, depending on the application and request. Meanwhile, Rackup starts the configured server adapter—typically Puma—which listens for HTTP connections and dispatches requests to the Rack application. So there are two related but different responsibilities: - Rails initializes the application and prepares request-handling behavior. - Rackup and the server adapter provide the interface that runs the application and accepts HTTP requests. That separation is one of the reasons Rails can run on different compatible Rack server implementations. The complete flow Here is the sequence in simplified form: bin/rails | v config/boot.rb | v rails/commands.rb | v Rails::Command.invoke | v ServerCommand | v Rails::Server | v config/application.rb | v server.start | v Rackup loads config.ru | v config/environment.rb | v Rails.application.initialize! | v Initializers run | v run Rails.application | v Rack middleware + server adapter | v The application is serving requests The diagram is a conceptual overview rather than a complete call stack. Some steps invoke other setup routines, and the exact implementation can vary by Rails and Rackup version. Final thoughts Tracing Rails initialization helped me understand that starting a Rails server is not one operation. It's a sequence of responsibilities shared between the Rails command system, application configuration, the initializer infrastructure, Rack, and the server adapter. If you want to explore the implementation further, the official Rails initialization guide is an excellent place to continue. Understanding this flow is especially useful when debugging boot errors, investigating initializer order, or figuring out where a configuration change takes effect.

  10. "Rails Magic" Isn't Magic. How a Request Actually Travels Through Rails. --- title: "\"Rails Magic\" Isn't Magic. How a Request Actually Travels Through Rails." published: true description: A visual, 10-minute deep dive into how Ruby on Rails actually runs under the hood. From Rack middlewares and Zeitwerk autoloading to ActiveRecord schema reflection and the view_assigns bridge. tags: rails, ruby, webdev, architecture cover_image: iili.io --- Ask any developer who has worked with modern web frameworks what they think of Ruby on Rails, and you will almost certainly hear the same word within thirty seconds: "Magic." You run a generator, and entire database tables spring to life. You write an empty model class, and it immediately knows every column in your schema without a single property declaration. You define an instance variable inside a controller action, and it appears inside a separate view template as if object boundaries ceased to exist. You reference a class you have never required, and Ruby finds it instantly. To newcomers, it feels like witchcraft. To skeptics, it feels like a dangerous black box that cannot be reasoned about or debugged when things go wrong. Except there is no magic in Rails. Every single convention in Rails is built out of plain Ruby metaprogramming, standard object-oriented design patterns, and an elegant pipeline architecture that has quietly powered some of the largest applications on the web for over twenty years. When you peel back the syntactic sugar, Rails reveals itself not as an inscrutable black box, but as a masterpiece of systems architecture. To truly master Rails and debug it with total confidence, you have to follow an HTTP request from the moment it hits your server socket, through the middleware onion, into the router, through the model reflection engine, and out through the view renderer. Here is what is actually happening behind the curtain. --- The Request Onion: From Puma to ActionDispatch Before Rails ever touches your code, your request must travel through what software architects call the Request Onion. At the outer edge sits your web server (typically Puma). Puma listens on a raw TCP socket, accepts an incoming HTTP request, parses the raw headers and body, and wraps that data into a plain Ruby hash known as the Rack environment (env). Puma then hands this hash to Rails using the universal contract of the Ruby web world: the Rack interface. [HTTP Request] ──> Puma Web Server ──> call(env) ──> [Rack Middleware Stack] ──> [ActionDispatch Router] A Rack application is disarmingly simple: it is any Ruby object that responds to call(env) and returns a three-element array containing: 1. An integer HTTP status code (such as 200). 2. A hash of HTTP response headers. 3. An enumerable body stream. !The Anatomy of a Rails Request: Puma, Rack Middlewares, ActionDispatch Router, Controller, and View When Puma calls Rails.application.call(env), the request does not jump straight into your controller. Instead, it cascades through a stack of more than twenty built-in Rack Middlewares. Each middleware wraps the next one like layers of an onion: 1. ActionDispatch::HostAuthorization: Guards against DNS rebinding attacks by verifying the Host header against authorized hostnames. 2. ActionDispatch::SSL: Enforces HTTPS redirection and sets Strict-Transport-Security headers. 3. Rack::Sendfile: Offloads static file serving to reverse proxies like Nginx when appropriate. 4. ActionDispatch::Cookies and CookieStore: Decrypts the signed session cookie, verifying its HMAC signature to prevent tampering, and deserializes your session payload into memory. 5. ActionDispatch::ShowExceptions: Catches unhandled errors raised deeper in the application, translating them into friendly HTML error pages or JSON diagnostics. Only after surviving this protective gauntlet does the request arrive at ActionDispatch::Journey, the Rails routing engine. Journey parses your config/routes.rb file on boot and compiles it into a deterministic routing tree. It matches the HTTP verb (GET) and path (/articles/42), extracts the parameters (id: "42"), determines that ArticlesController#show should handle the request, and dispatches the call. --- The Mystery of Missing Columns: ActiveRecord Schema Reflection Once inside the controller, you run your database query: class ArticlesController < ApplicationController def show @article = Article.find(params[:id]) end end Now consider your model file, app/models/article.rb: class Article < ApplicationRecord end Look closely at that class definition: there are no column declarations. There is no attr_accessor :title. There is no property :body, String. There is no schema definition file inside your model directory. Yet you can write article.title, article.body, and article.published?, and Ruby executes them flawlessly. How does a pure Ruby object know the names and types of its attributes when you never declared them? The answer lies in Dynamic Schema Reflection. !Why ActiveRecord Never Declares Columns: Schema introspection and GeneratedAttributeMethods module When your Rails application boots, or the very first time Article is referenced in code, ActiveRecord does something clever: 1. Database Catalog Introspection: ActiveRecord connects to your database adapter and queries the database catalog (in PostgreSQL, it queries information_schema.columns; in SQLite, it runs PRAGMA table_info(articles)). 2. Schema Ingestion: It retrieves the exact column names, data types, nullability constraints, and default values directly from the database engine itself. 3. Dynamic Module Generation: Instead of defining methods directly on Article, ActiveRecord creates a hidden internal module called GeneratedAttributeMethods. 4. Metaprogramming the Getters and Setters: For every column returned by the database catalog, ActiveRecord evaluates method definitions into this module: # Conceptual representation of what ActiveRecord generates internally: module GeneratedAttributeMethods def title _read_attribute('title') end def title=(value) _write_attribute('title', value) end def title? _query_attribute('title') end end 5. Module Inclusion: ActiveRecord includes GeneratedAttributeMethods into Article. Because the methods live inside an included module rather than the Article class itself, you can easily override def title in your own model, call super, and add custom formatting without breaking ActiveRecord's internal storage engine! The database remains the single source of truth for your data model. Rails reflects that truth dynamically into Ruby's method tables, eliminating the need to synchronize manual class declarations with database migrations. --- Crossing the Object Boundary: How Controller State Reaches Views Next comes one of the most baffling behaviors for programmers arriving from other languages: # app/controllers/articles_controller.rb class ArticlesController < ApplicationController def show @article = Article.find(params[:id]) @comments = @article.comments end end And in your view template: <!-- app/views/articles/show.html.erb --> <h1><%= @article.title %></h1> <p><%= @article.body %></p> In standard object-oriented programming, instance variables (@article, @comments) are strictly private to the specific object instance that created them. An instance of ArticlesController cannot expose its private @variables to an instance of ActionView::Base. Controllers and views are entirely different classes instantiated at different points in time. How does @article cross the boundary between two completely separate objects? The bridge is a method called view_assigns. !The Magic of @instance_variables in Views: The view_assigns extraction and injection bridge When your controller action finishes running, Rails initiates the rendering sequence: 1. Extracting Instance State: The controller calls view_assigns. This method inspects all instance variables on the controller object, strips internal Rails variables (like @_response and @_request), and converts the remaining user variables into a plain Ruby hash: # What view_assigns returns: { "article" => #<Article id: 42, title: "Rails Internals">, "comments" => [#<Comment id: 1>, #<Comment id: 2>] } 2. Instantiating the View Context: Rails instantiates a fresh ActionView::Base object responsible for rendering the template. 3. Metaprogramming the Handoff: Rails iterates through the assigns hash and injects each key and value directly into the new view instance using Ruby's reflection method: assigns.each do |key, value| view.instance_variable_set("@#{key}", value) end 4. Compiling the ERB Template: Rails takes your ERB template, compiles the HTML and embedded Ruby tags into a pure Ruby method, and executes that method in the context of the view instance. Because the view instance now possesses identical @article and @comments instance variables, your template reads them seamlessly. What looked like boundary-defying magic was simply an automated dictionary handoff executed through instance_variable_set. --- Zeitwerk: Why You Never Write require In standard Ruby, before you can use a class defined in another file, you must explicitly load it: require_relative "../services/payment_processor" In Rails, you simply write PaymentProcessor.charge(user) anywhere in your codebase, and it works. You almost never write require statements. For many years, Rails accomplished this using a custom mechanism hooked into Ruby's const_missing callback. But const_missing suffered from notorious edge cases: circular dependency deadlocks, thread-safety hazards, and confusing load-order bugs. Modern Rails solved this by adopting Zeitwerk, an autoloading engine written specifically for Ruby. !Zeitwerk Autoloading: Development on-demand reloading vs Production eager loading Zeitwerk enforces a clean convention: file paths must strictly mirror constant names. - Constant User maps to app/models/user.rb. - Constant Users::AccountNotifier maps to app/services/users/account_notifier.rb. How does Zeitwerk handle this across different environments? In Development: Instant Reloading Without Restarts In development, booting the server loads virtually zero application files into memory. When your code first encounters an unreferenced constant like User, Zeitwerk intercepts the lookup, inflects the name into app/models/user.rb, and requires the file on demand. More importantly, Zeitwerk integrates with a file-system watcher. When you modify a file in your text editor, Zeitwerk unloads the matching constants from Ruby's memory space. On your next browser reload, Rails re-evaluates the file freshly from disk. You get instant updates without restarting Puma or waiting for a compilation step. In Production: Eager Loading and Copy-on-Write In production, on-demand autoloading is completely disabled. Instead, during application boot, Zeitwerk executes an eager load. It crawls every directory inside app/ and loads every single class, module, and template into Ruby's memory space before Puma spawns its worker processes. This delivers two vital production benefits: 1. Zero Runtime Disk I/O: Incoming user requests never spend CPU cycles looking up or requiring files from disk. 2. Copy-on-Write (CoW) Memory Optimization: Because all classes are allocated in the master process before Puma forks child workers, all worker processes share the exact same physical RAM pages. A Rails application with five worker processes consumes drastically less memory than five separate instances started independently. --- The Thread Highway: ActiveRecord Connection Pooling When your application receives dozens of concurrent requests, how does Rails ensure that multiple threads do not corrupt your database connections? Puma is a multi-threaded web server. Each Puma worker process runs a thread pool (typically 3 to 5 threads per worker). Each thread handles an independent HTTP request concurrently. If two threads attempted to write raw SQL commands simultaneously across the same TCP connection to PostgreSQL, the protocol stream would interleave and crash the socket. To manage this safely, ActiveRecord uses a Connection Pool. !ActiveRecord Connection Pool Architecture: Puma Worker Threads checking out connections from ConnectionPool The connection pool acts as a thread-safe leasing office: 1. Connection Checkout: When Thread 1 executes Article.find(42), it asks the ConnectionPool for a database connection. 2. Thread Affinity: The pool assigns an available connection to Thread 1 and associates that connection with the thread's local storage. 3. Query Execution: Thread 1 executes its SQL statements safely across its dedicated connection. 4. Connection Check-in: When the HTTP request finishes and the Rack response leaves the controller, an outer Rack middleware (ActiveRecord::Rack::ConnectionManagement or the executor hook) checks the connection back into the pool, making it available for other waiting threads. The Pool Size Equation Understanding the connection pool prevents one of the most common production disasters in Rails: ActiveRecord::ConnectionTimeoutError. Your config/database.yml defines the pool size: production: adapter: postgresql pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> If Puma is configured to run 10 threads, but your database pool size is set to 5, the sixth concurrent thread will freeze, waiting up to 5 seconds for an active connection to become free before throwing a fatal timeout error. Keeping your database pool size equal to or slightly greater than Puma's max_threads guarantees that every active worker thread can execute queries without contention. --- Guarding the Mass Assignment Door: Strong Parameters One of the most consequential security innovations in Rails history is Strong Parameters. In early versions of Rails, developers wrote: # ❌ The old, dangerous way: def create @user = User.create(params[:user]) end This created a critical vulnerability known as a Mass Assignment Attack. A malicious user could send a crafted POST request including unintended attributes: { "user": { "name": "Attacker", "email": "attacker@evil.com", "admin": true } } Because ActiveRecord mapped all hash keys directly to database columns, the attacker could elevate themselves to an administrator simply by appending "admin": true to the form payload. Modern Rails prevents this by wrapping all incoming request parameters inside ActionController::Parameters. !Strong Parameters Pipeline: Raw untrusted payload filtered through require and permit before database insertion By default, an ActionController::Parameters instance is marked with an internal flag: permitted: false. If you attempt to pass an unpermitted parameters object directly into an ActiveRecord write method like User.create(params[:user]), ActiveRecord intercepts the call and immediately raises an ActiveModel::ForbiddenAttributesError. To persist the data, you must explicitly declare an attribute whitelist: private def user_params params.require(:user).permit(:name, :email) end The pipeline operates in two stages: 1. require(:user): Verifies that the :user key exists in the payload, raising ActionController::ParameterMissing (which automatically renders a 400 Bad Request) if absent. 2. permit(:name, :email): Iterates across the keys, strips out unauthorized attributes (such as :admin or :account_balance), and returns a new Parameters instance marked permitted: true. Only sanitized, explicitly authorized data ever reaches your database layer. --- The Paradigm Shift: From Magic to Mastery When developers complain about "Rails magic," they are usually reacting to the fact that Rails hides boilerplate code so effectively that the underlying mechanics become invisible. Yet none of these behaviors are mysterious: 1. The Request Onion is a modular chain of Rack middleware wrappers following a universal Ruby interface. 2. ActiveRecord Schema Reflection is dynamic database catalog introspection paired with runtime module inclusion. 3. The View Handoff is automated dictionary assignment via view_assigns. 4. Zeitwerk Autoloading is strict constant-to-path inflection optimized with production eager loading. 5. Connection Pooling is thread-safe connection leasing tailored to Puma's concurrency model. 6. Strong Parameters is strict parameter filtering protecting models from mass assignment exploits. When you understand the machinery powering these conventions, Rails stops feeling like an unpredictable trick. It becomes what it was always designed to be: an exceptionally well-engineered, productive framework that lets you focus on building features without getting bogged down in low-level plumbing. When you first started working with Rails, which of these "magical" behaviors felt the most puzzling? And what under-the-hood discovery made the biggest difference in how you write and debug your code? --- References: - Ruby on Rails Official Guides: Rails on Rack - Ruby on Rails Official Guides: Autoloading and Reloading Constants (Zeitwerk) - Ruby on Rails Official Guides: Action Controller Overview & Strong Parameters - ActiveRecord Source Code: ConnectionPool Architecture - Aaron Patterson: How ActiveRecord Finds Your Attributes

    23 reactions · 13 comments
Trending awards, every completed day and week
Gold
561st place finishes
Silver
1572nd place finishes
Bronze
1663rd place finishes
Awarded repositories
5Owned by rails now

Repositories

Ordered by gold, then silver, then bronze.

  1. railsBest: 1st by PR authors, 18 PR authors54154164
  2. turbolinksBest: 1st by stars, 372 stars220
  3. activejobBest: 2nd by stars, 173 stars010
  4. pjax_railsBest: 3rd by stars, 97 stars001
  5. rails-dev-boxBest: 3rd by stars, 99 stars001

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.