rails

Stars and mentions

Last 30 days, Oct 5 – Nov 3

GitHub stars100
mentions55
036912Oct 5Oct 19Nov 3
UTC days · repositories rails owns15 repositories

Package downloads

Cumulative recorded downloads · since 2025-10-17

npm145M
036.2M72.5M108.7M145MOct 5Oct 19Nov 3
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

60posts
Nov 45 posts
  1. Patch Your Rails: 21 Security Fixes in 8.0.5.2 and 8.1.4.1 --- title: Patch Your Rails: 21 Security Fixes in 8.0.5.2 and 8.1.4.1 published: true description: Rails 8.0.5.2 and 8.1.4.1 fix 21 security issues, including a critical Active Storage bug in apps using ImageMagick. Here's who is affected, what might break when you upgrade, and what to do if you can't upgrade yet. tags: activestorage, rails, ruby, security canonical_url: christine-seeman.com cover_image: dev-to-uploads.s3.us-east-2.amazonaws.com --- Rails 8.0.5.2 and 8.1.4.1 came out today with fixes for 21 security issues: 1 critical, 4 high, 13 medium, and 3 low. If your app accepts file uploads and uses ImageMagick, start with the critical one, because attaching a crafted file is enough to trigger it. If you're on 8.0, there's also a deadline. Security support for the 8.0 series ends on November 7, and the maintenance policy lists 8.1 as supported until October 10, 2027. This batch has no 7.2 release, and the announcement recommends moving to at least 8.0. Take the patch now and plan the move to 8.1. The issues are identified by GHSA IDs (GitHub Security Advisory numbers). None of the 21 had a CVE ID (the usual public vulnerability number) when I wrote this. The Five That Need a Closer Look Critical: File Write and Read in Active Storage Image Analysis GHSA-f3w6-w445-fwh9 (CVSS severity score: 9.8 out of 10). An unauthenticated attacker may be able to write arbitrary files on the server. When attachments use the Disk service (Active Storage saving files to local disk), they may also be able to read any file the app process can read, including secret_key_base and other credentials. ImageMagick picks a coder (its handler for a file format) based on the file's content, and some coders act on other files named inside the image. Active Storage didn't restrict them. The ImageMagick analyzer runs at attach time, so no variant (a resized copy of an image), preview, or analyze call is needed. You're affected if all three are true: - Your app sets config.active_storage.variant_processor = :mini_magick, which is the default on load_defaults 6.1 or earlier. - Your ImageMagick security policy doesn't already deny these coders. The policies packaged by Debian and Ubuntu don't. - Untrusted users can upload files. To check the first two: grep -rnE 'variant_processor|load_defaults' config identify -list policy Run the second command somewhere that matches production, like inside your production image. Look for rules denying MSL (ImageMagick's scripting format) and Ghostscript coders like PDF. In July's CVE-2026-66066, the exposed apps were the ones using libvips, the other image library Active Storage supports. This one affects apps using ImageMagick. High: File Read and Possible RCE in Video Previews and Analysis GHSA-v924-7rp8-4mxh (CVSS 8.8). Active Storage kept the uploaded file's extension on the tempfile it handed to ffprobe, ffmpeg, and ImageMagick, and those tools can choose a decoder from the extension. So whoever uploads the file can influence which decoder runs. On an FFmpeg built with support for AviSynth (a video scripting tool), a .avs file gets evaluated as a script, which could disclose files or lead to remote code execution (RCE, running the attacker's code on your server). You're affected if you accept untrusted uploads and either analyze video or audio, generate video previews, or process images with ImageMagick. Active Storage analyzes video and audio on attach by default, so moving to libvips doesn't rule you out here. The AviSynth attack also needs FFmpeg built with --enable-avisynth plus the AviSynthPlus runtime. The advisory says upstream FFmpeg and the common Linux distribution and Homebrew packages don't enable it, while the GPL builds from ffmpeg.org do. It also warns that other decoders may be reachable without AviSynth. To see how yours was built: ffmpeg -version | grep -o enable-avisynth No output means it isn't enabled. High: Strong Parameters Bypass in each_key GHSA-wm3x-v3qw-4jh3 (CVSS 7.5). ActionController::Parameters#each_key with a block returned the raw internal hash, which mass assignment (setting many attributes at once from a hash) accepts without a permit check. This only matters if your code passes that return value to new, update, or assign_attributes, and Rails itself never does. It's a quick search: grep -rn 'each_key' app lib Most hits will have nothing to do with params. You're looking for a call on params whose result gets used. High: Crash via safe_constantize and Action Cable GHSA-8474-xrp6-wh69 (CVSS 7.5). This one reminded me of the Ruby regexp bugs from last month. When a constant lookup raised LoadError, safe_constantize built a regular expression from the constant name without escaping it. A crafted pattern can trigger defects in Ruby's regexp compiler and possibly crash the server process. The advisory doesn't name those bugs or say whether a patched Ruby prevents this crash, so I'd upgrade Rails either way. Action Cable passes the channel value from each subscribe command to safe_constantize, and new Rails apps mount Action Cable at /cable by default. If Action Cable is mounted and you haven't added a check to ApplicationCable::Connection#connect, an unauthenticated client can reach this code, even if you never built a channel. High: IP Spoofing in remote_ip (8.1 Only) GHSA-2gff-2mmg-m73p (CVSS 7.4). Since 8.1.0 under Rack 3, request.remote_ip prefers the Forwarded header over X-Forwarded-For. If your proxy passes a client-supplied Forwarded header through unchanged, the client chooses its own IP. That can get around IP-based access rules, falsify logs, and bypass rate_limit, whose default by: uses the client address. Upgrading doesn't fully close this. The fix only detects spoofing when the proxy also sets X-Forwarded-For, so it's worth configuring your proxy to remove or overwrite Forwarded either way. The Other 16 Each row is what your app has to be doing to be exposed. XSS is cross-site scripting (an attacker's script running in another user's browser), DoS is denial of service (making the app unavailable), and proxy mode is when Active Storage streams files through your app instead of redirecting to the storage service. | Severity | Advisory | You're exposed if | | --- | --- | --- | | Medium | XSS in text helpers | You use the HTML5 sanitizer and pass untrusted text to highlight, or to simple_format with html_options | | Medium | XSS in highlight | A custom :highlighter puts a placeholder inside an attribute value, and users control the text or phrases | | Medium | XSS in strip_tags | You render strip_tags output from untrusted input inside an HTML attribute | | Medium | XSS in tag helpers | Users can specify custom HTML attribute names | | Medium | XSS in MessagePack serializer (8.1 only) | You use a MessagePack serializer and store a SafeBuffer (Rails' HTML-safe string type) mutated in place with untrusted input | | Medium | DoS in Action Controller Live | You stream with ActionController::Live or send_stream, or serve large files through Active Storage proxy mode | | Medium | DoS in proxy streaming | You store files on S3, an S3-compatible service, or Google Cloud Storage and use Active Storage's default routes, which are on unless you've turned them off | | Medium | DoS in filename handling | Users can upload attachments and you're on a database that accepts NUL bytes, like SQLite or MySQL/MariaDB. PostgreSQL isn't affected | | Medium | Unsigned Content-Type on GCS | You use Google Cloud Storage with cache_control configured and accept direct uploads | | Medium | File size bypass on GCS | You use Google Cloud Storage and accept direct uploads | | Medium | Destroyed sessions restored | You use config.session_store :cache_store | | Medium | Session leak in proxy mode | You serve files through the proxy controllers, have a cookie rotation or :hybrid serializer migration configured, and run behind a cache that stores Set-Cookie on public responses | | Medium | Cache poisoning | You render responses whose format depends on request headers from public actions behind a shared cache that honors Vary, such as Thruster or Rack::Cache | | Low | Local file disclosure via FFmpeg | You accept untrusted uploads and preview or analyze video or audio with FFmpeg | | Low | Mailgun signature replay | You receive inbound email through the Action Mailbox Mailgun ingress | | Low | Missing SameSite | You use a session store other than CookieStore and don't pass same_site: | What Might Break When You Upgrade These are the behavior changes I found in the advisories that may need action from you: | If your app… | Then… | | --- | --- | | Uses :mini_magick as the variant processor | You need mini_magick 5.1 or later, or the app raises at boot | | Renders PDF or PostScript through ImageMagick | Those are now denied. Re-allow the gs delegate (how ImageMagick calls Ghostscript, its PDF and PostScript tool) and the coders you use in the host's policy.xml | | Replaces MiniMagick.cli_env after boot, clears the environment with MiniMagick.cli_prefix, installs its own MiniMagick::Shell, or uses GraphicsMagick | The shipped policy doesn't reach you, so the rules need to go in ImageMagick's system policy | | Uses browser direct uploads to Google Cloud Storage | Add x-goog-content-length-range to responseHeader in the bucket's CORS config (its rules for browser requests from other sites), or uploads fail | | Previews or analyzes formats like MXF, DV, or AC3 | FFmpeg can now only open formats in config.active_storage.ffmpeg_allowed_formats, and other files lose previews and metadata. Add them with config.active_storage.ffmpeg_allowed_formats += %w[ mxf ] | | Uses strip_tags output outside HTML, like JSON or text templates | Quotes now appear as " and '. CGI.unescapeHTML decodes them | | Compares sanitized HTML as strings, including in tests | < and > in attribute values are now escaped, because the release requires rails-html-sanitizer 1.7.2 and Nokogiri 1.19.5 | | Is on 8.1 behind proxies that report the client differently in Forwarded and X-Forwarded-For | Requests now raise IpSpoofAttackError | | Is on 8.1 and stores unsafe SafeBuffers with MessagePack | During a rolling deploy, processes not yet upgraded may fail to load those cookies or cache entries | | Uses the Mailgun ingress with a clock over 2 minutes behind Mailgun's | Genuine webhooks get rejected, so sync the clock | Shared caches can also store more variants than before of responses whose format depends on request headers, because Vary now lists more headers. Aside: The One Thing CI Caught CI went fine for one app's upgrade to these releases, with one exception. Reek, a tool that flags code smells, reported six warnings on a class that had no entry in the project's baseline of known warnings. Most of them shared one cause: the class was handling a job that belonged elsewhere. A small refactor cleared them. It was weird that this came up during the Rails upgrade and not earlier. What to Do 1. Upgrade to 8.0.5.2 or 8.1.4.1. Run bundle update rails --conservative, which limits what else changes in Gemfile.lock. Then confirm the lockfile shows the new Rails version and Nokogiri 1.19.5 or later. 2. If you use :mini_magick and bundle info mini_magick shows a version below 5.1, run bundle update mini_magick. 3. Rotate secrets if you were exposed. The critical advisory says to do this if you met its conditions and store attachments with the Disk service. The video advisory says to do it if you meet its AviSynth requirements. That covers secret_key_base, the master key, everything in config/credentials.yml.enc, and storage, database, and third-party credentials, and the advisories say not to keep an exposed secret as a fallback. Changing secret_key_base expires active sessions and invalidates signed and encrypted cookies, signed global IDs, and Active Storage URLs, so users will need to log in again. 4. If you're on 8.1 behind a proxy, set it to remove or overwrite Forwarded. 5. If you're on 8.0, put the move to 8.1 on the calendar. If You Can't Upgrade Yet Each advisory links to patches for 8.0 and 8.1 where they apply, and many list workarounds. For the top five: - ImageMagick (critical): copy the rules from the policy Rails now ships, activestorage/lib/active_storage/image_magick/policy.xml, into your system policy, such as /etc/ImageMagick-7/policy.xml. The advisory's minimum is below, and it blocks Ghostscript, so it also affects PDF and PostScript rendering. <policy domain="coder" rights="none" pattern="MSL"/> <policy domain="delegate" rights="none" pattern="gs"/> <policy domain="coder" rights="none" pattern="{PS,PS2,PS3,EPS,EPS2,EPS3,EPI,EPSF,EPSI,EPT,EPT2,EPT3,PDF,AI,EPDF,PDFA,POCKETMOD,XPS,PCL}"/> - Video previews and analysis: remove ActiveStorage::Previewer::VideoPreviewer from config.active_storage.previewers, and ActiveStorage::Analyzer::VideoAnalyzer and ActiveStorage::Analyzer::AudioAnalyzer from config.active_storage.analyzers. Switching to an FFmpeg without AviSynth closes that attack but not the decoders ImageMagick picks from the extension. - each_key: don't use its return value. Pass the permitted Parameters instance to mass assignment. - safe_constantize: if you don't use Action Cable, set config.action_cable.mount_path = nil. If you do, call reject_unauthorized_connection in connect for unauthenticated clients, which limits exposure to logged-in users but doesn't remove it. - remote_ip: if your proxy sets X-Forwarded-For, add Rack::Request.forwarded_priority = [:x_forwarded] to an initializer. Rack will then also ignore Forwarded for the request's scheme, host, and port. If you're on 7.2 or older, there's no release for you in this batch. Most of the advisories list every version below 8.0.5.2 as affected, so moving to a supported series is the fix. The advisory patches may help in the meantime, though they target 8.0 and 8.1. More Information - Rails release announcement, with all 21 advisories and gem checksums - Rails maintenance policy - Active Storage changelog for 8.1.4.1

  2. Posted yesterday: CRITICAL: Possible arbitrary file write and read in Rails Active Storage image analysis github.com/rails/rails/security/advisories/GHSA-f… More: The Hacker News: Critical Rails Flaw Lets Attackers Write Server Files via Crafted ImageMagick Uploads thehackernews.com @thehackernews #infosec #vulnerability #RubyRails

  3. Rails warns crafted uploads can trigger unsafe ImageMagick coders through Active Storage, potentially writing server files and, with Disk storage, reading secrets. Affected apps use MiniMagick/ImageMagick with untrusted uploads and inadequate policy controls. Update Active Storage to 8.1.4.1 or 8.0.5.2. github.com/rails/rails/security/advisories/GHSA-f… #Cybersecurity #Infosec #RubyOnRails #AppSec

    1 favourite · 1 boost
  4. 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.…

  5. 新卒エンジニアが書き溜めた技術メモ 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」 ↩︎

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

    22 likes · 1 repost · 1,858 views
  7. 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周辺で広く使われる標準」の参考として言及しています。 ↩︎

  8. 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.…

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

    36 likes · 5 reposts · 924 views
  10. 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

Achievements

Awards earned by the repositories rails owns. Select an award to embed its badge.

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

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. rails
  2. rails
  3. rails
  4. rails
  5. rails
  6. rails
  7. rails
  8. rails

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.