Quantcast
Channel: Rubyの記事一覧|TechRacho by BPS株式会社
Viewing all 1080 articles
Browse latest View live

週刊Railsウォッチ(20210427後編)RactorでUDPサーバーを作る、JSONシリアライザalba gem、AppleのAirTagほか

$
0
0

こんにちは、hachi8833です。次回の週刊Railsウォッチはゴールデンウィーク翌週の5/10(月)を予定しています。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 Ruby 3でコアクラスをサブクラス化したときの挙動が変わった件について(Ruby Weeklyより)


つっつきボイス:「Ruby 3.0でいろいろ機能が追加されましたが、そういえばこの変更もあったなと思って取り上げました」「ArrayStringのようなRubyのコアクラスをサブクラスに継承して使うのはやめておく方がいいよという記事↓も紹介されてる」「継承でない方法にしましょうということですね」

参考: Why you shouldn’t inherit from Ruby’s core classes (and what to do instead) – avdi.codes

以下はつっつき後に手元で確認した動作です。

class Bar < String
end

bar = Bar.new

# Ruby 2.7までの場合
bar.upcase.class #=> Bar
# Ruby 3.0の場合
bar.upcase.class #=> String

参考: プロと読み解く Ruby 3.0 NEWS - クックパッド開発者ブログ

  • Arrayのサブクラスのメソッドが、サブクラスではなく、Arrayクラスのオブジェクトを返すようになった
  • Stringのサブクラスのメソッドが、サブクラスではなく、Stringクラスのオブジェクトを返すようになった
    同記事見出しより

「そういえばRailsにもこのあたりに関連した修正が入ってましたね」「そうだったかも」「探してみます」「知らないうちに型が変わるのはしんどいので、この挙動変更は知っておく必要がありますね」


後で探すと、銀座Rails#28の以下のスライドにありました↓。同ページのリンクも抜き書きしました。

🔗 alba: 高速JSONシリアライザ(Ruby Weeklyより)

okuramasafumi/alba - GitHub


つっつきボイス:「大倉さんの作ったJSONシリアライザgemがRuby Weeklyに取り上げられていました」「albaのリリースを見ると2020年1月からなので割と新しいgemなんですね」「fastest!」「JSONのシリアライズはとてもよく使われるので、シリアライズが高速化されるのはいいですね👍」

「ちなみにJSONシリアライザにはいくつか定番があるんですよ↓」

参考: 2020年のRuby/RailsのJSONシリアライザは何を使うべきか問題 - Bouldering & Com.

「Railsに入っているjbuilderは昔から遅いので有名(最近はわかりませんが)↓: それもあって他のJSONシリアライザがいろいろ登場したという側面はありますね」「なるほど」「自分は速度をそれほど気にする方でもないんですが、それでもjbuilderを最初に使ったときに遅いと思ったほどでした: jbuilderはpublic APIなどに利用してpage cacheなどを効かせられるのであれば問題なさそうですが、キャッシュに乗せられないようなAPIで使うには主に速度面でちょっと辛いかもしれないとは感じました」

rails/jbuilder - GitHub

「active_model_serializer↓は昔から定番で自分もよく使ってましたが、記事にもあるように最近は少し更新頻度が下がってるかな」

rails-api/active_model_serializers - GitHub

「amatsudaさんのjbは、jbuilder的にビューファイルを作るタイプですね↓」

amatsuda/jb - GitHub

「JSON APIはプログラムから操作することがほとんどなので、求められる速度やレスポンスの要求が通常のHTMLビューよりもシビアなんですよ」「たしかに」「JSONシリアライザが遅いのはよろしくないので、それもあってactive_model_serializerを使ってました」


以下はつっつき後に見つけたツイートです。

🔗 RactorでUDPサーバーを作る(Ruby Weeklyより)


つっつきボイス:「割と短い記事です」「お、RactorでUDPサーバーですか」「RactorでWebサーバーを書いた人がいたので(Writing a Ractor-based web server · Kir Shatrov)、UDPサーバーを作ってみたということみたいですね」

「UDPソケットを待ち受けるRactor listenerを1つ作って、そこで受信したデータグラムの処理をメッセージ処理用のRactor pipeで受信し、Ractor workerをCPU_COUNT数だけ動かして処理を振り分けている感じですね↓」「ふむふむ」「Ractorの参考になるコード例が増えるのはいいですね👍」

🔗 その他Ruby

つっつきボイス:「そういえば例年なら今頃RubyKaigiが開催されている時期ですよね」「あぁ、そうだった…」「オンラインのイベントや勉強会が主流になったことで参加の敷居は下がったのはよいことだと思いますけど、人に会ったりおいしいものを食べたりできないのが寂しい」「ですよね」

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

🔗 CloudflareのWorkers UnboundがGAに(StatusCode Weeklyより)


つっつきボイス:「Cron Triggersで15分かかるような処理がWorkers Unboundだと扱えるとありますね」「あ、なるほど」「昨年のCloudflare記事が予告的な感じで読みやすそうです↓」

参考: Workers Unboundのご紹介

Workers Unboundは、オリジナルのCloudflare Workers(現在、Workers Bundledと呼んでいます)と似ていますが、さらに長い実行時間を必要とするアプリケーション向けです。処理負荷がどれだけ高くても、お客様がそれをWorkersに移動させることができるようにCPU制限を拡張しています。そのため開発者は、Edgeで高速かつシンプルな作業をするか、リソースが無制限の集中型クラウドで重いコンピューティングを実行するか、どちらかを選ぶ必要がなくなります。
Workers Unboundのご紹介より

「以前からあるCloudflare WorkersはAWS Lambda@Edgeと競合するサービスですね: Workers Unboundは時間制限を緩和したもので、日本語記事によるとCloudflare Workersとは別サービスということらしい」「なるほど」「そこまで時間のかかる処理をクラウドにキャッシュしたいケースはすぐに思い当たらないけど、画像処理やAIから巨大な予測値を返すような処理が想定されているようなので、そうした用途には悪くないかもしれませんね」

参考: Lambda@Edge | AWS

🔗 AppleのAirTag


つっつきボイス:「AppleのAirTag、Tileより高いらしいですね」「4個で1万円超えか」「値段的にもTileで十分なので自分はAirTagが欲しいとは思わないかな」「性能より個数が欲しい方なのでTileにしたいです」

「AirTagはApple純正な分、初めて使うときの設定はやりやすそうなので、たとえば自分の親に使ってもらうときには楽かもと思いました」「あ、それもそうか」「Appleはそういう使い勝手の敷居を下げるのがうまいですね」「AirTagは向きまで特定できるのがちょっとスゴい」

「あとAirTagもオンラインで買うと刻印入れてくれるらしいですよ↓」「あ、物理の刻印なんですね」「刻印サービス、なぜかあまり人気がないんですよ」「MacBookやiPhoneに刻印入れると後で転売しにくくなりますし」

参考: 無料のメッセージ刻印とギフト包装 - Apple(日本)

「AirTagのようなものだと落としたときに名前がないと誰のものかわからなくなるので、そういう場合に刻印が役に立ちそう」「刻印にはApple絵文字も使えますよ」「Appleがプライバシー方面に力を入れている点は安心材料かな」

「Tileはラインナップも豊富で安いですし、この種のメーカーとしては一番長くやっていますよね」「そうそう、他の製品だとサービス終了したり」「Tileはマニア層だけではなく一般レベルで使われていますね」


「そういえば自転車にTile付けようかなと思ってたところです」「バイクの盗難防止的なやつですね」「自分も自転車と自動車とキーにTile付けてます」「心配なのはバッテリーがどのぐらい持つかなんですが」「Tileは余裕で1年は持ちますし、電池も交換可能なタイプも前から出ていますよ」「お、そうなんですね」

「ちなみにAirTagで使えるCR2032という電池はコンビニでも手に入るんですけど、ちょっと大きめで厚みがあるんですよ」「Wikipedia見てて気づいたんですけど、型番がそのままサイズを反映してるんですね↓」「ホントだ、CR2032だと20mm × 32mmなのか」

参考: コイン形リチウム電池 - Wikipedia

🔗言語/ツール/OS/CPU

🔗 研究名目でLinuxカーネルに意図的に不具合パッチを投稿したセキュリティ研究者が問題に

つっつきボイス:「既にあちこちで語られていますけど、Linuxに意図的に脆弱性を埋め込むのはもう言い訳しようがない」「これはそうとしか言いようがないですね」「コードやレビュー手順を解析して脆弱性を指摘する研究とかならまだしも、実践するのは完全にアウト」

「今の時点で(注: 2021/04/22 20:00頃)、ミネソタ大ドメインから投稿された全てのコミットをLinuxリポジトリからRevertするパッチができたようですね↓」「パッチを作ったGregさんは温厚な人柄で知られているんですけど、そのGregさんを怒らせるとは」

参考: [PATCH 000/190] Revertion of all of the umn.edu commits - Greg Kroah-Hartman
参考: グレッグ・クロー=ハートマン - Wikipedia

「今は情報がいろいろ出ている最中ですが、もう少し経てば情報をまとめたブログが出てくると思います」「本トピックのタイトル付け、悩んでます…」


以下はつっつき後の最新記事です。

参考: University of Minnesota security researchers apologize for deliberately buggy Linux patches | ZDNet
参考: Linux kernel team rejects University of Minnesota researchers’ apology | Ars Technica

🔗 その他CPU

つっつきボイス:「トランジスタでCPUを作ってロボットを動かしてる」「キットが5万円で、今品切れだそうです」「これで何するんですか?」「こういうのは作るだけで楽しいものなんですよ😋」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210426前編)Hotwireの詳細な解説記事3本、Rails 7に入る予定の機能ほか

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

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

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner


クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

更新情報

  • 2019/09/06: 初版公開
  • 2021/04/20: 原文更新を反映

訳注

Ruby on Whalesは、Ruby on Railsの他に、もしかすると「Boy on a Dolphin」にかけているのかもしれないと思いました。Whales(クジラ)はもちろんDockerのシンボルです。

まえがき

本記事は、私がRailsConf 2019で話した「Terraforming legacy Rails applications」↑の、いわばB面に相当します。この記事を読んで、皆さんがアプリケーション開発をDockerに乗り換えるとまでは考えていません(皆さんが以下の動画で若干言及しているのをご覧になっていたとしても)。本記事の狙いは、私が現在のRailsプロジェクトで用いている設定を皆さんと共有することです。それらのRailsプロジェクトは、Evil Martiansのproduction development環境で生まれたものです。どうぞご自由にお使いください。

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

原文免責事項: 英語版記事は最新の推奨事項に合わせて更新を繰り返しています。詳しくは記事末尾のChangelogをご覧ください(参考: 原文Changelog)。


私がdevelopment環境でDockerを使い始めたのは、かれこれ3年ほど前の話です(それまで使っていたVagrantは4GB RAMのノートパソコンではあまりに重たかったのでした)。もちろん、最初からバラ色のDocker人生だったわけではありません。自分のみならず、チームにとっても「十分にふさわしい」Docker設定を見つけるまでに2年という月日を費やしました。

私の設定を、(ほぼほぼ)すべての行に解説を付けてご覧に入れたいと思います。「Dockerをわかっている」前提のわかりにくいチュートリアルはもうたくさんですよね。


本記事のソースコードは、GitHubのevilmartians/terraforming-railsでご覧いただけます


本記事の例では以下を用います。

  • Ruby 2.6.3
  • PostgreSQL 13
  • NodeJS 11 & Yarn(Webpackerベースのアセットコンパイル用)

🔗 Evil Martians流Dockerfile

Railsアプリケーションの「環境」は、Dockerfileで定義します。サーバーの実行、コンソール(rails c)、テスト、rakeタスク、開発者としてコードとのインタラクティブなやりとりは、ここで行います。

ARG RUBY_VERSION
# 後述
FROM ruby:$RUBY_VERSION-slim-buster

ARG PG_MAJOR
ARG NODE_MAJOR
ARG BUNDLER_VERSION
ARG YARN_VERSION

