Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: Railsコアチームとコミッターに新メンバー、ruby-buildでのRust YJITサポートほか(20220524後編)

こんにちは、hachi8833です。Railsコアチームに3名、Railsコミッターに2名の新メンバーが加わりました。おめでとうございます!🎉

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Ruby

🔗 最近のRuboCop

現時点の最新RuboCopはv1.29.1です。


つっつきボイス:「Style/RedundantInitialize copってこれか↓」「あ〜、ただsuperするだけのinitializeメソッドね」「たしかに不要」

# https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Style/RedundantInitialize より
# bad
def initialize
end

# bad
def initialize
  super
end

# bad
def initialize(a, b)
  super
end

# bad
def initialize(a, b)
  super(a, b)
end

# good
def initialize
  do_something
end

# good
def initialize
  do_something
  super
end

# good (different number of parameters)
def initialize(a, b)
  super(a)
end

# good (default value)
def initialize(a, b = 5)
  super
end

# good (default value)
def initialize(a, b: 5)
  super
end

# good (changes the parameter requirements)
def initialize(*)
end

# good (changes the parameter requirements)
def initialize(**)
end

# good (changes the parameter requirements)
def initialize(...)
end

「goodの例の*...とかいろいろ参考になる」「デフォルト値が省略できる場合もあるんですね」「こういうのはRuboCopで指摘してもらえると嬉しい👍」

参考: メソッドの引数をアスタリスク1文字にするイディオム - Qiita
参考: 【Ruby】superしか呼び出していないinitializeメソッドは定義する必要ない | しきゆらの備忘録

「コードを書き換えているときに一時的に書いたinitializeを消し忘れることある😅」「ちょっとブレークポイントを置きたいときとかにやりますね」


「Ruby 2.5はサポート終了なのでもう使わなそうですけど」「2.5使っているところはまだありますけどね」「変更内容を見ると、#10577でRuboCopをRuby 2.5で動かすランタイムサポートを廃止したときに↓、Ruby 2.5のコードを対象とする解析の一部も廃止してしまったので元に戻したということらしい」「TargetRubyVersionって後者のことか」「ややこしい」

参考: Drop Ruby 2.5 support by koic · Pull Request #10577 · rubocop/rubocop

🔗 issue: ruby-buildでのRust YJITサポート


つっつきボイス:「Rust YJIT(ウォッチ20220511)をruby-buildでどうサポートするかというissueか」

最初に、ruby-buildという信じられないほど便利なツールを作ってくださってありがとうございます🙏
ご存知の通り、私たちはYJITをRustに移植中で、既に#5826でアップストリームされています。
しかしYJITは、releaseモードでのビルドにrustc 1.60が、developmentモードでのビルドにはcargoがそれぞれインストールされている必要があります。そのため、CRubyはデフォルトではYJITサポートなしでビルドされます。YJITをビルドするには、./configureを実行するときに--enable-yjitオプションの指定が必要です。ビルド方法についてはYJITのREADMEにあります。
ruby-buildは多くの人に主要なRubyデプロイ手段として使われており、Rubyエコシステムの中心にあることを考慮して、ruby-buildでRust YJITビルドがサポートされることを熱望しています。そうなればYJITを使うハードルが下がってより広く使われるようになるでしょう。
同僚の@byrootによると、ruby-buildメンテナーの方々はRustツールチェインを自動インストールすることに乗り気でないと伺っています。セキュリティ関連(およびダウンロードサイズの増加)の懸念ももっともです。何か他に方法がないか考えています。
とりあえず思いつく方法:

  • ruby-buildが、rustc >= 1.60がインストール済みかどうかをチェックしたうえで、--enable-yjitが指定されていれば設定する方法が考えられます
  • ruby-buildが、RubyのビルドがYJITサポートの有無をユーザーに通知する方法も考えられます。YJITなしでビルドされた場合は、YJITを有効にするにはrustc >= 1.60をインストールしておく必要があると通知するとよさそうです。

私たちは当面の間rustcの依存関係を1.60に固定するつもりなので、この要件はかなり安定したものになると思われます。ruby-buildメンテナーの方々がこれを容認いただければ、さまざまな選択肢の長所と短所、そしてruby-buildメンテナーがこれを実現するうえで私たちが協力する方法について喜んで議論したいと思います。
同僚の@byroot、@XrXr、@noahgibbsにもタグ付けします。
同issueより

