- 開発
週刊Railsウォッチ(20190701)RMagickのメモリ使用量が劇的に改善、インスタンス変数の定義順で速度が変わる?、GitLab CIランナーをローカルで回すほか
こんにちは、hachi8833です。kazzさんのアバター画像が変わったことにお気づきでしょうか。
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
※今回のつっつき録画に音声が入っていなかったので、今回は分割せず軽量版とします。申し訳ありません🙇。
⚓お知らせ: 第12回公開つっつき会(無料)
開始以来ついに1年目を迎える第12回目公開つっつき会は、今週7月4日(木)19:30〜にBPS会議スペースにて開催されます。引き続き皆さまのお気軽なご参加をお待ちしております🙇。
⚓Rails: 先週の改修(Rails公式ニュースより)
⚓スキーマダンプのstringをintegerに変更
# activerecord/lib/active_record/migration.rb#L1072
def get_all_versions
- if schema_migration.table_exists?
+ schema_migration.all_versions
schema_migration.all_versions.map(&:to_i)
else
[]
end
end
...
def load_migrated
- @migrated_versions = Set.new(@schema_migration.all_versions)
+ @migrated_versions = Set.new(@schema_migration.all_versions.map(&:to_i))
end
# activerecord/lib/active_record/schema_migration.rb#L47
def all_versions
- order(:version).pluck(:version).map(&:to_i)
+ order(:version).pluck(:version)
end
ここは変更すべきと思われるが、既にRC版に入っているので6.0-stableではやらない方がいいかと思い、アプリ側の変更を必要としない変更だけに留めようとしてみた。
これは、すべてのスキーママイグレーションのバージョン番号をintegerとしてダンプしていた#36439の一部を元に戻す。この変更は振る舞いが変わらないので別段問題ではない。6.1で改めてこれを非推奨化するべき。
cc/ @rafaelfranca そちらから本変更を求められ、structureファイルが変更されている。大きな問題ではないので、GitHubの現在の更新はそのままでよいと思うが、この振る舞いの変更の方が心配なので、いったんロールバックして6.1で改めて非推奨化しようかと思う。
同PRより大意
つっつきボイス:「プルリクメッセージを見ると、これは修正すべきだけど6.0-stableじゃない方がいいかもとあったのが、Oracleアダプタやってるyahondaさんがこの修正欲しいと言って入ったようです」「to_i
する方が長さが短くなってよさそうではあるけど、他に何か不都合があったのかも?🤔」
⚓init_with
のスキーマキャッシュをdeep_deduplicate
# activerecord/lib/active_record/connection_adapters/schema_cache.rb#L37
def init_with(coder)
- @columns = coder["columns"]
- @columns_hash = {}
- @primary_keys = coder["primary_keys"]
- @data_sources = coder["data_sources"]
- @indexes = coder["indexes"] || {}
- @columns = deep_deduplicate(coder["columns"])
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
+ @primary_keys = deep_deduplicate(coder["primary_keys"])
+ @data_sources = deep_deduplicate(coder["data_sources"])
+ @indexes = deep_deduplicate(coder["indexes"] || {})
@version = coder["version"]
@database_version = coder["database_version"]
end
つっつきボイス:「deep_duplicate
?」「いえ、deep_deduplicate
ですね」「ででゅぷりけーと!」「新しいみたいでググっても出てきません😢」
「GitHub上で例のコードジャンプ↑が効くぞ😋:なるほど」「-value
って何だっけ?」「stringをfreezeするショートハンドですね」「あ〜そうそう」
def deep_deduplicate(value)
case value
when Hash
value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
when Array
value.map { |i| deep_deduplicate(i) }
when String, Deduplicable
-value
else
value
end
end
参考: instance method String#-@ (Ruby 2.6.0)
self が freeze されている文字列の場合、self を返します。 freeze されていない場合は元の文字列の freeze された (できる限り既存の) 複製を返します。
docs.ruby-lang.orgより
⚓robotstxt.orgのURLを修正
# guides/source/configuring.md#L1543
To block just specific pages, it's necessary to use a more complex syntax. Learn
- it on the [official documentation](http://www.robotstxt.org/robotstxt.html).
+ it on the [official documentation](https://www.robotstxt.org/robotstxt.html).
つっつきボイス:「ドキュメントの小さな修正ですが、robotstxt.orgっていうのがあるって初めて知ったので」「ほほ〜☺️」
参考: 結局「robots.txt」ってなに?使う理由と基本の仕組みを解説|ferret [フェレット]
⚓uniquenessでない場合にsave!
の失敗がエラーにならない問題を修正
#35528の修正。
#35528は、親レコード1件を#save!
するときに関連する子レコードがuniqueness制約に違反した場合にのみ発生する。これまでのところ、presenceやinclusionなどの他のバリデーションが失敗した場合はActiveRecord::RecordInvalid
が(期待どおり)発生するが、uniquness制約が失敗した場合はActiveRecord::RecordInvalid
が発生せず、単にnil
が返されてしまっていた。
なお、トランザクションをサイレントにロールバックする機能はこの修正の影響を受けず、期待どおり動作する。
同PRより大意
つっつきボイス:「は〜こんなバグがあったとは😳」「トランザクションをサイレントにロールバックする機能というのがあるんですね」「この修正はテスト↓が重要な部分かな😋」
# activerecord/test/cases/autosave_association_test.rb#L1702
+ test "rollbacks whole transaction and raises ActiveRecord::RecordInvalid when associations fail to #save! due to uniqueness validation failure" do
+ author_count_before_save = Author.count
+ book_count_before_save = Book.count
+
+ assert_no_difference "Author.count" do
+ assert_no_difference "Book.count" do
+ exception = assert_raises(ActiveRecord::RecordInvalid) do
+ @author.save!
+ end
+
+ assert_equal("Validation failed: Published books is invalid", exception.message)
+ end
+ end
+
+ assert_equal(author_count_before_save, Author.count)
+ assert_equal(book_count_before_save, Book.count)
+ end
+
+ test "rollbacks whole transaction when associations fail to #save due to uniqueness validation failure" do
+ author_count_before_save = Author.count
+ book_count_before_save = Book.count
+
+ assert_no_difference "Author.count" do
+ assert_no_difference "Book.count" do
+ assert_nothing_raised do
+ result = @author.save
+
+ assert_not(result)
+ end
+ end
+ end
+
+ assert_equal(author_count_before_save, Author.count)
+ assert_equal(book_count_before_save, Book.count)
+ end
⚓番外: elsif
- commit: :golf: · rails/rails@c8a8460
# actionview/lib/action_view/template/resolver.rb#L272
def extract_handler_and_format_and_variant(path)
pieces = File.basename(path).split(".")
pieces.shift
extension = pieces.pop
handler = Template.handler_for_extension(extension)
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
format = if format
Template::Types[format]&.ref
- else
- if handler.respond_to?(:default_format) # default_format can return nil
- handler.default_format
- else
- nil
- end
+ elsif handler.respond_to?(:default_format) # default_format can return nil
+ handler.default_format
end
つっつきボイス:「以下のツイートで見つけたんですが、else
にif
が続いていたのをamatsudaさんがelsif
に変えていました」「あ、たしかにこれはelsif
の方が簡潔に書ける!」「うまい具合にnil
も書かなくて済みますね」「コミットメッセージのタイトルが⛳なのはコードゴルフ?😆」
RuboCopチョットデキル人これなんとかなりませんか?
一杯おごります🍶https://t.co/0yLbSDJroK— Ryuta Kamizono (@kamipo) June 15, 2019
「まだオープン中ですが、それに関連して以下のRuboCop設定更新もリクエストされてました」「IfInsideElse
は入れてもいい気がするし😋」「ちょい厳しいかもという声もありますね」
「でRuboCopの方では、これに絡んで後置のif
をありにできるオプションをリクエストしてて、こちらはマージされてました❤️」
参考: Class: RuboCop::Cop::Style::IfInsideElse
— Documentation for rubocop (0.71.0)
追記(2019/07/02)
“でRuboCopの方では、これに絡んで後置のifをありにできるオプションをリクエストしてて、こちらはマージされてました❤️“
rails/rails リポジトリで有効にするには Code Climate の RuboCop 0.72 対応待ちです。https://t.co/DUjOQoLSQG— Koichi ITO (@koic) July 1, 2019
情報ありがとうございます!🙇
⚓Rails
⚓ active_record_in_cache gem
- リポジトリ: sinsoku/active_record_in_cache: Provides a method to execute SQL while automatically caching.
はてなブログに投稿しました #はてなブログ
ActiveRecordでRails.cacheを良い感じに使うgemを作った - アジャイルSEの憂鬱https://t.co/Ny45PSCBgY— 神速 (@sinsoku_listy) June 26, 2019
つっつきボイス:「神速さんの作ったgemですが、Rails.cache
を初めて知りました😅: これはビューのキャッシュとかとは違うんでしょうか?」「Rails.cache
はクエリをキャッシュしたりできるヤツだったかな」「ほんとだ↓」
参考: Rails のキャッシュ: 概要 - Rails ガイド
ビューのフラグメントをキャッシュするのではなく、特定の値やクエリ結果だけをキャッシュしたいことがあります。Railsのキャッシュメカニズムでは、どんな情報でもキャッシュに保存できます。
railsguides.jpより
参考: Rails.cacheについて | 酒と涙とRubyとRailsと
Ruby on Railsで特定の値やクエリ結果をキャッシュするしくみとしてRails.cacheを紹介します。
この機能を使うとや有効期限を設定したり、キャッシュ内容を圧縮できます。
morizyun.github.ioより
「日本語記事に『中身は3行』とあるけどgemのコアは本当に3行だけなんですね↓😳」「はは〜なるほど、メモリに乗れば次からはデータベースを叩かなくて済むと: 記事ではinclude ActiveRecordInCache::Methods
をApplicationRecord
でやってるけど、自分なら特定のモデルでピンポイントにやるかな〜」
# 同記事より
def in_cache(column = :updated_at, options = {}, &block)
value = block_given? ? all.instance_exec(&block) : all.maximum(column)
name = "#{all.to_sql}_#{value}"
Rails.cache.fetch(name, options) { all.to_a }
end
⚓RMagickのメモリ使用量改善
日記( ˘ω˘) / “RMagick のメモリ使用量を改善した - @watson1978 の日記” https://t.co/eLHBQgVbyA
— Watson (@watson1978) June 15, 2019
つっつきボイス:「@Watsonさんのこの記事スゴいなと思って」「お、これRubyKaigi 2019で発表してたヤツだし!」「そういえば見たって言ってましたね(私見られなかった😢)」「あれはとてもいい発表でした❤️」
「上の記事によると、RubyKaigiの時点でリークは修正されたけどGCがなかなか発動しなかったので今回はRMagickとImageMagickの両方に手を入れたそうです」「おお〜、以前のメモリ使用量は青で、修正後は赤↓、まるで違うし」「ものすごい改善ですね💪」「今までは鍋の底に大穴開いてた感😆」「お風呂の栓がちゃんとしまってなかったというか😆」
「以前なりゆきでImageMagickとMiniMagick使ったけど、画像系はいろいろ大変でしたよ〜😅」「そういえば記事書いてましたね↓」
⚓GitLab CIランナーをローカルで回す
つっつきボイス:「GitLab CIランナーをローカルで動かすという少し前の記事なんですけど、ローカルでCI回せるとうれしいものでしょうか?」「おお、そりゃもちろん😍: GitLabにpushした後さんざん待たされた末にエラーとかつらいし😭」「GitLab CIランナーをDocker化してローカルで動かすか、以下からランナーのバイナリを取ってきて動かせばやれるみたいです😋」
参考: docs/install/bleeding-edge.md · master · GitLab.org / gitlab-runner · GitLab
なお記事の最後に、ローカルだとジョブのキャッシュが保存できないらしく、毎回スクラッチで回るので注意とあります。それでもローカルだと速いとも。
⚓リードレプリカのテスト
つっつきボイス:「@kamipoさんの記事ですけど、これと似たようなことってある気がして↓」「こういうつらみ、ありますね〜😢: キャッシュが効いているかどうかのテストの難しさとか思い出しちゃう😇」
あたかもマスターで更新が起きたっぽいときにリードレプリカにも更新が伝搬しているかのように見せかけるため、Active Recordはテストのときデフォルトですべてのコネクションプールをマスターのコネクションプールにすり替えるということで対処している。コミットが起きないんだったら全部マスターに接続してテストすればいいじゃないってやつです。しかしこれをされるとマスターではなくリードレプリカからデータを読んでることをテストしたいときにめっちゃこまるという話である。
同記事より(強調は編集部)
⚓Ruby
⚓Sorbetについて
つっつきボイス:「Steep gemをやっているsoutaroさんのブログです」「ああ、サブタイピングの困難とかsig
のオーバーライドの問題とか、いろいろわかりみあります」「Rubyの柔軟さを保ちながら型チェックでカバーするのって大変そう...」
以前RubyKaigi 2019のレポート記事↓にも書きましたが、Rubyの型チェックはLevel-1とLevel-2に分けて進められていて、SteepはSorbetやRDLと同じくLevel-2に該当するそうです。
⚓インスタンス変数のパフォーマンスを調べてみた(Hacklinesより)
つっつきボイス:「@tenderloveさんの記事なんですけど、インスタンス変数の定義順序で実行速度が変わることがあるみたいです」「マジで?😆」
何らかの理由で、インスタンス変数を逆方向に定義する方が、インスタンス変数を順方向に定義するよりも高速です。この記事ではその理由について説明しますが、さしあたってパフォーマンスの高いコードが必要なら常にインスタンス変数を逆方向に定義してください(ジョークにつき実際にはやらないでね)。
同記事より大意(強調は編集部)
「アニメーションGIF↓まで作ってくれてます🙏」「何というカリカリチューニング: まあそもそもインスタンス変数ってこんなにゴロゴロ作るもんじゃないし😆」「やっぱり😆」「それにしてもtenderloveさんよくこんなの見つけたし😳」
⚓DB
⚓私家版「SQLスタイルガイド」(DB Weeklyより)
つっつきボイス:「opinionatedとあるので独断と偏見のSQLスタイルガイドということだそうです」「ほほ〜☺️」
「しょっぱながいきなり『SQL句は小文字で書け』?」「え〜そうかな〜?😅」「『Shiftキー押し続けるのがつらいから』みたいです😆」
-- Good
select * from users
-- Bad
SELECT * FROM users
-- Bad
Select * From users
「他のは『行頭にカンマ置くな』とか割と初歩的なスタイルへの言及に終止してますね☺️」
「『引用符はシングルクォートを使え』?」「むむ、PostgreSQLだったか、シングルクォートとダブルクォートで意味がちょっと違ってた気がするナ🤔」
-- Good
select *
from users
where email = 'example@domain.com'
-- Bad
select *
from users
where email = "example@domain.com"
(ひとしきりググる)「そうそうこれ↓」「あ〜ほんとだ!😳」「むしろMySQLが例外だったとは...」
参考: えっ、まだPostgreSQLで「”」使ってるの? - Qiita
PostgreSQLなどの標準SQLでは、
* シングルクォーテーションで囲う:文字列定数として扱う
* ダブルクォーテーションで囲う:カラム名として扱う
という仕様になっている。
(中略)
しかしMySQLだけは独自の仕様を持っています。MySQLでは、
* シングルクォーテーション「'」で囲う:文字列定数として扱う
* ダブルクォーテーション「"」で囲う:文字列定数として扱う
* バッククォート「`」で囲う:カラム名として扱う
という仕様になっています。
Qiita記事より
「もうひとつ見つけましたけど↓、こっちではSQL標準でそうなってるとありますね😳」
参考: sql - What is the difference between single quotes and double quotes in PostgreSQL? - Stack Overflow
二重引用符はテーブル名やフィールド名に用いられるが、省略できることもある。一重引用符は文字列定数に用いる。これはSQL標準である。質問文のクエリを冗長に書くと次のような感じになる↓。
stackoverflow.comより大意
select * from "employee" where "employee_name"='elina';
後でSQL標準の該当箇所を探してみたのですが、うまく見つけられませんでした😇。
⚓CSS/HTML/フロントエンド/テスト
⚓この頃人気のCSSプロパティなど
「アンケートベースのCSS調査結果サイトで、表示とかめちゃめちゃ凝ってます」「Bootstrapの知名度つえ〜😆」
「GridとFlexboxは知名度は同じぐらいだけど実際に使われてるのはFlexboxっぽい」
参考: 2019年、CSSのプロパティ・機能やツールについて使用状況や認知度を徹底調査 -The State of CSS 2019 | コリス
というわけで、以下の実測データサイトと比べてみるとまたよいと思います。なおFlorianとは京都在住のW3Cのメンバーのことです。
⚓番外
⚓ナノグラフェン
つっつきボイス:「グラフェンって炭素なので、将来は炭素でLSIを作れるようになるかも?」「夢ある〜🥰」「30年ぐらいかかりそうですけど😅」
参考: ナノグラフェン - Wikipedia
参考: “夢の物質” 炭素素材の製造技術の開発に成功 名古屋大学 | NHKニュース
以下の動画は上とは別のグラフェンナノリボンの紹介です。
今回は以上です。
バックナンバー(2019年度第2四半期)
週刊Railsウォッチ(20190625-2/2後編)「Webpack入門」は秀逸、「システム設計入門」、Envoy Mobile登場、Docker Desktop for WSL 2ほか
- 20190624-1/2前編 6.1でActionView::Componentが入る、RuboCopのRuby/Railsスタイルガイドサイト、RailsガイドProプラン/Teamプランほか
- 20190624-1/2前編 6.1でActionView::Componentが入る、RuboCopのRuby/Railsスタイルガイドサイト、RailsガイドProプラン/Teamプランほか
- 20190617-1/2前編 マルチプルデータベースガイドが追加、mmcと「Ruby 3の型解析に向けた計画」、Ruby 2.6のCSVライブラリはいいほか
- 20190611-2/2後編 Dockerfileベストプラクティス、DBの冗長化、jQueryとの付き合い方ほか
- 20190610-1/2前編 RailsConf 2019のスライドを追う、Railsのファイル添付gem、Railsの技術的負債を返す、RuboCop 1.0間近ほか
- 20190604-2/2後編 Cloudflare Workers KVの可能性、PostgreSQL 12 Beta 1、Bootstrap 5でjQuery廃止ほか
- 20190603-1/2前編 Ruby 2.7.0-preview1リリース、RailsConf 2019を追う、pluckとincludesの組み合わせに注意、deep_transform_keys追加ほか
- 20190521-2/2後編 サーバーレスクラウドのベンチマーク比較サイト、VueJSパフォーマンス向上、GraalVM 19.0ほか
- 20190520-1/2前編 Evil Martians愛用の便利gemたち、render_asyncでRails表示を高速化、split gemでA/Bテストほか
- 20190514-2/2後編 Webpackerを現場で使う、Dockerfileベストプラクティス、SONYのユニークID生成ツールsonyflakeほか
- 20190513-1/2前編 6.0の地味に嬉しい機能、ActiveModelエラーの扱いが変更、Railsのリクエスト/レスポンスをビジュアル表示ほか
- 20190508-2/2後編 サロゲートキーのコスト、Cloud RunとLambdaの違い、miniredis、CSS Subgridほか
- 20190507-1/2前編 Rails 6.0.0rc1が4/24にリリース、Rails 6の新メソッド群、RubyリポジトリがCgitに移行ほか
- 20190416-2/2後編 最近のRDBMS市場、Flutterがデスクトップにも向かう、書籍『失敗から学ぶRDBの正しい歩き方』ほか
- 20190415-1/2前編 Railsバージョンアップに便利なstill_life gem、Zeitwerkの改修進む、named_capture追加ほか
- 20190409-2/2後編 Ruby 2.3系サポート終了、Thoughtbotのコーディング指南書、PostgreSQLのgenerated column、Chromebrewほか
- 20190408-1/2前編 RubyKaigiの予習資料、Rails「今年ベストのプルリク」、numbered parametersの議論ほか
- 20190402-2/2後編 Apache Arrowとは何か、prop drillingはアンチパターン、Node-REDほか
- 20190401-1/2前編 Rails 5.2.3/5.1.7がリリース、Railsdmの「Railsの正体」、Ruby 2.7のnumbered parameter、新元号「令和」ほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSなど)です。