- Ruby / Rails関連
週刊Railsウォッチ(20210510前編)属性メソッドをキャッシュして最適化、Railsのガバナンスに関する声明、bundle install高速化ほか
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は以下のコミットリストのChangelogのうち、セキュリティ修正以外のものから見繕いました。
🔗 stylesheet_link_tag
にextname
オプションが追加
つっつきボイス:「javascript_include_tag
にもextname
オプションがあるのでstylesheet_link_tag
にも付けようという感じのようです」「extname
はextension nameつまり拡張子名を指定するのね」
CSSのパスにデフォルト
.css
を追加するのをスキップするextname:
オプションをstylesheet_link_tag
に追加。
# 変更前:
stylesheet_link_tag "style.less"
# <link href="/stylesheets/style.less.scss" rel="stylesheet">
# 変更後
stylesheet_link_tag "style.less", extname: false, skip_pipeline: true, rel: "stylesheet/less"
# <link href="/stylesheets/style.less" rel="stylesheet/less">
Abhay Nikam
同Changelogより大意
「CSSの拡張子を.less
とかにしたい場合にデフォルトで.css
拡張子を付けないようにするオプションか、たしかにできないと困る」「ところでlessって使ったことないんですけど、どのぐらい使われているのかな?」「言われてみれば自分の身の回りでは見かけないかも」「MIMEタイプも"stylesheet/less"
になるのか」「使っている人がいる以上欲しいオプションですね👍」
🔗 生成された属性メソッドをキャッシュおよび再利用する最適化
つっつきボイス:「attributes系メソッドをキャッシュする、なるほど」「キャッシュしたことでメモリがだいぶ節約できたみたいですね」「METHOD_CACHES
を追加してこれを参照するようにしたのか↓」「define_method_attribute
というprivateメソッドがgemで使われていると動かなくなる可能性があるとプルリクメッセージに書かれてました」
# activemodel/lib/active_model/attribute_methods.rb#L360
private
- class CodeGenerator
+ class CodeGenerator # :nodoc:
+ class MethodSet
+ METHOD_CACHES = Hash.new { |h, k| h[k] = Module.new }
+
+ def initialize(namespace)
+ @cache = METHOD_CACHES[namespace]
+ @sources = []
+ @methods = {}
+ end
+
+ def define_cached_method(name, as: name)
+ name = name.to_sym
+ as = as.to_sym
+ @methods.fetch(name) do
+ unless @cache.method_defined?(as)
+ yield @sources
+ end
+ @methods[name] = as
+ end
+ end
「このキャッシュが効くシチュエーションは多そう: Railsワーカーのメモリを減らしてくれるいい高速化だと思います👍」
🔗 追いかけボイス
「同issueのコメントが興味深いですね: memory_profilerというgemはIMEMO
領域の分は見てくれないのでこれで計測するとむしろ劣化したように見えるけど、heap-profilerを使うとちゃんとIMEMO
を含んだallocated sizeが出るのでメモリが削減されたことが分かるそうです」
参考: Reducing Memory Usage in Ruby | Tenderlovemaking -- IMEMOの解説あり
「heap-profilerのREADMEにmemory_profilerとの違いも解説してある:ObjectSpace.each_object
では拾いきれないものがObjectSpace.dump_all
だと取れるらしい(以下の記事にもObjectSpace.dump_all
の解説あり↓)」
🔗 ActiveSupport::Safebuffer
のstringへの暗黙の型強制を非推奨化
ActiveSupport::SafeBuffer
でオブジェクトからstringへの正しくない暗黙の型強制を非推奨化。オブジェクトを文字列操作でStringに暗黙的に変換するためには
#to_str
を実装しなければならない(String#%
など一部のメソッドを除く)。ActiveSupport::SafeBuffer
は、特定の状況でそうしたオブジェクトに対して誤って明示的な変換メソッド (#to_s
) を呼び出していた。この動作が非推奨化された。
Jean Boussier
Changelogより大意
つっつきボイス:「implicit coercionはいわゆる暗黙の型強制」「coercionってそういう意味だったんですか」「MySQLなどでもcoercionという用語を見かけますね」
coercion {名-1} : 強制、無理強い
参考: Type coercion (型強制) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
参考: c# - EF Core to Mysql table binding - Stack Overflow
「ソースを見るとSafeBufferはStringを継承している↓: Ruby 3でStringのサブクラスを継承したときの振る舞いが変わったことに関連しているかなと思ったけど(ウォッチ20210427)、ここでは関係なさそうかな」
# activesupport/lib/active_support/core_ext/string/output_safety.rb#L133-L134
module ActiveSupport #:nodoc:
class SafeBuffer < String
...
「issue #22947を見るとp("".html_safe + Object.new)
のようなケースで暗黙の型強制による問題があったんですね↓」「html_safe
を呼んで+
で結合すると何でもstringに変換されちゃってたのか」「to_s
だとどんなオブジェクトでもtype conversionできてしまうのでエラーになるべき(to_str
が実装されていないなら文字列として結合されるべきではない)という意図かなと思いました」「なるほど」「現在動いているコードはたぶん影響を受けなさそうに見えますね」「よかった〜」
# #22947より
gem "activesupport", "4.2.5"
require "active_support/core_ext/string/output_safety"
p("".html_safe + nil) #=> ""
p("".html_safe + 123) #=> "{"
p("".html_safe + Object.new) #=> "#<Object:0x007fe664939778>"
🔗 ActiveSupport::Cache
でキャッシュエントリのシリアライザをカスタマイズ可能に
- PR: Implement an optimized Cache::Entry coder by casperisfine · Pull Request #42025 · rails/rails
- PR: Document the new Active Support cache format by casperisfine · Pull Request #42076 · rails/rails
つっつきボイス:「2つのプルリクのうち上が本体で、下がRailsガイドの更新でした」「キャッシュのシリアライズにたしかデフォルトでMarshal.dump
を使っていますね」「シリアライザとデシリアライザはここではCoderっていう名前なのか: 単にシリアライズ・デシリアライズするだけならSerializerでしょうけど、gzipすると書かれているので圧縮・解凍も行うという意味でCoderなのかも」「なるほど、圧縮解凍もやってるんですね」
- Ruby API:
Marshal.#dump
(Ruby 3.0.0 リファレンスマニュアル)
:coder
オプションはキャッシュエントリのデフォルトシリアライズメカニズムをカスタムのものに置き換えられる。coder
はdump
とload
に応答する必要があり、カスタムコーダーを渡すと自動圧縮は無効になる。
guides/source/caching_with_rails.md更新部分より
「config.active_support.cache_format_version
というコンフィグが互換用に追加されてますね↓」
ActiveSupport::Cache
シリアライズにより高速でコンパクトなフォーマットが追加された。これは
config.active_support.cache_format_version = 7.0
またはconfig.load_defaults(7.0)
で有効にできる。Active Support 7.0は設定に関わらずActive Support 6.1でシリアライズされたキャッシュエントリを読み込めるので、キャッシュを無効にすることなくアップグレード可能。ただしRails 6.1は新しいフォーマットを読み込めないので、新しいフォーマットを有効にする前にすべてのリーダーをアップグレードする必要がある。
Jean Boussier
同Changelogより大意
🔗Rails
🔗 Railsのガバナンスに関する声明
つっつきボイス:「上は連休前に持ち上がったBasecampの一連の騒ぎの後で出された声明ですね」「Railsの運営は(Basecampのような)特定メンバーや特定企業の一存だけで決まるものではないということを改めて確認する内容になっていました」「実際以前からそうなっていますが、Railsの今後についての不安を解消するためにも改めて声明を出したという流れですね」
これに関連してRailsからの公式発表。Basecamp単独ではRailsについての意思決定ができないような体制をとっている、とのこと。https://t.co/U5qDC3Th20
— ぜに@技術ブログ (@zenizh) May 2, 2021
「経緯についてはdiscuss.rubyonrails.orgの書き込みの冒頭にひととおりまとまっているようです↓」「もう落ち着いたのかな?」「そのうち詳しいまとめ記事が出ると思うのでそちらに期待しましょう」
- 元記事: Effect of the Last Week on Ruby on Rails - rubyonrails-core - Ruby on Rails Discussions (Ruby on Rails Discussionsより)
「今回Basecampから退社した人々の中にはRailsの一部のコアライブラリを業務としてメンテナンスしていた人たちもいましたが、声明にもあるようにRailsはこれまでも今後もBasecampだけのものではありませんし、多くの企業がRailsを使い続けているのも確かなので、Railsがこれで終わるというものではないと考えてよいと思います」「BasecampはBasecamp、RailsはRailsですよね」
参考: プロジェクト管理の老舗Basecampで「社員の政治的意見表明禁止」により社員3分の1が退社 | TechCrunch Japan
つっつきの後で、BasecampのCEOが一連の件について謝罪を表明したという記事が出ました↓。
参考: Basecamp CEO apologizes to staff in new post: ‘We have a lot to learn’ - The Verge(Ruby Weeklyより)
🔗 Railsでビューコンポーネントのライブラリを構築する(Ruby Weeklyより)
つっつきボイス:「Railsのビューコンポーネントの記事を久しぶりに見たので取り上げてみました」「この記事で使われているStorybookというJavaScriptライブラリはときどき目にしますね↓」「iOSにもStorybookってあったかも」「そうそう、紛らわしい」
参考: Storybook: UI component explorer for frontend developers
「記事はステップバイステップで進めている感じ」「以下のgemでビューコンポーネントとStorybookをつないでいるそうです↓」
「ビューコンポーネントは、こういうふうにRubyのコードでCSSを記述するスタイル↓を採用する決心がつくかどうかがポイントでしょうね」「あ、こういうふうに書くんですか」「クラスの行数が増えるとRuboCopに怒られそう」
# 同記事より: app/components/button_component.rb
frozen_string_literal: true
class ButtonComponent < ViewComponent::Base
attr_accessor :type
PRIMARY_CLASSES = %w[
disabled:bg-purple-300
focus:bg-purple-600
hover:bg-purple-600
bg-purple-500
text-white
].freeze
OUTLINE_CLASSES = %w[
hover:bg-gray-200
focus:bg-gray-200
disabled:bg-gray-100
bg-white
border
border-purple-600
text-purple-600
].freeze
DANGER_CLASSES = %w[
hover:bg-red-600
focus:bg-red-600
disabled:bg-red-300
bg-red-500
text-white
].freeze
BASE_CLASSES = %w[
cursor-pointer
rounded
transition
duration-200
text-center
p-4
whitespace-nowrap
font-bold
].freeze
BUTTON_TYPE_MAPPINGS = {
primary: PRIMARY_CLASSES,
danger: DANGER_CLASSES,
outline: OUTLINE_CLASSES
}.freeze
def initialize(type: :primary)
@type = type
end
def classes
(BUTTON_TYPE_MAPPINGS[@type] + BASE_CLASSES).join(' ')
end
end
「この記事でやっているのはサーバーサイドのビューコンポーネントということになりますね」「たしかに」「Rails側からは扱いやすいでしょうけど、フロントエンドエンジニアがどう思うかかな: この書き方にしないといけない理由をフロント側から聞かれたときに答えにくそう」「それですよね」
- サイト: Overview - ViewComponent
- PR: Introduce support for 3rd-party component frameworks by joelhawksley · Pull Request #36388 · rails/rails --ビューコンポーネントをサポートするPR(マージ済み)
🔗 Value Objectをクラスで定義してプリミティブな値と戦う
つっつきボイス:「Value Objectを使いたくなる気持ちはわかる」「Value Objectをイミュータブルにする、なるほど」
# 同記事より
class AnswerScore
def initialize(skill_id, score)
@skill_id = skill_id
@score = BigDecimal(score.to_s)
end
attr_reader :skill_id, :score
def +(other)
raise ArgumentError unless self.class === other
raise ArgumentError if self.skill_id != other.skill_id
ScoreSum.new(skill_id: skill_id, sum: score + other.score, n: 2)
end
def average_score
score.round(2)
end
def ==(other)
other.class === self &&
other.hash == hash
end
alias eql? ==
def hash
[skill_id, score].join.hash
end
end
「この記事のようにValue ObjectをStructとかではなくクラスとしてちゃんと定義しているのはいいですね👍」「なるほど、Structではなくクラスですか」「RailsでValue ObjectというとStructで手軽に作って使い捨てるのを割と見かけますが、それだとハッシュで定義するのとあまり変わらないかなという気持ちがありますね: こういうふうにクラスとして定義できるValue Objectなら値に意味を持たせられるので好ましいと思います」「ふむふむ」
「既存にない概念ならこうやってValue Objectのクラスにする意味があると思いますし、Rubyだと比較的やりやすいんですが、後はValue Objectにする意義がどのぐらいの頻度で生じるかかな」「メンテナンスのしやすさとのバランスでしょうね」
🔗 その他Rails
つっつきボイス:「bundle install
が--jobs
オプションでパラレルダウンロードできるとは知らなかった」「パラレルダウンロードの依存関係はちゃんと解決されるのかな?」「依存関係が衝突したら捨てるしかないでしょうね」
「記事によると、やってみたけど思ったほど速くならなくて、ネイティブgemのmake
に--jobs
オプションを付ける方が効いたそうです↓」「ああたしかに、ネイティブ系gemのコンパイルをパラレルにする方が効くでしょうし副作用もなさそう👍」「言われてみれば」
# 同記事より
$ time MAKE="make --jobs 8" bundle install
<SNIP>
bundle install
133.25s user 35.47s system 468% cpu 36.004 total
前編は以上です。
バックナンバー(2021年度第1四半期)
週刊Railsウォッチ(20210427後編)RactorでUDPサーバーを作る、JSONシリアライザalba gem、AppleのAirTagほか
- 20210427後編 RactorでUDPサーバーを作る、JSONシリアライザalba gem、AppleのAirTagほか
- 20210420後編 ShopifyのJITコンパイラYJIT、PicoRuby、DynamoDBの3つの制約ほか
- 20210419前編 RailsのN+1クエリを定番以外の方法で修正する、GitLabのセキュリティ修正リリースほか
- 20210413後編 RubyMineのRBSサポートとCode With Me、GitHub ActionとDockerレイヤキャッシュほか
- 20210412前編 Active Record属性暗号化機能がRails 7にマージ、RailsNew.ioでrails newオプションを生成ほか
- 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ウォッチタグ)