「まだ議論中なんですね」「Rust YJITが人によって動いていたりいなかったりするのは確かに何とかしたい」「自分の環境にはRustのツールチェインは入ってないな〜」「自分も」「自分は入れてます😆」

🔗 gem名のハイフンを悪用する乗っ取り(Ruby Weeklyより)


つっつきボイス:「先週ぐらいのGitHub Security Advisoryだそうです」「以下のようなgemが攻撃を受ける可能性があったのか」

「なるほど、攻撃者がsomethingというgemを持っているとsomething-providerというgemを乗っ取れる可能性がある」「へ〜!」「今のところこの脆弱性を突いた攻撃は行われていないと信じていると書かれてますね」「名前に-が含まれているgemの作者は読んでおく方がよさそう」

「2つ目の記事でも攻撃される可能性の条件について解説されてる↓」「図もわかりやすいですね」「詳しく書かれていて、きちんと読んでおくとよさそうな記事👍」

  • 名前に-が含まれている(rspec-coreconcurrent-rubyなど)
  • -の前の名前に相当するgemがまだ存在しない(kostya-sigarはあるがkostyaはない、googleapis-common-protos-typesはあるがgoogleapisはないなど)
  • 削除されるgemは、過去30日以内に作成されたか、100日以上更新されていないものである必要がある
    Impact Analysis: RubyGems Critical CVE-2022-29176 Unauthorized Package Takeover  | WhiteSourceより

-を暗黙で名前空間的に使う文化があったりすると狙われる可能性があるのかも」「2つ目の記事のこういうコードがそうなんでしょうね↓: 式展開しても元のgem名に戻らなくなる」「お〜なるほど」

# https://www.whitesourcesoftware.com/resources/blog/impact-analysis-rubygems-critical-cve-2022-29176-unauthorized-package-takeover/ より
find_by!(full_name: "#{rubygem.name}-#{slug}")

「先週話題に出たscoped gems(ウォッチ20220517)の必要性が高まりそう」「たしかに」「構造を持つ名前を無理に文字列として扱うと良くないという例だと思います: できればgem名に使えない記号を区切り記号にしたいところでしょうね」


このissueはRubyコミュニティに混乱を引き起こしかねない非常に重大なものだったが、私たちのデータとその後の調査に基づけば、私たちの知る限り危険にさらされたgemはなく、問題は軽減されたと結論づけている。
Impact Analysis: RubyGems Critical CVE-2022-29176 Unauthorized Package Takeover  | WhiteSourceより

🔗 その他Ruby


つっつきボイス:「Matzのインタビューですね」「MatzがRubyのドキュメントをいつ英語化したのか昔から興味があったので面白く読めました」


tric/trick2022 - GitHub

「お、TRICKのリポジトリが久しぶりにできた🎉」「RubyKaigiに併設されていたイベントでしたっけ」「いったん終わったのがまた復活したんですね」「それでReturnsと書かれてるのか」「2021年9月から応募開始してたんですね」「締め切りは2022年7月31日」「応募してみたい人はぜひ👍」

「リポジトリを見ると2013年、2015年、2018年に開催されたから4年ぶりの開催か」「こういうイベントは実際に会場に集まってやるのが楽しいですよね」「ルールを見るとMRI 3.1以上が推奨か」「JRubyやRubiniusを使ってもいいんですね」「サイズ制限もある」


「ところで@mametterさんの『The world's No.1 IOCCC player』という肩書すごい」「ついでにIOCCCのサイトを見てみると、こちらも毎年開催されているわけではなさそうですね」「こういうコアなイベントを毎年開催したら運営は大変だと思います」

参考: IOCCC - Wikipedia
参考: The International Obfuscated C Code Contest

🔗DB

🔗 1文字でSQLを200倍遅くする


つっつきボイス:「はてブで見つけた記事です」「使っているのはMariaDB 10.6.4とGo言語か」「created_atの精度がns(ナノ秒)になると遅くなるんですか」「インデックスの精度がms(マイクロ秒)で、そこにナノ秒の値を渡すとインデックスがヒットしなくなってフルスキャンされてしまったようですね」「使っているMySQLアダプタが変更されてナノ以下を丸めなくなったことで発生したそうです」「こういう問題を踏んだらつらそう」