# 共通の依存関係
RUN apt-get update -qq \
  && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    build-essential \
    gnupg2 \
    curl \
    less \
    git \
  && apt-get clean \
  && rm -rf /var/cache/apt/archives/* \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
  && truncate -s 0 /var/log/*log

# mimemagic gem用のMIMEタイプデータベースをダウンロード
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
  DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    shared-mime-info \
  && cp /usr/share/mime/packages/freedesktop.org.xml ./ \
  && apt-get remove -y --purge shared-mime-info \
  && apt-get clean \
  && rm -rf /var/cache/apt/archives/* \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
  && truncate -s 0 /var/log/*log \
  && mkdir -p /usr/share/mime/packages \
  && cp ./freedesktop.org.xml /usr/share/mime/packages/

# PostgreSQLをソースリストに追加
RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
  && echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list

# NodeJSをソースリストに追加
RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -

# Yarnをソースリストに追加
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list

# アプリケーションの依存関係
# 外部のAptfileでやってる(後ほどお楽しみに!)
COPY Aptfile /tmp/Aptfile
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
  DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    libpq-dev \
    postgresql-client-$PG_MAJOR \
    nodejs \
    yarn=$YARN_VERSION-1 \
    $(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
    truncate -s 0 /var/log/*log

# bundlerの設定
ENV LANG=C.UTF-8 \
  BUNDLE_JOBS=4 \
  BUNDLE_RETRY=3

# bundler設定をプロジェクトのルートに保存する場合は以下をコメント解除
# ENV BUNDLE_APP_CONFIG=.bundle

# `bin/`や`bundle exec`を付けずにbinstabを実行したい場合は以下をコメント解除
# ENV PATH /app/bin:$PATH

# RubyGemsをアップグレードして必要なバージョンのbundlerをインストール
RUN gem update --system && \
    gem install bundler:$BUNDLER_VERSION

# appコードを置くディレクトリを作成
RUN mkdir -p /app

WORKDIR /app

このDockerfileの設定は必要不可欠なもののみを含んでいますので、これを元にカスタマイズできます。このDockerfileで何が行われるかを解説します。

最初の2行に少々妙なことが書かれています。

ARG RUBY_VERSION
FROM ruby:$RUBY_VERSION-slim-buster

FROM ruby:2.6.3みたいに適当な安定版Rubyのバージョンを書いておけばよさそうなものですよね。ここではDockerfileを一種のテンプレートとして用い、環境を外部から設定可能にしたいのです。

  • ランタイム依存の厳密なバージョンは、docker-compose.ymlの方で指定することにします(後述)。
  • aptコマンドでインストール可能な依存のリストは、これも別ファイルに保存することにします(これも後述)。

また、PostgreSQLなどの他の依存関係に対応する正しいソースを追加するためにDebianのリリース(buster)を明示的に指定しています。

上に続く以下の4行は、PostgreSQL、NodeJS、Yarn、Bundlerのバージョンを定義します。

ARG PG_MAJOR
ARG NODE_MAJOR
ARG BUNDLER_VERSION
ARG YARN_VERSION

今どきDockerfileをDocker Composeなしで使う人などいないという前提なので、Dockerfileではデフォルト値を指定しないことにします。

続いて実際のイメージビルドが行われます。Dockerイメージのサイズを削減するためにslimベースのDockerイメージを使うので、最初にシステム共通の依存関係(GitやcURLなど)を手動でインストールします。

# 共通の依存関係
RUN apt-get update -qq \
  && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    build-essential \
    gnupg2 \
    curl \
    less \
    git \
  && apt-get clean \
  && rm -rf /var/cache/apt/archives/* \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
  && truncate -s 0 /var/log/*log

システム依存のインストールの詳細については、アプリケーション固有のものについて説明するときに後述します。

次に、mimemagic gemで使うMIMEタイプデータベースをダウンロードするためだけにRUNコマンドを実行します(mimemagic gemのライセンス問題はご存知ですか?)。

RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
  DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    shared-mime-info \
  && cp /usr/share/mime/packages/freedesktop.org.xml ./ \
  && apt-get remove -y --purge shared-mime-info \
  && apt-get clean \
  && rm -rf /var/cache/apt/archives/* \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
  && truncate -s 0 /var/log/*log \
  && mkdir -p /usr/share/mime/packages \
  && cp ./freedesktop.org.xml /usr/share/mime/packages/

mimemagic gemを使わないのであれば、上のブロックは削除して構いません。

PostgreSQL、NodeJS、Yarnをaptコマンドでインストールするために、それらのdebパッケージのリポジトリをソースリストに追加する必要があります。

RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
  && echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list

原注: ここが、OSリリースをbusterにしたことを利用している箇所です。

RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list

今度は依存関係のインストールです(apt-get installの実行など)。

COPY Aptfile /tmp/Aptfile
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
  DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
    libpq-dev \
    postgresql-client-$PG_MAJOR \
    nodejs \
    yarn \
    $(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
    truncate -s 0 /var/log/*log

まずAptfileという裏技について解説します。

COPY Aptfile /tmp/Aptfile
RUN apt-get install \
    $(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \

Aptfileというアイデアはheroku-buildpack-aptから拝借しました。heroku-buildpack-aptは、Herokuに追加パッケージをインストールできます。このbuildpackを使っていれば、同じAptfileをローカルでもproduction環境でも再利用できます(buildpackのAptfileの方が多くの機能を提供していますが)。

私たちのデフォルトAptfileに含まれているのパッケージは、たったひとつです(私たちはRailsのcredentialの編集にVimを使っています)。

vim

私が携わっていた直前のプロジェクトでは、LaTeXやTexLiveを用いてPDFを生成しました。そのときのAptfileは、さしずめ以下のような感じにできたでしょう(当時私はこの技を使っていませんでしたが)。

vim
texlive
texlive-latex-recommended
texlive-fonts-recommended
texlive-lang-cyrillic

このようにすることで、タスク固有の依存関係を別ファイルに切り出し、Dockerfileの普遍性を高めています。

DEBIAN_FRONTEND=noninteractiveという行については、「answer on Ask Ubuntu」という記事をご覧になることをおすすめします。

--no-install-recommendsスイッチを指定すると、推奨パッケージのインストールを行わなくなるので容量を節約でき、ひいてはイメージをもっとスリムにできます。詳しくは「Xubuntu Geek: Save disk space with apt-get option “no-install-recommends” in Xubuntu」をご覧ください。

RUNの最後の部分(apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && truncate -s 0 /var/log/*log)も目的は同じです。取得したパッケージファイルのローカルリポジトリ(ここまでで必要なものはすべてインストールできているので、これらはもはや不要です)や、インストール中に作成されたすべての一時ファイルやログをクリーンアップします。 特定のDockerレイヤにごみを残さないようにするため、このクリーンアップ作業は同じRUNステートメントの中で行う必要があります。

最後の部分は、もっぱらBundlerのためのものです。

# bundlerの設定
ENV LANG=C.UTF-8 \
  BUNDLE_JOBS=4 \
  BUNDLE_RETRY=3

# bundler設定をプロジェクトのルートに保存する場合は以下をコメント解除
# ENV BUNDLE_APP_CONFIG=.bundle

# `bin/`や`bundle exec`を付けずにbinstabを実行したい場合は以下をコメント解除
# ENV PATH /app/bin:$PATH

# RubyGemsをアップグレードして必要なバージョンのbundlerをインストール
RUN gem update --system && \
    gem install bundler:$BUNDLER_VERSION

LANG=C.UTF-8は、デフォルトロケールをUTF-8に設定します。これを行わないとRubyが文字列でUS-ASCIIを使ってしまうので、かわいいかわいい絵文字たちとおさらば👋になってしまいます。

プロジェクト固有のbundler設定(プライベートなgemで使うcredentialなど)の保存先に<root>/.bundleフォルダを使う場合は、BUNDLE_APP_CONFIGが必要です。デフォルトのRubyイメージではこの変数が定義されていて(#129)、bundlerがローカル設定にフォールバックしないようになっています。

また、bundle execを付けずにコマンドを実行できるよう、PATH変数に<root>/binフォルダを追加することも可能です。これはデフォルトではオフにしてありますが、理由はマルチプロジェクト環境(Railsアプリ内でローカルのgemやエンジンを使う場合など)でコードが動かなくなる可能性があるためです。

🔗 Evil Martians流docker-compose.yml

Docker Composeは、コンテナ化された環境をオーケストレーションするツールで、これを用いてコンテナ同士を接続し、永続化ボリュームやサービスを定義できます。

以下は、データベースにPostgreSQL、バックグラウンドジョブの処理にSidekiqを用いた、Railsアプリケーションの典型的な開発環境のためのdocker-compose.ymlです。

version: '2.4'

services:
  app: &app
    build:
      context: .dockerdev
      dockerfile: Dockerfile
      args:
        RUBY_VERSION: '2.6.3'
        PG_MAJOR: '13'
        NODE_MAJOR: '11'
        YARN_VERSION: '1.13.0'
        BUNDLER_VERSION: '2.0.2'
    image: example-dev:1.0.0
    environment: &env
      NODE_ENV: ${NODE_ENV:-development}
      RAILS_ENV: ${RAILS_ENV:-development}
      YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache
    tmpfs:
      - /tmp

  backend: &backend
    <<: *app
    stdin_open: true
    tty: true
    volumes:
      - .:/app:cached
      - rails_cache:/app/tmp/cache
      - bundle:/usr/local/bundle
      - node_modules:/app/node_modules
      - packs:/app/public/packs
      - .dockerdev/.psqlrc:/root/.psqlrc:ro
    environment:
      <<: *env
      REDIS_URL: redis://redis:6379/
      DATABASE_URL: postgres://postgres:postgres@postgres:5432
      BOOTSNAP_CACHE_DIR: /usr/local/bundle/_bootsnap
      WEBPACKER_DEV_SERVER_HOST: webpacker
      WEB_CONCURRENCY: 1
      HISTFILE: /app/log/.bash_history
      PSQL_HISTFILE: /app/log/.psql_history
      EDITOR: vi
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  runner:
    <<: *backend
    command: /bin/bash
    ports:
      - '3000:3000'
      - '3002:3002'

  rails:
    <<: *backend
    command: bundle exec rails server -b 0.0.0.0
    ports:
      - '3000:3000'

  sidekiq:
    <<: *backend
    command: bundle exec sidekiq -C config/sidekiq.yml

  postgres:
    image: postgres:13
    volumes:
      - .psqlrc:/root/.psqlrc:ro
      - postgres:/var/lib/postgresql/data
      - ./log:/root/log:cached
    environment:
      PSQL_HISTFILE: /root/log/.psql_history
      POSTGRES_PASSWORD: postgres
    ports:
      - 5432
    healthcheck:
      test: pg_isready -U postgres -h 127.0.0.1
      interval: 5s

  redis:
    image: redis:3.2-alpine
    volumes:
      - redis:/data
    ports:
      - 6379
    healthcheck:
      test: redis-cli ping
      interval: 1s
      timeout: 3s
      retries: 30

  webpacker:
    <<: *app
    command: ./bin/webpack-dev-server
    ports:
      - '3035:3035'
    volumes:
      - .:/app:cached
      - bundle:/usr/local/bundle
      - node_modules:/app/node_modules
      - packs:/app/public/packs
    environment:
      <<: *env
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0

volumes:
  postgres:
  redis:
  bundle:
  node_modules:
  rails_cache:
  packs:

このdocker-compose.ymlでは8つのサービスを定義しています。サービスを8つも定義しているのを不思議に思うかもしれませんが、その一部は単に他と共有する設定を定義しているだけで(appbackendといった抽象サービス)、残りはアプリケーションコンテナを用いる特定のコマンド(runnerなど)のためのものです。

私たちはDocker Composeのバージョン2を意図的に使っています。開発目的にはバージョン2が適しています。詳しくはDockerのissueをご覧ください(#7593)。

このアプローチでは、アプリケーションをdocker-compose upで実行するのではなく、docker-compose up railsのように、実行したいサービスを常にピンポイントで指定するようにしています。development環境ではWebpackerやSidekiqなどを全部立ち上げる必要はめったにないので、合理的です。

それでは各サービスを詳しく見ていくことにしましょう。

🔗 app

appサービスの主な目的は、(上のDockerfileで定義した)アプリケーションコンテナの構築に必要な情報をすべて提供することです。

build:
  context: .dockerdev
  dockerfile: Dockerfile
  args:
    RUBY_VERSION: '2.6.3'
    PG_MAJOR: '13'
    NODE_MAJOR: '11'
    YARN_VERSION: '1.13.0'
    BUNDLER_VERSION: '2.0.2'

contextディレクトリは、Dockerのbuild contextを定義します。これはビルドプロセスで用いる一種のワーキングディレクトリであり、COPYコマンドなどで用いられます。また、イメージがビルドされるたびにパッケージ化されてDockerデーモンに送信されるので、イメージのサイズはできるだけ小さくしておくのが望まれます。

ログや一時ファイルによってプロジェクトディレクトリのサイズが非常に大きくなるとビルドが遅くなる可能性があります。.dockerignoreファイルでサイズを減らすか、.dockerdevのような小さなディレクトリを指定しましょう。

私たちの設定ではDockerfileへのパスを明示的に指定しています。理由は、私たちはDockerファイルをプロジェクトのルートディレクトリに配置するのではなく、.dockerdevという隠しディレクトリの中に他のすべてのDocker関連ファイルと一緒に配置しているからです。

前述したように、Dockerfileではargsを用いて依存関係の正確なバージョンを指定しています。

ここでひとつ注意すべきは、イメージにタグ付けする方法です。

image: example-dev:1.0.0

Dockerを開発に用いるメリットのひとつは、設定の変更を自動的にチーム全体で同期できることです。これは、ローカルイメージ(引数や、イメージが依存するファイルでもよい)を変更するたびにローカルイメージのバージョン番号を常にアップグレードしておきさえすれば可能です。逆に最悪なのは、ビルドタグにexample-dev:latestを使うことです。

イメージのバージョン番号が正しく管理されていれば、異なる2つの環境同士で余分な追加作業を一切行わずに済むようにもできます。たとえば、長期間実行するchore/upgrade-to-ruby-3ブランチで作業している最中に、いつでもmasterブランチに切り替えて古いイメージや古いRubyを利用できます。しかもリビルド不要で。


重要: docker-compose.yml内のイメージでlatestタグを使うのは最悪です。


次に、共通の環境変数を追加します。この環境変数は、RailsやWebpackerなど複数のサービスで共有されます。

environment: &env
  NODE_ENV: ${NODE_ENV:-development}
  RAILS_ENV: ${RAILS_ENV:-development}
  # 高速化のため、マウントしたボリュームにYarnキャッシュを保存
  YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache

ここでもいろんなことが行われていますが、1つ説明しておきたい点があります。

まずX=${X:-smth}という構文についてです。これは「コンテナ内の変数Xで使う値は、ホストマシンに環境変数Xがあればそれを使い、ない場合は指定の値を使う」という意味です。これによって、RAILS_ENV=test docker-compose up railsのようにコマンドで別の環境を指定してサービスを実行できるようになります。

なお、environmentのフィールドがリスト形式(- NODE_ENV=xxx)ではなく辞書形式(NODE_ENV: xxx)になっていることにご注意ください。辞書形式にすることで、共通設定を再利用できるようになります(後述)。

他にも、コンテナ内で/tmpフォルダにDockerのtmpfsマウントを用いるように指定することでスピードアップしています。

tmpfs:
  - /tmp

🔗 backend

いよいよ本記事で一番美味しい部分にたどり着きました。

このbackendサービスは、あらゆるRubyサービスで共有する振る舞いを定義します。

まずはvolumes:を見てみましょう。

volumes:
  - .:/app:cached
  - bundle:/usr/local/bundle
  - rails_cache:/app/tmp/cache
  - node_modules:/app/node_modules
  - packs:/app/public/packs
  - .dockerdev/.psqlrc:/root/.psqlrc:ro

volumes:リストの最初の項目「- .:/app:cached」では、現在のワーキングディレクトリ(つまりプロジェクトのルートディレクトリ)をコンテナ内の/appフォルダにマウントし、かつcached戦略を用いています。このcachedという修飾子は、MacOSでのDocker環境の効率を高めるうえで重要なポイントです。cachedについては別記事を書いていますので😉、本記事ではこれ以上は深堀りしません。詳しくはこちらの「公式ドキュメント」をご覧ください。

その次の行では、/bundleという名前のボリュームに/urs/local/bundleの内容を保存するようコンテナに指示しています(gemはデフォルトでここに保存されます)。私たちはこのようにして、gemのデータを永続化して複数の実行で使えるようにしています。docker-compose.ymlで定義されたすべてのボリュームは、docker-compose down --volumesを実行するまで持続します。

以下の3行も、「DockerがMacだと遅い」という呪いをお祓いするために書かれています。私たちは、生成されたファイルをすべてDockerボリュームに配置することで、ホストマシンでディスク操作が重くなるのを回避しています。

- rails_cache:/app/tmp/cache
- node_modules:/app/node_modules
- packs:/app/public/packs

ポイント: macOSでDockerを十分高速に動かすには、ソースファイルを:cachedでマウントし、かつ、生成されたコンテンツ(アセットやbundleなど)の保存にはボリュームを使うこと。


末尾の3行では、特定のpsql設定をコンテナに追加しています。私たちはほとんどの場合、コマンド履歴をアプリのlog/.psql_historyに保存することで永続化する必要があります。psqlをRubyのコンテナに追加している理由は、rails dbconsoleを実行するときに内部で使われるからです。

私たちが追加している.psqlrcファイルには、履歴ファイルを環境変数経由で指定できるようにするために以下の仕掛けが施されています。履歴ファイルへのパスをPSQL_HISTFILE環境変数で指定できるようにし、利用できない場合は$HOME/.psql_historyにフォールバックします。

\set HISTFILE `[[ -z $PSQL_HISTFILE ]] && echo $HOME/.psql_history || echo $PSQL_HISTFILE`

環境変数について説明します。

environment:
  <<: *env
  REDIS_URL: redis://redis:6379/
  DATABASE_URL: postgres://postgres:postgres@postgres:5432
  WEBPACKER_DEV_SERVER_HOST: webpacker
  BOOTSNAP_CACHE_DIR: /usr/local/bundle/_bootsnap
  HISTFILE: /app/log/.bash_history
  PSQL_HISTFILE: /app/log/.psql_history
  EDITOR: vi
  MALLOC_ARENA_MAX: 2
  WEB_CONCURRENCY: ${WEB_CONCURRENCY:-1}

冒頭の<<: *envで、共通の環境変数を「継承」しているのがポイントです。

DATABASE_URL変数、REDIS_URL変数、WEBPACKER_DEV_SERVER_HOST変数は、Rubyアプリケーションを別のサービスに接続しますDATABASE_URL変数はRailsのActive Recordで、WEBPACKER_DEV_SERVER_HOST変数はRailsのWebpackerでいつでもサポートされます。ライブラリによってはREDIS_URL変数もサポートします(Sidekiq)が、どのライブラリでもサポートされているとは限りません(たとえばAction Cableでは明示的に設定が必要です)。

私たちはbootsnapを用いてアプリケーションの読み込みを高速化しています。bootsnapのキャッシュはBudlerのデータと同じ場所に保存しています。理由は、このキャッシュに含まれている内容のほとんどがgemのデータだからです。つまり、たとえばRubyを別のバージョンにアップグレードする場合は、それらを一括廃棄するべきということです。

HISTFILE=/app/log/.bash_historyは、開発者のUXにとって重要な設定です。この設定によってbashの履歴が特定の場所に保管され、永続化されるようになります。

EDITOR=viは、たとえばrails credentials:editコマンドでcredentialファイルを管理するのに用います。

末尾の2つの設定であるMALLOC_ARENA_MAXWEB_CONCURRENCYは、Railsのメモリハンドリングをチェックしやすくするためのものです。

他にbackendサービスで説明すべきは以下の行だけです。

stdin_open: true
tty: true

この設定によって、サービスをインタラクティブ(TTYを提供するなどの対話的な操作)にできます。私たちの場合、たとえばRailsコンソールやBashをコンテナ内で実行するのに必要です。

これは、-itオプションを付けてDockerコンテナを実行するのと同じです。

🔗 webpacker

webpackerで言及しておきたいのはWEBPACKER_DEV_SERVER_HOST=0.0.0.0という設定だけです。これによって、Webpack dev serverに「外部から」アクセスできるようになります(デフォルトではlocalhostで実行されます)。

🔗 runner

このrunnerサービスの目的を説明するために、私がDockerを開発に用いるときの段取りについて説明させてください。

  • 私はDockerデーモンの起動で以下のようなカスタムdocker-startスクリプトを作って実行しています。
#!/bin/sh

if ! $(docker info > /dev/null 2>&1); then
  echo "Docker for Macを開いています..."
  open -a /Applications/Docker.app
  while ! docker system info > /dev/null 2>&1; do sleep 1; done
  echo "Docker準備OK!"
else
  echo "Dockerは実行中です"
fi
  • 次に、コンテナのシェルにログインするために、プロジェクトでdcr runnerを実行します(dcrdocker-compose runのエイリアス)。つまりdcr runnerは以下のエイリアスになります。
$ docker-compose run --rm runner
  • 後はこのコンテナの中でほとんどの作業を行います(テストやマイグレーションやrakeタスクなど何でも構いません)。

以上でおわかりのように、私は何かタスクを1つ実行する必要が生じるたびにいちいちコンテナを1つ立ち上げたりせず、いつも同じ設定でやっています。

つまり私は、なつかしのvagrant sshと同じ感覚でdcr runnerを使っているのです。

私がこれをshellと呼ばずにrunnerと呼んでいる理由はただひとつ、コンテナの中で任意のコマンドをrunするのにも使えるからです。

メモ: このサービスをrunnerと呼ぶかどうかは好みの問題であり、(デフォルトのcommand/bin/bash)は別としても)webサービスと比べて何ひとつ目新しい点はありません。つまり、docker-compose run runnerdocker-compose run web /bin/bashと完全に同じです(ただし短い😉)。

🔗 ヘルスチェック

Railsでdb:migrateなどのコマンドを実行するときには、DBが起動していてコネクションを受け付けられる状態にしておきたいものです。依存するサービスが起動するまで待つようにDocker Composeに指示したい場合は、healthcheckが使えます。

既にお気づきかと思いますが、depends_on定義に書かれているのは単なるサービスリストではありません。

backend:
  # ...
  depends_on:
    postgres:
      condition: service_healthy
    redis:
      condition: service_healthy

postgres:
  # ...
  healthcheck:
    test: pg_isready -U postgres -h 127.0.0.1
    interval: 5s

redis:
  # ...
  healthcheck:
    test: redis-cli ping
    interval: 1s
    timeout: 3s
    retries: 30

メモ: ヘルスチェックはDocker Composeファイルフォーマットv2.1以降でのみサポートされています。私たちがヘルスチェックを開発環境でしか使っていない理由はこれです。

🔗 おまけ: Evil Martians特製のdip.ymlについて

訳注

dipについては以下の記事もどうぞ。

docker-composeを便利にするツール「dip」を使ってみた

Docker Compose式のやり方がまだ難しいとお思いの方に、Dipというツールをご紹介します。これは開発者がスムーズなエクスペリエンスを得られるようにと、Evil Martiansのあるメンバーがこしらえたものです。

dip.ymlは、複数のcomposeファイルを使い分ける場合や、プラットフォームに依存する複数の設定を使い分ける場合に特に便利です。dip.ymlはそれらをまとめて、Dockerでの開発環境を管理する一般的なインターフェイスを提供できるからです。

dip.ymlについては別記事にて詳しく説明しようと思います。どうぞご期待ください!

追伸

本記事のtipsを共有してくれたSergey PonomarevMikhail Merkushinに感謝いたします🤘

元記事のトップ画像のクレジット: © NASA/JPL-Caltech, 2009

🔗 原文Changelog

1.1.3 (2021-03-30)
minimagic gemのライセンス問題緩和のためDockerfileを更新(#35
docker-compose設定の環境変数を辞書形式に変更(#6
1.1.2 (2021-02-26)
依存関係のバージョンを更新(#28
Aptfileにコメントを書けるようになった(#31
Dockerfile内のAptfileへのパスを修正(#33
1.1.1 (2020-09-15)
.dockerdevディレクトリをプロジェクトディレクトリではなくビルドコンテキストとして使用(#26
1.1.0 (2019-12-10)
Rubyのベースイメージをslimに変更
Rubyバージョン用のDebianリリースを明示的に指定し、busterにアップグレード
bundlerのパスを/bundleからbundler標準の/usr/local/bundleに変更
Docker Composeのファイル形式はv2.4を使用
postgresサービスとredisサービスにヘルスチェックを追加

訳注

以下のスライドも合わせて読むことで、より理解が進むと思います。

関連記事

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)

The post クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳) first appeared on TechRacho.

RubyでISO国名コード2文字を絵文字の国旗に変換する(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

regional indicator symbolやregional indicator characterは、仮訳の「地域指示記号」で統一しました。
また、一部のサンプルコードについては見やすさのためGistを使っています。

RubyでISO国名コード2文字を絵文字の国旗に変換する(翻訳)

アプリケーションで、国名の参照をISO 3166-1 alpha-2標準の2文字のコードとしてインラインで保存することがよくあります。たとえば「GB」は英国、「US」は米国を表すという具合です。

しかし絵文字でやりたい人たちがいるならば受けて立ちましょう。

def emoji_flag(country_code)
  cc = country_code.to_s.upcase
  return unless cc =~ /\A[A-Z]{2}\z/

  cc.codepoints.map { |c| (c + 127397).chr(Encoding::UTF_8) }.join
end

しくみ

最初の2行は「防御的コード」と呼ばれるもので、無関係な文字が入力されてもメソッドがおかしくならないようにします。

cc = country_code.to_s.upcase

上のコードは、入力文字を英大文字に揃えます。

return unless cc =~ /\A[A-Z]{2}\z/

上のコードは、英大文字2文字でない文字列が渡された場合にnilを返します(早期脱出)。

「メソッドが常に文字列を返すよう、nilではなく""を返すべき」という意見もおありかと思いますが、面倒でもnilを返す方がRubyらしいと言えます。

少々うまくやれた点を解説

すべての国にはアルファベット2文字の一意のコードが割り当てられています(一部の国には以前から割り当てられていますが)。

UNICODEには英大文字に対応する(Enclosed Alphanumeric Supplement)と呼ばれるブロックがあり、その中にRegional indicator symbol(地域指示記号)と呼ばれる特殊な大文字が26個連続で存在します。たとえば大文字のAには🇦という地域指示記号が対応しています。

UNICODEにあるこの地域指示記号を2つ並べると、OS上で国旗の絵文字がレンダリングされます。たとえば、画面に表示される🇪🇺という絵文字は、🇪🇺が連続したUTF-8文字列で構成されています。

Unicodeの文字テーブルには、通常の英大文字の他にこうした地域指示記号も含まれています。

UNICODE英大文字Aのコードポイントは65、それに対応する🇦のコードポイントは127,462です。そして、大文字(AZ)と、それに対応する地域指示記号(🇦🇿)のコードポイントの差は、どの大文字でも常に127,397です。この「マジックナンバー」こそが変換方法の鍵となります。

冒頭のコードの主な機能は、2文字の文字列を分割し、それぞれの文字をRubyの#codepointsメソッドでASCII(UTF-8)文字テーブルの数値表現に変換することです。そして各コードポイントに127,397を足し、この新しい参照をUTF-8エンコード文字に戻します。最後に2つの地域指示記号を#joinしてStringに戻します。

フランスを例に、これまでの手順をおさらいすると以下のようになるでしょう。

このコードは、RubyのUTF-8テキストレンダリングとモダンなOSを組み合わせると「2個の地域指示記号」ではなく国旗が表示されるという動作に依存しています。

UNICODE文字テーブルの構造がうまく考えられているおかげで、このメソッドはある程度寛容に動作します。以下のように未割り当ての国コードを渡すと、2つの地域指標記号をフォールバック表示します。

関連記事

漢字のようで漢字でないUnicodeの「康熙部首」と「CJK部首補助」

The post RubyでISO国名コード2文字を絵文字の国旗に変換する(翻訳) first appeared on TechRacho.

Rubyの技: 文字列操作で便利かつ高速なdelete_prefixとdelete_suffix(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rubyの技: 文字列操作で便利かつ高速なdelete_prefixとdelete_suffix(翻訳)

私がRubyを書くのが好きな理由のひとつは、プログラマーが幸せになれるように最適化されているからです。Rubyコミュニティは非常に読みやすいコードを重視しています。

他のエコシステムから来たプログラマーは、Rubyがまるで疑似コードのように見えることにショックを受けることがよくあります。Ruby標準ライブラリやActive Supportのような拡張機能のおかげで、Rubyではコードを自然な方法で書けます。

その好例として、Stringクラスのdelete_prefixメソッドとdelete_suffixメソッドをご紹介します。

使い方

delete_prefixは文字どおり、文字列の冒頭からサブ文字列を削除できます。

"BoringRails!".delete_prefix("Boring")
#=> "Rails!"

"#programming".delete_prefix("#")
#=> "programming"

"ISBN: 9780091929787".delete_prefix("ISBN: ")
#=> "9780091929787"

"mailto:test@example.com".delete_prefix("mailto:")
#=> "test@example.com"

github_url = "https://github.com/rails/rails"
repo_name = github_url.delete_prefix("https://github.com/")
#=> "rails/rails"

gsubなどの既存のメソッドと比べてみましょう。

github_url = "https://github.com/rails/rails"
repo_name = github_url.gsub("https://github.com/", "")
#=> "rails/rails"

gsubは正規表現を受け取ることも複数箇所の置き換えもできる柔軟なメソッドですが、delete_prefixほど自然な書き方ではありません。しかも、delete_prefixgsubよりも高速なのです。

文字列の末尾からサブ文字列を削除したい場合の選択肢は、chompまたはdelete_suffixです。

"report.csv".chomp(".csv")
#=> "report"

"report.csv".delete_suffix(".csv")
#=> "report"

"150 cm".delete_suffix("cm").to_i
#=> 150

スタイル上の観点では、chompというメソッド名はRubyの中では少々毛色が違います1。一方delete_suffixというメソッド名はdelete_prefixと対になっていることがはっきりわかります。なお、chompdelete_suffixのパフォーマンスはsubよりも高速です。

参考資料

関連記事

https://techracho.bpsinc.jp/hachi8833/2021_04_22/107386


  1. 訳注: Perlには以前からchompメソッドがあり、RubyのchompはおそらくPerlの影響でこのメソッド名になったと思われます。参考: Ruby - chompって何かの略ですか?|teratail 

The post Rubyの技: 文字列操作で便利かつ高速なdelete_prefixとdelete_suffix(翻訳) first appeared on TechRacho.

週刊Railsウォッチ(20210510前編)属性メソッドをキャッシュして最適化、Railsのガバナンスに関する声明、bundle install高速化ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストのChangelogのうち、セキュリティ修正以外のものから見繕いました。

🔗 stylesheet_link_tagextnameオプションが追加


つっつきボイス:「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"になるのか」「使っている人がいる以上欲しいオプションですね👍

参考: Getting started | Less.js

🔗 生成された属性メソッドをキャッシュおよび再利用する最適化


つっつきボイス:「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ワーカーのメモリを減らしてくれるいい高速化だと思います👍


同PRより


追いかけボイス:「同issueのコメントが興味深い: memory_profilerというgemはIMEMO領域の分は見てくれないのでこれで計測するとむしろ劣化したように見えるけど、heap-profilerを使うとちゃんとIMEMOを含んだallocated sizeが出るのでメモリが削減されたことが分かるそうです」

SamSaffron/memory_profiler - GitHub

Shopify/heap-profiler - GitHub

参考: Reducing Memory Usage in Ruby | Tenderlovemaking — IMEMOの解説あり

「heap-profilerのREADMEにmemory_profilerとの違いも解説してある:ObjectSpace.each_objectでは拾いきれないものがObjectSpace.dump_allだと取れるらしい(以下の記事にもObjectSpace.dump_allの解説あり↓)」

Rubyのヒープをビジュアル表示する(翻訳)

🔗 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でキャッシュエントリのシリアライザをカスタマイズ可能に


つっつきボイス:「2つのプルリクのうち上が本体で、下がRailsガイドの更新でした」「キャッシュのシリアライズにたしかデフォルトでMarshal.dumpを使っていますね」「シリアライザとデシリアライザはここではCoderっていう名前なのか: 単にシリアライズ・デシリアライズするだけならSerializerでしょうけど、gzipすると書かれているので圧縮・解凍も行うという意味でCoderなのかも」「なるほど、圧縮解凍もやってるんですね」

  • :coderオプションはキャッシュエントリのデフォルトシリアライズメカニズムをカスタムのものに置き換えられる。coderdumploadに応答する必要があり、カスタムコーダーを渡すと自動圧縮は無効になる。
    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の今後についての不安を解消するためにも改めて声明を出したという流れですね」

「経緯についてはdiscuss.rubyonrails.orgの書き込みの冒頭にひととおりまとまっているようです↓」「もう落ち着いたのかな?」「そのうち詳しいまとめ記事が出ると思うのでそちらに期待しましょう」

「今回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 VergeRuby Weeklyより)

🔗 Railsでビューコンポーネントのライブラリを構築する(Ruby Weeklyより)


つっつきボイス:「Railsのビューコンポーネントの記事を久しぶりに見たので取り上げてみました」「この記事で使われているStorybookというJavaScriptライブラリはときどき目にしますね↓」「iOSにもStorybookってあったかも」「そうそう、紛らわしい」

参考: Storybook: UI component explorer for frontend developers

「記事はステップバイステップで進めている感じ」「以下のgemでビューコンポーネントとStorybookをつないでいるそうです↓」

jonspalmer/view_component_storybook - GitHub

「ビューコンポーネントは、こういうふうに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側からは扱いやすいでしょうけど、フロントエンドエンジニアがどう思うかかな: この書き方にしないといけない理由をフロント側から聞かれたときに答えにくそう」「それですよね」

🔗 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ほか

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

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

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

The post 週刊Railsウォッチ(20210510前編)属性メソッドをキャッシュして最適化、Railsのガバナンスに関する声明、bundle install高速化ほか first appeared on TechRacho.

週刊Railsウォッチ(20210511後編)AWS Lambda関数ハンドラをDSLで書けるyake gem、VPC Peeringが同一AZ転送量無料化ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 yake: AWS Lambda関数ハンドラをrake風に書ける(Ruby Weeklyより)

amancevice/yake - GitHub


つっつきボイス:「いぇーく?」「なるほど、handler DSLに続けてコードを書くとLambda Functionのコードに展開してくれる感じですね↓」「おぉー」「API Gatewayを統合する形でも書けるらしい」

# 同リポジトリより
# ./lambda_function.rb
require "yake"

handler :lambda_handler do |event|
  # Your code here
end

# Handler signature: `lambda_function.lambda_handler`
# 同リポジトリより
# ./lambda_function.rb
require "yake/api"

header "content-type" => "application/json"

get "/fizz" do
  respond 200, { ok: true }.to_json
end

handler :lambda_handler do |event|
  route event
rescue => err
  respond 500, { message: err.message }.to_json
end

# Handler signature: `lambda_function.lambda_handler`

参考: AWS Lambda を Amazon API Gateway に使用する - AWS Lambda

「こんなふうにSinatra風にルーティングを書くこともできる↓: yakeを使うとデプロイも含めてLambdaやAPI Gatewayを簡単に使えるようになるんでしょうね」

# 同リポジトリより
delete "/…" do |event|
  # Handle 'DELETE /…' route key events
end

get "/…" do |event|
  # Handle 'GET /…' route key events
end

head "/…" do |event|
  # Handle 'HEAD /…' route key events
end

options "/…" do |event|
  # Handle 'OPTIONS /…' route key events
end

patch "/…" do |event|
  # Handle 'PATCH /…' route key events
end

post "/…" do |event|
  # Handle 'POST /…' route key events
end

put "/…" do |event|
  # Handle 'PUT /…' route key events
end

「デプロイ方法はどこだろう?」「API Gatewayの定義もRubyで書いているから、デプロイ機能はあるはずですね(しばらく探す)READMEの下の方に、version.rbのバージョンを更新してからbundle exec rake releaseすると書かれてた」「あ、ここですか」「シンプルに使えるのはよさそう👍

🔗 AWS LambdaでRuby 2.5サポートが間もなく終了

「Lambdaといえば、AWS LambdaでのRuby 2.5のサポート終了がアナウンスされましたね↓」「おぉ、ついに」「AWSからのお知らせメールで知りましたが、おそらくRuby 2.5レイヤを使っているユーザーにしかこのメール通知は来ていないと思います」

参考: ランタイムサポートポリシー - AWS Lambda

「サポート終了のフェーズ1が2021年7月30日から開始、フェーズ2が2021年8月30日から開始なのか」「もうすぐじゃないですか」「まだちょっと時間はあるかな」「なおRubyコミュニティでのCRuby 2.5のサポートは今年4月で既に終了しています↓」

参考: Ruby 2.5.9 Released

「AWS公式のLambdaランタイムのRubyは2.7なのかー」「そう、Lambda公式のRubyは最新安定版の3.0じゃないんですよ: なるべく長く使いたいので最新安定版で始めたいけど、Ruby 2.7だとその分期間が短くなるのが残念」


「ちなみにAWS Lambdaは昨年Rubyのマイナーバージョンを2.5.7から2.5.8に予告なしでアップグレードしたことがあって、めちゃくちゃ困りました」「えぇ!いきなりLambda関数動かなくなるのはつらい…」「gemをbundleしているコードだったんですが、Ruby本体のbundled gemであるjson gemのバージョンが上がったことでGemfile.lockの記述との差違が出てしまい、突然『何もしてないのに動かなくなった』という事態に陥りました」「おぉ…」「そのときにAWSのサポートに詳しくフィードバックしたおかげか、今回はちゃんと通知されました👍」「フィードバックは大事ですね」

🔗 ビットフィールドをRubyで扱う(Ruby Weeklyより)


つっつきボイス:「ビットフィールドといえば、C言語にあるけどあまり使ったことのない機能」「C書いたことないです…」「ビットフィールドはビットマスクの演算に使ったりしますね」

参考: ビット演算 - Wikipedia

「そうそう、こういう0oで始まるRubyの8進数リテラル、あまり見かけないけどありますね↓」

# 同記事より
irb> 0o644.to_s(2).rjust(12, "0").each_char.map { |b| b.to_i == 1 }
=> [false, false, false, true, true, false, true, false, false, true, false, false]

参考: リテラル (Ruby 3.0.0 リファレンスマニュアル)

「Rustだとこういう感じでビットフィールドを定義できるのね↓」

// 同記事より
#[macro_use]
extern crate bitflags;

bitflags! {
    struct Flags: u32 {
        const A = 0b00000001;
        const B = 0b00000010;
        const C = 0b00000100;
        const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
    }
}

fn main() {
    let e1 = Flags::A | Flags::C;
    let e2 = Flags::B | Flags::C;
    assert_eq!((e1 | e2), Flags::ABC);   // union
    assert_eq!((e1 & e2), Flags::C);     // intersection
    assert_eq!((e1 - e2), Flags::A);     // set difference
    assert_eq!(!e2, Flags::A);           // set complement
}

「C言語だとまさにこう書く↓」

/* 同記事より */
#define S_IFMT   0170000
#define S_IFLNK  0120000
#define S_IFREG  0100000
#define S_IFDIR  0040000
#define S_IFCHR  0020000
#define S_IFIFO  0010000
#define S_ISUID  0004000
#define S_ISGID  0002000
#define S_ISVTX  0001000

#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)

「へ〜、Sequel::Modelを使うとenumっぽく書けるのか↓: ビットフィールドとenumは本来別物なのが気になるけど、enumっぽく書きたい人にはいいかも」

# 同記事より
class MyModel < Sequel::Model
  plugin :bit_fields, :status_bits, [ :started, :finished, :reviewed ]
end
model = MyModel.create

model.started?        # => false
model.started = true
model.started?        # => true
model.status_bits     # => 1

model.finished?       # => false
model.finished = true
model.finished?       # => true
model.status_bits     # => 3

# let's find all the finished instances
MyModel.finished(true).all

# let's find all unfinished instances
MyModel.finished(false).all

# let's find all the started and the finished instances
MyModel.started.finished.all

jeremyevans/sequel - GitHub

「そしてActive Recordでもbitfieldsというgemを使うとビットフィールドが使えるそうですよ↓」「こんなgemがあったとは」「1個のビットフィールドに最大64ビットまで定義できるらしい」「64ビットを使い切る場面はあまり想像できないかも🤔

# 同記事より
class User < ActiveRecord::Base
  include Bitfields
  bitfield :my_bits, 1 => :seller, 2 => :insane, 4 => :sensible
end

grosser/bitfields - GitHub

「ビットフィールドは、シリアル通信やネイティブのネットワークプログラミングでパケット処理を書くような低レイヤの処理で必要になることがあります」「組み込み系の処理にもよくありますね」「頻度は年に数回ぐらいですが、使うときは使います」

参考: ビットフィールド - Wikipedia

🔗 リモートのzipファイルのファイルリストだけを取り出す(Ruby Weeklyより)

つっつきボイス:「そうそう、zipファイルは冒頭の部分をうまく取り出せば、全体をダウンロードしなくてもファイルリストを取れますね」「それをRubyでやってみた記事だそうです」「記事でzipのファイル構造も引用されてる↓: ファイルエントリはこういうふうにzipファイルの冒頭に集まってます」「お〜」

ZIP-64 Internal Layout
commons.wikimedia.orgより

「この種のテクニックは結構応用が効きます: S3に置かれている超巨大なzipファイルの中身を知りたいときとか」「やりたいときありますね」「mpegなどの動画・音声ファイルもメタ情報はたいていファイルの冒頭に置かれているので、zipと同じ考え方で処理できますね」

🔗 その他Ruby

つっつきボイス:「この記事を書いたBurdette Lamarさんは、最近英語版のRubyドキュメントを熱心に更新しているそうで、その更新履歴です」「ドキュメントが新しくなるのはありがたい!」「偉大な仕事👍

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

🔗 AWS VPC Peeringの価格変更


つっつきボイス:「BPSの社内Slackに貼っていただいた記事です」「そうそう、VPC Peeringの転送にかかる料金が、同じAZ(Availability Zone)ID内では無料になるというお知らせ」「おー!」

参考: VPC ピア機能とは - Amazon Virtual Private Cloud

「ただし、AZの名前ではなくAZの”ID”を使う点に注意が必要です」「IDですか?」「ap-northeast-1aのようなAZは名前で、apne1-az3のように表記するのがIDです: AZ名がアカウントに依存する論理名だとすると、AZ IDはアカウントと無関係に物理データセンターにマッピングされます」「あ、なるほど」「AZ IDが同じならVPC Peeringのデータ転送が無料になるということですね(VPC Peeringそのものの利用料はかかりますが)」「AWSアカウント間で巨大なデータ転送を行いたいときとかにありがたい🙏」「ちょうど最近VPC PeeringとTransit Gatewayを使い始めたところです」

参考: リソースの AZIDs - AWS Resource Access Manager

🔗 Transit Gateway

「ちなみにVPC Peeringは文字どおり他のAWSアカウントと一対一で接続しますが、Transit Gatewayは、あるAWSアカウントのVPCにTransit Gatewayを作ると他の複数のAWSアカウントから接続できる、ルーター的な接続サービスです」「そんなことができるんですね!」「VPC Peeringは3拠点ぐらいまでなら何とかやれますが、拠点が増えると接続がメッシュ状に増えてしまうので、接続拠点が4つ以上ぐらいになったらTransit Gatewayの方が便利」「ふむふむ」

