- Ruby / Rails関連
週刊Railsウォッチ(20210412前編)Active Record属性暗号化機能がRails 7にマージ、RailsNew.ioでrails newオプションを生成ほか
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
今週は以下のコミットリストのChangelogを中心に見繕いました。
🔗 Cache-Controlヘッダーにprivate, no-store
を指定できるようになった
つっつきボイス:「Cache-Controlヘッダーは前にも改修がありましたね」「あ、そういえばありました(ウォッチ20201012)」「以前はno-store
を指定したら他の設定を含めないように変えられてたけど、今回の修正でprivate, no-store
を指定できるようになったということは、この組み合わせに意味があるということなんでしょうね」
#39461ではCache-Controlヘッダーの
no-store
ディレクティブで他の指定を含まないようにした(つまりCache-Controlヘッダーに'private, no-store'を指定しても'no-store'だけになる)。private
は不要であることが多いが、常に不要とは限らない。
たとえば、Fastlyのドキュメントには「現在はno-store
やno-cache
ディレクティブを尊重しない」かつ「FastlyとWebブラウザの両方でキャッシュを行わないようにする必要がある場合は、private
ディレクティブにmax-age=0
かno-store
を組み合わせることを推奨する」とある。
#39461で変更されたディレクティブを減らす振る舞いはオーバーライドできないので、FastlyユーザーがRailsをアップグレードできなくなっている。
このプルリクは、private
を指定した場合はprivate, no-store
ヘッダーを設定できるように変更する。no-cache
の場合にpublic
を指定できるのと同様だが、デフォルトではない。
修正されるissue: #40798
「FastlyのドキュメントにCache-Control: private, no-store
には意味があると書かれてた↓」「使いたい組み合わせを使えるようにしたという修正ですね」
参考: Configuring caching | Fastly Help Guides
参考: Cache-Control
- HTTP | MDN
「変更点を見ると、private
とno-store
を同時に指定できるように変わった↓」「今までは両方指定してもno-store
だけになってたのか」
# actionpack/lib/action_dispatch/http/cache.rb#L198
+ options = []
+
if control[:no_store]
- self._cache_control = NO_STORE
+ options << PRIVATE if control[:private]
+ options << NO_STORE
elsif control[:no_cache]
- options = []
options << PUBLIC if control[:public]
options << NO_CACHE
options.concat(control[:extras]) if control[:extras]
- self._cache_control = options.join(", ")
else
extras = control[:extras]
max_age = control[:max_age]
stale_while_revalidate = control[:stale_while_revalidate]
stale_if_error = control[:stale_if_error]
- options = []
options << "max-age=#{max_age.to_i}" if max_age
options << (control[:public] ? PUBLIC : PRIVATE)
options << MUST_REVALIDATE if control[:must_revalidate]
options << "stale-while-revalidate=#{stale_while_revalidate.to_i}" if stale_while_revalidate
options << "stale-if-error=#{stale_if_error.to_i}" if stale_if_error
options.concat(extras) if extras
- self._cache_control = options.join(", ")
end
🔗 ActiveSupport::TimeWithZone.name
が非推奨化
つっつきボイス:「今までのname
メソッドは"Time"という文字列をそのまま返してたの?↓」「えぇ?😳」「プルリクにも何でこういう実装になったのかわからないって書かれてますね」「文字列リテラルの"Time"を返す実装、マジで謎」
# activesupport/lib/active_support/time_with_zone.rb#L43
def self.name
+ ActiveSupport::Deprecation.warn(<<~EOM)
+ ActiveSupport::TimeWithZone.name has been deprecated and
+ from Rails 7.1 will use the default Ruby implementation.
+ EOM
+
"Time"
end
「機能をdeprecatedにするときはこういうふうに書くのか↑」「この書き方は定番ですね」
c00f2d2で
name
メソッドが実際のクラス名"ActiveSupport::TimeWithZone"ではなく"Timeを"返すようオーバーライドされていた。このようにした理由が不明だし、name
が実際のクラス名を返すことを期待するる開発者が混乱する可能性がある。この変更が追加された理由がわからないので、ひとまず非推奨化して開発者からのフィードバックを待ち、issueが上がったら適宜修正することにする。
同PRより大意
- Rails API:
name
--ActiveSupport::TimeWithZone
「ActiveSupport::TimeWithZone.name
が非推奨化されてRails 7.1でデフォルトの実装に戻される予定なのか」「次のRailsメジャーバージョンは7だから、リリースごとに非推奨化->削除が最短で行われれば7.1で削除されるでしょうね」
「ところで、Railsのメジャーバージョンが7に上がる理由のひとつに、おそらくRailsで必要な最小限のRubyバージョンが上がるからというのもあるんじゃないかな」「Rails 7だとRuby 3.0が最小バージョンになるんでしょうか?」「Rails 7で最小バージョンをいきなり3.0にしたら追従できない機能やgemが続出すると思うので、そこまではやらないでしょうね」「たしかに」
後でedgeガイドを見ると以下のように書かれていました↓。と思ったら以前のウォッチでも言及していました(ウォッチ20210208)。
- Rails 7 requires Ruby 2.7.0 or newer.
- Rails 6 requires Ruby 2.5.0 or newer.
- Rails 5 requires Ruby 2.2.2 or newer.
以下はつっつき後に教わったツイートです。
Rails 7.0 will require Ruby 2.7+. rails/main will stop testing against earlier Rubies shortly. Get your upgrade game on ✌️
— Ruby on Rails (@rails) February 4, 2021
🔗 Active Record属性暗号化がついにマージされた
- PR: Add encryption to Active Record by jorgemanrubia · Pull Request #41659 · rails/rails
- PR: Add Active Record encryption entry to CHANGELOG by jorgemanrubia · Pull Request #41821 · rails/rails
つっつきボイス:「少し前のウォッチ↓ではマージ前だったActive Record属性暗号化がついにマージされました」「お、きたか🎉」「これは嬉しい😋」「思ったよりスムーズにマージされましたね」
週刊Railsウォッチ(20210330後編)Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか
「Changelogがコンパクトにまとまっててありがたい↓」「HEYでセキュリティ監査を経て実績を積んだ属性暗号化機能が入るのはいいですね👍」
属性暗号化のサポート
暗号化された属性はモデルレベルで宣言される。これらは背後に同じ名前のカラムを備えた正式なActive Record属性である。「データベース保存前の属性暗号化」や「値読み出し前の解読」はシステムが透過的に行う。
class Person < ApplicationRecord
encrypts :name
encrypts :email_address, deterministic: true
end
詳しくは以下のガイドを参照。
Jorge Manrubia
Changelogより大意
「属性暗号化のガイドもまるまる追加されたんですね」「edgeガイドには実用的なコード例も丁寧に書かれていますし、このまま使えそうなくらい充実してる👍」「key_provider
の指定や暗号化キーのローテーションもちゃんとサポートしてる↓」「お〜優秀!」「キーのローテーションは後付けでやろうとするとつらいんですよ...」「Rails 7の目玉機能のひとつですね」
# edgeガイドより
config.active_record.encryption.previous = [ { key_provider: MyOldKeyProvider.new } ]
# edgeガイドより
active_record
encryption:
master_key:
- bc17e7b413fd4720716a7633027f8cc4 # Active, encrypts new content
- a1cc4d7b9f420e40a337b9e68c5ecec6 # Previous keys can still decrypt existing content
key_derivation_salt: a3226b97b3b2f8372d1fc6d497a0c0d3
参考: 鍵のローテーション | Cloud KMS ドキュメント | Google Cloud
「ところでRails 7、いつ出るのかな」「時が来たらとしか言いようがない😆」「マイルストーン↓を見るとまだ10件しかプルリクがないので、しばらくは出なさそうですね」
- 7.0マイルストーン: 7.0 Milestone
「お、以前話題になったRails Conductorが7に持ち越されてる↓」「そういえばRails Conductorってありましたね(ウォッチ20190311)」
🔗 ActiveSupport::Cache
のwrite
とfetch
にexpires_at:
で絶対時刻のTTLを設定できるようになった
ActiveSupport::Cache
のwrite
とfetch
に、キャッシュエントリのTTLを絶対時刻で設定するexpires_at:
引数を追加。
Rails.cache.write(key, value, expires_at: Time.now.at_end_of_hour)
Jean Boussier
同Changelogより大意
つっつきボイス:「キャッシュを絶対時刻で期限切れにするexpires_at:
オプションが追加された、なるほど」「何月何日の何時というふうに指定できるようになったんですね」「今までだと『あと60分』みたいな相対指定しかできなかったので、絶対時間にするなら自分で計算して渡すことになる」「と思ったら改修も絶対時間を計算してますね↓」「あると嬉しい機能👍」
# activesupport/lib/active_support/cache.rb#L805
- # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+.
- def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **)
+ # +:compress+, +:compress_threshold+, +:version+, +:expires_at+ and +:expires_in+.
+ def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, expires_at: nil, **)
@value = value
@version = version
- @created_at = Time.now.to_f
- @expires_in = expires_in && expires_in.to_f
+ @created_at = 0.0
+ @expires_in = expires_at&.to_f || expires_in && (expires_in.to_f + Time.now.to_f)
compress!(compress_threshold) if compress
end
🔗 update_all
、delete_all
、destroy_all
の後で@cache_keys
をクリアするようになった
calculatedなキャッシュキーはリレーションにキャッシュされるが、リレーションでコレクションに改変が発生した場合に再計算する方法がない。
@cache_keys
は少なくともupdate_all
、delete_all
、destroy_all
ではクリアすべき。
関連issue: #41784
同PRより大意
つっつきボイス:「以下みたいに1リクエストの処理中でrelationを再利用する場合に、最後のscoped.destroy_all
で消されたときのkeyがキャッシュに残ってしまう問題に対応したようですね」「なるほど」
scoped = Hoge.where(name: 'piyo')
scoped.destroy_all
pp scoped.map(&:name)
「今までこれを回避するために何かトリッキーなことしてた覚えがありますけど、それをやらなくてよくなった😂」「少なくとも*_all
系のメソッドでは@cache_keys
がクリアされるようになったので、その種の対応が少し楽になりそう👍」
「1レコードを指すActive Recordオブジェクトは何度も参照されることが多いので速度の関係からキャッシュヒットさせたいけど、複数レコードを対象とするRelationオブジェクトの更新系メソッドに対して、更新前に参照していたキャッシュを参照させたいというようなニーズはまずあり得ないだろうということですね、わかる」
🔗Rails
🔗 Tailwind CSS JITでCSSコンパイルを20倍高速化
つっつきボイス:「TechRachoのRails系翻訳記事でお世話になっているEvil Martiansの記事です」「Evil Martiansさんは最近Tailwind CSSがお気に入りみたい」
「Tailwind CSSって何だっけと思ったらBootstrapみたいなCSSフレームワークなんですね」「はい」「最近CSSフレームワークを選ぶ機会があったんですけど、これも検討しとけばよかったかな...」「CSSフレームワークみたいなものは作業者が慣れてないとよさを発揮しにくい面もあるので、Bootstrapみたいに長く使われているものが今も幅広く使われていますね」「それもそうか」
「ちなみにCSSフレームワークはBootstrap 5にしちゃいました」「お、5がもう出たんですか?」「実は5-beta3でした(後で正式版にする前提で)」「公式サイト↓のバージョンがv5.0.0-beta1と表示されているので正式版まであともう少しですね」
「そういえばBootstrap 5はIEサポートやめたってどこかに書かれてました」「IEは今やTwitterすら満足に表示できないのでサポート打ち切られても仕方ないでしょうね」「あ、そういえばそんな話もあったかも」「今どきのIE対応は特別に指定がない限り基本的にはサポートしない流れでいいと思います」
Internet Explorer はサポート外です。 Internet Explor のサポートが必要な場合は Bootstrap v4 を使ってください。
ブラウザとOS · Bootstrap v5.0より
🔗 RailsライブラリなしでRubyのWebアプリを作る(Ruby Weeklyより)
つっつきボイス:「Railsライブラリを使わずにRubyでWebアプリを作るという企画か」「おぉ、TCPServerを立ち上げるところから始めている↓」「何とプリミティブな😳」「ここはソケットプログラミングで最初にやるところですね」「Rackサーバーもない状態から始めるのが徹底してる」「without Rails libraryどころか、Ruby付属のgem以外使わないぞという勢いですね」
# 同記事より
#Use Rubys Socket Library
require 'socket'
server = TCPServer.new(1337)
参考: class Socket (Ruby 3.0.0 リファレンスマニュアル)
「なかなか長い記事ですね」「真ん中過ぎぐらいでやっとRackが登場↓」「文明が来た」
# 同記事より
require 'rack/handler/puma'
class HelloWorld
def call(environment)
status = 200
headers = { 'Content-Type' => 'text/plain' }
body = ['Hello', ' world!']
[status, headers, body]
end
end
Rack::Handler::Puma.run(HelloWorld.new)
「最終的にこういう感じになったんですね↓」
「Webアプリをスクラッチで作ったことがない人やソケットプログラミングをやったことのない人にとっては、かなりいい勉強になりそう👍」「たしかに!」「Railsのようなフレームワークで覆い隠されている各種レイヤをこうやって実際に触ってみると何かしら学びになると思います」
参考: ソケットプログラミング
🔗 Railsでビューのテストを書く理由(Ruby Weeklyより)
# 同記事より
# spec/views/books/index.html.erb_spec.rb
require 'rails_helper'
RSpec.describe "books/index", type: :view do
before(:each) do
assign(:books, [
Book.create!(
title: "Title",
description: "MyText",
download_url: "Download Url",
status: "Status"
),
Book.create!(
title: "Title",
description: "MyText",
download_url: "Download Url",
status: "Status"
)
])
end
it "renders a list of books" do
render
assert_select "tr>td", text: "Title".to_s, count: 2
assert_select "tr>td", text: "MyText".to_s, count: 2
assert_select "tr>td", text: "Download Url".to_s, count: 2
assert_select "tr>td", text: "Status".to_s, count: 2
end
end
つっつきボイス:「ビューのテストを書く理由を自分なりに考えてみた記事のようです」
「たしかにビューのテストは実際には書かないことも多いんですが、view specが必要な場合なら書くのはありだと思います」「どんな場合でしょう?」「たとえば外部からクローリングされることが前提のビューだと、ユーザーにとっての見た目よりもHTMLのDOM構造自体が仕様になるので、そういう場合はview specでDOM構造をテストしたいですよね」「あ、そうか」「あるいはSEO要件で特定のHTML構造が必要な場合とか」「なるほど」「必要な理由はいろいろ考えられると思うので、頭の体操的に考えてみるのも楽しいですよ」
🔗 Railsのアレを生成するWebサイト2つ
つっつきボイス:「ruby-jp Slackで見かけました」「1つ目のrails.helpというサイトは、Railsのモデルやマイグレーションのジェネレータ文字列をGUIで生成できるんですね↓」「こういうの地味に嬉しいかも」「いつもテキストにメモしてからやってました」「text
フィールドにlimitを指定できるとは知らなかった」
「主にRailsを学び始めたばかりの人が、ジェネレータのタイプミスで失敗してやる気をくじかれるのを防ぐのに便利そう」「たしかに」「マイグレーションでどのフィールドタイプにどんなオプションを指定できるかをGUIで確認できるのも教育向けによさそう: decimal
を指定したときはprecisionとscaleも必要になる、とか」「なるほど」「なお、自分は空のマイグレーションファイルを作ってそこに書く派です」
- サイト: RailsNewIo
「2つ目のrailsnew.ioはrails new
コマンドのオプションを生成するジェネレータサイトです」「omakaseオプションなんてのがあるのね」
「へ〜、--skip-gemfile
なんてオプションがあるとは」「Gemfileなしってどんなときに使うんでしょうね」「システムインストールされるgemを使う前提のときとかかな?」「minitestをスキップできない、と思ったらminimalにすると全部スキップされました」
「railsnew.ioは慣れた人にとっても便利そう👍」「rails new
は使う頻度が少ないので、turbolinksをオフにするのとか忘れがちですよね」「あ、それ自分もこないだ忘れてました😆」
🔗 devise-two-factor: Deviseで二要素認証(Ruby Weeklyより)
つっつきボイス:「Deviseで二要素認証が使えるgemだそうです」「ちょっと嬉しいかも: 今度使ってみようかな」
「だいぶ昔ですけど、sshに二要素認証を付けてみたのを思い出しました」「sshでですか?」「すぐ飽きてやめちゃいましたけど😆」
参考: sshの二段階認証(二要素認証)設定の方法3個 | 俺的備忘録 〜なんかいろいろ〜
前編は以上です。
バックナンバー(2021年度第1四半期)
週刊Railsウォッチ(20210407後編)エイプリルフールのRuby構文プロポーザル、AWSのVPC Reachability Analyzerほか
- 20210406前編 GitHubが修正したRailsセッションハンドリングの競合、erb/haml/slimの速度比較ほか
- 20210330後編 Active Recordモデル属性暗号化が標準で入る可能性、Flipper Cloud、awesome_printほか
- 20210329前編 特集: Rails更新版の臨時リリースとmimemagic gemのGPL問題
- 20210323後編 GitHub Actionsで使えるruby/setup-ruby、中高生国際Rubyプログラミングコンテスト2020ほか
- 20210322前編 Active Recordのstrict loadingの修正、セキュリティリリースのポリシー追加、N+1チェッカーprosopite gemほか
- 20210316後編 testdouble/standard gem、DockerfileベストプラクティスとDockerfileのlintツールhadolintほか
- 20210315前編 Active Recordのenum関連改修、Active SupportのEnumerableでpluckが使えるほか
- 20210309後編 RubyのIRBに隠れているイースターエッグ、Power Automate Desktop、SQLクエリのありがちなミス6つほか
- 20210303後編 Bundlerのセキュリティ修正、Rubyのガベージコレクション記事、Rubyが2/24に誕生日ほか
- 20210222 ActiveRecord::Relationの新メソッドload_asyncとexcluding、Active Jobのperform_laterの改善ほか
- 20210209後編 Rubyでミニ言語処理系を作る、Kernel#getsの意外な機能、CSSのcontent-visibilityほか
- 20210208前編 Rails次期リリースがバージョン7に決定、thoughtbotのアプリケーションセキュリティガイドほか](/hachi8833/2021_02_08/103801)
- 20210202後編 Ruby 3 irbのmeasureコマンド、テストを関数型言語のマインドセットで考えるほか
- 20210201前編 Webpackerのガイドがマージ、RailsはRuby 3でどのぐらい速くなったかほか
- 20210126後編 Google Cloud FunctionsがRubyをサポート、Ruby 3のパターンマッチングでポーカーゲームほか
- 20210125前編 Railsリポジトリのデフォルトブランチがmainに変更、Rails 6.1はMySQLのENUM型に対応済みほか
- 20210120後編 Ruby 3.0の新機能で遊ぶ、RubyスニペットをJSに変換するRuby2JS、rspec-parameterized gemほか
- 20210113後編 Ruby 3.0 Ractor解説記事、Vercelホスティングサービス、教育用OS xv6ほか
- 20210112前編 Active Recordの範囲指定バリデーション改善、soleとfind_sole_byメソッド、AlgoliaとRailsほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)