-- 同記事より
ANALYZE FORMAT=JSON SELECT * FROM `messages` WHERE `created_at` > '2022-04-17 00:00:00.123456789' ORDER BY `created_at` LIMIT 1;
// 同記事より
{
  "query_block": {
    "select_id": 1,
    "r_loops": 1,
    "r_total_time_ms": 236920.611, ← 遅い(下のより400万倍遅い)
    "table": {
      "table_name": "messages",
      "access_type": "index",
      "possible_keys": [
        "idx_messages_created_at"
      ],
      "key": "idx_messages_created_at", ← 偉い(ように見えるが遅い)
      "key_length": "8",
      "used_key_parts": [
        "created_at"
      ],
      "r_loops": 1,
      "rows": 1769333,
      "r_rows": 2182038,
      "r_table_time_ms": 234829.0258,
      "r_other_time_ms": 2091.581307,
      "filtered": 100,
      "r_filtered": 0.00004582872,
      "attached_condition": "messages.created_at > '2022-04-17 00:00:00.123456789'"
    }
  }
}

参考: ナノ秒 - Wikipedia

🔗 設計

🔗 Value Object


つっつきボイス:「最近Value Objectが話題になっているみたいですね」「記事は全体的に、Martin Fowler『Patterns of Enterprise Application Architecture』(PoFEAA)や『エリック・エヴァンスのドメイン駆動設計』という定番の書籍を典拠に常識的な議論をしている感じですね」

「記事で力の入っている部分はこのあたりなのかも↓: エンティティの全メンバーやデータベースの全カラムをクラス化してimmutableにすることをValue Objectだと考えるのは確かに変」「Javaみたいな堅い言語だとこうしたくなる人がいるのかな」「Rubyのようなスクリプト言語だとそういう気持ちになりにくいですよね」

観測した中で一番ひどかったのがこの勘違いである。「ドメインの語彙に数学上の概念である整数などは存在しないのですべての語彙を専用のクラスで包め」という行き過ぎた思想である。それはエンティティの全メンバーやデータベースの全列のために「顧客郵便番号」「送付先郵便番号」「事業所郵便番号」「契約日」などのクラス(メンバではなくクラス!)を定義して、immutableな振る舞いを強制する事を以てValue Objectであると言い張り、ドメイン知識の断片をそれぞれのクラスに書き散らして「高凝集になった」「型システムが守ってくれる」と喜ぶ奇行に走る。更に救えないのはこれを言語固有の問題ではなく他のプログラミング言語にも役立つプラクティスだと主張する点である。一言で言うと、DDDを誤解している。
同記事より

「DDDを誤解しているというより、ドメインモデリングに失敗している気がする」「ドメイン設計で契約日や郵便番号といった概念を深めていった結果、必要な部分をValue Objectにすることはもちろんありますが、一律に実装でValue Objectに落とし込むのは変」「よく考えて設計してから実装しないといけませんよね」


「たとえば日付を例に取ると、契約日という日付データとは別に、契約書に書かれた令和とか平成などの元号入りの契約日の文字列もドメインモデル上必要だとしたら、そのためのクラスを作ることはあっていいと思います」「そうそう」

「契約日の年号入り日付だとクラスの必然性はあまり感じられませんが、有効期限のような"未来の日付"だと、単純な日付にできないこともあるんですよ」「そういう日付は機械的に導き出せなかったりしますよね」

「ちなみに自分の免許証の有効期限は"平成36年"という実際には存在しない元号になってますが、これを元号なしのDateで保存してしまうと、ビジネスドメイン(免許証上)に書かれている情報が表現できていないという見方もできます: こうした場合には免許証記載文字列とDateという2つの値を持つValue Objectとしてオブジェクト化するという実装が要求されることもあるかもしれませんね」

「このようなものは当然ドメインモデルに影響するので、Value Objectなどで実装する場面はたしかにある」「大事なのは、それが本当に必要かどうかを設計時にみっちり検討することですね」