参考: AWS Transit Gateway(VPC およびアカウント接続を簡単にスケール)| AWS

「Transit GatewayはAWSアカウントのひとつをVPCホストにして他のAWSアカウントのVPC同士をルーターのように接続するので、ルーティングの設定も必要ですし、接続するVPC同士のIPアドレスが衝突してはいけないんですよ」「なるほど」「IPアドレスが衝突するとVPCを作り直さないといけなくなって、配下にあるEC2インスタンスなどもすべて作り直しになるので大変です😢

🔗JavaScript

🔗 Romeプロジェクトが法人化(Publickeyより)


つっつきボイス:「BabelやLintのようなJSツールをRomeに集約するということみたいですね」「ツール事業の法人化は、責任や問い合わせや寄付先が一元化されるという意味ではありがたい」「法人を軌道に乗せられるかどうかが勝負でしょうね」


以下はつっつき後に見つけたツイートです。

🔗 その他JS

つっつきボイス:「JSのArray.prototypeで提案されているイミュータブルなメソッド名が過去形の動詞になっているのが面白いと思って拾ってみました」「なるほど、popped()とかreversed()とかたしかに過去形」「Arrayを返すイミュータブルなメソッドが欲しいのはわかる」「後はこのメソッド名が受け入れられるかどうかでしょうね」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210427後編)RactorでUDPサーバーを作る、JSONシリアライザalba gem、AppleのAirTagほか

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

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

Ruby Weekly

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ(20210511後編)AWS Lambda関数ハンドラをDSLで書けるyake gem、VPC Peeringが同一AZ転送量無料化ほか first appeared on TechRacho.

週刊Railsウォッチ(20210517前編)Bootstrap 5リリース、productionでSQLiteがwarning表示、rails-ujsの舞台裏ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストのChangelogを中心に見繕いました。

🔗 CSPヘッダーのサポートを2つ追加


つっつきボイス:「require-trusted-types-fortrusted-types、知らないヘッダーだ」「issue #42034を見ると、MDNでexperimentalとなってるそうです」「これらはサーバー側が返すヘッダなので、Railsが対応してくれることでexperimentalな最新の機能を利用できるということですね」

参考: CSP: require-trusted-types-for - HTTP | MDN
参考: CSP: trusted-types - HTTP | MDN

# actionpack/lib/action_dispatch/http/content_security_policy.rb#L108
    MAPPINGS = {
      self:           "'self'",
      unsafe_eval:    "'unsafe-eval'",
      unsafe_inline:  "'unsafe-inline'",
      none:           "'none'",
      http:           "http:",
      https:          "https:",
      data:           "data:",
      mediastream:    "mediastream:",
      blob:           "blob:",
      filesystem:     "filesystem:",
      report_sample:  "'report-sample'",
      strict_dynamic: "'strict-dynamic'",
+     script:           "'script'",
      ws:             "ws:",
      wss:            "wss:"
    }.freeze

    DIRECTIVES = {
      base_uri:        "base-uri",
      child_src:       "child-src",
      connect_src:     "connect-src",
      default_src:     "default-src",
      font_src:        "font-src",
      form_action:     "form-action",
      frame_ancestors: "frame-ancestors",
      frame_src:       "frame-src",
      img_src:         "img-src",
      manifest_src:    "manifest-src",
      media_src:       "media-src",
      object_src:      "object-src",
      prefetch_src:    "prefetch-src",
+     require_trusted_types_for:  "require-trusted-types-for",
      script_src:      "script-src",
      script_src_attr: "script-src-attr",
      script_src_elem: "script-src-elem",
      style_src:       "style-src",
      style_src_attr:  "style-src-attr",
      style_src_elem:  "style-src-elem",
      worker_src:      "worker-src"
      script_src:                 "script-src",
      script_src_attr:            "script-src-attr",
      script_src_elem:            "script-src-elem",
      style_src:                  "style-src",
      style_src_attr:             "style-src-attr",
      style_src_elem:             "style-src-elem",
 +    trusted_types:              "trusted-types",
      worker_src:                 "worker-src"
    }.freeze

参考: Rails アプリケーションのセキュリティ対策(CORS/CSP/HSTS)

🔗 SQLiteをproductionで使うとwarningを出力

SQLiteをproductionで実行するとwarningをログ出力する。
warning出力はconfig.active_record.sqlite3_production_warning=falseで無効にできる。
closeするissue: #34715
同PRより


つっつきボイス:「SQLiteをproductionで使うとwarningを出すようになったそうです」「アプリの性質によってはSQLiteをproductionで使う可能性もなきにしもあらずですし、warning出すほどでもない気はしますけどね」「#34715のコメントには『自分はproductionで問題なくSQLite使ってるよ』という書き込みもありました」「コンフィグでwarning出さないようにできるのか」

参考: SQLite Home Page

「現実問題としてSQLiteをproductionで使う可能性はかなり低いと思うので、多いのは『間違ってSQLiteを使ってしまう』ケースでしょうね」「それありそうですね」

参考: SQLiteと他のデータベースの違いを紹介します。「SQLiteの特徴を教えてください」 - Python学習チャンネル by PyQ

ですが、SQLiteができるロックはそれほど柔軟でないのが特徴です。ロックする範囲をデータベース全体でしか制御できない(テーブル単位、行単位でのロックができない)特徴を持っています。
また、SQLiteはローカルのファイルを直接データベースとして扱うので、複数のサーバーとネットワークごしに接続するのには不向きです。Webサーバーを複数台起動する場合などはMySQLやPostgreSQLなどを使うほうが良いでしょう。
blog.pyq.jpより

「データベースの内容がユーザーから一切変更できないようなアプリなら、そうしたデータの保存にSQLiteを使うこともありますし、Androidのゲームアプリなんかでは、SQLiteのファイルをソフトウェア本体にbundleしてSQL互換アクセスできるread onlyなマスタデータベースとして使うなんてこともありますよね」「なるほど」「特に最近のRailsはマルチプルデータベースに対応しているので、変更が生じないマスターデータについてはSQLiteに入れるという選択肢はありでしょうね」「たしかに、私のenno.jp↓というアプリもデータ更新は管理者しか行わないので本当はSQLiteにしたかったんですけど、HerokuではSQLiteを利用できない仕様になってました😢

日本語エラーチェックサイトenno.jpを作った理由

「SQLiteは読み出し専用で使っている分には決して遅くありませんし、SQLiteのようなファイルベースのDBにはひとついい点があるんですよ: データがすべてOSのファイルキャッシュに乗ってくれること」「あぁそうか!」「OSのファイルキャッシュに乗るということは、プロセスをまたいでもキャッシュが効くということで、これが大きい」「そうそう😋」「RailsのSQL キャッシュは基本的にアクション単位なのでアクションをまたぐと解放されてしまいますが、OSのファイルキャッシュはプロセスをまたいで効くのが強い」

参考: 1.8 SQLキャッシュ — Rails のキャッシュ機構 - Railsガイド

「個人的にはproductionでのSQLiteでwarningを出すまでもないような気はしますけど、誤用を防ぐという意味では理解できます」「そうですね、理解したうえでSQLiteをproductionで使う人は自分でwarningをオフにできるでしょうし、それと誤用の可能性を秤にかけた結果この改修になったんでしょうね」「自分も本番でSQLiteを使うときは、よ〜く考えてからにしています」


SQLiteをRailsのproduction環境で使う主な問題は、

  • これがデフォルトになっているので初心者が気づかないまま使ってしまう可能性があること
  • SQLiteがディスクベースのデータベースでありながら、それを過渡的なクラウドベースの世界で使ってしまうこと

「自分のRailsアプリでは明確な意図を持ってSQLiteをproductionで使います」勢が多数派とは思えない。
#34715コメント(by DHH)より大意

🔗 has_one through:disable_joinsオプションが追加


つっつきボイス:「マルチプルデータベース関連でこれとよく似た改修がこの間もありましたね(ウォッチ20210426): あのときはhas_namy through:が対象だったかな」「今回はhas_one through:でもdisable_joinsが使えるようになったんですね」

#41937has_many ... through:が追加されたように、has_one ... through:リレーションにdisable_joinsオプションを追加する。目的は、マルチプルデータベースのRailsアプリでテーブルが別のデータベースクラスタに置かれている場合にhas_oneリレーションシップを使えうようにすること。この場合、クラスタ間でのJOINは行えないのでJOINを回避する必要がある。
同PRより大意

🔗 PostgreSQLでtimestamp with time zoneをネイティブでサポート


つっつきボイス:「お、PostgreSQLでタイムゾーンありのタイムスタンプを使えるtimestamptzが追加されたんですね」「デフォルトはタイムゾーンなしの方なのか」

  • PostgreSQL: ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_typeを導入
    この設定は、マイグレーションやスキーマでdatetimeを呼び出す際に、Active Recordで使うネイティブ型を制御する。この設定は、設定済みのNATIVE_DATABASE_TYPESのいずれかに対応するシンボルを受け取る。デフォルトは:timestampつまり、マイグレーションで、t.datetimeを使うと「timestamp without time zone」カラムが作成される。「timestamp with time zone」を使うには、イニシャライザで:timestamptzに変更する。
    これを変更した場合には、bin/rails db:migrateを実行してschema.rbを再構築する必要がある。
    Alex Ghiculescu
    同Changelogより大意

「たしかに、これまではPostgreSQLのtimestampを使うとタイムゾーンなしになってたんですよ」「タイムゾーンありの方が好きかも」「timestamptzという名前が微妙に長いけど、タイムゾーンのありなしを選べるようになったのはいいですね👍

🔗 Active SupportのEnumerable#sumArray#sumを非推奨化


つっつきボイス:「Active SupportにあるEnumerable#sumArray#sumが非推奨になったのね」「RubyネイティブのEnumerable#sumArray#sumの方が断然高速なので今後はそちらを使って欲しいそうです」「お〜、Active Supportのメソッドが昇格してRuby本体に取り入れられることがときどきありますけど、これもそのパターンですね」「Rails側としては冥利に尽きますね🎉

Ruby(2.4以降)にはパフォーマンスが著しく向上したネイティブのsum実装が含まれている。Rails 7.1からはEnumerable#sumArray#sumを削除してRubyのネイティブ実装を使うことにする。
このコミットでは、適切な初期引数を持たない非数値引数の呼び出しに非推奨の警告を追加している。以下で議論しているとおり、RailsからActive Support版の機能が削除されたときにこれらの引数が必要になる。

#41669 (comment)

以下のような例でdeprecation warningが表示されるようになった。

%w[foo bar].sum
[[1, 2], [3, 4, 5]].sum

warningを回避するには以下のように呼び出す必要がある。

%w[foo bar].sum('')
[[1, 2], [3, 4, 5]].sum([])

Rails 7.1での非推奨化を準備するため、Rubyのネイティブ実装でTypeErrorになる[nil].sum == 0についても非推奨化する。
同PRより大意

🔗Rails

🔗 Bootstrap 5がリリース


つっつきボイス:「そうそう、Bootstrap 5出ましたね」「IEサポートもついに終了」「今年の春学期に担当する大学の授業では検索のしやすさからBootstrap 4を使いますが、来年度やるならBootstrap 5でもいいかもですね」

「jQueryも必須でなくなったのはいい👍」「jQueryの目玉機能だった$("要素名")みたいなセレクタも今やquerySelectorAll()でできるようになりましたよね」「う、jQueryなしでできるようになってたとは知らなかった😅」「querySelectorAll()は普通にどのメジャーなブラウザでも使えるはずですよ」

参考: Document.querySelectorAll() - Web API | MDN
参考: Can I use... Support tables for HTML5, CSS3, etcquerySelectorAll()

「jQueryが当時画期的だったのは、$("")を1個書くだけでCSSセレクタでDOM要素を簡単に選択できたこと」「あれは感動的なインターフェイスでしたね」「jQueryのeach()的なメソッドも当時のブラウザでは標準で使えなかった」「現在のJavaScriptがそうしたものを標準として取り入れるようになったので、jQueryは役目を終えつつあるかなと感じますね」「今の人があの当時のJavaScriptを触ったらあまりに別物でびっくりするかも」

参考: .each() | jQuery 1.9 日本語リファレンス | js STUDIO

🔗 Hanami 2.0.0 alpha2がリリース(Ruby Weeklyより)


つっつきボイス:「おぉ、Hanamiの2.0.0が出そう」「コアを完全に書き直したそうです」「まったく新しいアプリをSinatraで書くぐらいならHanamiで書いてもいいかなという気持ちがありますね」「Hanamiのルーティングにroda gemを使うという提案がだいぶ前にあったのを思い出したんですが、結局ルーターもHanami独自のものを新たに作ったそうです」「ルーターはWebアプリで必ず通るところなので重要ですね: ここが重いと全体が重くなってしまうので」「たしかに」

jeremyevans/roda - GitHub

🔗 RSpecのネスト


つっつきボイス:「RSpecはsubjectletを使うかどうかみたいな議論になりがちなところがあるので、今はMinitestの方が好きです」「わかります」「RSpecだとネステッドなcontextを書きすぎてしまうんですよね」

「RSpecで書かれたテストが複雑になりすぎると、RSpecのテストに対するテストが必要なんじゃないかとすら思えるときもあります」「RSpecで頑張って抽象度を高めるほどそうなりがちかも」「テストコードに不安を抱えるぐらいならMinitestでベタに書きたい派」「RSpecはテストをきれいに書きたくなるところがあるんですけど、本来やりたいのはアプリケーションコードが正しく動作していることを確認することだという気持ちが強まって、今はMinitestで書いてます」

参考: Rails テスティングガイド - Railsガイド

RSpecえかきうた

🔗 Rails UJSの舞台裏(Ruby Weeklyより)


つっつきボイス:「RailsのUJS(Unobtrusive JavaScript: 控えめなJavaScript)についてかなり詳しく解説しているようです」

参考: Rails で JavaScript を使用する - Railsガイド

「RailsでAjaxコードを書くようになるとUJSの理解が重要になってきますね: この記事のようにremote: trueから追いかけ始めて、data-method属性のようなdata-系属性をRailsで書けるのはなぜかを調べたりするようになりますよ」「ふむふむ」

# 同記事より
link_to "Example", "#", remote: true
=> "<a href='#' data-remote>Example</a>"
# 同記事より
Rails.handleMethod = (e) ->
  link = this
  # gets the method provided
  method = link.getAttribute('data-method')
  return unless method
  ...

参考: Rails学習者にrails-ujsの動作説明したら感動された話 - INODEVLOG

「rails-ujsのコードはそんなに大きくないはずですし、jQueryがRailsから外されたことで以前よりもシンプルになっていて読みやすいので、一度読んで見ることをおすすめします🎓」「お〜!」


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210511後編)AWS Lambda関数ハンドラをDSLで書けるyake gem、VPC Peeringが同一AZ転送量無料化ほか

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

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

Rails公式ニュース

Ruby on Rails Discussions

Ruby Weekly

StatusCode Weekly

statuscode_weekly_banner

The post 週刊Railsウォッチ(20210517前編)Bootstrap 5リリース、productionでSQLiteがwarning表示、rails-ujsの舞台裏ほか first appeared on TechRacho.

週刊Railsウォッチ(20210518後編)RubyのGCを深掘りする、Psych gemのbreaking change、11月のRubyConf 2021ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 RubyのGCを深掘り


つっつきボイス:「RubyのGC(Garbage Collection)をDeep Dive、いいですね👍」「ちょうど先週のウォッチで追加コメントしたObjectSpaceIMEMOなどもRubyのメモリ管理やGCと関連しますね(ウォッチ20210510)」「あ、なるほど」「図が整ってて見やすいですね」「こういう記事は図が大事」

参考: ガベージコレクション - Wikipedia


同記事より

「ヒープが断片化すると、空きヒープ容量が同じでも新しいオブジェクトが入り切らないことがある↓: これはRubyの例ですが、OSでもまったく同じことが起きます」


同記事より

「このような言語のGCとかメモリ管理を勉強するときは、先に以下のような書籍でOSの基本的なメモリ管理を学んでからにするとよりわかりやすいと思います」「たしかに!」

「そもそもCRubyの実行系はVM(Virtual Machine)で、VMもOSのようなメモリ管理を行っているので、理解のためにはOSから学ぶ方がむしろ近道でしょうね: たとえばRubyの内部データアラインメントにはOSやCPUアーキテクチャのアラインメントも関連しているとか」「なるほど」「そういうふうにRubyのメモリ管理を理解するうえでOSのメモリ管理の前提知識が必要になってくる場面も出てきますので、OSのメモリ管理を理解してからこうした記事を読むとはかどると思います👍

参考: 第5章 ガーベージコレクション — 『Rubyソースコード完全解説』サポートページ

🔗 RuboCopがRuby 3.1のサポート対応開始

RuboCop AST 1.5はRuby 3.1をサポートするようになった#182
本PRではTargetRubyVersion 3.1をサポートする。
同PRより大意


つっつきボイス:「そうか、もう7か月後にはRuby 3.1が出るのか」「そう思うと近いですね」

🔗 Psych gemにbreaking change

ruby/psych - GitHub


つっつきボイス:「ruby-jp Slackで見かけたトピックです」「PsychというYAMLパーサーgemにあるloadメソッドが正しく使われないと脆弱になる可能性があるので、デフォルトでsafe_loadを実行するようにしようという提案か」「この変更がbreaking changesになるかもだそうです」「今まで読み込めたものが読み込めなくなるならその可能性はあるでしょうね」

「RailsでもYAMLローダーにPsychが使われていますね」「PsychはRubyの”default gem”に含まれていたと思います」

「ところでPsychっていうgemは前からちらほら見かけるんですが、何て読むんでしょう?」「あ、手元の辞書に載ってました↓」「サイク!」「サイケデリックとかサイキックとかに通じるんですね」

psych saɪk【名】《略式》=→psychology,
*【動】|他|《米略式》 …を精神分析する、…を正しい精神状態にする、…の精神を安定させる


Psych.loadは、信頼できないデータに対して使うと安全ではない。Psych.loadを誤って信頼できないデータに対して使い、ある種のセキュリティ脆弱性が生じているアプリケーションがあまりに多い。
本コミットはPsych.loadがデフォルトでsafe_loadを使うよう変更する。信頼できるデータをパースしたい場合はPsych.unsafe_loadを使える。
Psych.loadYAML.loadはRCE(Remote Code Execution)にさらされやすい。load関数そのものが問題なのではなく、システム内の他のオブジェクトを悪用してRCEを実行するのに使われてしまう可能性があることが問題。このブログ記事では、YAMLをRubyGemsと組み合わせてRCEに昇格させる方法が詳しく説明されている。信頼できないソースからYAMLを読み込もうとした場合、悪意のあるユーザーがこの問題に乗じて対象のシステムで任意のコードを実行できる可能性がある。

Psychでは、こうした攻撃に利用できないsafe_loadというメソッドも提供している。ここでの提案は、loadを実行したときにデフォルトでsafe_loadを実行するよう変更すること。当然ながら、safe_loadloadよりもずっと制約が大きいので、この変更によって既存のコードが動かなくなる可能性もある。

また、YAMLのload関数はRubyGems以外にもシステム内の他のオブジェクトで利用可能で、RCEに昇格する可能性があることについても言及しておく価値があるだろう。また、任意のオブジェクト読み込みに使えるもの(Marshal.loadなど)も同じように悪用される可能性があることも言及しておきたい。したがって、RubyGemsを変更しても別のシリアライズ方式(Marshalなど)に変更しても、この穴は塞げない。
同PRより大意


つっつきの後で確認してみました。

$ ruby -v
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
$ gem list
...
pstore (default: 0.1.1)
psych (default: 3.3.0)
public_suffix (4.0.6)
...

上のプルリクがマージされた後だったので、gem update psychすると4.0.0にアップデートされました。

$ gem list
...
pstore (default: 0.1.1)
psych (4.0.0, default: 3.3.0)
public_suffix (4.0.6)
...

参考: bundled gem と default gem の違い - @znz blog

🔗 Rubyとボロノイ分割でアニメーション(Ruby Weeklyより)

mike-bourgeous/mb-geometry - GitHub


つっつきボイス:「ボロノイパーティションって初めて見ました」「グラフが石鹸の泡みたいに動いてる」「配置されたさまざまな点を基準にして領域を分けるアルゴリズムらしい🤔」「それをRubyでやってみたという記事なんですね」「これは楽しそう」

参考: ボロノイ図 - Wikipedia


以下はつっつき後に拾った動画です。

🔗 11月のRubyConf 2021


つっつきボイス:「今年の11/8〜11/10にコロラド州デンバーで開催が予定されているRubyConf 2021のサイトを見ると、今回はハイブリッド方式で開催するとありました」「お〜、現地でも開催されるのかな?」「詳しくはわかりませんが、対面式とバーチャルの二本立てだそうです」「今後のカンファレンスはこういうハイブリッド形式が主流になるかもしれませんね」「パンデミックも国によっては収束に向かっているところもありますし、ワクチンパスポートを持っていれば現地参加できるみたいなふうになるかも」

Q: カンファレンスが対面式とバーチャルのハイブリッドとのことですが、もう少し詳しくお願いします。
A: 詳しくは今後数か月のうちに詰めていく予定です。現時点では、対面式のカンファレンスに戻る方向で進んでいます。既にコロラド州のコンベンションセンターでは対面式のイベントが開催されていますし、米国でCOVIDワクチンが入手しやすくなったこともあり、この傾向はさらに強まると思われます。パンデミックに備えて、CDCとコロラド州保健局の安全ガイドラインに沿って、対面式イベントの形式を調整することになります。
私たちはこれまで2つのカンファレンスをバーチャルで開催してきましたが、今年のRubyConfでは両カンファレンスでうまくいった部分を統合する予定です。コミュニティメンバーの多くがコロラドに来ないことが予想されるため、対面式のイベントと並行して、できる限り最高のバーチャル体験を提供したいと考えています。
FAQより大意

参考: 英「ワクチンパスポート」 イベント会場で実証実験|テレ朝news-テレビ朝日のニュースサイト

その他Ruby

つっつきボイス:「このRuby-dev office hourというイベントを今になってruby-jp Slackで知りました」「そうそう、開発者同士が気軽に話せる場を設けようという感じで、雑談を交えて毎週開催しているらしいですよ」

つっつき後の2021/5/17の回では上述のPsychの件も話題になっていますね。


🔗DB

🔗 OracleのHeatWave


つっつきボイス:「HeatWaveはOracleのクラウドでMySQLを高速化するエンジンだそうです」「図を見た感じでは、MySQLサーバー内からプラグイン経由でHeatWaveクラスタにアクセスするようですね↓」


Understanding MySQL’s New Heatwave | Severalninesより

参考: HeatWave | オラクル | Oracle 日本

「こういうふうにセカンダリエンジンとしてHeatWave(RAPID)を指定して、SECONDARY_LOADでテーブルを読み込むのか: クエリ呼び出しではHeatWaveを特に意識しないで実行できるらしい」

# severalnines.comより
mysql> CREATE TABLE orders (id INT) SECONDARY_ENGINE = RAPID;
or
mysql> ALTER TABLE orders SECONDARY_ENGINE = RAPID;
mysql> ALTER TABLE orders SECONDARY_LOAD;

「見たところ、投機的実行っぽく両方にやらせて結果が先に返ってきた方を使うか、あるいはクエリオプティマイザが判断していずれかにクエリを投げるのかなと想像してみました🤔」「ふむふむ」

参考: 投機的実行とは - IT用語辞典 e-Words

「そういえばHeatWaveはcolumnar(列指向)なエンジンと書かれていました」「たとえばカラムを絞ってGROUP BYするような処理は、行指向DBよりも列指向DBの方が圧倒的に高速になりますね」「なるほど」

参考: 列指向データベース管理システム - Wikipedia

「こういうふうに行指向なDBと列指向なDBを両方使うことのメリットは、クエリを変更せずに高速化できることでしょうね」「たしかに」「単に自分が欲しいデータを取得するクエリを書ける難易度と、そうした欲しいデータを十分高速に最適化して取得するクエリを書ける難易度の差は結構大きいので、ある程度の高速化の部分をクエリ自体は変更せずにDBMS側で対応してくれるというのはデータ処理を頻繁に行う人たちにとってはありがたいと思います」

「カラム指向データベースはAmazon Redshiftなども含めて以前からあって、行指向だと遅いクエリがカラム指向だとものすごく高速になる(あるいはその逆)ことも以前から知られていますが、計算資源を潤沢に使えるという前提条件を付けて良いなら、HeatWaveのように両方を使うというのはなかなか理にかなっているかもしれませんね」

参考: 列指向ストレージ - Amazon Redshift


「1時間あたり4万円超えという値段にびっくりしました」「エンタープライズ方面などならその価格で買うところはあると思いますよ: 極端に言えばクエリ最適化のためにプログラマーを雇わなくても高速化できますし、結果整合性も保証されるし、デプロイも短期間で済む」「たしかに」「結局人間が一番値段が高い」

「クエリを書き換えるタイプの最適化だとテストを全面的にやり直さないといけなくなったりしますが、ミドルウェア対応だけで高速化できるならそれをしないで済みますね」「なるほど」「ちなみにRedshiftはPostgreSQLベースになっていて、基本的にはPostgreSQLクエリを変えずに使える: それと同じような感じで、HeatWaveもMySQLのクエリを基本的に変えずに高速化できるのはエンタープライズ方面にアピールしそうですね」

「ただしRedshiftのクエリはPostgreSQLと100%互換ではなく制約もあります↓: HeatWaveにももしかするとそうした制約があるかもしれませんね」

参考: Amazon Redshift and PostgreSQL - Amazon Redshift

🔗JavaScript

🔗 zx: JavaScriptでシェルスクリプトを書く

google/zx - GitHub


つっつきボイス:「はてブでバズっていたのを見つけました: JavaScriptでシェルスクリプトを書けて、async/awaitやPromiseなども使えるとか」「Rubyにもバッククォート``でシェルのコマンドを実行できる構文がありますけど、それをJavaScriptで書けるようにした感じかな」

参考: コマンド出力 — リテラル (Ruby 3.0.0 リファレンスマニュアル)

#!/usr/bin/env zx

await $`cat package.json | grep name`

let branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`

await Promise.all([
  $`sleep 1; echo 1`,
  $`sleep 2; echo 2`,
  $`sleep 3; echo 3`,
])

let name = 'foo bar'
await $`mkdir /tmp/${name}`

「バッククォート構文でないと取れないデータもありますよね」「たとえばデータサイエンス系のツールやユニケージツールの出力をJavaScriptで組み立てて使いたい人向けかも」

参考: ユニケージ開発手法 - Wikipedia

「自分はRubyがプライマリ言語なのでzxはたぶん使わないと思いますが、JSがプライマリ言語の人にはこういうツールが便利でしょうね👍」「たしかにプライマリ言語は人によって違いますよね: この言語なら頑張れば必ずビジネスロジックを書けると確信できる言語」「そうそう」

🔗 その他JS


つっつきボイス:「短い記事ですが、ちょっとびっくりしました」「JavaScriptの配列にlengthというセッターがあるとはね〜」「面白いしわかるといえばわかりますけど、一瞬直感に反しているような気が…」「直感に反する書き方が流行ると後で読みにくくなったりすることもありますね」「たとえばTypeScriptからトランスパイルした結果にlength = 0が入るならいいけど、業務コードで直接書くのはためらうかも」

// 同記事より
const a = ['hoge', 'fuga']
console.log(a.length) // => 2

a.length = 0
console.log({a}) // => { a: [] }

🔗言語/ツール/OS/CPU

🔗 ミニLED


つっつきボイス:「次のMacBook ProのCPUはM2かM1Xか」「ここで言っているミニLEDは今度のiPadで採用されると言われているものですね↓: バックライトのLEDを領域ごとに分割してオンオフすることで消費電力を減らせる」「なるほど、黒い部分が液晶で遮られるんじゃなくて本当にその領域のバックライトをオフにするんですね」

参考: 液晶&有機ELに続く!「ミニLED」と「マイクロLED」って何? - 価格.comマガジン

いっぽうのミニLEDは、マイクロLEDと似て非なるもの。現在のところ、ミニLEDには明確な定義や規格がないのだが、おおむね液晶テレビを構成する技術の一部で、従来のバックライトを分割してエリアごとに輝度を制御する「局所輝度制御」あるいは「ローカルディミング」と呼ばれる技術の延長線上にあるものだ。バックライトの分割をより細かくすることで、画柄に合わせてよりきめ細やかな明るさ調整が行え、コントラストアップを狙える。
kakakumag.comより


「ちなみに手持ちのiPadがいい加減古くなったので新しいiPad注文しました: 届くのは7月ですけど」「いいな〜」「私もKindle Fireの昨年モデルを中古で見かけて衝動買いしちゃいました」

「そういえばリモートワークするようになって以来タブレットの利用頻度がとても上がりましたよ」「私はむしろ以前よりスマホ触るようになりましたけど」「タブレットはすごく使うようになって、逆にノートPCとスマホをほぼまったく触らなくなった」「そんなに!」


後編は以上です。

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

週刊Railsウォッチ(20210517前編)Bootstrap 5リリース、productionでSQLiteがwarning表示、rails-ujsの舞台裏ほか

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

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

Ruby Weekly

The post 週刊Railsウォッチ(20210518後編)RubyのGCを深掘りする、Psych gemのbreaking change、11月のRubyConf 2021ほか first appeared on TechRacho.


週刊Railsウォッチ(20210524前編)Active Supportの知られてなさそうな機能5つ、RSpecの歴史、書籍『Practicing Rails』ほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストのChangelogを中心に見繕いました。

🔗 無効なセッションストアへの書き込みでエラーが出力されるようになった

従来はconfig.session_store :disabledを設定しても、リクエスト処理が終了する時点で単にセッションハッシュを捨てるだけだった。
書き込み時に明示的に失敗させることで、バグの早期発見に役立つ。
読み出しは引き続き許可する。
不明な点がそこそこあるのでとりあえずこのプルリクをドラフトとしてオープンしている。
同PRより大意


つっつきボイス:「config.session_store :disabledという設定がRailsにあったとは知らなかった」「セッションを使わない設定にしたときに従来だと書き込んでも警告なしで動いていたのか」「warningでもよさそうかなと思いましたけど、明示的にエラーを出すように変えたんですね」「この改修はbreaking changeだけど、これを踏む人はめったにいなさそう」

「Railsでセッションストアを無効にすることってあるんでしょうか?」「たとえば、静的なコンテンツを一方的に配信するだけのCMSサイトを構築する場合とかならセッションストアは不要になるので、無効にすることはあると思いますよ」「それもそうですね」「あるいはCookieだけで十分やれる状況なら、セッションストアをオフにする方がリソースは節約できるでしょうね」

「どちらもやったことはありませんけど😆」「Railsでセッションストアをオフにするなら、Rails以外の静的サイトジェネレータなどで作る方がよさそうですよね」「Railsでセッションストアをオフにできる機能があることがわかったのは収穫」

🔗 Active JobでRangeシリアライザを追加

# activejob/lib/active_job/serializers/range_serializer.rb
+# frozen_string_literal: true
+
+module ActiveJob
+  module Serializers
+    class RangeSerializer < ObjectSerializer
+     KEYS = %w[begin end exclude_end].freeze
+
+     def serialize(range)
+       args = Arguments.serialize([range.begin, range.end, range.exclude_end?])
+       hash = KEYS.zip(args).to_h
+       super(hash)
+     end
+
+     def deserialize(hash)
+       args = Arguments.deserialize(hash.values_at(*KEYS))
+       Range.new(*args)
+     end
+
+     private
+       def klass
+         ::Range
+       end
+    end
+  end
+end

つっつきボイス:「お、Active Jobに渡す引数でRangeオブジェクトもシリアライズできるようになったんですね」

「この改修の意味はこういうことだと思います: まずActive Jobはジョブにenqueueするプロセスとジョブを処理するプロセスが分かれますよね」「はい」「そのため、ジョブのパラメータをシリアライズしていったん何らかのデータベースに保存しておく必要があるわけです」「ふむふむ」

「Active Jobを使ってコードを書いているとわかると思いますけど、Active Jobの引数ではオブジェクトそのものではなくオブジェクトのid(DBのid)を渡すのが普通です: そしてその後Workerがジョブを処理するときに改めてfindするなどして取り直します」「ジョブを投入するRailsサーバーとジョブを処理するWorkerでプロセスが違っているから取り直さないといけないんですね」

「そうした理由があるのでジョブの引数はシリアライズできないといけないんですが、今回の改修ではRangeオブジェクトを渡したときもシリアライズできるようになったということですね」「つまり今までRangeはシリアライズしてくれなかったということか」「自分でシリアライズすることはできそうですが、これまではサポートしてなかったんでしょうね」

「テストコードを見ると.....記法をサポートするようになってる↓」「ドキュメントにも自動でシリアライズされるオブジェクトにRangeが追加されていますね」「何度か話題にしたglobalidも含まれている(ウォッチ20181203)」

# activejob/test/cases/argument_serialization_test.rb#L21
  [ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
    "a", true, false, BigDecimal(5),
    :a,
    1.day,
    Date.new(2001, 2, 3),
    Time.new(2002, 10, 31, 2, 2, 2.123456789r, "+02:00"),
    DateTime.new(2001, 2, 3, 4, 5, 6.123456r, "+03:00"),
    ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, "59.123456789".to_r), ActiveSupport::TimeZone["UTC"]),
    [ 1, "a" ],
    { "a" => 1 },
    ModuleArgument,
    ModuleArgument::ClassArgument,
-   ClassArgument
+   ClassArgument,
+   1..,
+   1...,
+   1..5,
+   1...5,
+   Date.new(2001, 2, 3)..,
+   Time.new(2002, 10, 31, 2, 2, 2.123456789r, "+02:00")..,
+   DateTime.new(2001, 2, 3, 4, 5, 6.123456r, "+03:00")..,
+   ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, "59.123456789".to_r), ActiveSupport::TimeZone["UTC"])..,
  ].each do |arg|

参考: class Range (Ruby 3.0.0 リファレンスマニュアル)

🔗 Psych 4でのbreaking changesに対応

Psych 5のloadがデフォルトでsafe_loadになった: #487
そのため、エイリアスを明示的に許可する必要がある。
実際、Ruby 3.0でもload(aliases: true)を受け付けないPsychが同梱されている。
同PRより大意


つっつきボイス:「先週取り上げたPsych gemのbreaking changesがRailsにも来たそうです(ウォッチ20210518)」「先週出られなかったんですけどPsychって何でしたっけ」「yamlのパーサーですね」「修正はYAML.load()でエイリアスを有効にしたのか」

# activesupport/lib/active_support/configuration_file.rb#L21
    def parse(context: nil, **options)
-     YAML.load(render(context), **options) || {}
+     source = render(context)
+     begin
+       YAML.load(source, aliases: true, **options) || {}
+     rescue ArgumentError
+       YAML.load(source, **options) || {}
+     end
    rescue Psych::SyntaxError => error
      raise "YAML syntax error occurred while parsing #{@content_path}. " \
            "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
            "Error: #{error.message}"
    end

「#42257のテスト更新でもunsafe_loadloadの使い分けに対応している↓」「これは対応しないわけにいかないでしょうね」

# actionpack/test/controller/parameters/serialization_test.rb#L24
  test "yaml deserialization" do
-   params = ActionController::Parameters.new(key: :value)
+   roundtripped = YAML.load(YAML.dump(params))
+   payload = YAML.dump(params)
    roundtripped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)

    assert_equal params, roundtripped
    assert_not_predicate roundtripped, :permitted?
  end

ruby/psych - GitHub

「そういえば先週のhackmd.ioのRuby-dev office hour↓でもPsychが話題になっていました」「変更前にChangelogやアナウンスがあってもいいのにという気持ちはわかる」「Railsの場合はbreaking changesの前にdeprecationによる猶予期間を置きますけど、Railsの外のgemなのでリリースポリシーがRailsと違うのは仕方ないかも」

参考: Ruby-dev office hour - HackMD

🔗 ActiveRecord::Base.loggerclass_attributeになった


つっつきボイス:「mattr_accessorからclass_attributeに変更されたのか↓」「ロガーが速くなったんですね」「プルリクにも書かれているようにここがホットスポット(実行の頻度が高いコード)なのは確かなので、これが速くなるのはいいことだと思います👍」

# activerecord/lib/active_record/core.rb#L20
-     mattr_accessor :logger, instance_writer: false
+     class_attribute :logger, instance_writer: false

cattr_accessorが依存しているクラス変数は、長大なancestorチェインでのパフォーマンスがよくない(詳しくは#17763を参照)。
ActiveRecord::Baseclass_attributeはそれよりも7倍程度高速。

Calculating -------------------------------------
              logger      1.700M (± 0.9%) i/s -      8.667M in   5.097595s
             clogger     11.556M (± 0.9%) i/s -     58.806M in   5.089282s

Comparison:
             clogger: 11555754.2 i/s
              logger:  1700280.4 i/s - 6.80x  (± 0.00) slower

高速な理由はActiveRecord::Base.ancestors.size == 62による。
ActiveRecord::Baseのその他のcattrの利用についても考慮すべきだが、さしあたってloggerが最大のホットスポットなので、ここから議論を始めるのがよさそう。
同PRより大意

Rails: クラスレベルの3つのアクセサを比較する(翻訳)

🔗 NULLS FIRSTをどのデータベースでも使えるnulls_first()が追加


つっつきボイス:「NULLS FIRSTは、SQLを長くやっている人ならご存知の機能だと思います: nullableなカラムをソートしたときに、NULLが冒頭に来るか末尾に来るかを指定する」「そういえばそんな機能ありましたね」

「並び順でのNULLの位置を変更する機能はプルリクにも書かれているようにDBMS依存なので、DBMSによって使えるものとそうでないものがあります」「そうでした」「もっともSQLの概念上は、本来NULLはどの値でもないものなので、NULLを含むカラムをORDER BYするのは無理があるとは思いますけどね」「たしかに」「DBMSによってはNULLがあるカラムをORDER BYすると遅くなるものもあったと思いますし、まずはORDER BYするカラムがnullableにならないようにしておくことでしょうね」

「プルリクによると、ANSI SQLではASC NULLS FIRSTについてはサポートされているそうですね↓」「へ〜!」「MySQLだけFIRST固定でLASTを選べないのか…頑張って欲しいです」「DBMSごとにこういう微妙な差があることは知っておくべきですね」

ほとんどのデータベースではテーブルを並べ替えるとNULL値が冒頭に来て、その他の値がそれに続く。PostgreSQLではNULLS LASTが使える。
ありがたいことに、ANSI SQLにはNULLSをソート順の冒頭に置くか末尾に置くかをデータベースで指定できるオプションがある。

    ORDER BY column ASC NULLS FIRST

MS SQL、SQLite、Oracle、PostgreSQLは上の構文をサポートしているが、残念ながらMySQLはこの構文をサポートしていない。

変更前:
* PostgreSQL: .nulls_first().nulls_last()も動く
* その他のDB: どちらの場合もエラーになる

変更後:
* PostgreSQL: どちらも動く
* MySQL: .nulls_first()は動くが.nulls_last()はランタイムエラーになる
* その他のDB どちらも動く

PostgreSQL向けの機能は#38131で導入された。このプルリクは他のDBにもこの機能を追加する。
同PRより大意

🔗Rails

🔗 active_period: 時間や期間のサポートを強化(Ruby Weeklyより)

billaul/active_period - GitHub


つっつきボイス:「Period.todayPeriod.year('01/01/2021')みたいな書き方ができるgemだそうです」「こういうのは普通に自分で書くこともできるでしょうし、RailsのActiveRecord::Durationがそもそも優秀なので、自分がこのgemを入れることはあまりなさそうかも」

参考: ActiveSupport::Duration

# The FreePeriod from 01/01/2021 to 01/02/2021 has 5 weeks
Period.new('01/01/2021'...'01/02/2021').weeks.count # 5

# The StandardPeriod::Month for 01/01/2021 has 4 weeks
Period.month('01/01/2021').weeks.count # 4

# How many day in the current quarter
Period.this_quarter.days.count

# Get all the quarters overlapping a Period of time
Period.new(Time.now..2.month.from_now).quarters.to_a

「上のquarter(四半期)みたいなのはあってもいいかもと一瞬思いましたけど、四半期の概念は国や企業ごとに異なるのでたぶん統一的にやりづらいと思うんですよ」「言われてみれば、第1週みたいな”週”の概念も会社ごとに違ったりしますよね: 第1四半期の第一週はどの週なのか、とか」「たしかに」「週を日曜始まりにするか月曜始まりにするかとかも考え始めると大変そう」「日付と期間はややこしいですね…」

🔗 RSpecテストをシンプルにする5つのルール(Ruby Weeklyより)


つっつきボイス:「先週に続いてRSpecテストの書き方記事です」「ネストを深くしない、letはトップに置く、この辺はわかる」

「ところで、このcreate_user.(name: "Jane", email: "jane@doe.org")という書き方がカリー化的で変わってるな〜↓」

RSpec.describe CreateUser do
  subject do
    CreateUser.new
  end

  context "with valid params" do
    specify {
      expect(create_user.(name: "Jane", email: "jane@doe.org")).to be_success
    }
  end
end

4. デフォルトではモックを使わない

(中略)少なくとも本物を使うと以下の問題がある場合にのみモックを使おう:
* テストのセットアップが著しく複雑になる場合
* テストが著しく遅くなる場合
* 外部システム(ファイルシステム)で望ましくない副作用が起きる場合
* インターネット接続に依存する場合
同記事より抜粋・大意

「4.はデフォルトではモックを使うなとありますけど、これはどういうことでしょう?」「自分も、他で再利用されていないコードをわざわざモックでテストする必要性をあまり感じない方です: そういうコードは結合した状態でテストしないと意義が薄いのではないかと感じることもよくあります」「あ、なるほど」

「そのコードが複数の場所で相互に利用されているのであればモックを使う意味はあると思いますけど、1箇所でしか使われていないコードだったら、そのコードを使っている場所でまとめてテストする方がいいんじゃないかという気持ちになりますね」「言われてみればそうかも」「モックを使って両方でテストを書く方がたしかにきれいですし、エラーがどちらで起こったかがわかりやすいという面もあるんですけど、テストを2つ書くコストに見合うかどうかを考えるとモックを使わずに書くことが多いかな」

「5.はテストをDRYではなくベタに書くことを容認しようということですね」「よく言われるヤツ」「テストはそういうさじ加減が難しい…」


「ところで4.にもサンプルコードがあったらいいのにとちょっと思いました」「テストに関する記事って、あまり具体的すぎても伝わりにくいところがあるんですよ: テストを書いた背景を説明し始めるとテーマがどんどん広がって収拾がつかなくなりがち」「それたしかに!」「なので、テストについて説明しようとすると、この記事のように抽象的なさじ加減について書くことが多いですね: そういうのにハマったことのある人はこの記事を読んでみると思い当たるところがあると思います👍」

🔗 Active Supportのあまり知られていない機能5つ(Ruby Weeklyより)


つっつきボイス:「知らないかもしれないActive Supportの機能か、どれどれ」

ActiveSupport::Callbacksは意識せずに使っている人も多いんじゃないかな」「お〜」「こうやってincludeして使える↓: コールバックは育ちすぎると後で苦しむことになるので、ほどほどにするのがよいと思います」

# 同記事より
class BaseService
  include ActiveSupport::Callbacks
  define_callbacks :call

  def call
    run_callbacks :call do
      process
    end
  end
end

ActiveSupport::Configurableは単体で使ったことはありませんが、そういえばActive Supportにありますね」「自分ならconfig gemを使うかな」「自分もそうするかも」

# 同記事より
class Example
  include ActiveSupport::Configurable
end

Example.config.some_option = 'value'

Example.config.some_option
=> "value"

rubyconfig/config - GitHub

「お、知る人ぞ知るActiveSupport::CurrentAttributesも紹介されていますね: 一種のスレッドセーフなグローバル変数的な機能」「そういえば以前翻訳した記事↓では反対意見が出ていました」「CurrentAttributesの是非はともかく、存在を知っておくことは重要でしょうね: Railsらしく書く方法が他にどうしても見つからないときに、スレッドセーフかつグローバルなデータアクセス方法としてCurrentAttributesを知っておけば思い出せるので」「なるほど」

Railsの`CurrentAttributes`は有害である(翻訳)

CurrentAttributesではスレッドセーフは保証されていますが、グローバルであることには変わりないので、基本的には非推奨ぐらいに思っておくのがよいと思います」「そうですね」

参考: ActiveSupport::CurrentAttributes

ActiveSupport::MessageVerifierは、そういえば以前署名付きidに関連してRails 6.1で追加されてましたね(ウォッチ20200525)」「これはどういうものでしょう?」「署名付きidという一種のシグネチャを一方向に生成できて、後でverifyしたり期限切れにしたりできるというものですね」「お〜」

# 同記事より
key = 'my_secret_key' # use env variable or generate via ActiveSupport::KeyGenerator
verifier = ActiveSupport::MessageVerifier.new(key)
verifier.generate('my message')
=> "BAhJIg9teSBtZXNzYWdlBjoGRVQ=--078c6389020294311bb45f099ab56450d9127d44" 

参考: ActiveSupport::MessageVerifier

ActiveSupport::MessageEncryptorはちょうどDeviseがらみで暗号化を手動で解除するのに使いましたよ」「うう、大変そう…」「ちなみにこれはRails 7に入る予定のActiveRecordのカラムの暗号化(ウォッチ20210330)とは別物で、Active SupportがRubyの世界の範囲内で暗号化を行うもので、昔からあります」「なるほど」

# 同記事より
key = SecureRandom.random_bytes(32)
crypt = ActiveSupport::MessageEncryptor.new(key)

message = crypt.encrypt_and_sign('my message')

crypt.decrypt_and_verify(message)
=> "my message"

参考: ActiveSupport::MessageEncryptor


「ちなみにActive Supportはよくできていて、自作のクラスにしれっとincludeしても普通に使えるんですよ」「なるほど、独立性が高いんですね」「Active Supportのコードをたまに眺めてみると、自分のところでも使える機能がちょくちょく見つかるので、おすすめですよ👍」

参考: Active Support コア拡張機能 - Railsガイド

🔗 RSpecの歴史


つっつきボイス:「すべてはアサーションから始まって、やがてRSpecが誕生し、DSLでより自然言語的に書けるようになっていた…という感じで歴史をたどる記事」「Railsを始めて間もない人やこの辺の歴史を知らない人は読んでおくとよいと思います: よさそうな記事👍」「翻訳してみたいです」

以下はつっつき後のツイートです。

🔗 その他Rails


つっつきボイス:「ついにtherubyracer gemがexecjsの対象から外されたのか〜」「therubyracerがインストールできなくてつらかった思い出があります😢」「therubyracerは、元々ビルド済みのtherubyracerを使えば環境に悩まされずに使えるJSエンジンとして登場したので、それだと逆ですよね😆」「まったくです…」

「事情があってJSエンジンをaptコマンドなどでインストールできない環境でも、gemとしてJSエンジンをインストールできるのがtherubyracerだったんですよ」「そういうものだったんですか」「それにしては随分このgemで苦労しました」

「therubyracerはたしかバージョン番号が偶数と奇数で違うんですよ: どちらかがローカルでビルドが走って、どちらかがビルド済みという違いだった気がします」「そうだったかも」「そのあたりを説明した記事が見つからない…いずれにしろお役御免なので別にいいかな」「今はこの辺で苦労しなくなって本当によかったと思います」


「Practicing Railsは、Railsチュートリアルを終えたぐらいの人をターゲットにした書籍みたいです」「たしかにそうした読者層にとって助けになるものがあるといいでしょうね」「新技術をどうキャッチアップしていくかみたいな、Railsエンジニアとしての心構えなどに重点が置かれていそうな感じかな👀」

参考: Ruby on Rails チュートリアル:プロダクト開発の0→1を学ぼう


前編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210518後編)RubyのGCを深掘りする、Psych gemのbreaking change、11月のRubyConf 2021ほか

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ(20210524前編)Active Supportの知られてなさそうな機能5つ、RSpecの歴史、書籍『Practicing Rails』ほか first appeared on TechRacho.

週刊Railsウォッチ(20210525後編)Rubyのオブジェクトアロケーション改善、RubyKaigi Takeout 2021開催日発表、AWS App Runnerほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 Rubyのオブジェクトアロケーションを改善する方法


つっつきボイス:「翻訳記事でお世話になっているSchneemsさんの昨年の記事が面白そうなので拾ってみました」「ライフチェンジング!」

  • オブジェクトアロケーションの片付けについて
  • 片付け例1: Active Recordのrespond_to?ロジック
  • パフォーマンスと統計的有意性
  • 片付け例2: stringからtimeへの変換は時間がかかる
  • 片付け例3: 極めて高速なキャッシュキー
    同記事見出しより

「memory_profilerや自作のderailed_benchmarksを使って丁寧に測定しながらアロケーションを改善してるようですね」

SamSaffron/memory_profiler - GitHub

schneems/derailed_benchmarks - GitHub

# 同記事より
$ bundle exec derailed exec perf:objects

allocated memory by gem
-----------------------------------
    227058  activesupport/lib
    134366  codetriage/app
    # ...


allocated memory by file
-----------------------------------
    126489  …/code/rails/activesupport/lib/active_support/core_ext/string/output_safety.rb
     49448  …/code/codetriage/app/views/layouts/_app.html.slim
     49328  …/code/codetriage/app/views/layouts/application.html.slim
     36097  …/code/rails/activemodel/lib/active_model/type/helpers/time_value.rb
     25096  …/code/codetriage/app/views/pages/_repos_with_pagination.html.slim
     24432  …/code/rails/activesupport/lib/active_support/core_ext/object/to_query.rb
     23526  …/code/codetriage/.gem/ruby/2.5.3/gems/rack-mini-profiler-1.0.0/lib/patches/db/pg.rb
     21912  …/code/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
     18000  …/code/rails/activemodel/lib/active_model/attribute_set/builder.rb
     15888  …/code/rails/activerecord/lib/active_record/result.rb
     14610  …/code/rails/activesupport/lib/active_support/cache.rb
     11109  …/code/codetriage/.gem/ruby/2.5.3/gems/rack-mini-profiler-1.0.0/lib/mini_profiler/storage/file_store.rb
      9824  …/code/rails/actionpack/lib/abstract_controller/caching/fragments.rb
      9360  …/.rubies/ruby-2.5.3/lib/ruby/2.5.0/logger.rb
      8440  …/code/rails/activerecord/lib/active_record/attribute_methods.rb
      8304  …/code/rails/activemodel/lib/active_model/attribute.rb
      8160  …/code/rails/actionview/lib/action_view/renderer/partial_renderer.rb
      8000  …/code/rails/activerecord/lib/active_record/integration.rb
      7880  …/code/rails/actionview/lib/action_view/log_subscriber.rb
      7478  …/code/rails/actionview/lib/action_view/helpers/tag_helper.rb
      7096  …/code/rails/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
      # ...

「上のような大量の出力から以下のような行をいかに見つけて調べるかが腕の見せどころですね」「経験がものを言いそう」「この記事の改善でかなり速くなったものもあるみたいですね」

8440  …/code/rails/activerecord/lib/active_record/attribute_methods.rb

「アロケーション改善をステップバイステップで解説してくれているのがいい👍」「Rubyのbenchmarkライブラリは業務ロジックのようなコードをメインで書いていると普段は使わないけど、年に数回ぐらいは使わなければならなくなることもあります」「改善結果をRailsにプルリク投げたのかなと思ったけど、記事には見当たらないみたい…」

参考: library benchmark (Ruby 3.0.0 リファレンスマニュアル)

🔗 ruby/debugのバックトレース書式提案

ruby/debug - GitHub


つっつきボイス:「ko1さんのruby/debugに、st0012さんがバックトレースの書式案をいくつか提案していました」「こういうリッチな出力のデバッガーが標準で入ったらすごいかも」「入って欲しいですね」

# 同issueより: |でアラインする案
=>#0    foo.rb:21 | Foo#forth_call(num1=20, num2=10) #=> true
  #1    foo.rb:8  | block{|ten=10|} in second_call
  #2    foo.rb:15 | Foo#third_call_with_block(block=#<Proc:0x00007fc645174d10 foo.rb:7>)
  #3    foo.rb:7  | Foo#second_call(num=20)
  #4    foo.rb:3  | first_call
  #5    foo.rb:24 | <main>
# 同issueより: 2行ずつ出力する案
          | Foo#forth_call(num1=20, num2=10) #=> true
  #1    foo.rb:8
          | block{|ten=10|} in second_call
  #2    foo.rb:15 
          | Foo#third_call_with_block(block=#<Proc:0x00007fc645174d10 foo.rb:7>)
  #3    foo.rb:7 
          | Foo#second_call(num=20)
  #4    foo.rb:3 
          | first_call
  #5    foo.rb:24 
          | <main>

その後、ターミナルの幅に応じて書式を変えるかどうかも議論されています。

🔗 byebugとzeitwerkで問題(Hacklinesより)


つっつきボイス:「byebug gemがRailsのzeitwerkでうまく動かない問題が起きているのか」「issueも上がっていました↓」

「解決方法は『別のデバッガを使う』だそうです」「何と…」

deivid-rodriguez/byebug - GitHub

参考: 定数の自動読み込みと再読み込み (Zeitwerk) - Railsガイド

「byebugは最近使ってないな〜」「byebugはコードの任意の場所にアタッチできるのが長所」

🔗 RubyKaigi Takeout 2021の開催日が決定


つっつきボイス:「今年のRubyKaigiはTakeoutと銘打って9/9(木)〜9/11(土)開催とサイトに掲載されました」「今年は参加するためにチケットを買う必要があるそうです」

「今回はオンライン参加のみでしょうか?」「オンライン開催はサイトにも書かれていますが、詳しくは今後の更新待ちですね」「プロポーザルの募集もこれからみたい」「チケットを買えば動画を後から視聴できるかどうかとか、そのあたりが知りたいですね」

「現地開催イベントに参加しなくなって久しいですけど、やっぱり一抹の寂しさがありますよね」「旅行の楽しさとか、会場でもらえるグッズとか」「地元の料理やお酒とか」「出かけたいな〜」

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

🔗 AWS App Runner(StatusCode Weeklyより)


つっつきボイス:「新しく出たAWS App Runnerはまだ詳しく見てませんが、既存のサービスを組み合わせてテンプレート化した新サービスでしょうね: AWSのサービスはこういう形になっているものがよくあります」「AWSあるあるですね」

「App Runnerは、ロードバランサーやTLS証明書のような、コンテナでのサービス構築に必要なものがひととおり揃っている感じなのはよさそう」「GitHubリポジトリとApp Runnerを接続してgit pushで自動デプロイできるのか」「ECR Public↓のリポジトリも使えるんですね」

参考: ECR Public Gallery

「これは便利そうですね」「もちろん現在でもECSやFargateなどを自分で組み合わせれば同じことができますけど、CI連携のパイプラインなども自分で書かないといけないとか、IAMアカウントの作成やらポリシーの設定やら、要するにAWSエンジニアが必要なんですよ」「あ、たしかに」「一度構築が完了すれば後は楽なんですけど、最初の構築が大変」

「App Runnerはそういう面倒な初期構築を詰め合わせにして楽にやれるようにしたということですね」「おそらくAWSに詳しくない人でもAWSエンジニアの手を煩わせずに構築できる方針で作っているんじゃないかな: 眺めた感じではRailsも普通に動かせそうに見えるので、今度使ってみよう」「お〜」「もしかするとApp Runnerは、自分でやる場合に比べて細かいコンフィグがやりにくいということはあるかもしれませんけどね」

🔗 GitHubで動画アップロード


つっつきボイス:「動画を貼れるのはありがたい」「GitHubも地道に改良を重ねてますね」「そういえばGitHubは、たしかCSVファイルをissueに添付できなかったんですよ」「え、知りませんでした」「Excelファイルなども以前貼れなかった覚えがあるので、おそらく情報漏えいにつながりそうな一部のファイル形式はアップロードできないようにしているんじゃないかなと想像してます」

参考: Issue およびプルリクエストのファイル添付 - GitHub Docs

🔗JavaScript

🔗 domevents.dev


つっつきボイス:「はてブでバズってました」「サイト上のDispatchをクリックすると、DOMイベントが伝搬する様子をビジュアル表示してくれるのね」「多少カスタマイズもできるみたい」「こういう動作はこれまで自分の頭の中にはありましたけど、ここまで作り込んだのは凄い」「こんなふうにしくみをビジュアライズするのは貴重ですね👍

参考: Document Object Model - Wikipedia

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

🔗 IEサポート終了の足音


つっつきボイス:「IEもついに終了に向かうか」「長かったですね😂」「上の記事はMicrosoft公式のもので、下はWordPressでのIEサポート打ち切り宣言でした」

「上の記事を見ると、EdgeブラウザにIEモードというのがあるんですって↓」「やっぱり欲しい人はいるんですね」

参考: Microsoft Edge の Internet Explorer モード - Office サポート

「実際IEを使うことが本当になくなった」「いいことです!」「大昔はMacにもIEがあったのに」「Mac版IE、覚えてます」

参考: Internet Explorer for Mac - Wikipedia

🔗言語/ツール/OS/CPU

🔗 M1 Macのスケジューラ


つっつきボイス:「この記事見た見た: macOSのスケジューラは前から優秀でしたけど、M1 Macではそれをチップレベルで制御している、スケジューラが優秀だと処理速度の体感が違ってくる、という話」「へ〜、知らなかった」「ちなみにQoSはもともとネットワーク方面の用語ですけど、この記事では操作上の応答性能のような感じで使われてますね」

参考: QoS(Quality of Service)とは

「M1チップのマルチコアはヘテロジニアスな構成になっていて↓、図上半分のIcestormコア(Efficiency)は遅いけど消費電力が少ない、下半分のFirestormコア(Performance)は消費電力は多いけど高パフォーマンス、というように分かれています」「お〜」「タスクをどのコアに振り分けるかについてもスケジューラがチップレベルでさらに有効活用するようになった感じですね」


How M1 Macs feel faster than Intel models: it’s about QoS – The Eclectic Light Companyより

参考: ヘテロジニアスマルチコア - Wikipedia

「macOSは前からスケジューラにそういう機能がちゃんとあるんですよ: それもあってmacOSはWindowsと比べるとトラブル時に完全にグリッチすることが少ないですね」「それ実感してます」「大昔のMac OS 9までは爆弾アイコンが頻発してましたけど」「そうそう、OS 9はひどかった」「NeXTSTEPベースのOS Xになってからはグリッチすることがめったになくなりましたね」

参考: What do we call the CPU or the process scheduling algorithm used in macOS? - Quora
参考: グリッチ - Wikipedia
参考: Mac OS 9 - Wikipedia
参考: NEXTSTEP - Wikipedia

「ヘテロジニアスな構成は、特定のタスクに特化したASICなんかで割と見かけるんですが、M1のような汎用CPUで使われるのはちょっと珍しく思えました」「なるほど」「ヘテロジニアスなASICだと命令セットに互換性がないのが普通ですが、M1のIcestormコアとFirestormコアはどちらも汎用プロセスを動かす命令を持っているんだろうと思いました」

🔗 中古PC価格

「実はおとといIntelチップのMacを買っちゃいまして」「ありゃ、M1にしなかったんですか?」「今のMacだとマイグレーションとかが遅すぎたので買い換えないとつらくって😢

「ちなみに今は中古PCが高いので、今買うならむしろ新品の方がいいかもしれませんね」「おぉ?」「今はGPUの値段がすごく高いので、最近メルカリを見ていると中古コンピュータの値段がびっくりするぐらい上がっていますよ」「マジですか?」「自分が持ってる数世代前のGTX1070 16GB i7 6700あたりでメルカリを検索しただけで、自分が買ったときの半額ぐらいの7万円とか9万円とかで出てますよ」「ホントだ」「すげ〜」「いいGPUを積んでるPCなら中古でも高値で売れます」


後編は以上です。

バックナンバー(2021年度第1四半期)

週刊Railsウォッチ(20210524前編)Active Supportの知られてなさそうな機能5つ、RSpecの歴史、書籍『Practicing Rails』ほか

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

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

Ruby Weekly

Hacklines

Hacklines

StatusCode Weekly

statuscode_weekly_banner

The post 週刊Railsウォッチ(20210525後編)Rubyのオブジェクトアロケーション改善、RubyKaigi Takeout 2021開催日発表、AWS App Runnerほか first appeared on TechRacho.

RSpecの作者が振り返る歴史(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

RSpecの作者が振り返る歴史(翻訳)

私がTDD(テスト駆動開発)をチームで教え始めたのは2001年のことでした。当時のTDDはまだかなり新しい概念でしたので、テストを自動化したチームもほとんどなく、XP(エクストリームプログラミング)やTDDについて聞いたことがある人も皆無でした。テストを最初に書くことで設計を進めるという概念は当時まったく知られていなかったので、TDDを理解するのに皆とても苦労していました(20年経った今でも、この事実が完全に変わったとは言えません)。

思い返せば、あの当時は厳しい状況でした。最善を尽くしてTDDの概念を説明し、どうにかしてチームの関心を惹こうと心血を注いだものです。TDDの普及は困難の連続で、私もかなり手こずりました。もっと言えば、私に教わる人たちがTDDの理解と実践に苦労していたのを解決したかったのです。

当時よく遭遇した問題のひとつは、testという言葉そのものにつまづく人が多かったことです。testという言葉も含め、TDDに関連する語彙は当然ながらどれもこれも「テスト中心」だったので、当時は誰もが非常に戸惑いました。「存在しないコードはテストしようがない」と言われるのもざらでしたし、「あなたの前提がそもそもおかしいんだよ」とドヤ顔で返されることすらありました。私が大きな価値を感じた概念や手法が、testという言葉を使った途端に「たかがテストじゃないか」とばかりに軽くあしらわれるありさまでした。私はそこを変えようとしたのです。

突破口を探しているうちに、サピア=ウォーフの言語的相対性仮説というものを知りました。これは「同じアイデアでも言葉を変えて伝えると、アイデアの受け止められ方や解釈が変わる」ものです。同じことに気づいた人は他にもいて、Aslak Hellesøy、Dan North、Liz Keoghらが早くからこの分野に取り組んでいたおかげで、多くの洞察を得られました。

そこで私は、トレーニングで使うツールを作り始めました。私はテスト中心の語彙をなくしたかったのです。とある預言者の言葉1を借りれば「testという名前がよくない」ということです。2003年までに私は、RubyでTDDを教え始めました。当時のRubyはまだニッチな言語で、Ruby on Railsもまだ世界を席巻する前でしたが、これらのアイデアを実装するのにふさわしいオブジェクト指向言語であることを私は評価していました。

互いに考えの似通った人たちとペアを組んでいるうちに、彼らのテストがおしなべてtest_should_do_this_thingのような名前になっていることに気づきました。testという言葉がまだ残っていたものの、意図を伝えるのによりふさわしい言葉shouldが使われていたことに目を止めました。

このtestという語を巧妙に消し去るにはどうすればよいでしょうか?Rubyなら簡単に取り除けます。そこで私は、完成したソフトウェアが持つ「べき(should)」「振る舞い(behavior)」を「記述(describe)」する方法として、TDDを教えるためのツールを作ることを思いつきました。当時このアイデアはまだ新しかったのですが、TDDの生みの親たちが一貫して主張していた「自動テストは、完成したソフトウェアが持つ”べき” “振る舞い”を伝えるものである」がそこに盛り込まれていました。

私が最初に作ったツールはdescriber.rbという以下の小さなRubyファイルで、testという語を消し去ることだけを目的としています。

require 'test/unit'

Description = Test::Unit
Context = TestCase

# Test::Unit::TestCase.suiteを再定義して"test"ではなく"should"で始まるメソッド名を使うようにする

このツールを使うと、「テスト」は以下のように書けます。

require 'describer'

class SimpleAdditionDescription < Description::Context
  def should_add_one
    assert_equal 2, add_one 1
  end
end

これでtestという言葉を使わずにテストが書けるようになりました。素晴らしくシンプルな解決策です。TDDを新たに実践する人が、Ruby組み込みの標準ライブラリに存在する堅牢なランナーセットなどの素晴らしいツールをすべて使い、しかもtestという言葉を使わずにテストを書けるようになりました。testという言葉が繰り返し出現して認知的不協和を生じることのないように仕組んだのです。

このツールをTDDのトレーニングに取り入れました。私のワークショップでは、このファイルを使ってソフトウェアの振る舞いを記述するようになり、トレーニング中はtestという言葉を使わないよう努めました。すると「テスト」という言葉にまつわるもろもろの悩みが一気に解消されたのです。

このトレーニングを最終日まで毎回繰り返し、最終日にはこんな種明かしをしました。「本トレーニングでソフトウェアを”記述”するために書いているものは、実は、ソフトウェアをテストして振る舞いの正しさを確認するために実際に実行可能な自動テストスイートを表しているのです」。そしてワークショップの参加者に「ここを卒業したら実際のRubyのテストは以下のように書きましょう」と教えました。

require 'test/unit'

class SimpleAdditionTest < Test::Unit::TestCase
  def test_add_one
    assert_equal 2, add_one 1
  end
end

この方法は見事に功を奏しました。参加者が「標準的なツール」を使うようになることと、参加者がこれまでと違う新たな考え方でテストにアプローチできるようになることを両立したいと考えていました。テスト用語の導入をあえてワークショップの最終日に回したことで、参加者は私の意図したとおりの視点から問題にアプローチしながら、標準的なツールを使うようになりました。私のトレーニング方法によって、参加者が自分たちに必要な価値を学ぶようになっただけではなく、より多くの人々、つまりTDDに強く抵抗してきた層にもリーチできるようになりました。

当初は気づきませんでしたが、ワークショップの参加者がアサーションのパラメータを取り違えてしまう問題が残っていました。アサーションのexpected(期待値)とactual(実際の値)をつい逆さまに書いてしまいがちで、エラーメッセージが混乱する原因になっていたのです。正直に申し上げれば、私もたまにこのようなミスをすることもありましたが、経験を積んでいたおかげですぐ間違いに気づけました。パラメータ逆さま問題を解決するために、expectationをより平易な言葉で書くことにしました。ブロックを受け取るassert_thatというメソッドを追加して、返されたオブジェクトに対してアサーションを使えるようにしました。

assert_that { expected }.equals actual

このライブラリファイルは、もともとワークショップの問題を解決するためだけに作ったのですが、そのままでは多くのコードが必要だったので、もっとシンプルな方法を探しました。これはRubyの力を借りれば解決できます。そこでメタプログラミングの世界に足を踏み入れ、以下のようにアサーションをObjectクラスに直接足しました(リリースされるライブラリとしては悪手ですが、教育用としては実に有用でした)。

class Object
  def should_equal expected
    assert_equal expected, self
  end

  # その他のアサーション
end

この変更で教育用ツールは完成しましたが、当時一般にはリリースしておらず。私のワークショップにしか存在していませんでした。このツールを自分のワークショップ以外で使ってもらうつもりはありませんでした(標準ライブラリのツールを使うべきだと思っていたので)。xUnitパターンの概念は十分普及していましたし、ほぼすべての言語で利用可能で、サポートも充実していました。今さら新しいライブラリを増やしてメンテナンスに時間を取られるのは大きな過ちだと思っていたのです。

この新しいツールは言葉の使い方を変えるだけで、誰にも必要とされていないと思っていました。今にして思えば、私の活動が有意義なのはワークショップという場であり、現場では必ずしもそうではないと考えていた私が浅はかでした。

私はこのツールをリリースするつもりは毛頭ありませんでした。xUnitは既に存在していましたし、xUnitで目的を果たせました。後にRSpecと呼ばれるようになったこのツールは、私にとって単なる教育用ツールでしかなく、ワークショップの外で使うべきものではないと思っていました。しかしリリースしないわけにいかない事情が発生したのです。

RSpecの誕生

2000年代半ば、Bob MartinはTDDを紹介するときに、私のやろうとしていたのと同じことを印象付けようとしていました。Bobもまた、他の人の言葉を別の言葉で言い換えていました。いわく「テストとは検証(verification)ではなく仕様(specification)である」と。さらに「ソフトウェアのテストとは、実行可能な仕様のことである」と。

この切り口は私も気に入っていたのですが、私には「仕様」という言葉がどうも引っかかりました(今でもそうです)。インターネットの黎明期からRFCや各種仕様書を元にさまざまなソフトウェアを実装してきた私にとって、「仕様」(specification)という言葉は予約語であり、他の意味で使われるべきではないと思えたので、新しい言葉を探す必要を感じていました。

しかしBob Martinは当時の私にとってヒーローであり、「仕様中心」の語彙で支持層を広げていました。そのBobが、私がワークショップ向けに作ったツールのデモを見て、ぜひこのツールをリリースしようと持ちかけてきたのです。Bobは、ツールの語彙を「仕様中心」に整備することに専念するよう熱心に働きかけました。

私は、RSpecという名前は良くないと今でも思っています。最初にリリースを考えた頃はまだ「describer」と呼んでいたので、当時の流行りっぽくeを捨ててDescribRにしようかとも思いましたが、この名前にも満足できず、どんな名前にするかをBob Martinと議論する資格が自分にあるとも思えませんでした。こうしてRSpecが誕生しました。

RSpecは2005年にリリースされました。

当時の私は、教育用ツールとしてのRSpecには大いに満足していましたが、実際に使われるべきではないと思っていました。しかし多くの人々がこのアイデアに惚れ込み、この方法でテストを書きたいと思うようになったのです。RSpecがこれほどまでに広く、そして急速に普及したことに驚いています。ある調査によると、新規Ruby on Railsプロジェクトの90%が、Rails組み込みのxUnitスタイルのフレームワークではなくRSpecを使っているそうです。それから間もなく、他の言語でも「RSpecクローン」が多数出現するようになりました。

DSLとしてのRSpec

RSpecがリリースされた当時、DSL(ドメイン固有言語: Domain-Specific Languages)という考え方が流行り出していました。私は言語を使って人々のソフトウェアに対する考え方を変えるのが好きなので、ソフトウェアの動作を指定する完全なDSLを作成すれば、RSpecがさらに良くなるのではないかと期待したのです。

私はJoe O’Brienと組んで、RubyConf 2005でRSpecの最初のバージョンを発表しました。使っていた語彙は現在と異なりますが、この時点でアイデアは既に固まっていました。RubyConf 2005でJoeと私が発表したのは、最終的に次のようなものでした。

specify "add numbers" {
  it_should "add one to the provided numer" {
    add_one(1).should_equal 2
  }
}

RSpecをこのように変更することで、人々がコードのことばかり考えるのではなく、ソフトウェアの「望ましい振る舞い」に注目することを期待しました。RSpecをDSL化したことについては、今でも満足しています。

プロジェクトとの別れ、そして教訓

このプロジェクトのコードに対する私の実質的な最後の貢献は、RSpecを完全なDSLにしたことでしょう。私自身は依然としてRSpecを使っていませんでしたし、RSpecはあくまで教育用ツールだと考えていたので、「標準」のテストツールを使いたかったのです。また、当時の私は仕事でさまざまな言語を使っていたので(今もそうですが)、どの仕事でも同じようなツールを使いたいと思っていました。せっかくTDDの概念を正しく学んだ人々が、DSLとオブジェクトモデルとの間で厄介な相互作用を引き起こす非標準ライブラリをわざわざ選ぶ理由が私には理解できませんでした。

RSpecが使われる理由が当時の私には本当に理解できませんでしたし、自分自身も使わなかったので(私はRSpecを使い始めていたプロジェクトでしか使ったことがなく、自分からRSpecを選んだことはありません)、自分がこのプロジェクトの管理者としてふさわしいと思えませんでした。このプロジェクトの将来について私よりも強く期待している人がたくさんいましたので、2006年か2007年のある時点で、これまでで最も多作かつ重要なコントリビュータであるDavid Chelimskyにこのプロジェクトを譲りました。

Chelimskyは、このプロジェクトの世話役にうってつけでした。これについて私の人選はこれ以上ないほど的確だったと思います。RSpecをDavid Chelimskyという有能な(特に私よりも有能な)コントリビュータに譲ったことは、おそらくプロジェクトに対する私の最もポジティブな貢献でしょう。私がRSpecを作ったことよりもずっとポジティブです。

また、(これまでの歴史が示すように)私が将来に渡ってソフトウェア業界全体に貢献できるのはRSpecだけではないという自負もありました。2007年頃の私は「自分は一発屋ではない」と主張していました。私にできることはまだまだあると思っていますが、RSpecのときと同じように時計の針が再び動き出すという自信はありません。正直に申し上げると、私はこのことで長年にわたって非常に悲しい思いをしました。

教訓、そして陳謝

私もたくさんの失敗をしてきました。比較的ささやかではあるものの、私の主たる失敗を申し上げます。私が作ったもの(RSpec)は、人々に楽しんでもらいながらテストに対する考え方を変えることができましたが、それをリリースして人々に思い思いの方法で使ってもらうことについて私は根強く抵抗してしまいました。何年も後になって、RSpecに出会うまでTDDやBDD、あるいはテストそのものをまったく理解できなかった人たちから「ありがとう」というメッセージを受け取るようになって、初めてそのことに気がつきました。いただいたメッセージに心から感謝するとともに、皆さんがRSpecを独自の方法で使うことによい顔をしなかったことを申し訳なく思っています。

技術面で言うと、アサーションをObjectクラスに足したのは失敗でした。これでRSpecの初期ユーザーを大勢苦しめてしまったので、プロジェクトの初期段階では、アサーションの形式を再検討してexpectationを変更するよりも、この決定を撤回して問題を回避することに多くの時間を費やしました。これは後のバージョンでexpect(expected).to equal(actual)と修正されました。私見ではこれがexpectationを記述する最も完全な方法であり、Objectクラスに入れるというミスを犯す前の私が行ったオリジナルの実験に近いものです。もし初期のRSpecでこの点に悩んでいた方がいらっしゃったら、私の間違った決定で二重に苦しめてしまったことを、そして頑丈なソフトウェアを作ろうとする尊い努力を妨げてしまったことをここにお詫びしたいと思います。

もうひとつ悔やまれるのは、自分の功績を奪われた事件です。私の初期のメンター(自己中心的で嫌われ者であることを自ら露呈したので名前は伏せます)が、RSpecを作ったのは自分であり、RSpecが作られた後に自分が作った部分こそがRSpecのベースでありインスピレーションの源であると吹聴しようとしたのです。RSpecは多くの人から寄せられたコードや知識の貢献の上に築き上げられていますが、この人物によるプロジェクトへの実際の貢献は最小限でしかなく、不埒にも初期の頃に他人のコードをパクってはライセンスヘッダを捨てていたに過ぎませんでした。

そして最後に、自分が作ったものから遠ざかってしまったことを悔やんでいます。自分が作ったものが違う形で使われることを気持ちよく受け入れていれば、私もそうした場でより便利なツールを作れたでしょうし、さらに多くの人の助けになれたでしょう。より多くの人の助けになれなかったことを申し訳なく思います。

感謝の気持、そして将来

微力ながらソフトウェアコミュニティに貢献できたこと、そして人々が自分たちの可能性に気づく手助けができたことに感謝します。そして今後も、より多くの人々の可能性を引き出すために役に立ちたいと願っています。私の作ったものが評価され、世界中で使われ続けていることに、私はいつまでも感謝しています。私がリリースを望まなかったものをリリースしたお礼にと、たくさんのビールをいただいたことにも感謝いたします。

私にもまだまだできることがあると期待していますが、当面は個人やチームが最高の仕事ができるためのサポートに集中することにします。私はこれからも、自分ができる多くのささやかな貢献に満足し、私の貢献をサポートしてくださる方々と一緒に働ける機会に感謝していきたいと思います。

そして、今後も私を助けてくださる人たちとともに貢献できることに感謝しつつ、ソフトウェアの開発を関係者全員にとって少しでもよいものにしていきたいと思っています。


  1. 訳注: 映画『ビッグ・リボウスキ』のセリフ(参考)。 

The post RSpecの作者が振り返る歴史(翻訳) first appeared on TechRacho.

週刊Railsウォッチ(20210531前編)RailsConf 2021の動画が公開、GraphQLのN+1を自動回避、Ruby 3のJITとRailsほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストのChangelogを中心に見繕いました。

🔗 ActiveModel::Type.lookupの委譲を...に変更

# activemodel/lib/active_model/type.rb#L33
-     def lookup(*args, **kwargs) # :nodoc:
-       registry.lookup(*args, **kwargs)
+     def lookup(...) # :nodoc:
+       registry.lookup(...)
      end

つっつきボイス:「引数の*args, **kwargsをトリプルドット構文...に変えてバグを修正したのね」「...構文ってありましたね」「空ハッシュを渡すとargs={}になるはずがargs=nilになっていたのか」

変更しない場合、以下のような新しいテストが失敗する。

  Failure:
  ActiveModel::TypeTest#test_registering_a_new_type [test/cases/type_test.rb:21]:
  Expected: #<struct args={}>
    Actual: #<struct args=nil>

(*args, **kwargs)の委譲は、Ruby 2.7ではターゲットが常にキーワード引数を受け取れるようになっていないと正しくない(Struct.new(:args).newは正しくないケース)。以下を参照。
参考: Correct Delegation with Ruby 2.6, 2.7 and 3.0 · On the Edge of Ruby
同様の#42266も同じように修正された。
同PRより大意

...は、受け取った引数を丸ごと渡す感じなんですね」「トリプルドットはRuby 2.7から入った機能か↓」

「そういえばRuby 3.0で...を改良して引数の先頭以外を...で渡せるようになってましたね↓」

引数のフォワーディングの記法で先頭に引数を書けるようになりました。
www.ruby-lang.orgより

# www.ruby-lang.orgより
def method_missing(meth, ...)
  send(:"do_#{ meth }", ...)
end

「トリプルドット記法使ったことなかった」「コードで見かけたら二度見しそう」「このようにコンストラクタで受けた引数を親クラスのコンストラクタにそのまま渡すのは昔からよくある操作ですね」「新しい書き方ですし、使うなら適切な場所を見きわめてからにしたい気持ち」「このプルリクでトリプルドット記法を思い出せました」

参考: Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 3.0.0 リファレンスマニュアル)

受け取った引数をそのまま別のメソッドに渡すための記法です。受け取る側も渡す側もカッコでくくる必要があります。
docs.ruby-lang.orgより

# docs.ruby-lang.orgより
    def foo(...)
      bar(...)
    end

🔗 assert_no_changesfrom:オプションが追加


つっつきボイス:「Minitestのアサーションでfrom:を書けるようになった: いかにもRSpec的な記法ですね」「なるほど」

# 同PRより
assert_no_changes -> { Status.all_good? }, from: true do
  post :create, params: { status: { ok: true } }
end

「RSpecだとfromtoで”〜から〜に変わる”というのが書けます↓: 変更前の値も指定できるのがポイント」「それと似たものがMinitestにも入ったということですね」「アサーションを見るだけで”〜から〜に変わる”という仕様がわかるので、この書き方は昔から好き❤」「この機能が入ったら使ってみたいです」

# relishapp.comより
require "counter"

RSpec.describe Counter, "#increment" do
  it "should increment the count" do
    expect { Counter.increment }.to change { Counter.count }.from(0).to(1)
  end

  # deliberate failure
  it "should increment the count by 2" do
    expect { Counter.increment }.to change { Counter.count }.by(2)
  end
end

🔗 rails dbconsoleでマルチDBのreplicaをサポート


つっつきボイス:「rails dbconsoleっていう機能があるとは!」「rails db:なんちゃらしか使ったことなかった」「rails dbだけでもいいみたい」「今までdatabase.yml見ながらpsqlやMySQLクライアントを使ってたけど、まだまだ知らない機能があるな〜」「修正はrails dbでマルチDBのreplicaを使えるようにしたんですね」

現在はrails dbconsoleでreplicaがすべて無視されるが、replicaが使えればかなり有用(例: 分析のために、production環境から動いていないデータベースにクエリをかける)。
これが何らかの理由でbreaking changeになるのであれば、dbconsoleの引数に--include-replicas flagを足してもよい。
同PRより大意

「手元のMySQL+Railsでやったら動かない…」「rails dbconsoleは単にデータベースクライアント(mysqlコマンドやpsqlコマンド)を呼び出すようですね: つまりデータベースクライアントが入ってなければ動かない」「あ、それで動かなかったのか」「コンテナ環境のRailsだとクライアントライブラリは入っててもコマンドラインのCLIプログラムが入っていないのはありがちかも」「まさにそれでした😅」「こちらはPostgreSQL+Railsでrails dbをやってみるとたしかにpsqlが起動しました」

🔗 Active Storageのタイムスタンプにprecision:を追加


つっつきボイス:「Active Storageでデータベースのconnectionsupports_datetime_with_precision?に対応している場合にprecision: 6が指定されるようになった」「今までは普通のタイムスタンプだったのか」

# activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs do |t|
      t.string   :key,          null: false
      t.string   :filename,     null: false
      t.string   :content_type
      t.text     :metadata
      t.string   :service_name, null: false
      t.bigint   :byte_size,    null: false
      t.string   :checksum,     null: false
-     t.datetime :created_at,   null: false
+
+     if connection.supports_datetime_with_precision?
+       t.datetime :created_at, precision: 6, null: false
+     else
+       t.datetime :created_at, null: false
+     end

      t.index [ :key ], unique: true
    end

precisionというオプションがあるんですね」「この記事↓にActive Recordのprecisionのことが載ってる: MySQLの場合、Rails 5まではタイムスタンプの秒に小数部分がなくて、Rails 6からはprecision: 6がデフォルトになった」「precisionはkamipoさんが2年前に#34970でデフォルトにしたんですね」「このprecision: 6がActive Storage用のテーブルでも付けられるようになったようですね」

参考: Rails6 のちょい足しな新機能を試す65(timestamps precision編) - Qiita

-- 同記事より: Rails 6.0.0.rc1の場合
mysql> select * from users;
+----+------+----------------------------+----------------------------+
| id | name | created_at                 | updated_at                 |
+----+------+----------------------------+----------------------------+
|  1 | Taro | 2019-07-26 06:52:38.606642 | 2019-07-26 06:52:38.606642 |
+----+------+----------------------------+----------------------------+
1 row in set (0.00 sec)
-- 同記事より: Rails 5.2.3の場合
mysql> select * from users;
+----+------+---------------------+---------------------+
| id | name | created_at          | updated_at          |
+----+------+---------------------+---------------------+
|  1 | Taro | 2019-07-26 06:42:34 | 2019-07-26 06:42:34 |
+----+------+---------------------+---------------------+
1 row in set (0.00 sec)

🔗Rails

🔗 RailsConf 2021の動画が出揃う(Ruby Weeklyより)


つっつきボイス:「おぉ、RailsConf 2021の動画が公開されたんですね🎉」「今回のRailsConfは有料だった気がする: FAQを見るとたしかに有料チケットを販売していたけど、動画を公開してくれたんですね」「ありがたいです🙏」「ワークショップやLTもひとつひとつ動画を切り分けられていて、動画数すっごく多いですね」「ちゃんと手間とコストをかけて制作したのがわかる👍

🔗 GraphQLのN+1クエリを自動で回避する(Ruby Weeklyより)


つっつきボイス:「GraphQLのN+1って割とよく聞きますよね」「GraphQLは構造上ネステッドにするとN+1になるから仕方ないでしょうね」「たしかに」「GraphQLはスキーマでネステッドな階層を組んでhas_manyしたデータを取れるようにしていくと簡単にN+1クエリが発生するので、この記事のように自動化したい気持ちもわかる」

「記事ではbatch-loader gemを使ったようです↓」

exAspArk/batch-loader - GitHub

「N+1って自動で回避できるものなんでしょうか?」「どうだろう…N+1が起きると事前にわかっていればincludesを使ったり以下のようにActiveRecord::Associations::Preloaderでeager lodingできるんですが」

# 同記事より
class Types::PreloadableField < Types::BaseField
  def initialize(*args, preload: nil, **kwargs, &block)
    @preloads = preload
    super(*args, **kwargs, &block)
  end

  def resolve(type, args, ctx)
    return super unless @preloads

    BatchLoader::GraphQL.for(type).batch(key: self) do |records, loader|
      ActiveRecord::Associations::Preloader.new.preload(records.map(&:object), @preloads)
      records.each { |r| loader.call(r, super(r, args, ctx)) }
    end
  end
end

「実はごく最近、GraphQLの#resolveメソッド中の条件処理でeager loadingしたことで、Active Recordのキャッシュが効き過ぎて結果が期待どおりにならないいう事案に遭遇しまして」「う、それはつらそう…」

「たしかそのときは、#resolveメソッド中で結果セットをhas_many :throughしたテーブルの条件をfilterするような処理を書いていて、その中で#eager_loadingしました。そして#resolveの戻り値にはそのままeager_loading済みのActive Recordオブジェクトを設定したんですが、GraphQL Query側でeager loadingしたテーブルのデータを要求すると、filter済みのデータしか取れなくて想定と異なる、みたいなことが発生しました(言葉で伝わるかな💦)」「何と」「お疲れさまです…」「調べてみたらeager loaingの部分が問題だったことがわかったので修正しました」

「こういうことが起きるかもしれないので、GraphQLの#resolveで返却するActive Recordオブジェクトでは、eager loadingの使い方に気をつけたいと思いました」「ごもっともです」「検索に使ったActive Recordインスタンスと、GraphQLのレスポンスに渡すActive Recordのオブジェクトは、想定しないクエリキャッシュが効かないように注意して使おうという教訓を得た思いです」

「クエリキャッシュについては、たとえば個別にreloadすることで対応できます: このreloadも書き忘れがちなんですが、RailsのコードならActive Recordでhas_manyしているものを取り出すときなどにreloadを書き忘れたら即結果が変わるので、その場で気づけるんですよ」「なるほど」「でもGraphQLの場合は#resolveが返す結果だけ見ても一見問題がないのに、Query側でネステッドなデータを取り出すときにはじめて影響することが分かるので気づきにくい」「あ〜そうか!」

Rails: N+1クエリを「バッチング」で解決するBatchLoader gem(翻訳)

🔗 Ruby 3のJITとRails


つっつきボイス:「こちらはk0kubunさんの記事で、RailsがRuby 3のJITで遅くならないようにできたそうです」「お〜それは凄い!」

「記事の読ませ方がうまいですね: “MJITでRailsが速くならない”から始まってOptcarrotやGCCなどこれまでの話題や経緯をたどってから本題に入るという構成」「さすがですね」「Ruby 2.7ではベンチマーク対象のトップ100メソッドだけをコンパイルしていたのを、すべてのメソッドをコンパイルするように変えてみたら少し速くなったそうです」

「JITでSinatraが11%速くなって、Railsの2つのベンチマークもVMと比べて1.04倍と1.03倍速くなったんですね」「DiscourceのRailsアプリもJITで速くなったのが凄い↓: 増加は1.03倍でも、Discourseのように実際に使われている大規模RailsアプリがJITで遅くならなかったことが大事」「この結果は頼もしいですね」「RailsがJITで遅くならなければJITをオンにすることも増えると思います」「これはいい記事👍」「後で読もうっと」


同記事より

discourse/discourse - GitHub

🔗 その他Rails

つっつきボイス:「はてブでバズっていたGist集記事です」「true/falseを厳密にバリデーションするのか、なるほど↓」

「空白を自動でstripするStrippedString型↓も面白いですね」

「見出しを見ているとこういうニーズがあるというのがよくわかる」「よさげなGistがいろいろ載ってますね: 使っちゃおうかな」「使うならちゃんと内部ロジックも読んだ上で理解して使っていきたいですね」「はい!」


前編は以上です。

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

週刊Railsウォッチ(20210525後編)Rubyのオブジェクトアロケーション改善、RubyKaigi Takeout 2021開催日発表、AWS App Runnerほか

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ(20210531前編)RailsConf 2021の動画が公開、GraphQLのN+1を自動回避、Ruby 3のJITとRailsほか first appeared on TechRacho.

週刊Railsウォッチ(20210601後編)Python使いから見たRuby、MySQLのインデックス解説、GitHubが採用したOpenTelemetryほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 書籍『Webで使えるmrubyシステムプログラミング入門』


つっつきボイス:「前回の銀座Rails#33でこの本の著者がmrubyのプレゼン↓をやってたのを見て、面白そうな本なのでその場で注文かけました」「私もこの本買ってました(読まなきゃ😅)」

🔗 Python使いから見たRuby


つっつきボイス:「これはいい記事でしたね」「私も楽しく読みました」

「この記事ではPythonですが、他の言語(PHP、Java、JavaScriptなど)をやっている人もこの記事で解説されていることを押さえておけば “ある程度まで” Rubyを読めるようになれるでしょうね」

「見方を変えると、他の言語を知っている人が予備知識なしでRubyのコードを読もうとすると、なかなかフィーリングだけでは読めないということでもあると思います」「言われてみるとRubyの構文は少し独特かも」「たとえばJavaをやっている人なら、JavaScriptやPythonを初めて読むときにもある程度までフィーリングで読めると思うんですけど、そういう人がいきなりRubyを読むと、まずeachの読み方がわからなかったりするんですよ」「あ、そうかも」「わかる気がします」

「Rubyには他にもシンボルの概念や%記号、変数名や関数名は小文字で始めてクラス名は大文字で始めるといった縛りなど、他の言語にないものも多いんですよ」「Rubyの例外処理にbeginがなくて、rescueensureのインデントが飛び出ている↓のも、Javaから入った自分は最初戸惑いました」「Rubyを使っているとそれが普通なんですけどね」

# 同記事より
def hoge
  p "main process"
  raise "just for test"
rescue => e
  p "error occurred: #{e.message}"
ensure
  p "this line is always called"
end

「この記事はそういう他の言語から来る人たちにとって役立つと思います👍」「ボリュームがそんなに多くないのもありがたいですね」

🔗 Cの側からRubyを覗く(Ruby Weeklyより)


つっつきボイス:「こちらはCRubyを記述するC言語の側からRubyを覗くという企画のシリーズ記事だそうです」「CRubyの内部実装を解説する記事ですね」「第3回はメソッド呼び出しや可変長引数、キーワード引数、ブロック付きメソッド呼び出しが解説されてる」「最後に練習問題も付いてますね」「CRubyのコードを追う機会は普段なかなかないな〜」

🔗 hashdiff: ハッシュ同士を比較するgem

liufengyun/hashdiff - GitHub


つっつきボイス:「ハッシュのdiff?」「あ〜、こういうふうに差分を配列で取り出せるのか↓」「+-が追加と削除で、~が変更かな」「GitHubとかのdiff viewの感覚で差分を出せる感じ」「巨大なarrayの比較には使わないでくれと書かれてました」

# 同リポジトリより
a = {a:{x:2, y:3, z:4}, b:{x:3, z:45}}
b = {a:{y:3}, b:{y:3, z:30}}

diff = Hashdiff.diff(a, b)
diff.should == [['-', 'a.x', 2], ['-', 'a.z', 4], ['-', 'b.x', 3], ['~', 'b.z', 45, 30], ['+', 'b.y', 3]]

「ところで、こういうdiff viewの挙動ってどこでもだいたい同じだけど、共通のdiffライブラリがあるのかな?🤔」「ググってみるとGoogleのdiff-match-patchというライブラリが出てきた↓」「C++、C#、Python、Objective-C、JavaScriptなどいろんな言語に対応してるんですね」「Rubyはないのか〜」「Luaはあるのに」

google/diff-match-patch - GitHub

🔗 その他Ruby

# 同PRより
$ irb
irb(main)[01:0]> ls ERB.new('test')
ERB#methods:
  def_class         def_method        def_module        encoding          filename          filename=         lineno
  lineno=           location=         make_compiler     result            result_with_hash  run               set_eoutvar
  src
instance variables: @_init  @encoding  @filename  @frozen_string  @lineno  @src

つっつきボイス:「小ネタですけど、RubyのIRBにpryと同じようなlsコマンドが入っていたそうです」「k0kubunさんのお仕事」「最近のIRBの使い勝手、本当によくなったと思います」「自分もpryをすっかり使わなくなりましたね」「私も」「本体にbundleされたライブラリの機能が向上するのはいい👍


「ついに教科書進出!」「よく見たら地理の教科書でした」「Rubyって地域の特産物なのかしら?」「焼き物とか漆器みたいな趣」

🔗DB

🔗 スライド『MySQLとインデックスと私』


つっつきボイス:「そうそう、このスライドはよかった: 令和時代のMySQLインデックスのお話」

「インデックスを図示する方法がうまい↓」「たしかに見やすいですね」「実際のMySQL内部のデータ構造がこうなっているというわけではないと思いますが、RDBMSが与えられたクエリからデータを検索していく順番がうまく図示されているのがとても良いと思います👍」「なるほど」「データがフィルタされる過程はだいたいこのスライドのとおりです」

「複合インデックスのしくみやインデックスマージの話などいろいろ解説されていますね」「結局このあたりは自力でデータベースをチューニングするようにならないとなかなか身につかないんですよ」「データベースとみっちりお付き合いしないとだめか…」

「入っているデータによってクエリプランが変わるというのも割と大事なポイントですね」「はい」「これはPostgreSQLの話ですが、以前stagingではmerge joinしてるのにproductionではフルスキャンする、みたいなケースがありました: 原因は恐らくproductionのレコード数が多すぎて、merge joinするためのメモリ容量が足りなくなったんじゃないかと結論づけました」「お〜」「こういうのを追いかけるにはRDBMS自体の知識がそれなりに必要ですね」

🔗 RDB内の実行順序

「RDBを学ぶときは、RDBの中でどのような順序で処理が進められるかという流れが大事で、これを最初に理解しておかないと今のインデックスの話などもなかなかしっくりこないと思います」「なるほど」「これまでも引き合いに出した(ウォッチ20190416)、そーだいさんの『失敗から学ぶRDBの正しい歩き方』にもそうした知見が豊富にあります↓」「そうそう、これ買いました」「買いました〜」

「同書は現場で実際によくある事例がたくさん紹介されていてとてもよい本です👍」「この本すごくわかりやすかった」「特に同書の『6.2 リレーショナルモデルとソートのしくみ』図6.1『RDBMSとエクゼキュータ』はとても大事: この実行順序を理解しておかないと、最適化するポイントを間違えてしまったりして最適化の意味がなくなる」「この図大事ですね」「この図を理解するためだけにこの本を買ってもいいぐらい」

Julian Evansさんの以下の記事に、同書よりもう少し簡単な図が掲載されています↓。

参考: SQL queries don’t start with SELECT

「今回取り上げたスライドも、そーだいさんのツイートで知ったと思います」

つっつきの後で探してみました↓。

🔗 Active RecordとRDB

「ところでActive RecordのメソッドチェーンもRDBの実行順序を意識しないといけないんでしょうか?」「SQLの最終的な実行順序はRDBの中の話なので、Active Recordのメソッドチェーンの順序は基本的に影響しません」「なるほど!」

「その代わりActive Recordのメソッドには、典型的なデータと典型的なクエリの場合にはクエリをこう書き換えると速くなるといったクエリ効率化のノウハウがいろいろ盛り込まれているので、 単純かつ頻繁に使われるようなユースケースであれば、RDBのことを気にしなくてもある程度速度を出せます」「お〜」「クエリが複雑になって典型的なパターンでなくなってくる場合には、to_sqlで取り出した生SQLのクエリプランを確認したり、想定しているインデックスが使われてるかなどを確認して最適化する必要があるでしょう」

「あとActive Recordのscopeが複雑になってくると、これなら速いだろうと思っていた処理が実際にはやたら遅くて、よく調べたらscopeの中で複雑な条件を付けていてそれが遅かった、みたいなことはあります」「そうそう」「実際の業務アプリでは典型から外れることもよくありますね」

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

🔗 App Runner続報


つっつきボイス:「先週(ウォッチ20210525)に続いてAWS App Runnerの話題です」

「記事を見てみると、サービス設定にいろいろ制約があるみたい」「大規模なアプリケーションは対象にしてない感じでしょうか?」「たぶんサービスが立ち上がって間もないだけでしょうね」「それもそうか」「AWS Lambdaも初期は数秒程度のexecution timeしか許されていなかったので、それと同じように最初は大規模にしないだろうと思います」

「KMSは使える」「AWS Copilotは使ったことなかった」「Copilotは今使ってて、ちょっとGoogle App Engineに似た雰囲気を感じました」「Systems Manager パラメータストアが使えないのはちょっとしんどそうだけど、必要とされる機能だし、このあたりは今後連携できるようになっていくんじゃないかなという気がする」「そうなって欲しいですね」

参考: AWS Key Management Service(マネージド型の暗号化キー作成と管理)| AWS
参考: AWS Copilot のご紹介 | Amazon Web Services ブログ
参考: AWS Systems Manager パラメータストア - AWS Systems Manager

「早くもApp RunnerでRailsを動かすスクリーンキャストが公開されてました↓」「今のAWSでRailsを動かすのはなかなか大変なので、App Runnerでもっと楽にやれるといいでしょうね」

🔗 GitHubがOpenTelemetryを採用(Hacklinesより)


つっつきボイス:「OpenTelemetryとは?」「下の日本語記事によると、OpenCensusOpenTracingというオープンなサービス監視標準が最近統合されてOpenTelemetryができたそうです」「お、どこかで見たと思ったら、銀座Rails#24で発表されてたのをこの図↓で思い出した」「お〜」


同記事より

参考: OpenTelemetry とは  |  Google Cloud
参考: OpenTelemetry: 計装器を長く使い続けるために - New Relic公式ブログ
参考: OpenCensus(OpenTelemetry)とは | フューチャー技術ブログ

「現代のサービスはユーザーからリクエストを受けてレスポンスを返すまでにたくさんのサービスを経由しますよね: たとえばAWS API Gatewayを通り、Lambdaを呼んで、LambdaがRDBにアクセスし、RDBはSQLを発行するとか」「ですね」「そうしたすべてのサービスがモニタリング用の共通のリクエストIDを共有して、各処理内での所要時間などを付与するフォーマットが標準化されていることで、異なるサービス群を複数使っていてもトレーシングを共通化できるというメリットがあります」「ふむふむ」「そのための規格がいくつかあって、OpenTelemetryはそのひとつだと理解しました」「お〜、そういうのがあったとは」

「この記事に載っているRailsのInstrumentation機能↓は、Railsのあらゆるステータスログを出力できるんですけど、これをOpenTelemetryに対応させられれば、OpenTelemetryをサポートするあらゆる監視サービス(DataDogとか)やビジュアライズツールを使えるようになります」「それでNewRelicも上の日本語記事でもOpenTelemetryに協力していると書いていたんですね」

# 同記事より
# in an initializer, or config/application.rb
OpenTelemetry::SDK.configure do |c|
  c.use 'OpenTelemetry::Instrumentation::Rails'
  c.use 'OpenTelemetry::Instrumentation::PG', enable_sql_obfuscation: true
  c.use 'OpenTelemetry::Instrumentation::ActiveJob'
  # This application makes a variety of outbound HTTP calls, with a variety of underlying
  # HTTP client libraries - you may not need this many!
  c.use 'OpenTelemetry::Instrumentation::Faraday'
  c.use 'OpenTelemetry::Instrumentation::Net::HTTP'
  c.use 'OpenTelemetry::Instrumentation::RestClient'
end

参考: Active Support の Instrumentation 機能 - Railsガイド
参考: クラウド時代のサーバー監視&分析サービス | Datadog

「最初の記事に戻ると、GitHubがOpenTelemetryを採用したというのは、どうやらGitHub内部のRailsでOpenTelemetryを利用しているということみたい」「外部サービスかと思ったらユーザーには直接関係なさそう」「GitHubはOpenTelemetryを使いつつOpenTelemetryを支援しているという記事のようですね」

🔗JavaScript

🔗 SICPのJavaScript版

参考: 計算機プログラムの構造と解釈 - Wikipedia


つっつきボイス:「SICPって何だっけと思ったら、紫色の表紙を見て思い出した」「コンピュータサイエンスをScheme言語(LISP言語の方言)で学ぶ有名な教科書でしたね」

「昔大学でScheme動かしながら学んだ覚えがありますけど、ある時期からはPython版も出てますよ」「え、知りませんでした」「米国だと最近の学生はPythonでないとやってられないとか何とか」「オリジナルのSICPはMITから出版されたんだったかな」

参考: Scheme - Wikipedia
参考: Introduction | SICP in Python

「SICP(Structure and Interpretation of Computer Programs)は日本語だと『計算機プログラムの構造と解釈』」「日本語版を出していたピアソンも今はないんだな…」「そのJavaScript版が出たということですね」「JavaScriptでコンピュータサイエンス学びたい人にはよさそう」「いい本だと思いますけど、ゼミで集まってやるならともかく、ひとりで学ぶのは相当つらいんじゃないかな〜」


後編は以上です。

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

週刊Railsウォッチ(20210531前編)RailsConf 2021の動画が公開、GraphQLのN+1を自動回避、Ruby 3のJITとRailsほか

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

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

Ruby Weekly

Hacklines

Hacklines

The post 週刊Railsウォッチ(20210601後編)Python使いから見たRuby、MySQLのインデックス解説、GitHubが採用したOpenTelemetryほか first appeared on TechRacho.

Ruby: アンパサンドとコロン`&:`記法について調べてみた

$
0
0

更新情報

  • 2019/07/19: 初版公開
  • 2021/05/27: 更新

参考: class Symbol (Ruby 3.0.0)

&:メソッド名とは

Rubyのシンボルには任意の文字列を使えます(引用符を使えば:"記号含み&*(の文字列"も使えます)が、本記事では簡単のため&との組み合わせでは:メソッド名と限定して表記します。

Rubyの&:メソッド名という記法についてググって調べようとするとなかなか大変だったのでメモします。というのも、この記法そのものには定まった名称がないからです。

&
手続きオブジェクト(Procオブジェクト)をブロックに変換するショートハンド
「Proc coercion」演算子と呼ばれることも
:メソッド名
Rubyのシンボル

参考: メソッド呼び出し(super・ブロック付き・yield) (Ruby 2.6.0)

ブロックの部分だけを先に定義して変数に保存しておき、後からブロック付きメソッドに渡すことも出来ます。それを実現するのが手続きオブジェクト(Proc)です。それをブロックとして渡すにはブロック付きメソッドの最後の引数として&で修飾した手続きオブジェクトを渡します
docs.ruby-lang.orgより

&:メソッド名は上記の通り2つの記法の合わせ技なので、定まった名称がない理由も理解できました。

見た目にはあたかも&:メソッド名のようではありますが、実は&:メソッド名の組み合わせなので、一度わかってしまえばいいのですが少々紛らわしいです。

とはいうものの、ぼっち演算子のような「いい名前」が&:メソッド名にもあればと思います。誰か付けませんか?

&によるProc coercion

以下の記事によると、&によるProc変換は「Proc coercion」(coercion: 強制、強要など)と呼ばれているとのことです。ググった限りでは用語にまではなっていないような雰囲気ですが。

&によるProc coercionはRubyの最初期からある機能なので、後から導入されたぼっち演算子&.とは異なり、Rubyでは昔から使える記法ということになります。

参考

おたより発掘

参考: Class: RuboCop::Cop::Style::SymbolProc — Documentation for rubocop (1.15.0)

関連記事

Ruby: `&:メソッド名`はブロック変数渡しより若干高速

Rubyのぼっち演算子はRailsの`Object#try`より高速(翻訳)