「記事の後半でも、DDDの練習の一環として全部をValue Objectで実装することを繰り返した結果、一律そうすべきと思い違いしたのではないかという可能性に言及している」「練習自体はとても良いことなんですけどね」

「DDDではユビキタス言語↓というものを重視していて、業務で使われる各用語がドメイン上でどういう意味を持つのかを徹底的に追いかけるんですけど、DDDの学習でそういう練習を繰り返すうちに一律でValue Objectにすればいいみたいな発想になる人がまれにいるのかも」「もちろん、この部分にはもしかすると専用のクラスが必要なのではないかというふうに疑って詳しく検討すること自体は良いことです」

参考: ドメイン駆動設計 第2章 ユビキタス言語を読みなおした - yoskhdia’s diary

🔗クラウド/コンテナ/インフラ/Serverless

🔗 GitHub Actionsの設計


つっつきボイス:「GitHub Actionsの設計は見よう見まねでやっているとわかりにくいので、こういう記事はいいですね👍」「実行コンテキストの話や、Stepをまたいで結果を受け渡すときの注意とか」「今週GitHub Actionsいっぱいやりました」「考えて構築しないとキャッシュが効かなくて激重になったりしますね」「処理分岐はやめた方がいい、たしかに」


「こういう記事を見ていると、その昔Jenkinsのジョブをどう書くとメンテしやすくなるかという議論が盛んだった頃を思い出す」「そうそう、Jenkinsは今でも使っているところがありますね」「歴史は繰り返すのかも」「GitHub Actionsはクラウド前提ということもあって、ワークフローがStepで閉じているとか、キャッシュのしくみがJenkinsなどで提供されるストレージと違っているあたりは特殊な感じですね」

参考: Jenkins

🔗JavaScript

🔗 importmapをRailsなしで使う(Ruby Weeklyより)


つっつきボイス:「importmapそのものは、当然Railsなしでも使えますね」

<!-- 同記事より -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Import maps without Rails - Local-time example</title>

    <script async src="https://unpkg.com/es-module-shims@1.2.0/dist/es-module-shims.js"></script>
    <script type="importmap-shim">
      {
        "imports": {
          "local-time": "https://ga.jspm.io/npm:local-time@2.1.0/app/assets/javascripts/local-time.js"
        }
      }
    </script>
    <script type="module-shim">
      import LocalTime from "local-time"
      LocalTime.start()
    </script>

    <style>
      time { color: #c11; font-size: 1.1em; }
    </style>
  </head>
  <body>
    <h1>Import maps without Rails - Local-time JS example</h1>

    <p>
      Last time I had chocolate was <time datetime="2022-05-08T23:00:00+02:00" data-local="time-ago">8th of May</time>
    </p>

  </body>
</html>

Rails 7: importmap-rails gem README(翻訳)

🔗CSS/HTML/フロントエンド/テスト/デザイン

🔗 HTML&CSS Tips集


つっつきボイス:「BPS社内で評判のよかった記事です」「テキストを円形に回り込ませるのってCSSだけでやれるのか」「この色付きの部分をshape-marginって言うんですね↓: こういう図はわかりやすくて好き👍」


同記事より

display: flow-root;でfloatを解除できるのか: 自分は最近float使わなくなったけど」「emptyという疑似クラス初めて見た」

参考: float - CSS: カスケーディングスタイルシート | MDN
参考: display - CSS: カスケーディングスタイルシート | MDN
参考: :empty - CSS: カスケーディングスタイルシート | MDN

data-*属性でテキスト表示、面白いけど表示する文字をdata-*属性に入れると後で探しにくそう」

See the Pen
Data attributes heading
by Kobayashi (@Pulp_Kobayashi)
on CodePen.

参考: data-* - HTML: HyperText Markup Language | MDN

:first-line疑似要素で最初の行だけスタイルを付けられるのね」「いろいろ参考になる」

参考: ::first-line (:first-line) - CSS: カスケーディングスタイルシート | MDN


後編は以上です。

バックナンバー(2022年度第2四半期)

週刊Railsウォッチ: Hotwireの用途解説記事、RubyKaigi 2022プロポーザル募集開始ほか(20220523前編)

今週の主なニュースソース

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby Weekly


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。