The post Ruby: アンパサンドとコロン`&:`記法について調べてみた first appeared on TechRacho.

RubyのGCを深掘りする(1)GC::INTERNAL_CONSTANTS(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

RubyのGCを深掘りする(1)GC::INTERNAL_CONSTANTS(翻訳)

昔から「ゴミを見ればその人がわかる」と申しますが、プログラミング言語でも同じことが言えます。より正確には、プログラミング言語のガベージコレクションを調べると、そのプログラミング言語について多くのことを学べます。本記事は、RubyのGCシリーズ記事の第1回です(最終的には書籍にします)。

今回は(イマイチなメタファーですが)、Rubyのゴミ箱の中に必ずあるものをあさってみるところから始めます。RubyはGC::INTERNAL_CONSTANTSをpublicにしているので、これを使ってガベージコレクタの各種定数を調べられます。

GC::INTERNAL_CONSTANTS

Ruby 3.0でGC::INTERNAL_CONSTANTSを出力すると以下のようになります。

{
  :DEBUG=>false,
  :RVALUE_SIZE=>40,
  :HEAP_PAGE_OBJ_LIMIT=>409,
  :HEAP_PAGE_BITMAP_SIZE=>56,
  :HEAP_PAGE_BITMAP_PLANES=>4,
  :HEAP_PAGE_SIZE=>16384
}

これらの定数を1つずつ見ながら定義することで、Rubyがどのようにメモリを構成しているかをさらに詳しく知ることができます。

:DEBUG => FALSE

:DEBUG という定数は、Rubyでプログラムを書くときのデバッグよりも、Rubyそのもののソースコードを書くときのデバッグに便利です。実行するほとんどのRubyプログラムは:DEBUG => FALSEになっているので、通常気にする必要はありません。

興味しんしんの方向けに説明すると、:DEBUGTRUEになるのは、Rubyのソースコードをコンパイルする際に、cppflagでDGC_DEBUGをtrueに設定した場合のみです(参考)。しかしRubyのソースコードをコンパイルすることは通常ないので、ほとんどの場合FALSEになります。

:RVALUE_SIZE => 40

最初の重要なポイントは、*_SIZEで終わる定数がすべて「バイト単位」になっていることです。つまり定数:RVALUE_SIZE => 40は、RVALUEが40バイトに設定されていることを示します。

なるほど、ところでRVALUEとは何かが気になってきますね。RVALUEはRubyオブジェクトの基本的な情報を格納します。RVALUEは、RubyオブジェクトをCで表現するさまざまなCの構造体が共用体(union)になったものです(参考)。

RVALUEには「Rubyオブジェクトの実際の値」という情報が含まれますが、この値が40バイトの制限を超える場合は、代わりにRubyオブジェクトの値がOSのヒープ上のどこにあるかを示すポインタが含まれます。

(細かい話ですが、文字列が23文字以下の場合はRVALUESの中に値が格納され、23文字を超える場合はOSのヒープ上に値が格納されます。詳しくはPat Shaughnessy氏のブログ記事をどうぞ)。

:HEAP_PAGE_OBJ_LIMIT => 409

お次はHEAP_PAGE_OBJ_LIMITです。409は、HEAP_PAGEごとのオブジェクトの最大数です。ところでHEAPPAGEとは何なのでしょうか。

RubyのHEAPとは、Rubyのオブジェクト空間全体、つまりメモリのことです。HEAPにはすべてのRVALUESが格納されます。HEAPはいくつかのPAGEに分割されます。ガベージコレクタで追加のメモリが必要になった場合、オブジェクトごとに新たなメモリをOSに要求することはありません。もしそうすると、上述のRVALUE_SIZEで学んだようにメモリを40バイト単位で要求することになるので効率が非常に悪くなります。

その代わり、HEAPがさらにメモリを必要とする場合は、まったく新しいPAGEを要求します。1個のPAGEには最大で409個のオブジェクトが含まれています。その理由についてはAaron Patterson氏の素晴らしいブログ記事に書かれています。この定数からもわかるように、1個のPAGEあたりのオブジェクト(つまりRVALUE)の個数の上限は409個に設定されています。

:HEAP_PAGE_BITMAP_SIZE => 56

HEAPPAGEについてはこれで理解できましたが、今度のBITMAPは何でしょうか。各HEAP_PAGEには、オブジェクトのビットマップ表現も含まれています。各オブジェクトは1つのビットで表現されます。

ところで、:HEAP_PAGE_BITMAP_SIZE => 56は、ビットマップ用にメモリを56バイト使うという意味です。ここで算数のお時間です。1バイトは8ビットなので、56バイトは56 * 8 == 448ビット、つまり448個のオブジェクトを扱えるということなので、上の:HEAP_PAGE_OBJ_LIMIT => 409個のオブジェクトを表現するのに十分な値です。

しかし、そもそもすべてのオブジェクトを表現するのになぜビットマップが必要なのかという疑問が残っています。ビットマップは、Rubyがガベージコレクションに使われる実際のアルゴリズム(具体的には「3色マークアンドスイープ」アルゴリズム」に「統合」されています。このアルゴリズムを詳しく説明しようとするだけで優にブログ記事1本分になるでしょう。さしあたっては、ビットマップがページごとに存在することと、ページ内の各オブジェクトを格納するのに十分なスペースが確保することが重要であるとお考えください。

:HEAP_PAGE_BITMAP_PLANES => 4

正直申し上げると、この定数は謎です。Rubyのソースコードを検索してみると値が4に設定されていることはわかったのですが、変数自体がまったく使われていないのです。この値は私の知る限り未使用です。planeをググるはずがついつい飛行機(airplane)をググって最近ご無沙汰の旅行気分にしばし浸ってしまいました。

冗談はともかく、HEAP_PAGE_BITMAP_PLANESの意味についてご存じの方がいらっしゃったら、ぜひ教えてください。知りたくてたまりません。いにしえのRubyの名残りか何かだと勘ぐっているのですが…

訳注

著者のツイートへの返信を見つけたので貼っておきます。なお、HEAP_PAGE_BITMAP_PLANESを削除するプルリク#4154を著者が出していますが、現時点(2021/06/04)でオープンのままです。

:HEAP_PAGE_SIZE => 16384

最後の定数はページそのもののサイズです。16384バイトということは、1ページあたり16KB強ということです。これらのページにあるのはは、若干の情報を含むヘッダーと、すべてのRVALUEです。RVALUEを格納する場所は「スロット(slot)」と呼ばれます。先ほど HEAP_PAGE_OBJECT_LIMITは409個と書きましたので、言い方を変えれば1ページあたり最大で409個のスロットがあるということになります。

再び算数のお時間です。1つのページはすべてのRVALUEを十分格納できるはずであり、それに加えてヘッダー情報のスペースも若干使われます。1ページあたりのRVALUEが最大409個であり、1つのRVALUEが40バイトであることがわかったので、409 * 40 == 16,306バイトとなって16384バイトを下回っています。

まとめ

図で理解したい方のために、上を図で解説しました。

Heap diagram

ご紹介した定義も手短にまとめました。

Heap(可変サイズ)
Rubyのオブジェクトスペース(つまりメモリ)。Rubyはページをすべてここに保存する。
Page(〜16KB)
Rubyによるヒープの分割単位。ヒープには複数のページが含まれる。ページ全体で最大409個のスロットが含まれる。
Slot(40バイト)
1つのページ上の領域。1個のRVALUEがここに保存される。
RVALUE(40バイト)
RubyオブジェクトのC表現。オブジェクトの値が含まれることもあれば、OSヒープ上のオブジェクトを指すポインタを含むこともある。

今回の記事は以上です。初めに述べたように、今後もガベージコレクションについてブログ記事と本の両方でいろいろ書いていく予定です。新着記事を見逃したくない方は、原文末尾のフォームでメールアドレスをお知らせいただければ新着記事を通知いたします。

関連記事

Rubyのヒープをビジュアル表示する(翻訳)

Ruby内部の文字列を共有してスピードアップする(翻訳)

The post RubyのGCを深掘りする(1)GC::INTERNAL_CONSTANTS(翻訳) first appeared on TechRacho.


週刊Railsウォッチ(20210607前編)ActiveRecord::Relationのone?とmany?が高速化、RubyKaigi Takeout 2021登壇者募集開始ほか

$
0
0

こんにちは、hachi8833です。RubyKaigi Takeout 2021の登壇者募集が始まりましたね。

週刊Railsウォッチについて

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

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

🔗Rails: 先週の改修(Rails公式ニュースより)

Rails公式ニュースが久しぶりに更新されたので、その中からこれまで取り上げていなかったものを中心に拾いました。

🔗 ActiveRecord::Relationone?many?を高速化

# activerecord/lib/active_record/relation.rb#L296
    def one?
      return super if block_given?
-     limit_value ? records.one? : size == 1
+     return records.one? if limit_value || loaded?
+     limited_count == 1
    end

    def many?
      return super if block_given?
-     limit_value ? records.many? : size > 1
+     return records.many? if limit_value || loaded?
+     limited_count > 1
    end

つっつきボイス:「SELECT COUNT(*)をやめてLIMIT 2を使って高速化したようですね: 巨大テーブルのCOUNTは遅いですけど、SELECT 1したうえでLIMIT 2にすると速いですよ」「お〜、ベンチマークも1300倍以上になったとありますね」「100M行ならこのぐらい差が出ますよ」「速度が欲しい人たちはこれまで同様の独自メソッドを書いていたかもしれませんね」

LIMIT 2で速くなるかどうかは状況にもよるかも: たとえばMySQLでSELECT COUNT(*) FROM tableのようにテーブル内の行をすべて取得するようなクエリを投げると、MySQLのinformation_schemaに既にある値を使うのでLIMIT 2よりも速くなるかもしれませんね(訂正↓)」「それは速そう!」「LIMIT 2より速くできる方法は他にもありそうですが、Active Recordでscoped chainされることを考えればこのPRの方法がよさそう👍

訂正(2021/06/08)

MySQLのエンジンがInnoDBの場合はこのようにならないとのご指摘をいただきました(ツイート)。ありがとうございます!

参考: InnoDBのINFORMATION_SCHEMA TABLES Tableと実データのレコード数比率を出してみた - カイワレの大冒険 Third

LIMIT 2に加えてSELECT 1も速くするのによく使われますね」「1ですか?」「1というスタティックな値だけを返すという意味ですね: 定数であればよいので1でなくても構いません」「あ〜なるほど」「行データが不要で件数だけ欲しいようなときに使うことがあります」

参考: sql server 2008 - What does “select 1 from” do? - Stack Overflow


ActiveRecord::Relationone?メソッドやmany?メソッドは、背後でSELECT COUNT(*) FROM postsのようなクエリを生成してから、結果が1に等しい(または1より大きい)かどうかを比較している。巨大テーブルや複雑な条件ではCOUNTが非常に遅くなることがあるが、この場合は全レコード件数は完全に不要。それならLIMITを追加してSELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM posts LIMIT 2)のようにするだけでずっと高速になる。実際any?などのメソッドではLIMITを使っている。
なおこれはすべてのバージョンのRailsで発生している。
100M行のテーブルでいくつかベンチマークを取ってみると、スピードが1300倍以上増加した。
同PRより大意

🔗 ネストしたsecretsにメソッド名でアクセスできるようになった


つっつきボイス:「ネストしたsecrets自体は前からサポートされていた覚えがありますけど、今回はそれにメソッド名でもアクセスできるようにしたようですね」「なるほど」

参考: Rails 5.2 credentials cheat cheat — ネストした値をフェッチする例が記載されています

  • Rails.application.credentialsのキーにネステッドアクセスできるようになった

従来はcredentials.yml.encのトップレベルにあるキーにしかメソッド呼び出しでアクセスできなかったが、任意のキーに対してできるようになった。
たとえば以下のsecretsがあるとする。

aws:
   access_key_id: 123
   secret_access_key: 345

この改修で、Rails.application.credentials.aws.access_key_idRails.application.credentials.aws[:access_key_id]と同じものを返すようになった。
Alex Ghiculescu
Changelogより大意

🔗 ActionController::Live#send_streamが追加


つっつきボイス:「send_streamも見覚えある: 最近はsend_dataよりもなるべくsend_streamを使おうみたいな話があったような気がしますね」

「既に生成されてファイルになったものを送信するのであればsend_streamでもsend_dataでも変わらないんですが、以下のようにeachで処理を回すものを送信するのであればsend_streamの方が送信開始を早められます」「あ、send_dataだと処理が完了するまで送信できないのか」「そういうことですね: どちらも最終的に同じことをやれるのであれば基本的にsend_streamを使う方がいいかなと思いました」「なるほど」

生成済みのストリームをより送信しやすくするActionController::Live#send_streamを追加。

   send_stream(filename: "subscribers.csv") do |stream|
     stream.write "email_address,updated_at\n"

     @subscribers.find_each do |subscriber|
       stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
     end
   end

同PRより大意

Rails 7でActiveStorage::Streamingサポートが追加(翻訳)

🔗 ActiveStorage::Streamingが切り出された


つっつきボイス:「DHHによる改修です」「streaming.rbがActive Storageから切り出された↓」「リファクタリングっぽいですね」「send_blob_streamというメソッドもできた」

# streaming.rb
# frozen_string_literal: true

module ActiveStorage::Streaming
  DEFAULT_BLOB_STREAMING_DISPOSITION = "inline"

  include ActionController::Live

  private
    # Stream the blob from storage directly to the response. The disposition can be controlled by setting +disposition+.
    # The content type and filename is set directly from the +blob+.
    def send_blob_stream(blob, disposition: nil) #:doc:
      send_stream(
          filename: blob.filename.sanitized,
          disposition: blob.forced_disposition_for_serving || disposition || DEFAULT_BLOB_STREAMING_DISPOSITION,
          type: blob.content_type_for_serving) do |stream|
        blob.download do |chunk|
          stream.write chunk
        end
      end
    end
end

Active Storageからストリーミングするコントローラを独自に作りたいのであれば、ストリーミングを適切に行えるメソッドがあると便利。そういうものがActiveStorage::BaseControllerに切り出されていないまま既に存在していた。
同PRより大意

🔗 fresh_whenstale?Cache-Controlヘッダーを上書きできるよう修正

stale(形容詞)新鮮でない、気の抜けた


つっつきボイス:「Cache-Controlヘッダーにまた修正が入った(ウォッチ20201012ウォッチ20210412)」

「コントローラのアクションでCache-Controlヘッダーに積極的に情報を渡す手段が追加されたということみたいですね↓: Cache-Controlを直接書き変えずにfresh_whenメソッドやstaleメソッドのオプション経由で変えられるようにした」「なるほど」

    # When overwriting Cache-Control header:
    #
    #   def show
    #     @article = Article.find(params[:id])
    #     fresh_when(@article, public: true, cache_control: { no_cache: true })
    #   end
    #
    # This will set in the response Cache-Control = public, no-cache.
  • fresh_whenstale?cache_control: {}オプションを追加。
    これらのメソッドでresponse.cache_controlを設定するショートカットとして使える。
    Jacopo Beschi
    同Changelogより大意

「クライアントキャッシュの制御はコントローラがやるものと考えれば、そのためのヘルパーメソッドがあるのは理にかなっていそうな気もする」「あ、Cache-Controlヘッダーが制御するのは一瞬Railsサーバーのページキャッシュのような気がしたけど、クライアントキャッシュの方だったか」

「コントローラでCache-Controlヘッダーを制御できるようになるのはいいことだと思う一方で、コントローラではサーバーのページキャッシュとクライアントキャッシュのどちらについても気にする場面があるので、どちらを制御しているかに気をつけないといけないかも」「それもそうですね」

参考: Cache-Control - HTTP | MDN
参考: RailsにおけるCacheの概念と使い方 - Qiita

🔗Rails

🔗 PostgreSQLのRow-Level SecurityをRailsで使う(Ruby Weeklyより)


つっつきボイス:「PostgreSQLに行レベルのセキュリティ機能があるとは」「略してRLS」「ENABLE ROW LEVEL SECURITY;で有効にできるのか」「どういうふうに使うのかな?」「なるほど、こんな感じでCREATE POLICYでポリシーを記述するんですね↓」

-- 同記事より
CREATE POLICY transactions_app_user
  ON transactions
  TO app_user
  USING (customer_id = NULLIF(current_setting('rls.customer_id', TRUE), '')::bigint);

参考: PostgreSQL: Documentation: 13: 5.8. Row Security Policies

「記事ではこれをRailsで使ってる↓」「やるな〜」

-- 同記事より
class CreateTransactions < ActiveRecord::Migration[6.1]
  def change
    create_table :transactions, id: :uuid do |t|
      t.bigint :customer_id
      t.text :description
      t.bigint :amount_cents
      t.timestamptz :created_at
    end

    # Grant application user permissions on the table (this migration should run as the admin user)
    reversible do |dir|
      dir.up do
        execute 'GRANT SELECT, INSERT, UPDATE, DELETE ON transactions TO app_user'
      end
      dir.down do
        execute 'REVOKE SELECT, INSERT, UPDATE, DELETE ON transactions FROM app_user'
      end
    end

    # Define RLS policy
    reversible do |dir|
      dir.up do
        execute 'ALTER TABLE transactions ENABLE ROW LEVEL SECURITY'
        execute "CREATE POLICY transactions_app_user ON transactions TO app_user USING (customer_id = NULLIF(current_setting('rls.customer_id', TRUE), '')::bigint)"
      end
      dir.down do
        execute 'DROP POLICY transactions_app_user ON transactions'
        execute 'ALTER TABLE transactions DISABLE ROW LEVEL SECURITY'
      end
    end
  end
end

「モデルのコードを見ると、コネクションの部分で'SET rls.customer_id = %s'のようにRLSを設定してますね↓: PostgreSQLコネクションのセッション変数的な部分にこれを設定することで、クエリが適切かどうかをポリシーでチェックできるようになる感じかな」「複雑になるけどその分安心できそう」

# 同記事より
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  SET_CUSTOMER_ID_SQL = 'SET rls.customer_id = %s'.freeze
  RESET_CUSTOMER_ID_SQL = 'RESET rls.customer_id'.freeze
  def self.with_customer_id(customer_id, &block)
    begin
      connection.execute format(SET_CUSTOMER_ID_SQL, connection.quote(customer_id))
      block.call
    ensure
      connection.execute RESET_CUSTOMER_ID_SQL
    end
  end
end

「RLSをセキュリティ上の防衛という観点から見た場合、PostgreSQLのRLSはあくまで生SQLの中で設定しているので、仮にセッションを乗っ取られて生SQLを実行されたら回避できないでしょうね」「それもそうか」

「自分なら、たとえば今度Rails 7に入る予定のモデル暗号化機能(ウォッチ20210412)みたいな暗号化用gemを使って、暗号化の鍵はアプリケーション側だけで持ち、PostgreSQLには暗号化済みのデータを保存する方が、生SQLされたときの対策としては有効かなと思いました」「あ〜なるほど」「もちろんPostgreSQLのRLSも、有効性の範囲を理解したうえでポリシー設定に使う分にはよいものだろうと思います👍

「面白い機能ですね」「ぽすぐれならこういう機能があっても不思議じゃない」「記事の最後には、RLSを使う場合はパフォーマンスにも注意しようと書かれてますね」

🔗 Active Recordのnewにはブロックも渡せる(Ruby Weeklyより)


つっつきボイス:「Active Recordのnewcreateのような作成系メソッドは以前からこういうふうにブロックも渡せますね↓」「あ、そうでしたか」

# 同記事より
u = User.new do |user|
  user.first_name = "Jordan"
  user.last_name = "Knight"
end

「Rubyだと『こういう書き方、やってみたら動くかな?』と思いついてドキュメントも読まずにやってみると本当に動くことがちょくちょくありますけど、そういうノリでnewにブロック渡ししてみたらできたという感じでしょうね」「たしかにRubyだとそれよくありますよね」「Ruby名物の『やったらできた』」


追いかけボイス:「同記事の後半では、Rubyのtapメソッドを使った場合にブロック渡しの挙動が異なることについても触れられていますね↓」

# 同記事より
# tapにブロックを渡す場合
new_user = User.create(first_name: "Jordan", last_name: "Knight").tap do |u|
  u.first_name = "Jonathan"
end

new_user.first_name        #=> "Jonathan"
new_user.reload.first_name #=> "Jordan"
# createに直接ブロックを渡す場合
new_user = User.create(first_name: "Jordan", last_name: "Knight") do |u|
  u.first_name = "Jonathan"
end

new_user.first_name        #=> "Jonathan"
new_user.reload.first_name #=> "Jonathan"

参考: Object#tap (Ruby 3.0.0 リファレンスマニュアル)

🔗 Hotwire記事2本


つっつきボイス:「1本目は今翻訳中のEvil Martiansの記事で、2本目は少し前のですがwillnetさんのHotwire記事です」「もしかすると自分が本当に欲しかったのはHotwireだったのかもしれない、という気持ちもある一方で、Turboがね…Turboを使うかどうかまだ悩み中なんですよ」「そこですよね」「Evil Martiansの記事は、いい意味で古き良き時代のWeb開発に戻れるよと言いつつ、既存Railsアプリに導入するとそこそこ直しが必要だったようです」「HotwireとRails本体の相性についてはあまり心配していませんが、むしろRails以外のものとHotwireとのインテグレーションが気になっています」「あ、そうか!」「いずれにしろHotwireについてはもう少し調べておかないといけないでしょうね」「たしかに」


速報: Basecampがリリースした「Hotwire」の概要

🔗 その他Rails


つっつきボイス:「小ネタですが、過去のRails gemがインストールされていれば、こんなふうにバージョン番号の前後にアンダースコア_を付けるとバージョン指定できるそうです↓」「そうそう、Railsにはこんなオプションが隠れていますね」「たまにとても欲しくなりそうな機能」

# 同記事より
# Create Rails 6.1 project
$ rails _6.1.3_ new rails6_1

# Create Rails 6.0 project
$ rails _6.0.3_ new rails6_0

# Create Rails 5.2 project
$ rails _5.2.3_ new rails5.2

前編は以上です。

バックナンバー(2021年度第3四半期)

週刊Railsウォッチ(20210601後編)Python使いから見たRuby、MySQLのインデックス解説、GitHubが採用したOpenTelemetryほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines

The post 週刊Railsウォッチ(20210607前編)ActiveRecord::Relationのone?とmany?が高速化、RubyKaigi Takeout 2021登壇者募集開始ほか first appeared on TechRacho.

週刊Railsウォッチ(20210608後編)RubyでAppleのLZFSE圧縮データ解凍、AWS Lambda Extensionsが正式リリース、unixgame.ioほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 object_tracer: 旧TappingDeviceがリネーム(Ruby Weeklyより)

st0012/object_tracer - GitHub


つっつきボイス:「以前@st0012さんが作ったTappingDevice gemがリネームされていたのをRuby Weeklyの記事で知りました」「TappingDeviceという名前でだいぶ違うものを想像していましたけどデバッガーなんですね」「オブジェクトにアタッチして動きをtappingするイメージだからそこから命名していたのかも」

tapping(名詞)傍受、盗聴

「考えてみればこのgemの機能はトレーサーなので、新しい名前の方がいいですね👍」「そうですね」「前の名前はアタッチするための手段が使われていましたけど、新しい名前はgemのユースケースや振る舞いを表していて的確」「なるほど、何をするためなのかという目的がわかる名前ですね」「tappingはあくまで手段のはず」

「ちなみに以下は最近のruby/debugへのマージ回数で、st0012さんが今のところトップです」「この調子でruby/debugに参加する人が増えてさらに使いやすくなるといいですね」

ruby/debug - GitHub

🔗 提案: Rubyのバックトレースに位置情報を含める


つっつきボイス:「@mameさんからの提案は、1行内でHashの添え字アクセスがメソッドチェインで複数回発生するとき、どのオブジェクトへのメソッド呼び出しの時点でエラーが出たのかを特定できるようにThread::BacktraceLocationを拡張するというもの: どこで発生したかがわかりにくいundefined methodメッセージってすごくありがちなので、これが欲しい気持ちわかる」「発生場所がわかるなら教えて欲しいヤツ」「ここだよ、ここですね(古)」

# 同issueより
# どこがundefinedかわかりにくい
data["data"].first["field"] #=> undefined method `[]` for nil:NilClass

# こうやって発生位置を知らせる
$ ruby -r ./sample/no_method_error_ext.rb err1.rb
err1.rb:2:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)

data["data"].first["field"]
                  ^^^^^^^^^

「Rust言語のエラーメッセージがこういう位置情報を丁寧に出してたのを思い出しました↓」「もしかするとそういうのを意識したのかもしれませんね」

参考: Improved error messages - The Edition Guide

// doc.rust-lang.orgより
error[E0506]: cannot assign to `x` because it is borrowed
 --> foo.rs:4:5
  |
3 |     let y = &x;
  |              - borrow of `x` occurs here
4 |     x += 1;
  |     ^^^^^^ assignment to borrowed `x` occurs here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0506`.

🔗 Arbre: RubyコードでHTML生成(Awesome Rubyより)

activeadmin/arbre - GitHub

# https://activeadmin.github.io/arbre/ より
html {
  head {
    title "Welcome page"
  }
  body {
    para "Hello, world"
  }
}

つっつきボイス:「アーブレ?」「HTML on Rubyとあるので、上のようなDSLからHTMLを生成できるということですね」「一度は作ってみたくなるヤツかも」「ArbreはActive Admin gem↓から切り出されたそうです」

activeadmin/activeadmin - GitHub

🔗 Appleの圧縮フォーマットで遊んでみた(Hacklinesより)

woodruffw/lzfse.rb - GitHub


つっつきボイス:「マニアックそうな記事です」「なるほど、LZFSE圧縮されているシステムファイルをRubyで解凍したりしてる」「著者が作ったruby-machoというgemもありますけど、マッチョかと思ったらマークオーでした」「Mach-Oバイナリを追いかけるとは強い」「使い慣れたRubyでやってみたということでしょうね」

# 同記事より
require "lzfse"

# LZFSE (de)compression
LZFSE.lzfse_compress
LZFSE.lzfse_decompress

# LZNV (de)compression
LZFSE.lznv_compress
LZFSE.lznv_decompress

参考: Mach-O - Wikipedia
参考: LZFSE - Wikipedia

Homebrew/ruby-macho - GitHub

🔗 Rubyの文字列削除メソッドの速度比較


つっつきボイス:「Railsのパフォーマンス最適化でもこの種の情報を見かけますね」「同記事では文字列のサイズも変えて試してるようです」「実装にも影響されるでしょうね」

「用途が限定的なほど速く汎用的なほど遅い、そうそう↓」「特定の用途に特化したメソッドほど速くなる傾向はありますね」

測定その 1 の考察
メソッドを速い順に並べると String#delete, String#tr, String#sub, String#gsub, String#remove の順番でした。用途がより限定的なメソッドは速く、より汎用的なメソッドは遅いという結果となりました。便利な String#gsub をいつも使うのではなく、用途に合わせて適切なメソッドを使うことが良いということですね。
同記事より

「測定その2では、文字列が長くなると結果が変わるのね」「正規表現を使うと遅くなる、ごもっとも」「正規表現好きですけどホントそのとおりです」

🔗DB

🔗 書籍『Production Ready GraphQL』


つっつきボイス:「GraphQLの本!」「GraphQL API側の設計に関する書籍みたい: GraphQLが登場してからだいぶ経ってきたこともあってベストプラクティスがだいたい固まってきた感じはありますね」

目次を見た感じではGraphQLの一般的なスキーマ設計を扱っていて、graphql-rubyとかには言及してなさそうかな」

rmosolgo/graphql-ruby - GitHub

「graphql-rubyを詳しいノウハウを解説している本があったら欲しいと思っているんですよ: GraphQL Rubyの公式サイト↓にリファレンスも一応あるんですけど、現場で使おうとするとまだまだ大変」

「どの辺がつらいんでしょうか?」「テストの書き方などはGraphQL Rubyサイトにひととおり載っていますけど、作り始めてみると、自分が欲しいスキーマを得るにはRuby側でどのように書けばいいのかという部分が意外に難しい」「あ〜」「GraphQL単体の情報はApollo↓とかに割とあるんですけどね」

「サーバー側の実装言語に依存せずにGraphQLスキーマを設計するという視点であれば良さそうな本だと思います👍


「探しているうちにGraphQL::Proというのを見つけた↓」「上のGraphQL Rubyの関連サイトみたいですね」「CanCanCanやPunditとのインテグレーションもできるらしい」「年900ドルか」

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

🔗 AWS Lambda Extensions(Publickeyより)


つっつきボイス:「お、Lambda Extensionsが正式リリースされた」「前からあったかと思ったら去年からなんですね」「以前ウォッチで扱ったのをこの図↓で思い出しました(ウォッチ20201021)」


同記事より

「Lambda Extensionsは、コンテナのライフタイムに合わせたAPIを持っていて、Lambdaと違うのはLambdaの関数が起動する前や終了後などにもアタッチして監視などを行える点ですね」「そういえばそうだったかも」「記事にDatadogやNew Relicといった監視サービスが名を連ねているのも、これができるから: 先週話したようなOpenTelemetry(20210601)のような監視を行うと、コンテナのライフサイクルも含めて監視したくなるものなんですよ」「なるほど」

「Lambda Extensionsがないとできないこともあるので、正式版になったことで安心して使えるようになったのはいいですね👍

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

🔗 QUICがRFC 9000に


つっつきボイス:「ついにQUICにRFC番号が付与された🎉

参考: HTTP/3の基盤となる「QUICプロトコル」の標準化プロセスが完了、IETFの「RFC 9000」として - Publickey

「ところで以下の図↓を見ていて、前方互換性さえ保てれば本当はUDPもQUICで上書きしたかったんじゃないかなと想像しました」「😆」「さすがにそれをやると、IP層より上のプロトコル番号をチェックするファイアウォールのようなL4機器を通れなくなってしまいますけどね」


www.publickey1.jpより

「SCTPが流行らなかったのもそれと同じ理由じゃないかな〜」「SはセキュアのSでしょうか?」「ストリームのSですね: SCTPは、HTTP/2のようなマルチストリーム、つまり1個のSCTP接続の中に複数のストリームを同時に流せるプロトコルで、しかもネットワーク経路の異なる複数の接続を一つのSCTPストリームとして扱えます(いわゆるマルチホーム)」「へ〜」

参考: Stream Control Transmission Protocol - Wikipedia
参考: HTTP/2 - Wikipedia

「HTTP/3はまだ詳しく追っていませんが、QUICを取り込みつつ他にも機能があるようですね」「HTTP/3勉強しないといかんな〜」「よほどエッジなWebサービスとかでないとHTTP/3になったときの影響はすぐには見えてこないかもしれませんね」

参考: HTTP/3はどうやってWebを加速するか? TCP、TLS、HTTP/2の問題とHTTP/3での解決策~Fastly奥氏が解説(前編) - Publickey
参考: HTTP/3はどうやってWebを加速するか? TCP、TLS、HTTP/2の問題とHTTP/3での解決策~Fastly奥氏が解説(後編) - Publickey

🔗 Chromeの10080番ポートが使えなくなっていた


つっつきボイス:「これびっくりしましたね」「10080番、普通に使ってるのに😢

「どうして使えなくしたんでしょうか?」「記事によればNAT Slipstreaming v2という攻撃手法で10080番ポートがよく使われるかららしいですね(1191)」

参考: NAT Slipstreaming v2 攻撃とブラウザ側の対策 - ASnoKaze blog

「こんな理由でブラウザのポートが塞がれるのはちょっと不本意」「この調子で8080番や3000番もダメということになったらたまりませんよね」「気軽にfetch APIにアクセスできなくなる」「ポートを変えればいいのはわかっているんですけど」

「443->10443みたいに本来のwell-knownポートの番号を10000台に変えて使うことはよくありますよね」「私も使ってます」「この問題を踏んだときにブラウザに表示してくれるならいいけど、そうでなかったら知らない人が踏んだときになかなか原因がわからないでしょうね」「こういうことが起きる可能性もあるんだなと改めて感じました」

「このissueが掲載されているfetchって、WHATWGのリポジトリのようですけど、fetchって何でしょうか?」「fetchはWeb API標準のひとつですね: MDNのドキュメントを見れば↓、Web APIの中にFetch APIも含まれています」「なるほど」

参考: Fetch API - Web API | MDN

whatwg/fetch - GitHub

参考: Fetch Standard
参考: Web Hypertext Application Technology Working Group - Wikipedia — WHATWG

🔗言語/ツール/OS/CPU

🔗 Mouse without Borders


つっつきボイス:「Windowsで複数PCの間でマウスが共有できるんですね」「Mouse without Borders、私は割と前から使ってますよ」「サードパーティ製品ではすごく昔からありますけど、標準で入ってるんですか?」「自分でダウンロードして追加インストールする必要がありますけど、ちゃんとしたマイクロソフトのソフトウェアですね」「複数のブラウザにまたがってチェックしたいときとかに便利そう」


その後、ちょうど本日macOS Montereyが発表されましたが、それに入っている「Universal Control」がちょっとMouse without Bordersを思わせますね。

参考: Mac向けの最新OS「macOS Monterey」が発表、Macの隣にiPadを置くだけで1つのマウスですべてを操作可能に - GIGAZINE

🔗 組版処理システムTwight


つっつきボイス:「リポジトリは公開されてないようですが、TeXのようなテキストベースの組版処理システムというのが目を惹きました」「印刷業界の中の人ではなく筑波大に在籍してるんですね」「よく作ったな〜」「以下と同じ人だそうです↓」

参考: 筑波大の授業DB代替ツールを作った学生、「未踏」のスーパークリエータに認定 オープンソースの組版処理システム開発で(ITmedia NEWS) - Yahoo!ニュース

「Twightは弊社の超シリーズと方向性近いのかな?Slackで聞いてみよう」「(Slackで返信)見た感じTwightは”制作ツール”のようです: 弊社の超シリーズで作っているのはビューアが中心なのでターゲット層は違うでしょうね」「なるほど、ありがとうございます!」

参考: EPUBで縦書きや組版を綺麗に表示する電子書籍ソリューション | BPS株式会社

業界随一の仕様準拠性:EPUB3 ビューア「超縦書」Windows版 無料公開のお知らせ

🔗 unixgame.io


つっつきボイス:「BPS社内Slackにも貼りましたけど、Nokiaのベル研が出しているunixgame.ioはなかなか楽しめました」「お〜、ScratchみたいなGUIでUnixシェルワンライナーを解くんですね」「やってみるとわかりますけど、GUIなので使えるコマンドが限定されているので、普段使い慣れたコマンドを使わずに解くしかないところがパズルとして面白い」「なるほど、シェルの縛りプレイ」「おかげで普段使わないコマンドを知ることができました: 正規表現を使わずにtrコマンドで大文字小文字変換するとか」「そうそう」「シェルを普通に使いこなしている人でも楽しめるのがいいですね👍

「このGUIはどんなライブラリを使っているのかな?」「ソースを見てみるとBlockly↓というのを使ってる」「Googleのライブラリなんですね」「Blockly昔使ったことあります!」

参考: Blockly  |  Google Developers


後編は以上です。

バックナンバー(2021年度第3四半期)

週刊Railsウォッチ(20210607前編)ActiveRecord::Relationのone?とmany?が高速化、RubyKaigi Takeout 2021登壇者募集開始ほか

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

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

Ruby Weekly

Awesome Ruby

Hacklines

Hacklines

StatusCode Weekly

statuscode_weekly_banner

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ(20210608後編)RubyでAppleのLZFSE圧縮データ解凍、AWS Lambda Extensionsが正式リリース、unixgame.ioほか first appeared on TechRacho.

週刊Railsウォッチ(20210614前編)Pumaのgraceful restart、partial_writesコンフィグが非推奨化、Active Recordの楽観的ロックほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Rails: 先週の改修(Rails公式ニュースより)

今回は以下のコミットリストのChangelogを中心に見繕いました。

🔗 ActiveModel::AttributeSet#values_for_databaseActiveRecord::Base#attributes_for_databaseが追加


つっつきボイス:「追加されたattributes_for_databaseメソッドは、instantiateしてattributesしたものが元オブジェクトのattributesに完全に等しくなる形で取るのに使えるようですね: シリアライズ <-> デシリアライズの等価変換保証のための機能という感じ」「お〜」「before_type_castだと生値かつreaderなので触れないから、シリアライズしたもので取れるメソッドを用意したということだと思います」

参考: APIドキュメント instantiateActiveRecord::Persistence::ClassMethods


要約: レコードをinstantiateで再生成するときに使えるような、レコードの属性を返すメソッドが欲しかった。知っている限りではそういうメソッドがなかったので追加した。
今やっているシリアライズでは、レコードの属性を取り出してレコードの再作成に使えるような形でシリアライズを行おうとしている。その基準は、シリアライザを外部から見たときにMarshalと完全に同じように振舞うこと。
当初はfoo.attributes_before_type_castでシリアライズし、Foo.instantiate(attributes_before_type_cast)でデシリアライズしてみるとjson属性で渡すキーがシンボル値のときに動かないことがわかった。foo.attributes_before_type_castは属性をシンボルキーで返すが、Marshal.load(Marshal.dump(...))はjsonカラムがstring値のキーを持つインスタンスを返す。

ここで問題なのはattributes_before_type_castで、名前からもわかるように型キャストする前の属性を返しているが、自分たちはデータベース内にある属性をそのままinstantiateに渡したい。

このプルリクはそれ用のattributes_for_databaseを追加する。このプロパティは以下のように一意の形で元に戻せる。

Foo.instantiate(foo.attributes_for_database).attributes == foo.attributes

言い換えれば、このような属性を使えば元のレコードを完全に再作成できる。これはシリアライズで使うのに理想的。
それと合わせてActiveModel::AttributeSet#values_for_databaseも追加した。こちらは値を実際に変換するときに適しているだろう。
同PRより大意

🔗 PostgreSQLのactive?メソッドのSELECT 1を空クエリに変更

# activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L293
      def active?
        @lock.synchronize do
-         @connection.query "SELECT 1"
+         @connection.query ";"
        end
        true
      rescue PG::Error
        false
      end

つっつきボイス:「SELECT 1を空のクエリ(;)に変えるとちょっとだけ速くなったそうです」「なるほど、active?はPostgreSQLのコネクションが生きているかどうかを確認するためだけのメソッドだから、クエリを投げなくてもいいですよね」「言われてみればなるほど」「疎通確認ならこれでイケる👍」「コメントなしで;だけ見ると一瞬バグかと思っちゃいそう」

「参考に張られているGitLab issueに一般的なベンチマークが載ってる↓」「たしかに速くなってますね」

# 同issueより抜粋
# SELECT 1の場合
progress: 30.0 s, 53157.6 tps, lat 0.150 ms stddev 1.418
progress: 60.0 s, 56619.2 tps, lat 0.141 ms stddev 0.022
progress: 90.0 s, 56882.5 tps, lat 0.141 ms stddev 0.010
progress: 120.0 s, 56631.8 tps, lat 0.141 ms stddev 0.027

# 空クエリの場合
progress: 30.0 s, 66476.7 tps, lat 0.120 ms stddev 0.010
progress: 60.0 s, 66723.3 tps, lat 0.120 ms stddev 0.024
progress: 90.0 s, 66661.8 tps, lat 0.120 ms stddev 0.010
progress: 120.0 s, 66596.9 tps, lat 0.120 ms stddev 0.024

🔗 partial_writesコンフィグが非推奨化


つっつきボイス:「今後はpartial_insertspartial_updatesを使うようにとのことです」「partial_writesってそもそも何を行うのかな?」「部分書き込みに関連してそう」「stackoverflowを見るとpartial_updatesメソッドはRails 4.1で削除されたとあるな」(しばらく探す)「メソッドがないなと思ったら、partial_writespartial_updatespartial_insertsはメソッドじゃなくてRailsのコンフィグなのか」「あ、コンフィグでしたか」「insertとupdateコンフィグでパーシャル書き込みの挙動を変えられるようにするためにpartial_writesコンフィグを非推奨化にしたということのようですね」


これによってupdateとcreateで振舞いを変えられるようになる。
たとえば、コンカレントなマイグレーション実行によってカラムのデフォルト値が削除され、それによって古いスキーマを用いているプロセスがinsertに失敗するといった可能性を防ぐために、partial insertを無効にするのが望ましい状況が考えられる。
自分たちはまさにこの問題に遭遇したため、2015年から同様のパッチを走らせている。
partial_insertsをデフォルトで無効にしておきたい気もするが、何か自分の知らないメリットがあるだろうか?
同PRより大意

参考: 3.7 Active Recordを設定する — Rails アプリケーションを設定する - Railsガイド

config.active_record.partial_writes: 部分書き込みを行なうかどうか(「dirty」とマークされた属性だけを更新するか)を指定する論理値です。データベースで部分書き込みを使う場合は、config.active_record.lock_optimisticallyで楽観的ロックも使う必要がある点にご注意ください。これは、更新がコンカレントに行われた場合に、読み出しの状態が古い情報に基づいて属性に書き込まれる可能性があるためです。デフォルト値はtrueです。
railsguides.jpより

🔗 音声チャネル関連の改修


つっつきボイス:「動画音声がらみの改修が2つありました」「動画に音声が入っているかどうかのmetadataと↓Analyzer::AudioAnalyzerクラスが追加されたのね: 音声なしの動画もあるので、これを入れたのはわかる」

# activestorage/lib/active_storage/analyzer/video_analyzer.rb#L26
    def metadata
-     { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio }.compact
+     { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio, audio: audio? }.compact
    end

🔗 Active Storageのsigned_idexpires_in:キーワード引数を渡せるようになった

# activestorage/app/models/active_storage/blob.rb#L154
- def signed_id(purpose: :blob_id)
+ def signed_id(purpose: :blob_id, expires_in: nil)
    super
  end

参考: signed_idActiveStorage::Blob


つっつきボイス:「expires_in:を指定できるようになったんですね: find_signedで期限切れの日時を絞れるようになってる↓」「お〜、これうれしいかも」「いい改修だと思います👍

# activestorage/test/models/attachment_test.rb#103
  test "getting a signed blob ID from an attachment with a expires_in" do
    blob = create_blob
    @user.avatar.attach(blob)

    signed_id = @user.avatar.signed_id(expires_in: 1.minute)
    assert_equal blob, ActiveStorage::Blob.find_signed!(signed_id)
  end

  test "fail to find blob within expiration date" do
    blob = create_blob
    @user.avatar.attach(blob)

    signed_id = @user.avatar.signed_id(expires_in: 1.minute)
    travel 2.minutes
    assert_nil ActiveStorage::Blob.find_signed(signed_id)
  end

🔗Rails

🔗 Railsアプリのディレクトリ編成(Ruby Weeklyより)


つっつきボイス:「Railsを10年やってきた結果こういうディレクトリ構成に落ち着いたという感じの記事みたいです」「ざっと眺めた感じでは、Railsガイドとだいたい同じようなことを形を変えて書いているように見えますね」「特に変わったことはやってなさそうかも」

参考: Ruby on Rails ガイド:体系的に Rails を学ぼう


  • concernは使っている。悪く言われることもあるけど、concernそのものではなく「よくないconcern」が問題なのだと思う(関連記事)。
  • バックグラウンドジョブ: ジョブワーカーもコントローラ同様できるかぎり薄くしている(関連記事)。ワーカーにいろんなことをさせるのではなく、何かを呼ぶだけにとどめておくべきと信じている。
  • JavaScriptはできるだけ使わないようにしている。使うときはStimulusでPOJO(plain old JavaScript Object)にすることが多い。
  • テスト: RSpecを使っている。TDDの学習には随分時間をかけたが全然使ってない。テストのほとんどはmodel specとsystem spec。
  • Service Object: 人気はあるようだけど、自分は使わない。普通にOOPでやっている。多くの人はService Objectを手続き的なコードとしてモデリングしているが、自分は宣言的なオブジェクトでモデリングしている(関連記事)。
    同記事後半より大意

🔗 Pumaをgraceful restartする(Ruby Weeklyより)


つっつきボイス:「Pumaのgraceful restartにもいろいろあるよという記事のようですね」

  • regular restarts: connections are lost, and new ones are established after booting the new application process
  • hot restarts: connections are not lost but remain to wait for the new application server workers to boot
  • phased restarts: current connections finish with old workers and new workers handle new ones
    同記事より

「3番目のphased restartは、自分たちが一般的によく使う意味でのgraceful restartに相当するでしょうね: コネクションがないワーカーは即リスタートするけど、コネクションが生きているワーカーはコネクションが終了するまでリスタートしない」「そうそう、終わるまで待ってくれるリスタート」

「1番目のregular restartはコネクションが切れるタイプで、問答無用でリスタートする: これはgracefulではないでしょうね」「そう思います」「記事でも2番めと3番目をgracefulと呼んでました」

「2番目のhot restartは何だろう?」「アプリケーションサーバーの新しいワーカーが起動するまではコネクションを残す、現在のリクエストは終了させて新しいワーカーで同じものを再度動かそうとする、という感じみたい」「phased restartに近そう」「それぞれにシグナルがあるのね↓」

# 同記事より
$ kill -SIGUSR2 25197 # hot restartの場合
$ kill -SIGUSR1 25197 # phased restartの場合

pumactlでリスタート時にhotやphasedを指定できるともありますね」「へ〜」「他にも、tcp/9293をLISTENしてHTTPで叩けるcontrol-urlを使うと、/restart/phased-restartにHTTPリクエストを送信してリスタートできる」「APIっぽい使い方もできるんですね」「Puma 1.2.2から--controlで同じことができたようですが、puma 5から--control-urlに変わったんですね」「最近のPumaに詳しくなれそうな記事👍

puma/puma - GitHub

「Webサーバー再起動の考え方は、制御の仕方こそ変わっても、ここ10年ほどアーキテクチャレベルでは大きくは変わっていない感じがしますね」「そうそう、Apacheが2.0だった頃を思い出します」「ApacheのMPMでも同じようなことやってた」

参考: マルチプロセッシングモジュール (MPM) - Apache HTTP サーバ バージョン 2.2

🔗 Hotwireスライド


つっつきボイス:「先週のウォッチ20210607で紹介した@willnetさんのHotwire記事↓に、元になったスライドがあったことに今頃気づきました😅」「これと同じかどうかわかりませんが、イベントでも見たような覚えありますね」

参考: まるでフロントエンドの“Rails” Hotwireを使ってJavaScriptの量を最低限に - ログミーTech
参考: クライアント側のJavaScriptを最小限にするHotwire - ログミーTech

「今さらですけどHotwireってJSのフレームワークなんですね」「そうですね、TurboはRailsと強く結合している感じはあります」

HotwireはRailsを「ゼロJavaScript」でリアクティブにできるか?前編(翻訳)

🔗 Active Recordのoptimistic lock(RubyFlowより)


つっつきボイス:「optimistic locが楽観的ロックで、pessimistic lockが悲観的ロックだったかな」「日本語だとわかった😅」「楽観的ロックは、ロックを取得できてもトランザクションが成功するとは限らない(実行開始後に競合して失敗する可能性がある)、悲観的ロックは、ロックを取得できればデッドロックやタイムアウトにならない限りトランザクションが成功する(ロックの取得に失敗する可能性はある)」「そうそう」

参考: 排他制御(楽観ロック・悲観ロック)の基礎  - Qiita
参考: APIドキュメント ActiveRecord::Locking::Optimistic


# 同記事より: ActiveRecord::StaleObjectErrorでエラーを出すようにした例
# PATCH/PUT /products/1 or /products/1.json
def update
  respond_to do |format|
    if @product.update(product_params)
      format.html { redirect_to @product, notice: "Product was successfully updated." }
      format.json { render :show, status: :ok, location: @product }
    else
      format.html { render :edit, status: :unprocessable_entity }
      format.json { render json: @product.errors, status: :unprocessable_entity }
    end
  rescue ActiveRecord::StaleObjectError => _error
    @product.errors.add(:base, "Oops. Looks like the product has changed since you last opened it. Please refresh the page")
    format.html { render :edit, status: :unprocessable_entity }
    format.json { render json: @product.errors, status: :unprocessable_entity }
  end
end

参考: APIドキュメント ActiveRecord::StaleObjectError


前編は以上です。

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

週刊Railsウォッチ(20210608後編)RubyでAppleのLZFSE圧縮データ解凍、AWS Lambda Extensionsが正式リリース、unixgame.ioほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

The post 週刊Railsウォッチ(20210614前編)Pumaのgraceful restart、partial_writesコンフィグが非推奨化、Active Recordの楽観的ロックほか first appeared on TechRacho.

週刊Railsウォッチ(20210615後編)RubyのRBSを理解する、シンボルがGCされないとき、Terraform 1.0リリースほか

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 Rubyの新しい型注釈システムRBSを理解する(Ruby Weeklyより)


つっつきボイス:「記事はRBSとは何かから始めて詳しく解説している感じですね」「JetBrains IDEもRBSに対応するようになりましたし、RBSの記事が増えるのはいいですね👍

ruby/rbs - GitHub

🔗 Codewarsで見かけたRBS

「ところで、今日のWebチーム内ミーティングの発表で取り上げられていたCodewars↓という最近のプログラミング練習サイトはUIも含めてなかなかよくできていて、そこのRubyの練習問題の中にRBSが書かれているものもあったんですよ」「お〜、RBSがそんなところに!」

「全部の問題というわけではなさそうでしたが、たまたま開いたRubyの問題文でRBSを説明に使っていました: RBSのこういう使い方を見たのは初めて」「面白そう〜」「Rubyのバージョンも 2.5.0と3.0.0を選べるんですね」「CodewarsはブラウザエディタのキーバインドをVimやEmacsにもできるところが個人的にポイント高い: そのままだとブラウザのショートカットとぶつかることもあるようですけど」「あるあるですね」


CodewarsのGitHubリポジトリにissue trackerも見つけました↓。

codewars/codewars.com - GitHub

以下はつっつき後に見つけたツイートです。

🔗 YJITの現在(Ruby Weeklyより)

Shopify/yjit - GitHub


つっつきボイス:「Shopifyが作っているもうひとつのRuby JITであるYJITは以前もウォッチで取り上げましたね(ウォッチ20210420)」「Yはyet anotherのY」「MJITの後で始まったプロジェクトだったと思いますが盛んに活動してますね」


同記事より

「この記事の途中でEuruko 2021でのMatzが引用されていて↓、しばらくはRuby言語仕様への追加を控えるそうです」「なるほど、言語仕様を変更するとJITなどのソース自体を解析して処理するような実装にも影響が出てしまうので、そういう方針も大事なのかもしれませんね」

Matz has stated in his recent talk at Euruko 2021 that Ruby would remain conservative with language additions in the near future. We believe that this is a wise decision as rapid language changes can make it difficult for JIT implementations to get off the ground and stay up to date. It makes some sense, in our opinion, for Ruby to focus on internal changes that will make the language more robust and deliver competitive performance in the future.
同記事より


以下のEuroko 2021の動画↓(1:16:50で頭出し)で、Ruby 3.1の言語仕様変更は極力控えめにするとMatzが話しています。

🔗 死なないシンボル(Hacklinesより)


つっつきボイス:「メタプロでシンボルがGCされないことがあるという記事のようです」「mortalが『死すべき定めの』で、タイトルで使われているのがその反対のimmortal(不死身の)ですね」

# 同記事より
$ ruby inmortal.rb
{:T_SYMBOL=>28, :T_STRING=>10136}
{:T_SYMBOL=>38, :T_STRING=>10228}
{:T_SYMBOL=>38, :T_STRING=>7793}  # GC後もシンボルの個数が減っていない

respond_to_missingmethod_missingの中でメソッド新規作成を回避すると、不死身のシンボルが発生しなくなり、インスタンスで必要なものだけになります。
同記事より大意

🔗 その他Ruby

🔗DB

🔗 pg_query: PostgreSQLのSQL文字列からPostgreSQL準拠のパーサーツリーを取り出すgem(Ruby Weeklyより)

pganalyze/pg_query - GitHub


つっつきボイス:「pganalyze.comというサービスが出しているgemだそうです」「こういうパーサーツリー↓を出力できるということは、PostgreSQL内部の挙動を調べるのに使うのかな」

# 同リポジトリより
#=> #<PgQuery::ParserResult:0x00007fb69a958820
  @query="SELECT 1",
  @tree=<PgQuery::ParseResult:
    version: 130002,
    stmts: [
      <PgQuery::RawStmt:
        stmt: <PgQuery::Node:
          select_stmt: <PgQuery::SelectStmt:
            distinct_clause: [],
            target_list: [
              <PgQuery::Node:
                res_target: <PgQuery::ResTarget:
                  name: "",
                  indirection: [],
                  val: <PgQuery::Node:
                    a_const: <PgQuery::A_Const:
                      val: <PgQuery::Node:
                        integer: <PgQuery::Integer: ival: 1>
                      >,
                      location: 7
                    >
                  >,
                  location: 7
                >
              >
            ],
            from_clause: [],
            group_clause: [],
            window_clause: [],
            values_lists: [],
            sort_clause: [],
            limit_option: :LIMIT_OPTION_DEFAULT,
            locking_clause: [],
            op: :SETOP_NONE,
            all: false
          >
        >,
        stmt_location: 0,
        stmt_len: 0
      >
    ]
  >,
  @warnings=[]>

includeフォルダにあるヘッダーファイルは同じくpganalyzeのlibpg_queryのものみたい: これでPostgreSQLのライブラリを参照してクエリパーサーの内部を解析して正規化したツリーを返すネイティブ拡張のように見えますね」「サンプルコード↓にもコネクションの記述がないので、PostgreSQLサーバーがなくても動きそう」

pganalyze/libpg_query - GitHub

# 同リポジトリより
parsed_query = PgQuery.parse("SELECT * FROM users")

# Modify the parse tree in some way
parsed_query.tree.stmts[0].stmt.select_stmt.from_clause[0].range_var.relname = 'other_users'

# Turn it into SQL again
parsed_query.deparse
#=> "SELECT * FROM other_users"

「以下の1番目と2番目のクエリのfingerprintが一致しているのは、SQL文字列では差異があっても、パーサーレベルでは値部分が抽象化されたり、コメントなどSQL実行と無関係なものが取り除かれたりして一致するということでしょうね↓」

# 同リポジトリより
PgQuery.parse("SELECT 1").fingerprint

=> "50fde20626009aba"

PgQuery.parse("SELECT 2; --- comment").fingerprint

=> "50fde20626009aba"

# Faster fingerprint method that is implemented inside the native C library
PgQuery.fingerprint("SELECT ?")

=> "50fde20626009aba"

「このツールを使えばお手製パーサーを作らずにPostgreSQL内部のクエリパーサーの挙動を確実に追えるのがよさそう👍」「こんなツールを作る人たちがいるのか〜」「Ruby以外にもGoやNodeやPython版もありますね」

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

🔗 Fastlyの障害(Publickeyより)


つっつきボイス:「Fastlyの障害はニュースでも取り上げられていました」FastlyのようなCDNサービスはこれまであまり一般に知られていませんでしたけど、これをきっかけにCDNの認知度が高まるかも」

「今回のFastlyの障害でAmazonのECサイトの画像が配信されなくなっていたことで(購入とかは普通にできていたようです)、Amazon自身のECサイトがFastlyを使っていることがわかったのが個人的に発見でした」「あら、自社でCloudFront持っているのに使ってないのか〜」「でもAmazonは今回の障害でCloudFrontを使って復旧させてましたけどね」「最初からそうすればよさそうですけど」「もしかするとFastlyの方が安いなどの事情があって、CloudFrontをバックアップ用にしているのかもしれないと想像してみました」

参考: Amazon CloudFront(グローバルなコンテンツ配信ネットワーク)| AWS

🔗 VarnishのVCL

「FastlyはVarnishをホスティングしてエッジ配信しているサービスですが、自分はほとんど使ったことがなかったので、この機会にちょっと調べてみると、ひとつ驚いたのがVarnishのVCLという設定ファイルをFastlyのユーザーが書けるという点」「え、なかなか大胆ですね」

参考: Varnish (ソフトウェア) - Wikipedia
参考: Fastly VCL 入門 - Qiita

「VCLはnginxのコンフィグと似たような感じで宣言型言語っぽく書けるんですが、ifなどの条件式や正規表現なども書けるのでコンフィグできる範囲が結構大きい」「ユーザーが設定間違えたらまた何か起きたりして」「Fastlyの顧客側の設定変更がきっかけでバグが顕在化したそうですが、VCLの記述自由度が高いことによるかどうかは推測の域を出ません」

参考: CDNのFastly、世界的な障害の原因は「ソフトウェアのバグ」 - CNET Japan

「コンフィグに正規表現が使えるということは、もしかすると最近流行りのCatastrophic Backtrackingが起きたのかも」「VCLの正規表現はそれほど強力ではなかったと思ったんですが、今見るとPerl互換正規表現(PCRE)構文を使っていると書かれているので↓、バックトラックを使うタイプだとしたらその可能性もあるかもしれませんね」

参考: VCL 正規表現早見表 | Fastly ヘルプガイド

はじめての正規表現とベストプラクティス10: 危険な「Catastrophic Backtracking」前編

🔗 CDNを冗長化できるか

「今後こういう事故に備えるにはどうしたらいいんでしょうね」「WebサービスでCDNを使おうとする場合、CDNサービスを複数使って冗長化しようとするととても複雑になりますし、常にスタンバイさせておかないといけないのでコストもかさみますね」「それもそうか」「マルチCDNでディザスタリカバリ的なことをやるのはあまり現実的ではないかなと思います: FastlyのVCLと同じ設定を、他のCDNでも別の言語で書くのも大変ですし」

「どうしてもマルチCDN構成にしたいなら、エッジ側でのコンピューティングを諦めてコンテンツキャッシュだけ行うようにすれば、DNSを切り替えるだけでCDNを切り替えられるでしょうね」「なるほど」「たとえばAWSのRoute 53にはヘルスチェック機能があるので、理論上はマルチCDNをヘルスチェックして失敗したら切り離すことはできそうかなと思いました」

参考: Amazon Route 53(スケーラブルなドメインネームシステム (DNS))| AWS

🔗 Terraform 1.0がリリース(StatusCode Weeklyより)


つっつきボイス:「Terraformって今まで1.0じゃなかったんですか」「リリースノートによると0.15.5から1.0のバージョンアップで、特に大きな変更はなさそうなので、節目の意味で記念にリリースしたのかも」「とにかくおめでとう🎉」「以前のRuboCopもなかなか1.0が出ませんでしたけど、それでもみんな使ってるという意味ではTerraformもそうですね」

🔗 Terraformよもやま話

「ちなみにTerraformコマンドは実行する際のTerraformバージョンをtfstateファイルのバージョンと合わせる必要があります: HashCorpのTerraform Cloudを使うか、事情があってローカルで実行するならtfenvなどの任意バージョンを切り替えられるツールを使うと便利です」「なるほど」

tfutils/tfenv - GitHub

参考: Infrastructure as code with Terraform and GitLab | GitLab

「Terraformでしくじると大変なので、特にtfstateファイルをS3などに保存する設定にしておくのが重要: Terraformを最後に実行した人のtfstateファイルが残っていないと後でつらいことになります」「お〜」「なお、使ったことはありませんが、GitLabにもTerraformのインテグレーションがあります↓」

参考: Terraformのtfstateをざっと理解する - Qiita
参考: Infrastructure as code with Terraform and GitLab | GitLab


後編は以上です。

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

週刊Railsウォッチ(20210608後編)RubyでAppleのLZFSE圧縮データ解凍、AWS Lambda Extensionsが正式リリース、unixgame.ioほか

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

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

Ruby Weekly

Hacklines

Hacklines

StatusCode Weekly

statuscode_weekly_banner

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ(20210615後編)RubyのRBSを理解する、シンボルがGCされないとき、Terraform 1.0リリースほか first appeared on TechRacho.

Ruby 3.1にArray#intersect?メソッドが追加(翻訳)

$
0
0

概要

原著者の許諾を得て翻訳・公開いたします。

なお、Setクラスには以前からintersect?という共通集合の要素を返すメソッドがあります(Ruby 2.1)。

参考: Set#intersect? (Ruby 3.0.0 リファレンスマニュアル)

Ruby 3.1にArray#intersect?メソッドが追加(翻訳)

Rubyで配列を扱っていると、配列同士の交差(intersection)が欲しくなることがよくあります。

2019年のブログ記事ではArray#intersectionというメソッドを取り上げました。これは以下のように、配列同士に共通する要素を配列で返します。

a = [1, 2, 3]
b = [2, 3, 5, 7]
c = [5, 3, 8, 7]

a.intersection(b)
#=> [2, 3]

a.intersection(b, c)
#=> [3]

しかし、配列同士の交差そのものではなく、交差があるかかどうかだけを知りたいときもあります。

改修前

Ruby 3.1より前は、結果をtrueまたはfalseで得るために、intersectionメソッドに#any?メソッドまたは#empty?メソッドをチェインします。

a = [1, 2, 3]
b = [2, 3, 5, 7]

a.intersection(b).any?
#=> true

a.intersection(b).empty?
#=> false

この方法では、配列同士の交差結果を出してから#any?#empty?で評価しています。

改修後

メモリ削減およびパフォーマンス向上のため、Ruby 3.1にArray#intersect?メソッドが追加されました(#1972)。Array#intersect?は、2つの配列に共通要素が1個でもあればtrueを返し、なければfalseを返します。

a = [1, 2, 3]
b = [2, 3, 5, 7]
c = [4, 7, 9]

a.intersect?(b)
#=> true

a.intersect?(c)
#=> false

メモ

#intersectionメソッドは引数に複数の配列を渡せますが、#intersect?メソッドの引数には複数の配列を渡せません。

a.intersect?(b, c)
#=> ArgumentError (wrong number of arguments (given 2, expected 1))

関連記事

Rails 7のenumに新しい構文が導入(翻訳)

The post Ruby 3.1にArray#intersect?メソッドが追加(翻訳) first appeared on TechRacho.

Viewing all 1080 articles
Browse latest View live