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

Railsセキュリティ修正がリリースされました(7.0.2.2、6.1.4.6、6.0.4.6、5.2.6.2)

$
0
0

Ruby on Rails セキュリティ修正(7.0.2.2、6.1.4.6、6.0.4.6、5.2.6.2)がリリースされました。該当バージョンの早急なアップグレードが強く推奨されています。詳しくは公式情報を参照してください。

以下はGItHubのリリースタグです。

以下はコミットの差分です。

🔗 セキュリティ修正の概要

🔗 Action Packで情報漏えいの可能性

影響を受けるRailsバージョン
Rails 5.0.0以降
修正済みバージョン
7.0.2.2、6.1.4.6、6.0.4.6、5.2.6.2

上記修正済みバージョンへのアップグレードが強く推奨されていますが、やむを得ない場合は以下のパッチを当てることも可能であると公式情報に記載されています。

class GuardedExecutor < ActionDispatch::Executor
  def call(env)
    ensure_completed!
    super
  end

  private

    def ensure_completed!
      @executor.new.complete! if @executor.active?
    end
end

# Ensure the guard is inserted before ActionDispatch::Executor
Rails.application.configure do
  config.middleware.swap ActionDispatch::Executor, GuardedExecutor, executor
end

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

関連記事

Rails 7.0.2がリリースされました

The post Railsセキュリティ修正がリリースされました(7.0.2.2、6.1.4.6、6.0.4.6、5.2.6.2) first appeared on TechRacho.


週刊Railsウォッチ: Bundler自身のバージョンロック機能、gem署名メカニズムの提案ほか(20220216後編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 提案: gemに署名するメカニズム(Ruby Weeklyより)


つっつきボイス:「Shopifyの記事です」「お、gemに署名を付けやすくするしくみについての提案のようですね」「なるほど」「gemへの署名を何らかの形で自動化しないとgem作者がつらくなるからでしょうね」「署名の手間を軽減する提案なんですね」「gemに邪悪なコードを入れられてしまう問題はときどき起きるので、gemへの署名はたしかに必要: この間のrubygems.orgのAPIキー発行の改良もその流れの一環かもしれませんね(ウォッチ20220209)」

「記事が長いので流し読みの範囲ですが、たとえばトークンを適切な時期に失効させる議論もしていそう」「その辺大事そうですね」「有効期限が長過ぎるトークンはそれ自体セキュリティ上よくないので」

🔗 二要素認証アプリよもやま

「ところで話は変わるんですが、この間iPhoneをやむを得ず初期化したときに、Google Authenticatorアプリに登録した二要素認証の設定が飛んでしまって、他の二要素認証は手動で復元したものの、Rubygems.orgの二要素認証の復元方法が見当たらなくて、ruby-jp Slackで教えていただいたサポートのメアドに今問い合わせているところです😢」「ありゃ〜それは大変そう」「リカバリーキーは持っているのでログインはできるんですが、二要素認証を解除する画面でワンタイムパスワードを求められてしまいました」

「そうならないためにはクラウド上に設定を保存できる二要素認証アプリを使うのが確実でしょうね」「はい、Rubygems.orgのヘルプページにもそういうアプリの方がいいよと注意書きがありました😅」「Google Authenticatorのように特定デバイスのみで使える二要素認証の方がたしかにセキュアですけど、デバイスを紛失したり使えなくなったときが痛い」「たしかに」

「ちなみに1Passwordには二要素認証機能もありますよ」「え、自分も1Passwordの有料ユーザーですけどそんな機能が入っているんですか?自分もそうしようかな…🤔

参考: 家族、ビジネス、チームのためのパスワード管理ツール | 1Password


追いかけボイス:

ごもっともでした🙏。Rubygems.orgで紹介されていたAuthyやAuthenticator Plusなどのクラウドにデータを置く二要素認証アプリを検討してみることにします。

参考: 「Twilio Authy」をApp Storeで
参考: Authenticator Plus


その後Rubygems.orgのサポートから連絡をいただき、二要素認証を復元できました🙇。リカバリーキーをワンタイムパスワードの欄に入力するのがポイントでした。

参考: Setting up multi-factor authentication – RubyGems Guides

🔗 RubyコードのWasmをブラウザ実行するのに1日かからなかった(Ruby Weeklyより)


つっつきボイス:「RubyをWasmとブラウザで動かす記事だそうです」「他のスクリプト言語の詳しい事情はわかりませんが、少なくともRubyは多くの基本機能が本体に入っているので、こういうときに外部gemを追加しなくてもかなりのことができるのがいいですよね」

🔗 依存関係の混乱による脆弱性と戦う(Ruby Weeklyより)


つっつきボイス:「これもShopifyの記事で、600以上のアプリで依存関係の混乱による脆弱性を解決したそうです」「Shopify頑張ってる」「Bundlerでの依存関係の混乱ってこういう感じのヤツか↓」「こういうのって自分で見ていてもわからなくなりがち」「このgemとこのgemが特定バージョンの組み合わせでインストールできなくなったりすることってたまにありますよね」

# 同記事より: 新しいバージョン
GEM
  remote: https://example.com
  specs:
    private_gem (1.8.0)
      dependency_1
      dependency_2 (>= 2.7, < 4)

GEM
  remote: https://rubygems.org/
  specs:
    public_gem (6.1.4)
      dependency_1
# 同記事より: 古いバージョン
GEM
  remote: https://example.com
  remote: https://rubygems.org/
    specs:
       private_gem (1.8.0)
         dependency_1
         dependency_2 (>= 2.7, < 4)
       public_gem (6.1.4)
         dependency_1

「普段のBundlerは何も考えずに楽に使えますけど、ひとたび問題が起きるとトラブルシューティングで手間取りがち」「そうそう」「古いRailsプロジェクトで起きがちなのも大変…」「ちょうど次の記事もBundler関連です」

🔗 Bundler v2.3でBundler自身のバージョンがロックされるようになった(Ruby Weeklyより)


つっつきボイス:「Bundler v2.3からBundler自身のバージョンをロックするようになったそうです」「今までは該当バージョンのBundlerがなかったときは最新バージョンにフォールバックしていたのが、v2.3からはBUNDLED WITHのバージョンどおりのBundlerを使うようになったのね」


「Bundlerはある時期からGemfile.lockの末尾にBUNDLED WITHを追加するようになりましたけど↓、メンバーの誰かが不用意にローカルでbundle installしたものをコミットしたりするとCIが止まったりすることがありましたね」「あ〜そうそう」「あれはマジでつらい😢

# Gemfile.lockの末尾の例
RUBY VERSION
   ruby 3.1.0p0

BUNDLED WITH
   2.3.5

「この問題が起きるとBundlerが期待どおりに動かなくなるので、BUNDLED WITHを取り入れるなら互換性を失わない形にして欲しかったとよく思ったものです」「今の自分の環境でBundlerがうまく動かないのってもしかするとこの辺の問題なのかも…」

「BundlerがRuby本体に取り入れられてgem install bundlerしなくてよくなってからはあまり起きなくなりましたけど、それまではメンバーごとにBundlerのバージョンが合っていない可能性がちょくちょくあった」「そんな時代もありましたね」「それでも個別にBundlerを手動でインストールすると起きたりしますけどね: レビューで気づけばいいけど見逃すとCIが死ぬ」

🔗 その他Ruby


つっつきボイス:「最近流行りのWordleをRubyで解いてみたそうです」「英語圏でどえらく流行してますよね」「まだ遊んでないけど」「自分も」

参考: Wordle – The New York Times

「Wordleはノリ的には女神転生のコードブレイカーっぽいかも」「リモートワーク流行りのおかげで盛り上がってるのかもしれませんね」

参考: コードブレイカー – 真・女神転生IMAGINE攻略Wiki
参考: code breakerの意味・使い方・読み方 | Weblio英和辞書 — code breakerは推理小説のジャンルでもあるようです


「ついでにゲームの話をすると、最近のイベントで見たこのGeoGuessrというゲームを集団で解く動画がものすごかった↓」

参考: GeoGuessr | AGDQ2022 – Twitch

「ジオゲッサーという名前からして、Googleマップのストリートビューを見て場所を当てるとか?」「そうそうそんな感じ: 推測した場所を地図上でクリックして正解の場所と距離が近いほど高得点になる」「お〜なるほど」「最初のうちはエッフェル塔みたいなわかりやすい名所ですけど、だんだんどこの山だか砂漠だかわからないような場所に放り出される」「うまいところに目を付けたゲームですね」「元の動画は英語ですけど日本語で解説されてます」

🔗DB

🔗 mini_sql: 最小限のSQLエグゼキュータ(Ruby Weeklyより)

discourse/mini_sql - GitHub


つっつきボイス:「Discourseのgemだそうです」「PostgreSQLとSQLiteのインターフェイスを持つ、よくある感じのシンプルなクエリエグゼキュータのようですね: プレースホルダ機能や、クエリ結果をeachで回すみたいなことができるらしい↓」「デコレータを足してRubyっぽく書けるんですね」

# 同リポジトリより
pg_conn = PG.connect(db_name: 'my_db')
conn = MiniSql::Connection.get(pg_conn)

puts conn.exec('update table set column = 1 where id in (1,2)')
# returns 2 if 2 rows changed

conn.query("select 1 id, 'bob' name").each do |user|
  puts user.name # bob
  puts user.id # 1
end

# extend result objects with additional method
module ProductDecorator
  def amount_price
    price * quantity
  end
end

conn.query_decorator(ProductDecorator, "select 20 price, 3 quantity").each do |user|
  puts user.amount_price # 60
end

p conn.query_single('select 1 union select 2')
# [1,2]

p conn.query_hash('select 1 as a, 2 as b union select 3, 4')
# [{"a" => 1, "b"=> 1},{"a" => 3, "b" => 4}

p conn.query_array("select 1 as a, '2' as b union select 3, 'e'")
# [[1, '2'], [3, 'e']]

p conn.query_array("select 1 as a, '2' as b union select 3, 'e'").to_h
# {1 => '2', 3 => 'e'}

「mini_sqlは昔にさかのぼったかのようなデータベースコネクションライブラリという感じかな」「そういえばRuby Weeklyではsafeと紹介されていました」「どれどれ、mini_sqlは毎回できるだけ結果セットをクリアするからmemory bloatしにくいというのがsafeということみたい」「あ、そういうことですか」「ちなみに、一般的にアプリケーション側のメモリ効率としては、結果セットを一気に全部取り出すよりSQLのカーソルで取り出す方が有利」

参考: Ruby’s external malloc problem

「mini_sqlはおそらくeachで結果セットを取り出すたびに結果セットをクリアするんじゃないかな: lib/の下のコードを見てみるとquery_eachで毎回result.clearを呼んでいる↓」「なるほど、こまめに片付けてmemory bloatを避けているんですね」「GCに任せてもいいんですが、GCされるまではメモリに乗り続けるので、明示的にクリアする方が片付くのは早いでしょうね」

# https://github.com/discourse/mini_sql/blob/main/lib/mini_sql/postgres/connection.rb#L106
      def query_each(sql, *params)
        raise StandardError, "Please supply a block when calling query_each" if !block_given?
        if params && params.length > 0
          sql = param_encoder.encode(sql, *params)
        end

        raw_connection.send_query(sql)
        raw_connection.set_single_row_mode

        loop do
          result = raw_connection.get_result
          break if !result

          result.check

          if result.ntuples == 0
            # skip, this happens at the end when we get totals
          else
            materializer ||= deserializer_cache.materializer(result)
            result.type_map = type_map
            i = 0
            # technically we should only get 1 row here
            # but protect against future batching changes
            while i < result.ntuples
              yield materializer.materialize(result, i)
              i += 1
            end
          end

          result.clear
        end
      end

「mini_sqlはどんなときに使えるんでしょうか?」「少なくともActive Recordの代わりに使うものではないと思いますが、プレースホルダのような最小限の機能は備わっているので十分コードは書けますね: 十数年前のPHPでもmysqli_fetch_assocとかでこんな感じでコードを書いてましたよ」「あ〜、PHPerだったので今のですべて理解できました、あのノリですね」

参考: PHP: mysqli_result::fetch_assoc – Manual

「毎回メモリをクリアするということは効率は高い代わりに当然速度が犠牲になるので、mini_sqlが合うかどうかは用途次第ということになるんでしょうね」「そうそう、ものすごく巨大なデータセットから取り出したいときとか、メモリが潤沢でない環境で使いたいとか」

「Railsはメモリが潤沢な環境が前提なので、極端なことをしない限りそうそうスラッシングしませんけど、たとえばAWS Lambdaのようにメモリが限られたFaaS的環境で巨大なデータセットからちょっぴり取り出したいときだったら、mini_sqlのようにこまめにクリアするアプローチが合うかも👍」「なるほど」「型変換とかはできないので、そういう機能が豊富なActive Recordとはあり方からして違うでしょうね」

参考: スラッシング – Wikipedia

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

🔗 Google CloudでサーバーレスSparkが一般公開(Serverless Statusより)


同記事より


つっつきボイス:「お、Sparkだ」「GoogleのBigQueryからも使えるようになるんですって(現時点では非公開プレビュー)」「Sparkって何でしたっけ?」「Apache Projectのひとつであるオープンソースの分散フレームワークですね」「言われてみればApacheのサイトでロゴを見たことあったかも」「そのSparkをサーバーレスで使えるようになったのか」

参考: Apache Spark™ – Unified Engine for large-scale data analytics
参考: Apache Sparkとは何か――使い方や基礎知識を徹底解説:Amazon EMRで構築するApache Spark超入門(1)(1/3 ページ) – @IT

「SparkはHadoopのお仲間的な感じ」「HadoopはたしかHDFSが元になっていましたね」

参考: 分散処理技術「Hadoop」とは:NTTデータのHadoopソリューション
参考: HDFS(Hadoop Distributed File System) とは | 技術関連用語集 | BBTowerレポート | 株式会社ブロードバンドタワー

「AWS S3はHDFSでもあるそうなので、EMRでHDFSを使いたいときはS3に置けばいいというのが魅力的だなと思いました: S3にJSONとかを置いたらHadoopが読み込んでくれるようにできる」「HadoopのストレージをS3で代替できる?」「はいそんな感じです: AWSでHadoopを使いたければECS上にHDFSのクラスタを構築しないといけないのかなと思ってたら、それS3でできるよということでした」「へ〜」

参考: Amazon EMR(Hadoopなどのビッグデータフレームワークを簡単に実行)| AWS

「言われてみればS3には当初からHFDSがあったかも」「そうそう、ありましたね」「S3の中身はHDFSとかなり違いそうな気もしますけど、オーバーレイネットワーク上でマルチノードをまたがる単一のファイルシステムを構築するという意味では、S3とHDFSがやりたいことはそんなに遠くないかも」「中身は違ってそうですけどそんな感じですね: Sparkはそれをメモリ上に構築するヤツです」「ははぁなるほど」

「サーバーレスのSparkってペタバイト級のメモリでも使えるのかな?」「Googleならやれそうな気もしますけどね: お金次第でしょうけど」

🔗 GCPとAWS

「何度か話していますが、GCPのあり方はAWSといろいろ違うので使う側の学び方も違ってきますよね: AWSは責任共有モデルで、基本的にコンピュテーションパワーやインフラやAPIの部分だけを提供して、その上で何をするかは自由だけど責任もユーザー側にある、FaaS的なサービスもランタイムまでは用意するけど運用やメモリ管理などはユーザーに委ねるというスタンス」「そうそう」

参考: 責任共有モデル | AWS

「GCPの印象は、Googleのサポートがアプリケーションレイヤまで及んでいる感じで、むしろアプリケーションライブラリ的な側面を感じる」「わかります」「アプリケーションエンジニアとしては、GCPにどっぷり浸かる方がいろいろ便利に使えるのかもしれませんね」

「その分GCPはアプリケーションライブラリのドキュメントをみっちり読まないといけないんですが」「それそれ!ドキュメントをもうちょっと親切に書いて欲しいです😢

「逆にAWSはLinuxの知識があれば基本的にやれますよね(AWS自体の知識を別にすれば)」「ほんとそう😆」「AWSはLinuxの知識と経験がそのまま通用するし新サービスの概要もつかみやすいけど、GCPの場合はまずサービスのパラダイムから確認しないといけない感じ」「そこなんですよ」「結果セットが1000件以上のときはこっちのメソッドを使うこと、みたいなのをGCPで見かけたりしますね」

🔗言語/ツール/OS/CPU

🔗 IntelがRISC-Vに大規模投資


つっつきボイス:「今さらだけどRISC-Vってリスクファイブって読むんですね」「RISC-Vはオープンソースの命令セットアーキテクチャ(ISA)」「Intelがそこにドカンと投資したのか」(しばしItaniumなどの話題)

参考: RISC-V – Wikipedia

「ツイートにもありますけど、量販店で売られているような民生用機器の組み込み系で最近RISC-Vが続々使われ始めているのが自分的にとてもアツい」「たしかにワイヤレスイヤフォンとかもろ民生用ですね」「そうなるとARMの組み込み市場と完全に競合するので、ARMや日本のルネサスあたりが今後どうなるかも含めて気になっています」「黒船感ありますね」

参考: ARMアーキテクチャ – Wikipedia
参考: ルネサスエレクトロニクス – Wikipedia


後編は以上です。

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

週刊Railsウォッチ: Rubygems.orgのAPIキーに権限スコープが追加、RailsのDBパフォーマンス改善ほか(20220209後編)

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

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

Ruby Weekly

Serverless Status

serverless_status_banner

The post 週刊Railsウォッチ: Bundler自身のバージョンロック機能、gem署名メカニズムの提案ほか(20220216後編) first appeared on TechRacho.

Rails 7: importmap-rails + tailwindcss-railsでnode.jsが不要な理由

$
0
0

Rails 7 : rails newのフロントエンド関連オプションの組み合わせを調べてみた

上の記事に書いたように、Rails 7でimportmap-railsを使う場合、node.jsは不要になり、node_modules/ディレクトリも作成されません。

# デフォルトでimportmap-railsがインストールされる
$ rails new . 

ちょっと不思議だったのは、importmap-railsとtailwindcss-railsを組み合わせた場合にもnode.jsが不要なことです。なお、このセットアップではconfig/importmap.rbにtailwindのエントリは入らず、app/assets/builds/の下にtailwind.cssがビルドされます。つまりCDNは参照していません、

# importmap-railsとtailwindcss-rails gemがインストールされる
$ rails new . -c tailwind

どうしてimportmap-railsとtailwindcss-railsの組み合わせだとnode.jsが不要なんでしょう?

なお、esbuild・webpack・rollupのいずれかを使う場合は、tailwindcssはgemとしてではなくyarn add tailwindcssでインストールされます。当然node_modulesも作成されます。

# tailwindcssはyarn add tailwindcssでインストールされる
$ rails new . -c tailwind -j esbuild

tailwindcss-cliのバイナリ版がリリースされていた

rails/tailwindcss-rails - GitHub

tailwindcss-railsのREADMEを見ると、冒頭にこんなことが書いてありました。

This gem wraps the standalone executable version of the Tailwind CSS 3 framework. These executables are platform specific, so there are actually separate underlying gems per platform, but the correct gem will automatically be picked for your platform. Supported platforms are Linux x64, macOS arm64, macOS x64, and Windows x64. (Note that due to this setup, you must install the actual gems – you can’t pin your gem to the github repo.)
tailwindcss-rails READMEより

何と、本家tailwindcssで、tailwindcss-cliのスタンドアロンバイナリがアーキテクチャごとにリリースされるようになっていたそうです。2021年12月5日のv3.0.3からなので、ごく最近の話ですね。

tailwindlabs/tailwindcss - GitHub

現時点では、以下のバイナリがリリースされています。

  • Linux(x64)
  • Linux(Arm64)
  • macOS(x64)
  • macOS(Arm64)
  • Windows(x64)

tailwindcss-rails gemは環境に応じたtailwindcss-cliバイナリを選んでインストールするので、node.jsがなくてもビルドを実行できるということがわかりました。対応するバイナリがないレアな環境だと利用できないことになります。

Procfile.devで呼び出されるtailwindcss:watchは、つまりこのバイナリを実行しているんですね。

# Procfile.dev
web: bin/rails server -p 3000
css: bin/rails tailwindcss:watch

なお、tailwindcss-railsのrakelib/package.rakeでバイナリを指定しています↓。

このtailwindcss-cliバイナリがtailwindcss-rails gemに取り入れられたのは12月18日でした↓。Rails .7.0.0のリリースが12月16日だったので7.0.0には惜しくも間に合いませんでしたが、以後普通に利用できます。

当初はx64系のみでしたが、Arm64系バイナリにも対応しています。

Rails環境では、必要に応じてbundle exec tailwindcssも実行できるそうです。importmap-rails環境では直接使う機会はあまりなさそうですが。

$ bundle exec tailwindcss --help

tailwindcss v3.0.7

Usage:
   tailwindcss [--input input.css] [--output output.css] [--watch] [options...]
   tailwindcss init [--full] [--postcss] [options...]

Commands:
   init [options]

Options:
   -i, --input              Input file
   -o, --output             Output file
   -w, --watch              Watch for changes and rebuild as needed
       --content            Content paths to use for removing unused classes
       --postcss            Load custom PostCSS configuration
   -m, --minify             Minify the output
   -c, --config             Path to a custom config file
       --no-autoprefixer    Disable autoprefixer
   -h, --help               Display usage information

関連記事

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

The post Rails 7: importmap-rails + tailwindcss-railsでnode.jsが不要な理由 first appeared on TechRacho.

Ruby: パーセント記号 `%` の使い方まとめ

$
0
0

更新情報

  • 2019/03/01: 初版公開
  • 2019/09/04: Ruby 2.6で追加されたstepのエイリアスとしての%を追記
  • 2022/02/10: Ruby 3.1.0で更新

Ruby公式ドキュメントに記載されている`%(パーセント)記号の説明の中から、以下についてまとめました。なお、検証にはRuby 2.6.1とRuby 3.1.0を使いました。

  1. 剰余(割り算の余り)を表す %
  2. %記法リテラル記法)で使う %
  3. 出力フォーマット(書式設定)で使う %
    • printf%
    • String#%メソッド
  4. String#%メソッドへの3種類の引数渡し方法
  5. Range#stepのエイリアスとしての%(2.6〜)

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


なお公式ドキュメントにある% ruby -e "puts 'Hello'"という%の用い方は説明文向けの記法であり、それが「コマンドラインへの入力を表す」という目印でしかありません(実を言うと実際に見かけた記憶がありませんが)。

ちょうど、説明文でのみインスタンスメソッド名に#を付けて#to_sのように書き、実際のRubyスクリプトではそう書かないのと似ています。

🔗 1. 剰余の%

剰余(割り算の余り)演算を表す%は比較的単純です。レシーバー(左辺)と引数(右辺)が数値の場合に剰余演算子と解釈されます。

12 % 5
#=> 2

12.0 % 5
#=> 2.0

12.0 % 5.1
#=> 1.8000000000000007

12 % 5.1
#=> 1.8000000000000007

12r % 5r
#=> (2/1)

一方が複素数の場合はさすがに未定義エラーになります。

Complex(3.14, 0) % Complex(3.14, 0)
#=> test.rb:1:in `<main>': undefined method `%' for (3.14+0i):Complex (NoMethodError)
#=>Complex(3.14, 0) % Complex(3.14, 0)
#=>                 ^

🔗 2. %記法の%

ここから少々ややこしくなります。

Rubyでは、%qなどのように%の後ろの1文字で機能を指定し、それに続く記号ペアで囲むことでさまざまなリテラルを表現できます。

囲みに使う記号ペアは、{}[]のように対になっているものも、!!のように同じ記号を使うこともできます。

%q<Hello>
#=> "Hello"   -- 対になる記号で囲んだ場合

%q!World!
#=> "World"   -- 同じ記号で囲んだ場合

%記法で記号ペアを適宜カスタマイズすると、たとえば二重引用符"と一重引用符'が混在するメッセージで自動的に引用符をエスケープしてくれます。エスケープを書かずに済むので便利です。

%q{Warning: "invalid character '%' has been found" }
#=> "Warning: \"invalid character '%' has been found\" "

その気になればバッククォートやバックスラッシュ、%自身すら囲み記号として使えます。

%q`World`   #=> "World"
%q\World\   #=> "World"
%q%World%   #=> "World"

ただし記号ペアに使えるのはASCIIの記号に限るようです。以下は全角疑問符でエラーになった例です。

%q?World?
#=> unknown type of %string (SyntaxError)
#=> %q?World?
#=> ^~~~~

🔗 %記法の機能一覧

参考: Ruby Programming/Syntax/Literals - Wikibooks, open books for an open world

以下の説明では簡単のため囲みを[ ]で代表しています。上述のとおり、他にもさまざまなASCII記号を囲みに使えます。

なお、Rubyの式展開(string interpolation)は、二重引用符" "で囲まれた文字列リテラル内に#{ 式 }の形で式を記述することで、式が評価された結果を展開できる機能です。一重引用符' 'では式は展開されません。

以下にいくつもの記法がありますが、「式展開できるか」「\でエスケープしたり特殊文字を置けるか」の2点に注目すると理解しやすいと思います。


%[ ]%Q[ ]でも同じ)による二重引用符文字列リテラルの代替

表記 意味 式展開 その他
%[ ]
%Q[ ]
二重引用符" "で囲むのと同等 できる \でエスケープや特殊文字を表せる
  • 二重引用符の文字列リテラルと同様、式展開が使えます。
# 式展開できる
%[Time: #{Time.new}]
#=> "Time: 2019-02-27 16:35:58 +0900"

# 式展開できる
%Q[Time: #{Time.new}]
#=> "Time: 2019-02-27 16:35:58 +0900"
  • \nで改行を表すことも、囲み文字を\[\]のようにエスケープすることもできます。
# \nで改行を表したり、囲み文字を\[や\]でエスケープできる
puts %[a\n\[\]b]
a
[]b
  • 以下の場合にエスケープなしで記号を使えます(式展開#{}をうっかり作らないようにしましょう)。
# 開始/終了が異なるペアの囲み記号で、かつ正しく入れ子になる場合はエスケープなしでもよい
%[a[bracket]b]
#=> "a[bracket]b"

# 二重引用符とバックスラッシュを除く記号はエスケープなしで書ける
%[!\#$%&'()*,-./:;[<>]?@^_`{|}~\\\"]
#=> "!\#$%&'()*,-./:;[<>]?@^_`{|}~\\\""
  • 以下の場合はエスケープしないとエラーになります。
# 囲み記号が正しく入れ子になっていない場合
%[abracket]b]
#=> syntax error, unexpected local variable or method, expecting end-of-input (SyntaxError)
#=> %[abracket]b]
#=>            ^

# 開始/終了が同じ記号の場合
%!abracket!b!
#=> syntax error, unexpected method, expecting end-of-input (SyntaxError)
#=> %!abracket!b!
#=>            ^~

🔗 %qによる一重引用符文字列リテラルの代替

表記 意味 式展開 その他
%q[ ] 一重引用符' 'で囲むのと同等 できない \でエスケープや特殊文字を表現できない
\は単なる文字扱い)
  • 式展開は行われません(一重引用符と機能が同等なので)。
    • なお、IRBなどでは、%qと一重引用符のどちらも二重引用符として出力されます。
# 式展開されない
%q[Time: #{Time.new}]
#=> "Time: \#{Time.new}"
  • \が無視されますので、\nなどもそのまま出力されます。
# \nで改行にならずそのまま出力される
puts %q[a\nb]
a\nb

🔗 その他の%記法

上の2つを押さえておけば後はさほどではないと思いますので、残りは簡潔にとどめます。

表記 意味 式展開 その他
%r[ ] 正規表現リテラル //と同等 できる \でエスケープや特殊文字を表現できる
・末尾にフラグを追加できる
%i[ ] スペース区切りの文字列をシンボルの配列にする できない ・Ruby 2.0以降
%I[ ] スペース区切りの文字列をシンボルの配列にする できる \でエスケープや特殊文字を表現できる
・Ruby 2.0以降
%w[ ] スペース区切りの文字列を二重引用符" "で囲まれた語の配列にする できない
%W[ ] スペース区切りの文字列を二重引用符" "で囲まれた語の配列にする できる \でエスケープや特殊文字を表現できる
%x[ ] シェルでコマンドを実行する(` `と同等) できる
(セキュリティに注意)
%s[ ] 文字列全体を1つのシンボルに変換する できない
# 正規表現(式展開/エスケープ記法あり)
%r[課長代理補佐|課長代理|#{Time.now}]
#=> /課長代理補佐|課長代理|2019-02-27 18:15:11 +0900/

# シンボルの配列
%i[yamboo marboo tenkiyohoo]
#=> [:yamboo, :marboo, :tenkiyohoo]

# シンボルの配列(式展開/エスケープ記法あり)
%I[yamboo marboo #{Time.now}]
#=> [:yamboo, :marboo, :"2019-02-27 18:19:44 +0900"]

# 文字列の配列
%w[yamboo marboo tenkiyohoo]
#=> ["yamboo", "marboo", "tenkiyohoo"]

# 文字列の配列(式展開/エスケープ記法あり)
%W[yamboo marboo #{Time.now}]
#=> ["yamboo", "marboo", "2019-02-27 18:21:02 +0900"]

# シェルコマンドの実行
%x[uname]
#=> "Darwin\n"

# シェルコマンドを変数の式展開で実行(危険: やってはいけません)
userinput = "rm -rf /"
%x[#{userinput}]
#=> ☠

# 文字列全体をシンボルにする
%s[yamboo marboo tenkiyohoo]
#=> :"yamboo marboo tenkiyohoo"

🔗 3. 出力フォーマット用の%

ある意味最もややこしいのが、出力フォーマットで使われる%記号です。

まずは基本形から。

"i = %d" % 10
#=> "i = 10"

"i = %d" % -10
#=> "i = -10"

二重引用符に囲まれた部分"i = %d"%dは、渡された数値を十進数で出力するという書式指示になります。

以下のように小数値10.0を渡しても、出力は10になります。

"i = %d" % 10.0
#=> "i = 10"

String#%メソッド

上の式には%が「もうひとつある」ことにお気づきでしょうか。これは実はString#%というメソッドです。String#%は書式を設定して\dなどに渡します。標準ライブラリのメソッドなので、Rubyの構文ではありません。

この二重引用符で" "で囲まれた文字列では、この%メソッドがないとエラーになります。カンマを置いてもだめです。

"i = %d" 10.0
#=> syntax error, unexpected float literal, expecting end-of-input (SyntaxError)
#=> "i = %d" 10.0
#=>          ^~~~
"i = %d", 10.0
#=> syntax error, unexpected ',', expecting end-of-input

メソッドである証拠に、以下のように"文字列".%と書いても動作は変わりません。つまり"文字列"はメソッドのレシーバーということになります。

"i = %d".% 10.0
#=> syntax error, unexpected ',', expecting end-of-input (SyntaxError)
#=> "i = %d", 10.0
#=>         ^

実を言うと、この2番目の%が何なのかわからなかったのでこの記事を書いたのでした。

ただし、printfメソッドやsprintfメソッドの引数の形になっていれば、以下のように%メソッドとカンマ,区切りのどちらでも書けます。

printf("i = %d\n", 10)
#=> i = 10

printf("i = %d\n" % 10)
#=> i = 10

sprintf("i = %d", 10)
#=> "i = 10"

sprintf("i = %d" % 10)
#=> "i = 10"

%を使った書式指定について詳しくは以下の公式ドキュメントを参照いただければと思います。

参考: String#% (Ruby 3.1 リファレンスマニュアル)

🔗 4. String#%メソッドへの3種類の引数渡し方法

ここでは、String#%メソッドへの引数渡しについてまとめます。同ドキュメントの末尾で「利用頻度が低いので最後に説明します」と記載されていますが、そこそこ利用されているように思えます。

  1. "%": 引数(配列)の順序で指定
  2. "%<名前>フォーマット指定": 引数(ハッシュ)のキーで指定(フォーマットあり)
  3. "%{名前}": 引数(ハッシュ)のキーで指定(フォーマットなし)

🔗 1. 引数(配列)の順序で指定

先ほどの%d" % 10.0の引数10.0は、実際には配列として扱われます。つまり、レシーバーの文字列リテラルで%フォーマットが複数ある場合は、以下のように明示的に[ ]などで配列リテラルにすることで渡せます。なお、Rubyでは引数を囲む丸かっこを省略できます。

  • レシーバー(文字列リテラル): "%フォーマット"
  • String#%メソッド
  • 引数: 配列 [ ]
"%d %f" % [1, 2]
#=> "1 2.000000"

"%d %f" % ([1, 2])
#=> "1 2.000000"
  • もちろん引数が1つの場合も配列の形にできます。引数が1つであれば[]は省略できます。
"%d"    % [1]      #=> "1"
"%d"    % ([1])    #=> "1"
"%d"    % 1        #=> "1"
  • 配列リテラルの要素が複数の場合は[ ]省略できません。囲まないとカンマ区切りを正しく処理できません。丸かっこ()で囲むだけではダメです。
# 以下はできない
"%d %f" % 1, 2
#=> syntax error, unexpected ',', expecting end-of-#=> input (SyntaxError)
#=> "%d %f" % 1, 2
#=>            ^

"%d %f" % (1, 2)
#=> syntax error, unexpected ',', expecting ')' (SyntaxError)
#=> "%d %f" % (1, 2)
#=>             ^

もちろん、配列を変数に入れて渡すのはOKです。

ary = [1, 2]
"%d %f" % ary
#=> "1 2.000000"

🔗 2. 引数(ハッシュ)で名前付き引数的に指定

以下のように、ハッシュの形式で引数を渡すことで、名前付き引数的に値を渡せます。%<名前>という<>を使った書式であり、直後にprintf系のフォーマット指定文字を追加できます。

  • レシーバー(文字列リテラル): "%<名前>フォーマット"
  • String#%メソッド
  • 引数: ハッシュ { }

具体的には以下のように使います。String#%メソッドは省略できません

"%<minute>d %<second>f" % { minute:1, second:2 }
#=> "1 2.000000"
  • ハッシュリテラルの{ }省略できません。ハッシュが1つであっても必要です。もちろんブロックではないのでdoendには置き換えられません。
"%<minute>d" % minute: 1
#=> syntax error, unexpected ':', expecting end-of-input (SyntaxError)
#=> "%<minute>d" % minute: 1
#=>                      ^
  • これも、ハッシュを変数に入れて渡すのはOKです。
hash = { minute:1, second:2 }
"%<minute>d %<second>f" % hash
#=> "1 2.000000"
  • フォーマット指定文字は省略できません。省略してもエラーにはなりませんが、値は出力されず、%だけが文字列として出力されてしまいます。
"%<minute>" % { minute: 1 }
#=> "%"

🔗 3. 引数(ハッシュ)で名前付き引数的に指定(フォーマット指定なし)

%{名前}という{ }を使った書式でも名前付き引数的にハッシュで値を渡せます。こちらの場合、printf系のフォーマット指定文字は使えません

  • レシーバー(文字列リテラル): "%{名前}"
  • String#%メソッド
  • 引数: ハッシュ { }
"%{minute} %{second}" % { minute: 1, second: 2.0 }
#=> "1 2.0"

もっともここまでくると、以下のように普通に式展開を使う方がよい気もしますが、ハッシュで値をまとめて渡せるのはメリットかもしれませんね。

minute = 1
second = 2.0
"#{minute} #{second}"
#=> "1 2.0"

🔗 5. Range#stepのエイリアスとしての%(2.6〜)

以下の記事を見て気が付きました🙇

参考: サンプルコードでわかる!Ruby 2.6の主な新機能と変更点 - Qiita

# 2.5以前
(1..10).step(2).to_a
#=> [1, 3, 5, 7, 9]

# 2.6以降
((1..10) % 2).to_a
#=> [1, 3, 5, 7, 9]

後者の場合、記法上()が不可欠になります。

🔗 参考: printfsprintfの書式はC言語由来

上に登場したprintfsprintfの書式指定は、どうもRubyっぽくありません。

その理由は、これらのメソッドの書式はC言語の同名の関数を踏襲しているからです。正確には、Rubyでprintfメソッドやsprintfを呼び出すとC言語の機能が呼び出されるのですが、完全な丸投げではなく、sprintfについてはC言語と若干の違いがあるとのことです。

Rubyのsprintfフォーマットは基本的に C 言語のsprintf(3)のものと同じです。ただし、short や long などのC特有の型に対する修飾子がないこと、2進数の指示子(%b, %B)が存在すること、sprintfのすべての方言をサポートしていないこと(%': 3桁区切り)などの違いがあります。
Ruby には整数の大きさに上限がないので、%b, %B, %o, %x, %Xに負の数を与えると (左側に無限に1が続くとみなせるので) ..f のような表示をします。絶対値に符号を付けた形式で出力するためには%+x% xのように指定します。
ruby-lang.orgより

Ruby公式ドキュメントから、printfのサンプルを引用します。非常に生々しいですね。

# ruby-lang.orgより
printf("%d %04x", 123, 123)
#=> "123 007b"

printf("%08b '%4s'", 123, 123)
#=> "01111011 ' 123'"

printf("%1$*2$s %2$d %1$s", "hello", 8)
#=> "   hello 8 hello"

printf("%1$*2$s %2$d", "hello", -8)
#=> "hello    -8"

printf("%+g:% g:%-g", 1.23, 1.23, 1.23)
#=> "+1.23: 1.23:1.23"

printf("%u", -123)
#=> "..4294967173"

ただし上のサンプルはirbだと以下のように=> nilも表示されます。

irb(main):001:0> printf("%d %04x", 123, 123)
123 007b=> nil

printfsprintf固有の%記号の使われ方については長くなりすぎるので本記事では触れず、以下のリンクにとどめることにします。

参考: 1Kernel.#printf1 (Ruby 3.1 リファレンスマニュアル)
参考: sprintf フォーマット (Ruby 3.1 リファレンスマニュアル)

おたより発掘

関連記事

Rubyの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由

The post Ruby: パーセント記号 `%` の使い方まとめ first appeared on TechRacho.

Railsのenumを使いこなす方法(翻訳)

$
0
0

概要

元サイトの許諾を得て翻訳・公開いたします。

Railsのenumを使いこなす方法(翻訳)

Railsのenumについて

enum(enumeration: 列挙)は、名前を整数の定数に割り当てるのに使われるデータ型です。名前は言語の定数として振る舞う識別子なので、整数を直に扱う場合よりもプログラムの読みやすさとメンテナンス性が向上します。

ActiveRecord::EnumはRails 4.1で導入されました。enumの属性値はデータベース内の整数に対応付けられますが、クエリでは名前で参照できます。enumを使うと、データのステートを非常に高速に変更できるようになります。enumはRailsで手軽に利用可能で、enumが提供する動的メソッドによって開発時間を大幅に短縮できます。

データベースにenum用のカラムを作成する

Railsのモデルでは、テーブルにinteger型のカラムを追加するというかなりシンプルな形でenumを追加できます。

ここで、Postモデルを持つRailsアプリケーションを考えてみましょう。1件のpostにはdraft(下書き)、published(公開中)、archived(アーカイブ)、 trashed(ゴミ箱)というステートがあるとします。こうしたステートをPostのテーブルに文字列として書き込む代わりに、012といった整数を利用できます。

アプリケーションにPostのテーブルが既に存在していると仮定すれば、statusをenumとして追加するDBマイグレーションは以下のようになります。

class AddStatusToPosts < ActiveRecord::Migration[7.0]
  def change
    add_column :posts, :status, :integer, default: 0
  end
end

原注: このマイグレーションでは、デフォルト値に0を指定しています。つまり、postのステータスはデフォルトでdraftになります。

モデルでenumを定義するさまざまな方法

rake db:migrateでマイグレーションを実行したら、以下のコード例のようにPostモデルでenumを定義する必要があります。enumメソッドの第1パラメータにはカラム属性名を渡し、第2パラメータにはpostのステータスにしたい値のリストを渡します。

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, [ :draft, :published, :archived, :trashed ]
end

以下のように%i()形式でもenumを宣言できます。

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, %i(draft published archived trashed)
end

enumの宣言で配列を利用したので、第1要素のdraftはデータベース上の0に対応付けられ、第2要素のpublishedは1に対応付けられる、という具合に名前が整数に対応付けられます。

以下のように、配列の代わりにdraftpublishedなどのキーを持つハッシュも渡せます。この場合、enumの値は開発者が指定できます。

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, { draft: 0, published: 1, archived: 2, trashed: 3 }
end

配列よりもハッシュがおすすめです。配列は値の順序が変わるとRailsアプリ内部のロジック全体が壊れるからです。

enumの動作

以下のようにカラム名を複数形にすることで、enumのすべての値をフェッチすることも、enumの特定の値をフェッチすることもできます。

Post.statuses
#=> { "draft" => 0, "published" => 1, "archived" => 2, "trashed" => 3 }

Post.statuses[:archived]
#=> 2

Post.statuses["trashed"]
#=> 3

Post.statuses[:unarchived]
#=> nil

ステータスがpublishedのpostを作成する

post = Post.create(title: "First post", description: "First post description...")

post.status
#=> "draft"

post = Post.create(title: "Second post", description: "Second post description...", status: :published)

post.status
#=> "published"

上で最初に作成したstatusが未指定のpostには、デフォルトでdraftが設定されます。statusカラムに保存されているのは整数値ですが、キーの値として:publishedのようにシンボルも渡せます。Railsはこのstatusカラムがenumであることを認識して、内部でシンボルを整数値に置き換えます。

postのステータスを照合する

Railsは、特定のpostのステータスを照合する動的メソッドを提供しています。

postを1件作成して公開するときは、post.status == 'published'を用いて照合するのが普通です。enumを使うと、Railsが提供するenumヘルパーで以下のように照合できます。

post.published?
#=> true

post.draft? || post.trashed?
#=> false

postのステータスを更新する

?付きのステータスでpostを照合するときと同様に、Railsのenumにはenum値を更新するときのヘルパーも用意されています。post.update(status: :archived)と書く代わりに、以下のように!付きのメソッドで更新できます。

post.archived!

post.published?
#=> false

post.archived?
#=> true

enumでスコープを使う

このRailsアプリのPostモデルにはさまざまなステータスがあるので、そのうち指定のステータスのレコードだけを取り出したくなるでしょう。Railsにはこのクエリを解決する動的なメソッドが追加されています。

たとえばステータスがpublishedのpostをすべてフェッチするなら、RailsのコントローラでPost.where(status: "published")のように書くことも一応可能です。

そのように書く代わりに、Postでpublishedメソッドをスコープとして使えます。Railsは、enumにあるすべてのステータスごとに、ステータスと同じ名前のクラスメソッドを動的に追加します。この場合、Postモデルに#draft#published#archived#trashedメソッドが生成されます。

Post.published
select "posts".* from "posts" where "posts"."status" = $1 [["status", 1]]

Rails 6では、enumのスコープの否定条件も利用できます。ステータスがpublishedでないpostをすべてフェッチするには、以下のようにpublishedメソッド名の冒頭にnot_を追加できます。

Post.not_published
select "posts".* from "posts" where "posts"."status" != $1 [["status", 1]]

モデルで定義されたenumにscopes: falseオプションを渡してenumのスコープを無効にすることも可能です。無効にしたスコープを使うとNoMethodErrorがraiseされます。

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, { draft: 0, published: 1, archived: 2, trashed: 3 }, scopes: false
end

Post.published
=> NoMethodError: undefined method `published' for #<Class:0x009hg431k236t8>

enum値にプレフィックスやサフィックスを追加する

enumの値だけだと意味がわからないこともあるので、enum名にプレフィックスやサフィックスを適用する方がよいでしょう。ここでは、アプリケーションでenumとして定義されたstatusカラムを持つUserモデルを考えてみましょう。

# app/models/user.rb

class User < ApplicationRecord
  enum :status, { invited: 0, active: 1, deactivated: 2 }
end

たとえばuser.active?メソッドは、user.active_status?のように正確に書けます。他のenum名についても同様です。こう書けるようにするには、以下のようにenumのオプションにsuffix: trueを追加します。

# app/models/user.rb

class User < ApplicationRecord
  enum :status, { invited: 0, active: 1, deactivated: 2 }, suffix: true
end

更新された動的メソッドを用いて、以下のように等価チェック、更新、userオブジェクトやモデルへのクエリを実行できるようになります。

user.invited_status?

user.active_status!

User.deactivated_status

prefix: trueオプションを渡すと、上のメソッドは以下のように変わります。

user.status_invited?

user.status_active!

User.status_deactivated

プレフィックスやサフィックスは、enum値を持つカラムがモデルに2つある場合に便利です。たとえばPostモデルにenumのcategoryカラムがあり、freeまたはpremiumという値を持つとします。

# app/models/post.rb

class Post < ApplicationRecord
  enum :status, { draft: 0, published: 1, archived: 2, trashed: 3 }
  enum :category, { free: 0, premium: 1 }
end

プレフィックスやサフィックスのないenumを定義すると、Post.freepost.published?post.premium!がどちらのカラムを参照しているかがわかりにくくなり、開発者が戸惑ってしまいます。

代わりに、以下のようにプレフィックスやサフィックスをメソッドに追加して、必要なメソッドを呼び出せるようにできます。

# app/models/post.rb

class Post < ActiveRecord::Base
  enum :status, { draft: 0, published: 1, archived: 2, trashed: 3 }, prefix: true
  enum :category, { free: 0, premium: 1 }, suffix: true
end

Post.free_category

post.status_published?

post.premium_category!

メモ

Rails 7ではenumの新しい構文が導入されました。詳しくは以下の過去記事をご覧ください。

関連記事

Rails 7のenumに新しい構文が導入(翻訳)

The post Railsのenumを使いこなす方法(翻訳) first appeared on TechRacho.

週刊Railsウォッチ: orderでコレーション指定をサポート、awesome_nested_set、GitHub Copilotほか(20220221前編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

🔗 Active Recordのdestroy_association_async_jobコンフィグが効くように修正

アプリケーションでdependent: :destroy_asyncオプションを指定したhas_many関連付けでは、バックグラウンドで関連レコードをdestroyするジョブをconfig.active_record.destroy_association_async_jobコンフィグで指定できるはずだが、無視される。つまり、常にデフォルトのActiveRecord::DestroyAssociationAsyncJobがバックグラウンドでレコードをdestroyする。

このPRはconfig.active_record.destroy_association_async_jobの設定を無視しなくなる。
同PRより


つっつきボイス:「非同期でレコードをdestroyするジョブをカスタマイズするコンフィグが常にデフォルトで上書きされていたのか」「これはバグ」「=||=にすることで修正されてますね↓」

# activejob/lib/active_job/railtie.rb#L45
-     ActiveSupport.on_load(:active_record) do
        self.destroy_association_async_job = ActiveRecord::DestroyAssociationAsyncJob
+       self.destroy_association_async_job ||= ActiveRecord::DestroyAssociationAsyncJob
      end

参考: §3.7.34 config-active-record-destroy-association-async-job — Rails アプリケーションを設定する – Railsガイド

🔗 orderCOLLATEを安全なSQL文字列として使えるようになった

#36448でORDER BYに関数を渡せるようになった。COLLATEも利用できるようにすべき。

Post.order('title COLLATE "C"')

その他
PostgreSQLではコレーション名を引用符で囲む必要がありそうだが、MySQLやSQLiteはそうではない。
同PRより


つっつきボイス:「お〜、ついにorderでコレーションが書けるようになった🎉」「今までできなかったのは何ででしょう?」「単にこれまでActive Recordのインターフェイスではこの書き方が許可されていなかったんですよ」「あ、そういうことですか」

「たしか以前はselectするカラムにコレーションを書いて、それをorderしたような覚えがありますが、orderの中でコレーションを直接書けるようになったのはありがたい👍」「なるほど」「今までも回避方法はありましたけど、そもそもコレーションの構文がRDBMSによって違うんですよ」

「言われてみればコレーション(照合順序)は標準のSQLじゃなくてRDBMSの拡張だから違っててもおかしくないか」「テストコードでもRDBMSによってコレーションに書けるものも違ってくるのがわかりますね↓」

# activerecord/test/cases/unsafe_raw_sql_test.rb#169
  test "order: allows valid arguments with COLLATE" do
    collation_name = {
      "PostgreSQL" => "C",
      "Mysql2" => "utf8mb4_bin",
      "SQLite" => "binary"
    }[ActiveRecord::Base.connection.adapter_name]

参考: 【MySQL】照合順序とは? – Qiita

🔗 mysql2アダプタでActiveSupport::Durationを適切に扱うよう修正

#44404 の作業中にテストカバレッジを拡大しようとしたところ、MySQLアダプタでActiveSupport::Durationオブジェクトもquoteで処理する必要があることに気づいた。これは#42440#16069の「一般的なRailsのプラクティスとMySQLの組み合わせでクエリを外部から操作される可能性の緩和」の続き。

このケースでは、ActiveSupport::Durationオブジェクトで.to_sを呼ぶとセクション数を整数で返す。これがMySQLアダプタでquoteされないままだと、抽象のConnectionAdaptersに渡され、そこで文字列ではなく数値として処理されてしまう。

これは技術的にはセキュリティ修正という形になるが、(攻撃の)ターゲットベクタ(媒介者)とするのは難しそうではある。自分はその点について概念実証(PoC: Proof of Concept)を行っていない。
同PRより


つっつきボイス:「mysql2アダプタでActiveSupport::Durationが文字列にキャストされていなかったのを修正したようですね」「他のアダプタのテストも足したみたい」「コメントによるとキャストの修正前はPostgreSQLだとエラーになったけどMySQLだと通っちゃったのか」

# activerecord/lib/active_record/connection_adapters/mysql/quoting.rb#L8
      module Quoting # :nodoc:
        def quote_bound_value(value)
          case value
-         when Numeric
+         when Numeric, ActiveSupport::Duration
            quote(value.to_s)
          when BigDecimal
            quote(value.to_s("F"))
          when true
            "'1'"
          when false
            "'0'"
          else
            quote(value)
          end
        end
# activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb#75
        def test_where_with_duration_for_string_column_using_bind_parameters
          count = Post.where("title = ?", 0.seconds).count
          assert_equal 0, count
        end

後で読み返すと、rafaelfrancaが「びっくりさせないで〜😂」「このissueを攻撃に転用するのは難しそう、それ以外の問題はありそうだけど」みたいな感じでコメントしていました。

🔗 DBのrakeタスクでVERSION envの数値に_が使えるようになった


つっつきボイス:「これはコミットのみでした」「環境変数のVERSIONの数値にアンスコを使えるようになった」「これはマイグレーションのバージョンっぽいですね」「バージョン指定でこんな書き方ができるのか」「Rubyは数値のアンスコでこうやって桁区切りできますよね」「マイグレーションの日時みたいな数値をフラットに書くと読みづらいので、アンスコを使えるようにしたい気持ちはワカル」

# activerecord/test/cases/tasks/database_tasks_test.rb#1430
      ENV["VERSION"] = "2000_01_01_000042"
      assert_equal 20000101000042, ActiveRecord::Tasks::DatabaseTasks.target_version

🔗 ルーティングのtrailing_slash: trueオプションを修正


つっつきボイス:「こちらはルーティングの修正だそうです」「trailing_slash: trueって、ルーティングでURLの末尾にスラッシュを付けるという意味なんですか」「ヘルパーでURLを取得したときにスラッシュを付けられるようですね: これがやりたくなるときもたまにありそう👍

# 同Changelogより
get '/test' => "test#index", as: :test, trailing_slash: true

test_path() # => "/test/"

🔗Rails

🔗 RailsのRubyはRails方言(Ruby Weeklyより)


つっつきボイス:「RailsのActive Supportのことかな」「記事にもあるように、Active SupportはRubyにたくさんパッチを当てていますね↓」「こうして見ると多い」

# 同記事より
{Array=>{:original_methods=>196, :as_methods=>251, :added_by_as=>55},
 Class=>{:original_methods=>117, :as_methods=>172, :added_by_as=>55},
 Date=>{:original_methods=>132, :as_methods=>240, :added_by_as=>108},
 DateTime=>{:original_methods=>142, :as_methods=>277, :added_by_as=>135},
 File=>{:original_methods=>236, :as_methods=>279, :added_by_as=>43},
 Hash=>{:original_methods=>182, :as_methods=>246, :added_by_as=>64},
 Integer=>{:original_methods=>148, :as_methods=>206, :added_by_as=>58},
 Module=>{:original_methods=>114, :as_methods=>165, :added_by_as=>51},
 Object=>{:original_methods=>63, :as_methods=>83, :added_by_as=>20},
 Range=>{:original_methods=>133, :as_methods=>171, :added_by_as=>38},
 String=>{:original_methods=>188, :as_methods=>256, :added_by_as=>68},
 Symbol=>{:original_methods=>92, :as_methods=>114, :added_by_as=>22},
 Time=>{:original_methods=>121, :as_methods=>257, :added_by_as=>136}}

「記事の下の方では、Active Support出身のメソッドがRuby本体にもたくさん取り込まれたとありますね」「そうそう」「自分の中では出世メソッドと呼んでます😆

「Railsを動かしているRubyはあくまでRails方言、たしかに」「素のRubyを書いていると、たまにActive Supportにしかない機能を使いそうになっちゃうことある」「そうそう、present?と書いた直後にないことに気づいたり」「blank?はRubyにありますけどね」「どちらにもあるけど挙動が違うメソッドもありますね: Rubyだと引数が使えないけどRailsだと使えるように拡張されていたり」

Rails: present?より便利なActiveSupportのpresenceメソッド(翻訳)

「Active Supportはちと大きいので、HanamiのようにActive Supportがない環境なら記事にもあるようにdry-rbを使う手もありますね↓: dry-rbは必要最小限かつよくできてる👍

参考: dry-rb – Home

🔗 awesome_nested_set: 入れ子集合を扱うgem(Ruby Weeklyより)

collectiveidea/awesome_nested_set - GitHub


つっつきボイス:「awesome_nested_setは、acts_as_nested_setやBetterNestedSetを置き換えられるそうです」「acts_as_で始まるのは古いライブラリに多いですね」

lftとかrgtというこの書き方は見覚えある↓: ネストした集合を扱える感じ」「lftrgtは左と右なんですね」

# 同リポジトリより
class CreateCategories < ActiveRecord::Migration
  def change
    create_table :categories do |t|
      t.string :name
      t.integer :parent_id, null: true, index: true
      t.integer :lft, null: false, index: true
      t.integer :rgt, null: false, index: true

      # optional fields
      t.integer :depth, null: false, default: 0
      t.integer :children_count, null: false, default: 0
      t.timestamps
    end
  end
end

なお、この記事ではacts_as_nested_setが使われています↓。

Railsで木構造を扱うには

🔗 SQLでネステッドセットを扱うパターン

「ネストしたデータセットをデータベースでやろうとすると大変そうですよね」「実はそのような場合のベストプラクティスはある程度確立しているんですよ: こういう構造を適切に作ると、たとえばネストの上の層のデータを短いSQLクエリで書けて、かつインデックスが効くようにできたりします」「お〜」

「たとえばこのancestryというgem↓の場合は、内部でスラッシュ区切りの階層データ構造を持つ形で多階層カテゴリを扱っている」

stefankroes/ancestry - GitHub

参考: 多階層カテゴリでancestryを使ったら便利すぎた – Qiita

「awesome_nested_setのようにlftrgtを指定する方法は、たしかSQLのパターンにあったはず: そうそう、このQiita記事↓にあるNested set model(入れ子集合モデル)やNested intervals(入れ子区間)なんかがそうですね」「こういうパターンがあるのか〜」

参考: 階層構造(入れ子集合モデル)について – Qiita
参考: Nested set model – Wikipedia
参考: Nested intervals – Wikipedia

lftrgtというとツリー構造みたいなものでしょうか?」「Nested set modelはツリーとは違って、たしかid空間を効率よく配置する戦略ですね」

「ちょうどこの記事の図↓にあるように、上のベン図に合うように下の区間でidが設定される: このようにデータを配置することでidの区間の指定にインデックスが効くようになります」「お〜なるほど!」「SQLのテクニック集的な書籍には必ず載っていると言ってもいいと思います: naive treeと一緒に紹介されることが多いですね」


gihyo.jp/dev/serial/01/sql_academy2/000501より

参考: 第5回 SQLで木構造を扱う~入れ子集合モデル (1)入れ子集合モデルとは何か :SQLアタマアカデミー|gihyo.jp … 技術評論社
参考: SQLアンチパターン ナイーブツリー – Qiita

🔗 Railsアプリの脆弱性警告対策


つっつきボイス:「Railsにインストールしたgemに含まれているGemfile.lockで警告が出たのか」「gemの問題なのですぐ対応しにくいヤツですね」「記事では可能なものについてはgem作者に対応してもらいつつ、すぐ対応できないものはDockerからgemのGemfile.lockを削除した、なるほど」

🔗 JetBrains IDEのGitHub Copilotプラグイン


つっつきボイス:「JetBrains IDEのGitHub Copilotプラグインは今使っていてなかなか便利👍」「私はVSCodeでGitHub Copilotを使ってます: VSCodeのIntelliSenseとケンカするっぽいので後者はとりあえず外してますが」

「GitHub Copilotってそんなにいいんですか?」「いいですよ〜❤、ぜひTechnical Previewに申し込んで使ってみてください↓」

参考: GitHub Copilot · Your AI pair programmer

「GitHub Copilotの補完が邪魔になることもないではないけど、かなり賢い(JetBrains IDEではEscでキャンセルできます)」「空のファイルだとRubyにJSのコードを突っ込んできたりすることもありましたけど、コードを書く前に自然言語で構わないのでコメントに概要を書いたりして、なるべくヒントを与えてあげるとどんどんよくなりますね」「そうそう、下手するとコメントまで補完してくれる」

「単に一部を補完させるより、コメント以外のコードをいったん全部消してGitHub Copilotになるべく書かせるぐらいの気持ちで使うといいみたいです」「使ってみたい〜、早速申し込んでみました」「この間GitHub Copilotの使いこなしを見せてくれた人は、GitHub Copilotの振る舞いを完全に掌握したら生産性が3倍は向上したと話してました(個人の感想です)」

「GitHub Copiloはtコード以外にも、このツイートみたいにロケール情報なんかもビシバシ補完してくれますね↑」「GitHub Copilotは自分のプロジェクト内のコードを優先的に扱うので、補完されるメソッドチェーンなどもちゃんとプロジェクトに沿ってくれる」「いいな〜」「もちろん補完されたコードは調べますけど、メソッド名を探さなくて済むのが楽」「変数名やメソッド名をちゃんと付けるモチベも高まりそうですね」「楽しみに待ってます」

🔗 その他Rails

「小技記事です」「fixtureのデータをdb:seedでも使えるようにする、誰もが一度はやりたくなるヤツ」「同じデータを2つも作りたくないですよね」「もちろんdb:seedで入れるデータが開発・テスト用限定で、productionには入れないものでないといけないと思いますが」「たしかにseedの扱いってプロジェクトで違うことがありますよね」


「お、Hanami v2.0.0のアルファ版」「Ruby 3.0以上が必須ですって」「Hanamiを使っている人ならきっと大丈夫」



「正確には配列の配列をハッシュの配列に置き換えたんですが、アロケーションはたしかに減っていました」「<<で配列に追加してループで回したりするとハッシュよりアロケーションが増えそうな気もしますね」「今回はJeremy Evansさんの教え↓にしたがってハッシュに変えたんですが、たしかに前はそれをやってました😅」「何でも配列にしてよかったのはRuby 1.8までですね」「そういえば元々Ruby 2.0のときに書いたコードでした」

『Polished Ruby Programming』(Jeremy Evans著)を読みました


前編は以上です。

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

週刊Railsウォッチ: Bundler自身のバージョンロック機能、gem署名メカニズムの提案ほか(20220216後編)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: orderでコレーション指定をサポート、awesome_nested_set、GitHub Copilotほか(20220221前編) first appeared on TechRacho.

週刊Railsウォッチ: 端末文字幅とRubyのreline、SQLのプリペアドステートメント、Terraformほか(20220222後編)

$
0
0

こんにちは、hachi8833です。本日2022年2月22日は2づくしですね。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 端末の文字幅問題


つっつきボイス:「BPS社内Slackに貼っていただいた記事です」「East Asian Widthの話とかいろいろ面白かった👍」「東アジアの文字幅が一定しないのは、踏むと厄介な問題ですね」「記事は一般的な端末の文字幅の話ですが、途中でRubyのrelineの話も出てきた↓」「relineといえばaycabtaさんがメンテしている、Rubyのirbでも重要な役割を果たしているgemですね」「そうそう、GNU ReadlineライブラリのRuby版」

ruby/reline - GitHub

参考: 東アジアの文字幅 – Wikipedia
参考: GNU Readline – Wikipedia

「relineで面白かったのは、正確な文字幅を取得するためにいったん(U+25BD)という幅があいまいになりがちな文字を一瞬だけ画面に出力して現在のカーソル位置を取得していること↓」「なるほど、たしかにレンダリングして測定すれば正確な文字幅は取れますね」「そうそう、泥臭いけど実用性は高い: レンダリング後は即座にを消して何事もなかったようにするけど、環境によっては一瞬見えることがあるらしい」「Vimも同じようなことをしてると書かれてる、へ〜!」

# 同記事より
private def may_req_ambiguous_char_width
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
  return if defined? @ambiguous_width
  Reline::IOGate.move_cursor_column(0)
  begin
    output.write "\u{25bd}"
  rescue Encoding::UndefinedConversionError
    # LANG=C
    @ambiguous_width = 1
  else
    @ambiguous_width = Reline::IOGate.cursor_pos.x
  end
  Reline::IOGate.move_cursor_column(0)
  Reline::IOGate.erase_after_cursor
end

「記事後半ではいろいろなCLIツールで文字幅がちゃんと表示されるかを調べてる」「PuTTYとか懐かしい」

参考: PuTTY – Wikipedia

👨‍👩‍👧‍👦みたいな合字もこういう話題でよく引き合いに出されますね」「relineはこういうのをちゃんと表示できてるんですって」「すごい」「relineは大丈夫でも、tmuxやbyobuあたりを通してRubyを使うと結局文字幅は崩れてしまうんですけどね」「これはRubyだけではどうしようもない…」「tmuxは普段からとてもよく使っているので文字幅が乱れるとつらい😢

できるtmux-5分でわかる?仮想端末入門-

🔗 Glimmer DSL for OpalをRails 7で動かす(Ruby Weeklyより)

AndyObtiva/glimmer-dsl-opal - GitHub

つっつきボイス:「Glimmerって何だろうと思ったらこれみたいです↓」「Glimmer DSL for OpalはそれをOpalで使えるラッパーらしい」

AndyObtiva/glimmer - GitHub

opal/opal - GitHub

「Glimmer DSL for Opalのデモサイト↓を見てみると、普通のWebアプリっぽく動きますね: Glimmer DSLでGUIを書くと、こうやってWebでもデスクトップGUIにもできるということみたい」「お〜」

「ノリとしてはTcl/Tkみたいな感じっぽいかな」「Grimmerのリポジトリを見ると、Tcl/TkやGTKや、JRubyのSWTなどいろんなGUIに対応していますね」「こんなに移植されているとは」「JFX(JavaFX)にまで対応している、懐かしい」「JavaFXって初めて聞きました」

参考: Tcl/Tk – Wikipedia
参考: JavaFX

🔗 その他Ruby

つっつきボイス:「こちらは無料で読めます」「他の言語の人たちがRubyを使ったときに驚く点というのは目の付け所がいいですね👍」「Rubyに慣れた人が他の言語を使ったときの驚きについても読みたいかも」「そういえば今度なりゆきでrakeタスクをGoで書き直すことになりそうです…」

🔗DB

🔗 SQLのプリペアドステートメント


つっつきボイス:「はてブで見つけた記事です」「SQLインジェクション怖い」「この記事読みました: PHPなどのライブラリではとっくの昔に解決されている脆弱性が、今の時代にNode.jsのメジャーなライブラリで見つかったというのは、かなり驚きでしたね」

参考: SQLインジェクション – Wikipedia


なお、https://github.com/mysqljs/mysqlはREADME以外は最近ほとんど更新されていません。また、以下の記事によるとMySQL 8ではnode-mysql2が必要だそうです↓。node-mysql2は活発に更新されているようです。

参考: Node.js Tips: mysql8にはmysql2を使う | マサトッシュブログ

sidorares/node-mysql2 - GitHub


「おそらくですが、最終的にSQLのプリペアドステートメントを使わない形になってしまったんじゃないかな: 今どきデータベースライブラリでプリペアドステートメントを使わないというのはありえないと思います」

参考: プリペアドステートメント(prepared statement)とは – IT用語辞典 e-Words

「今さらですが、プリペアドステートメントとは?」「たとえば記事にあった以下のようなコード↓を見れば。誰でも内部で安全に処理されていると思いますよね」「思います思います、中でうまいことやってくれてるはずだって」

// 同記事より
...

app.post("/auth", function (request, response) {
 var username = request.body.username;
 var password = request.body.password;
 if (username && password) {
  connection.query(
   "SELECT * FROM accounts WHERE username = ? AND password = ?",
   [username, password],
   function (error, results, fields) {
    ...
   }
  );
 }
});

...

「記事によると、このライブラリはSQLクエリを単なるフラットな文字列に展開していたそうです」「え?」「ライブラリがDBのAPIを呼び出すときにプリペアドステートメントを使っていれば、変数部分に渡された値の内容がSQLとして解釈されることは仕組み上起こり得ない」「たしかに」「詳しくはこのあたりの記事を読むとよいと思います↓」

参考: IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第6章 入力・注入対策:SQL注入攻撃: #1 実装における対策


「もしプリペアドステートメントが使われていなかったとしたら、文字列で生クエリを発行していることになるので、SQLのクエリプランキャッシュもろくに効かなくなってたんじゃないかな」「毎回愚直にSQLクエリを解析する感じですか?」「そうそう、たとえばidだけが違って他は同じSQLクエリをたくさん発行する場合にクエリプランキャッシュが効かなくなる」「あ〜そういうことですか」

「そもそもプリペアドステートメントを使えば、たとえば以下のコード↓のように$1などで変数化されるので、クエリオプティマイザが以前の同型のクエリプランキャッシュを再利用できる、つまり高速化されるわけです」「主語や述語は違うけど文は同じとみなしてくれるから構文解析しなくてよくなるわけですね、なるほど」

// www.ipa.go.jpより
<?php
String parameter = ユーザが入力した値;
$dbc = pg_connect("dbname=pg_db");  // データベース接続

// 値を埋め込む前の形のSQL文をコンパイルし、構文を確定
$result = pg_prepare($dbc, "query1", 'SELECT name, price FROM product_table WHERE code=$1');

// $1の場所に値を指定してクエリの実行
$result = pg_execute($dbc, "query1", array(parameter));
?>

「でも値が文字列としてSQLクエリに入ってくると、値が変わるたびにSQL文の構文木も変わってしまうので、クエリオプティマイザが毎回SQL文を解析しなければならなくなってしまう」「クエリプランのキャッシュまで効かなくなるとは悲しすぎますね…」「クエリプランのキャッシュはクエリ結果のキャッシュよりは小さいですが、チリも積もれば影響は無視できなくなります: プリペアドステートメントを使わないと、セキュリティもパフォーマンスもいっぺんに損なわれてしまいます」

「プリペアドステートメントでは、$1?のように後で変数に置き換わる部分を含んだSQL文をDBMSに送信し、変数の値そのものはその直後に別途送信して、DBMS側で値を置き換えます: つまりDBMSのAPIレベルでSQL文と値が正しく分離される」「ふむふむ」

「しかし、たとえばJSならJSのライブラリが、ユーザーから受け取った値を自分で展開して、それをSQLクエリで本来プリペアドステートメントの変数にすべき場所に文字列として直接置くと、値を含むクエリ全体が単なる文字列の生SQLになってしまいます: そんな状態でライブラリがユーザー入力で邪悪なSQL文を値として受け取ると、それを含んだSQL文全体がそのままDBMSで実行されてSQLインジェクションが発生してしまう」「なるほど!」

「ちょうどIPAの同じ記事にも、PEARというPHPのライブラリで同じことが起きていたと書かれてますね↓」「PEARは覚えてますけど、今も使われてるのかな?」「何しろ2007年の記事ですから」「そんな昔の問題がNode.jsのMySQLライブラリで繰り返されていたとは…」

ただし、PEAR(PHP Extension and Application Repository)を使ってDBアクセスを行い、PEARでのプリペアドステートメントを利用している場合は、注意が必要である。
PEARは、コミュニティーにより運営されるプロジェクトのことで、コード配布およびパッケージ管理のためのシステムやPHPのコード作成に関する標準スタイル、PHP拡張モジュール・コミュニティライブラリ等を提供することを目的にしている。
ここでは、PEARをPHPの拡張モジュールとして記述している。
このPEARのプリペアドステートメントは内部的にバインド変数をエスケープしてSQL文を組み立るため、有効に機能していない。
IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第6章 入力・注入対策:SQL注入攻撃: #1 実装における対策より

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

🔗 Terraform


つっつきボイス:「今日のWebチーム内発表がTerraformの話題だったので取り上げてみました」「Terraform v4でS3の破壊的変更が入るのがちょっと面倒そう: よく使われる部分なので」「最近Terraformを追いかけてない…」

参考: HashiCorp Terraform AWS Provider Introduces Significant Changes to Amazon S3 Bucket Resource

「上の記事によると、たとえば以下のserver_side_encryption_configurationがこれまでaws_s3_bucketというリソースのオプションとして入っていたのが、変更後はリソースの外に切り出されてアタッチする形になったらしい」「へ〜」「この変更だけなら特に大変でもなさそうだけど、他にも変更はあるようなので、どのリソースが切り出されるか調べないといけなさそう…」

# www.infoq.comより: 変更前
resource "aws_s3_bucket" "example" {
  # ... other configuration ...
  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        kms_master_key_id = aws_kms_key.mykey.arn
        sse_algorithm     = "aws:kms"
      }
    }
  }
}
# www.infoq.comより: 変更後
resource "aws_s3_bucket" "example" {
  # ... other configuration ...
}

resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
  bucket = aws_s3_bucket.example.id

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.mykey.arn
      sse_algorithm     = "aws:kms"
    }
  }
}

「Terraformのデバッグは手間取りがち」「わかります」


「2つ目のTerraform Importの記事は?」「Terraformはスクラッチから組むよりも、最初はTerraform Importから始める方が実用性が高いかなと思います」「既存のリソースを書き出してくれるんでしたっけ?」「正確には書き出しではなくて、既存リソースをTerraformの管理下に置く」「なるほど」「Terraform Importはそれなりに泥臭い作業にはなりますけどね」

「たとえばVPCやVPCサブネットのようなリソースはTerraformで作り直したくないですよね」「それ、やりたくない作業です😅」「Terraformで作り直すと、VPCに依存するリソースを全部消さないといけなくなるので、そういうのはTerraform Importでやることがよくあります」

「そもそもVPCのようなクリティカルなリソースは、間違えて消したときのリスクが大きすぎるのでTerraformに置かないことも多い」「わかります」「場合によっては安全のためにインフラ用のTerraformとWebアプリ用のTerraformを分けることもあります」

🔗 capistrano、itamae、ansible

「Terraformで思い出したんですが、capistranoやitamaeとの使い分けはどういう感じでしょうか?機能もスコープも違うとは思うんですが」「最近はコンテナでデプロイすることが増えてきたので、capistranoやitamae、あとansibleあたりは新規ではあまり使わないかも」「ansible、言われて思い出しました」

capistrano/capistrano - GitHub

itamae-kitchen/itamae - GitHub

ansible/ansible - GitHub

「今の3つのツールのように、動いているインスタンスにsshして作業するIaaSツールはトレンドではなくなりつつあるかなとは思いますが、もちろん今でもよく使われています」「たしかに一度作ったものは使い続けますよね」

参考: IaaS「イアース」|気になるIT用語| NECフィールディング

「コンテナでのデプロイはそれなりに時間もかかりますし、たとえばEC2インスタンスにDBや画像のような巨大なファイルを置かないといけないようなものはコンテナデプロイにまったく向いていないので、capistranoのようなツールでやることになると思います」「コンテナにでかいファイルを置きたくないですよね」「コンテナを差し替えずにsshでやるタイプのデプロイなら、そういうツールを使うのは全然ありだと思います」

「自分はcapistranoとの付き合いも長いし、やっていることもそんなに複雑ではないので、そういう要件なら今でもcapistranoでいいと思っていますが、新たにcapistranoを導入するプロジェクトは少し大変かもしれませんね」「ふむふむ」「既にそういうツールを使っているプロジェクトならいいんですが」

「なおTerraformは低レベルを含むインフラを管理するものなので、Railsのようなアプリケーションをデプロイするそうしたツールとは位置づけがまったく違いますね」「なるほど」

🔗言語/ツール/OS/CPU

🔗 書籍『ソフトウェアアーキテクチャの基礎』

つっつきボイス:「3月8日に発売されるそうです」「これ買うつもり👍


後編は以上です。

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

週刊Railsウォッチ: orderでコレーション指定をサポート、awesome_nested_set、GitHub Copilotほか(20220221前編)

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

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

Ruby Weekly

Publickey

publickey_banner_captured

Serverless Status

serverless_status_banner

The post 週刊Railsウォッチ: 端末文字幅とRubyのreline、SQLのプリペアドステートメント、Terraformほか(20220222後編) first appeared on TechRacho.

Ruby 3.1.1がリリースされました

$
0
0

Ruby 3.1.1がリリースされました。

詳しくはリリース情報をご覧ください。3.1.1はTEENY(小さな)と銘打たれたバグ修正です。

なお、#18436で言及されているRuby 3.1.0でのDir.globの挙動変更は、Ruby 3.1.0のリリースノートおよびNEWS.mdに記載されていませんでした。

参考: Ruby 3.1.0 リリース
参考: ruby/NEWS.md at v3_1_0 · ruby/ruby

🔗 更新の概要

可能な範囲でプルリクとコミットも記載しました。

関連記事

Ruby 3.1.0がリリースされました

The post Ruby 3.1.1がリリースされました first appeared on TechRacho.


Ruby 3.1: error_highlight gemが追加された(翻訳)

$
0
0

概要

元サイトの許諾を得て翻訳・公開いたします。

Ruby 3.1: error_highlight gemが追加された(翻訳)

プログラミング言語で例外やエラーが発生すると、エラーのスタックトレースを扱います。スタックトレースは、エラーの発生場所を整った書式で示し、プログラムのサブルーチンに関する情報を提供します。

Rubyを使っていると、さまざまな場所でエラーや例外が発生することに気づきます。例を見てみましょう。

# test.rb
class Addition
  attr_reader :num1, :num2

  def initialize(num1, num2)
    @num1 = num1
    @num2 = num2
  end

  def result
    num1 + num2
  end
end

Addition.new(1, "2").result
Terminal$ ruby test.rb
test.rb:10:in `+': String can't be coerced into Integer (TypeError)
    from test.rb:10:in `result'
    from test.rb:14:in `<main>'

irbでも同様に、エラーが発生した行番号が示されます。

(irb):10:in `+': String can't be coerced into Integer (TypeError)

改修前

上のエラー程度なら、どこが間違っているかは割とすぐにわかるでしょう。そこで、とあるAPIレスポンスをパースして、ネストしたJSONオブジェクトからユーザーデータをフェッチする場合の例を見てみましょう。

# test.rb
class ParseResponse
  attr_reader :data

  def initialize(data)
    @data = data
  end

  def user_name
    data[:result].first[:first_name]
  end
end

data = { result: {} }
ParseResponse.new(data).user_name
Terminal$ ruby test.rb
Traceback (most recent call last):
    1: from test.rb:14:in `<main>'
test.rb:9:in `user_name': undefined method `[]' for nil:NilClass (NoMethodError)

このundefined method [] for nil:NilClassエラーでは、data[:result]nilなのか、それともdata[:result].firstnilなのかがわかりません。もっと複雑にネストしたJSONレスポンスだったら、どのパラメータがnilなのかを特定するだけで一苦労です。

改修後

ruby/error_highlight - GitHub

Ruby 3.1にerror_highlight gemが同梱され、Rubyのプロセスが起動するときにrequireされるようになりました。このgemを利用するのに追加のセットアップは不要です。

上のコードをRuby 3.1.0で動かすと、データがnilになっている箇所が行内のどこにあるかがわかりやすく表示されます。

$ ruby test.rb
test.rb:9:in `user_name': undefined method `[]' for nil:NilClass (NoMethodError)

    data[:result].first[:first_name]
                       ^^^^^^^^^^^^^
    from test.rb:14:in `<main>'

.firstの ^^^^^^^^^^^^^で示された 部分がnilになっています。

空のdataを渡すと、以下のように.firstの部分がエラーとしてハイライトされます。

$ ruby test.rb
test.rb:9:in `user_name': undefined method `first' for nil:NilClass (NoMethodError)

    data[:result].first[:first_name]
                 ^^^^^^
    from test.rb:14:in `<main>'

ハイライト形式をカスタマイズする

error_highlight gemには独自のフォーマッタを追加する機能が提供されています。追加するには、以下のように#message_forメソッドをオーバーライドしてErrorHighlight.formatter=に代入する必要があります。

formatter = Object.new
def formatter.message_for(spot)
  marker = " " * spot[:first_column] + "*" * (spot[:last_column] - spot[:first_column] - 1)

  "\n\n#{ spot[:snippet] }#{ marker }"
end

ErrorHighlight.formatter = formatter

上のフォーマッタを適用すると、エラーハイライトが^から*に変更されます。

$ ruby test.rb
test.rb:9:in `user_name': undefined method `first' for nil:NilClass (NoMethodError)

    data[:result].first[:first_name]
                 ******
    from test.rb:14:in `<main>'

error_highlightを無効にする

rubyコマンドに--disable-error_highlightを渡すと、error_highlight gemを無効にできます。

$ ruby --disable-error_highlight test.rb
test.rb:9:in `user_name': undefined method `[]' for nil:NilClass (NoMethodError)
    from test.rb:14:in `<main>'

原注: error_highlight gemはMRIでのみ動作します。また、Rubyバージョンは3.1以上が必須です。

関連記事

Rais 7のbyebugがruby/debugに置き換わる(翻訳)

The post Ruby 3.1: error_highlight gemが追加された(翻訳) first appeared on TechRacho.

週刊Railsウォッチ: dartsass-railsがリリース、webpack-mergeツール、Rubyが29歳にほか(20220228前編)

$
0
0

こんにちは、hachi8833です。つっつきの日がお誕生日でした🎉

週刊Railsウォッチについて

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

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

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

公式更新情報で1件見逃していたものがあったので、そこから見繕いました。

🔗 Objectinstance_valuesを最適化

#44257のフォローアップ。
#44213のレビューでas_jsonのパフォーマンスを調べてみた。Object#instance_variable_namesではシンプルな最適化がいくつか行えた。
Array#to_hは本質的にHash::[]よりかなり高速(インターフェイスが厳密で正しいサイズのハッシュを事前にアロケーションできる)。
また、Hash#[]で文字列が重複しないよう早い段階でfreezeさせる。
#44258より


つっつきボイス:「instance_variable_namesの方は少し前にも見た覚えがありますね(ウォッチ20220131)」

# activesupport/lib/active_support/core_ext/object/instance_variables.rb#L14
  def instance_values
-   Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
+   instance_variables.to_h do |ivar|
+     [ivar[1..-1].freeze, instance_variable_get(ivar)]
    end
  end

🔗 ActiveModel::Name#humanhuman_attribute_nameを最適化

ActiveModel::Name#humanのアロケーションを削減して、訳文が定義されていない場合のパフォーマンスを2倍程度改善するリファクタリング。
#44299より


つっつきボイス:「humanhuman_attribute_nameはI18n定義を使って読みやすく変換するヤツですね」「お〜だいぶ速くなったらしい」

# https://api.rubyonrails.org/classes/ActiveModel/Name.html#method-i-humanより
BlogPost.model_name.human # => "Blog post"
# https://api.rubyonrails.org/classes/ActiveModel/Translation.html#method-i-human_attribute_nameより
Person.human_attribute_name("first_name") # => "First name"

🔗 Active RecordのConnectionPoolをFiberセーフにした

Active RecordのConnectionPoolをFiberセーフにする。IsolationExecutionStateに少し手を加えてプール内のさまざまなキャッシュキーメソッドを実装し、それらをThread.currentから切り離せるようにする必要があった。
まだテストは追加していないが、ローカルで動かしてみたところうまくいっている様子。
その他の情報
Active RecordのConnectionPoolをFiberセーフにすることが望ましい理由については#42271を参照。
このアプローチでは、Sidekiq内のActive RecordもFiberセーフになる。app.reloader.wrapでリクエストやジョブなどをラップする他のgemも同様。
同PRより


つっつきボイス:「お〜、ConnectionPoolがFiberセーフになった」「むしろ今までそうなってなかったとは」

ActiveSupport::IsolatedExecutionState.isolation_level:fiberに設定されると、コネクションプールが同じThreadから複数のFiberをチェックアウトすることをサポートするようになった。
Alex Matchneer
同Changelogより

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

isolation_levelでFiberセーフにするかどうかを切り替えられるのが面白い: falconあたりを使うときにこのスイッチをオンにするとよさそうだけど、#42271を見るとやはりfalconが取り上げられていますね↓」「ほんとだ」「falconはまだ使ったことはありませんが、Fiberスケジューラを利用しているといえばfalconぐらいしか思いつかない」

socketry/falcon - GitHub

🔗 ガイド改修

逆関連付けが具体的に何をするのか戸惑ったことを思い出す。このガイドは役に立ったが、いくつかのユースケースが抜けている。それらをまとめてコード例とともにドキュメント化することで、Railsが逆関連付けを推論できないときに:inverse_ofを設定する理由が理解しやすくなることを期待する。
このコミットではドキュメントに以下の改修を行う。

  • Authorオブジェクトがすべて同じで、追加のクエリが実行されないことを明示する例を追加。
  • データ不整合の防止に関する例は維持(ただし1文字変数は置き換えた)。
  • オートセーブの例を追加(他の場所で明示的にドキュメント化されているかどうかはわからない)。
  • バリデーションの例を追加し、関連するバリデーションガイドへリンクした。

:inverse_ofセクションがよりクリアになるよう、:inverse_ofセクションから双方向関連付けセクションへのリンクも追加した。
同PRより


つっつきボイス:「inverse_ofのドキュメントが改善された」「そういえばinverse_ofの部分はわかりにくかったかも」「Railsのバージョンによって少しずつ変わっていたりするのでややこしい: 外部キー周りの解説なども増えたのはよさそう👍」「ところでabsenceバリデータというのもあるんですね↓、presenceバリデータは使ってますが」

# guides/source/association_basics.md#L803
+* Validate the [presence](active_record_validations.html#presence) and
+ [absence](active_record_validations.html#absence) of associations in more
+ cases
+
+   ```irb
+   irb> book = Book.new
+   irb> book.valid?
+   => false
+   irb> book.errors.full_messages
+   => ["Author must exist"]
+   irb> author = Author.new
+   irb> book = author.books.new
+   irb> book.valid?
+   => true
+   ```

参考: §2.10 presence — Active Record バリデーション – Railsガイド
参考: §2.11 absence — Active Record バリデーション – Railsガイド

🔗Rails

🔗 Dart Sass for Rails(Rails公式ニュースより)

rails/dartsass-rails - GitHub


つっつきボイス:「Rails公式更新情報のエントリをここに置きました」「Dart Sass↓のバイナリ版がdartsass-railsに入ったので、DartをインストールしなくてもSassが使えるようになったんですね」

sass/dart-sass - GitHub

「そういえばこの間記事にしたtailwindcss-railsもバイナリ版のツールを使うところがちょっと似ていますね↓」

Rails 7: importmap-rails + tailwindcss-railsでnode.jsが不要な理由

「見たところ他のプラットフォームでは普通にバイナリを使うけど、Windowsだけ別途dart.exeが必要なのね↓」「あら、惜しい」「さらにLinuxとMacにはArm版バイナリはあるけどWindows版にはない」

# dartsass-rails/exe/mingw32/sass.batより
@echo off
REM This script drives the standalone dart-sass package, which bundles together a
REM Dart executable and a snapshot of dart-sass.

set SCRIPTPATH=%~dp0
set arguments=%*
"%SCRIPTPATH%\src\dart.exe" "%SCRIPTPATH%\src\sass.snapshot" %arguments%

「あれ、WindowsってそもそもArmで動くんですか?」「動きますよ、まだプレビューですが、Arm版Windowsを動かしている人たちをちらほら見かけます」「Arm版Windowsは話に聞いたことしかありませんね〜」「Armといえば、Windows RTが動くArm版タブレットSurfaceというのもありました、全然盛り上がりませんでしたが😆

参考: “Arm版Windowsノート”のスゴさ知ってますか?スマホの手軽さとPCのパワーが同居する「HP Elite Folio」 – PC Watch[Sponsored]
参考: Microsoft Windows RT – Wikipedia

🔗 webpack-merge: webpackのコンフィグをマージするツール

survivejs/webpack-merge - GitHub


つっつきボイス:「お〜、webpackの複数のコンフィグをマージできるんですね↓: 移行中に設定を少しずつ更新して様子を見ながら設定の差分を作ったりするのに便利そう👍

// 同リポジトリより
const { merge } = require('webpack-merge');

// Default API
const output = merge(object1, object2, object3, ...);

// You can pass an array of objects directly.
// This works with all available functions.
const output = merge([object1, object2, object3]);

// Keys matching to the right take precedence:
const output = merge(
  { fruit: "apple", color: "red" },
  { fruit: "strawberries" }
);
console.log(output);
// { color: "red", fruit: "strawberries"}

「ところでこのwebpack-mergeは以下のWebpacker->Shackpacker移行ガイド↓を翻訳しているときに知ったんですが、まだ少しタイポとかが残っているようなので、落ち着いたら翻訳を再開しようかなと思っています」「こうやってレビューされることでドキュメントは良くなっていきますよね☺

参考: shakapacker/v6_upgrade.md at master · shakacode/shakapacker

その後ドキュメントにいくつか修正が入り、自分が投げたプルリクもマージされました↓。

参考: docs: improve v6 upgrade guide by G-Rath · Pull Request #71 · shakacode/shakapacker
参考: Fix typos for v6_upgrade.md by hachi8833 · Pull Request #72 · shakacode/shakapacker

🔗 Railsコミュニティアンケート2022年度版(Ruby Weeklyより)


つっつきボイス:「これまでもウォッチで何度か取り上げた、rails-hosting.comによるRailsコミュニティ向けのアンケートの募集が始まったそうです」「2年に一度やっている感じかな」「以前回答したのを思い出した」「アンケートに答えながら話してみようかなと思ってやってみたけど、エントリー数が思ったより多かった…」

なお、以下は2020年度の結果です↓。

参考: 2020 Ruby on Rails Community Survey Results | 2020 Ruby on Rails Community Survey Results

🔗 JavaScript SPAからRailsでやることにした話(Ruby Weeklyより)


つっつきボイス:「Reactも使うけど、Railsの方が考えることが少なくて助かるという感じの趣旨でした」「どんなアプリを作るかにもよると思いますけど、JavaScriptはフレームワークもツールも豊富でユーザーも多い分、どれとどれをどう組み合わせるかなど決めないといけないことが多いという面はあるでしょうね」「慣れてくればいいと思うんですけど、最初が大変そうですね…」

🔗 その他Rails

つっつきボイス:「1つ目はDHHブログのPropshaft記事です」「これまでDHHが述べてきた路線を踏襲している感じですね」

記事の末尾によると、Rails 8まではPropshaftをデフォルトとして検討することはなさそうです↓。

This isn’t a quick migration, and there’s no prospect of considering Propshaft as the new default until Rails 8.
Introducing Propshaftより

「2つ目は今日社内で話しましたが、nokogiriが依存しているlibxml2やlibxsltで脆弱性が発生したのでアップデートが出たことをbundler-auditくんが知らせてくれました↓」「そうそう、これはアップデートしないと」

rubysec/bundler-audit - GitHub


前編は以上です。

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

週刊Railsウォッチ: 端末文字幅とRubyのreline、SQLのプリペアドステートメント、Terraformほか(20220222後編)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: dartsass-railsがリリース、webpack-mergeツール、Rubyが29歳にほか(20220228前編) first appeared on TechRacho.

週刊Railsウォッチ: Ruby標準のCSVライブラリは優秀、if代入のコーディングスタイル、rambulanceほか(20220301後編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 Rubyのif値代入のスタイル


つっつきボイス:「そうそう、この記事盛り上がってましたね: 銀座Railsの懇親会でも話題になってた」

# 同記事より
# = と if を同じ行に書く
greeting = if japanese?
             'こんにちは'
           else
             'Hello'
           end
# 同記事より
# if を一段改行する
greeting =
  if japanese?
    'こんにちは'
  else
    'Hello'
  end

「自分は元々前者寄りだったんですが、後者ならインデントがスペース2文字で揃うよと銀座Railsでjnchitoさんに言われてなるほどなと思いました」「たしかに」「どちらにするかは好みの問題だとは思いますし、記事で言う謎の空白地帯は元々気にならない方です」「他の言語でもここの書き方はさまざまですよね」

「前者だと、文字数次第ではインデントにスペース1文字が入ってくることがあるじゃないですか」「インデントが奇数なのはちょっと座りが悪い感じですね」「diffがわかりやすくなるというのももっとも」

「RuboCopはこういうスタイルをチェックするんでしょうか?」「そういうcopはあるのかもしれないけどデフォルトではチェックしないんじゃないかな」

後で試すと、素のRuboCopではどちらのスタイルでも怒られませんでした。Layout/EndAlignment copを見ると、デフォルトではどちらも許容されているようです↓。

参考: Layout :: RuboCop Docs

# docs.rubocop.orgより
# bad

variable = if true
    end

# good

variable = if true
           end

variable =
  if true
  end

🔗 RubyでCSVをパースする


つっつきボイス:「実はRuby標準のCSVライブラリはかなり優秀なんですよ: ExcelからエクスポートしたBOM(Byte Order Mark)付きのCSVもちゃんと扱える」「たしかオプションで指定するとBOMが扱えるんですよね」「そうそう、オプションを付けただけでBOM付きのCSVを正確に扱えるとか、あの厄介なセル内改行も扱えるのはスゴい」「へ〜」

「言語標準のCSVパーサーは、BOMを取り除かないとまともに読み込めなかったりセル内改行の扱いが不十分だったりすることもよくますけど、Ruby標準のCSVはその点よくできていると思います👍

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

# docs.ruby-lang.orgより
require 'csv'

p CSV.parse_line("1,taro,tanaka,20")
# => ["1", "taro", "tanaka", "20"]

p CSV.parse_line("1|taro|tanaka|20", col_sep: '|')
# => ["1", "taro", "tanaka", "20"]

# 列をダブルクオートで囲むとその中にカンマや改行を含める事もできる。
# 他の仕様も含め詳しくはRFC4180を参照。
p CSV.parse_line("1,\"ta,ro\",\"tana\nka\", 20")
# => ["1", "ta,ro", "tana\nka", " 20"]

参考: XML用語事典 [BOM (Byte Order Mark)]

「BOMやセル内改行、いろいろ面倒くさい思い出があります: BOMを取り除いたり付け直したりするドロップレットを昔使ってました」「今でもそういうツールは現役でしょうね: CSVをダブルクリックしてExcelで開くにはBOMが必要」「そういうバッドノウハウありますね」「CSVがUTF-8でもBOMが付いているとExcelで文字化けせずに開けた覚えがあります」

参考: UTF-8 – Wikipedia — バイト順マークの使用

「自分はCSVよりもタブ区切りのTSVを使っていました: TSVならExcelやGoogleスプレッドシートで直接コピペできるしスタイルも維持されるので」「それもよくある方法ですね、ただ数値を含むデータはウィザードで型を指定する方が桁数が狂わずに済みますけど」「そういえば当時は多言語の訳文ファイルを扱っていたので数字はデータに入っていませんでした」「シートの書式を全部文字列にすれば少なくとも数字が変わらないはず」「そうそう、やってました」

参考: Tab-Separated Values – Wikipedia

🔗 Rubyネイティブ拡張をRustで書いてみた(Ruby Weeklyより)


つっつきボイス:「RustでRubyのネイティブ拡張を書く、なるほど」「記事で言及されている、Rustのcargoビルダーをgemで使えるようにするプルリクはまだマージはされていないようです↓」

参考: Add cargo builder for rust extensions by ianks · Pull Request #5175 · rubygems/rubygems

「ところで、Rubyネイティブ拡張をGo言語で書くという試みをこれまでほとんど見かけないんですよ」「他の言語で使うライブラリをRustでビルドするのはわかるけど、Goでやる意味ってどのくらいあるんでしょうね」「Goでdllやsoみたいな共有ライブラリをビルドするのは一応できるみたいですけど、そういえばあまり盛んではないかも」

「Goはマルチプラットフォーム向けの実行可能シングルバイナリをビルドするのがメインという印象が強いかな」「自分もそう思います」「OpenSSL並に定番の汎用ライブラリがGoで書かれるようになったら、Rubyネイティブ拡張で使いたくなるかもしれませんね」「たしかに今のところ思い当たらないかも」

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

🔗 rambulance: Railsアプリのエラーページを生成(Ruby Weeklyより)

yuki24/rambulance - GitHub


つっつきボイス:「rambulanceは社内でも使っているプロジェクトがあります」「お、そうでしたか」「Railsアプリで500エラーや404エラーなどを条件に応じて出し分けできる、汎用のエラーページ生成gemですね」「Railsのambulance(救急車)だからrambulanceなのかも」「もっともrambulanceは通常の静的なエラーページをビューで動的に生成する機能の方がメインですが」

# 同リポジトリより
# config/initializers/rambulance.rb
config.rescue_responses = {
  "ActiveRecord::RecordNotUnique" => :unprocessable_entity,
  "CanCan::AccessDenied"          => :forbidden,
  "YourCustomException"           => :not_found
}

「デフォルトのRailsは、たとえばActive Recordでレコードが見つからなければ404エラーになるけど、そういうもの以外は基本的に500エラーになります」「ふむふむ」「もっと細かく扱いたければ、通常はapplication_controller.rbにrescue_fromでエラーハンドラを書いて自分でエラーコードを出し分けることになりますが、その部分をコンフィグに書いてやれる」「なるほど」

参考: 404 Not Found – HTTP | MDN
参考: 500 Internal Server Error – HTTP | MDN
参考: rescue_fromActiveSupport::Rescuable::ClassMethods

「動的なエラーページを使わずに単にエラーを出し分けるだけなら、gemでやらなくてもapplication_controller.rbにrescue_fromを書くとかすればいいのではと思いますが、既にプロジェクトに入っている分には別に構わないという感じ」「なるほど、自分で実装しても大差ないあたりはパンくずリストのgemと位置づけが似ているかも↓」「gemを増やさずにやれるなら自分はそっちを選ぶかな: もちろんgemがあるならそれを使うという流儀もあります」

Railsの技: パンくずリストをgemなしで実装する(翻訳)

🔗 その他Ruby


つっつきボイス:「optcarrot(Ruby製ファミコンエミュレータ)がWasmで動いてる」「何のゲームだろう?」「ファミコンゲームのデータは著作権が絡むので、フリーのNESバイナリを使っていると思います」「それもそうか」「そういうNESゲームをパブリックドメインとして作っている人たちもいるんですよ、このLan Masterというゲームもどうやらそうですね」

参考: Nintendo Entertainment System – Wikipedia — NES

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

🔗 AWS Backup for Amazon S3(Publickeyより)


つっつきボイス:「AWS S3には今もオブジェクト単位で履歴を保存する機能があるけど、このAWS Backup for S3はバケット単位のタイムスタンプで履歴を差分で保存して復元できるのがよさそう👍」「お〜なるほど!」「データが大きいと料金どうなるかな…」

参考: アーカイブされたオブジェクトの復元 – Amazon Simple Storage Service

「S3バケットは大きくなりがちなのでバックアップが大変なんですよ: s3 syncで半日かかることもざらにある」

参考: AWS CLI での高レベル (S3) コマンドの使用 – AWS Command Line Interface

「バケット単位のバックアップだとどう違うんでしょうか?」「S3のバケットは、使う側としてはディスクドライブのような感覚で扱えて、その中にいくらでもパスを置ける感じ: S3オブジェクトはそのバケットの中に置かれる」「ふむふむ」

参考: バケットの概要 – Amazon Simple Storage Service

「でもオブジェクトを復旧するには、バックアップをスキャンしてオブジェクトを探さないといけないんですよ: 一方AWS Backup for Amazon S3は個別のオブジェクトではなくバケット単位で丸ごとバックアップできるので、バックアップ時刻を指定すれば個別のオブジェクトを考えずにリストアできるところが違うということになりますね」「なるほど」「AWS Backup for Amazon S3は差分でバックアップできるとあるので、最初は全量バックアップで以後は差分バックアップになるんでしょうね」

🔗 WSL2


つっつきボイス:「いろいろ興味深い記事👍」「WSL2では別のコンテナの/procをマウントできるという話も面白い: たしかにコンテナならできるはず」「権限周りの話も気になりました」

「WSL2は突き詰めるとVMなのかそれともコンテナなのかという話題について、WSL2になってVMらしい部分が増えたけどコンテナらしい部分もまだあるということで、WSL2は軽量VMであり、かつコンテナでもあるという見解も面白い」「そう思うとWSL2の挙動の不思議な点も腑に落ちそうですね」

🔗JavaScript

🔗 The State of JS 2021(Publickeyより)


つっつきボイス:「Reactも人気だけど、Angularjsも結構使われているな」「バックエンドでJSを使うことって実際にはどのぐらいあるんだろう?」「ビルドツールではwebpackが今のところ一番人気なんですね↓」「esbuildは速いという評判だけど、順位ではrollupより下なのか」


同記事より

「最後の『JavaScriptエコシステムの変化が速すぎるかどうか』は、最近になってJavaScriptに参入したエンジニアだとあまり気にしなさそうですけどね」「10年ぐらいやり続けてないとそういう気持ちになりにくいかも」

🔗言語/ツール/OS/CPU

🔗 BIOSとUEFI


つっつきボイス:「この大原雄介さんの記事は好きでいつも追いかけてます: この記事も面白い👍」「連載長いですよね」「これだけ書き続けられるのがスゴい」

参考: 大原雄介 – ITmedia 著者別インデックス

「最近はもうUEFIにあらかた移行したのかな?」「おかげでPC起動後の管理画面でもいきなりマウスが使えるようになりましたよね」「BIOS画面ではUSBキーボードが使えなかったのでPS/2キーボードが必要だったけど、もうPS/2キーボードやマウス捨てちゃったかも」「久しぶりにPCを自作したときに改めてそのあたりを思い出しました」「Macだとほとんど縁のない世界…」

参考: Basic Input/Output System – Wikipedia
参考: Unified Extensible Firmware Interface – Wikipedia
参考: PS/2コネクタ – Wikipedia


後編は以上です。

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

週刊Railsウォッチ: dartsass-railsがリリース、webpack-mergeツール、Rubyが29歳にほか(20220228前編)

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

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

Ruby Weekly

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ: Ruby標準のCSVライブラリは優秀、if代入のコーディングスタイル、rambulanceほか(20220301後編) first appeared on TechRacho.

Rails 7: dartsass-rails gemはNode.jsなしで使える

$
0
0

rails/dartsass-rails - GitHub

週刊Railsウォッチ20220228でもお伝えしましたが、つい最近、dartsass-rails gemがRailsのGitHubリポジトリで正式に登場しました。

dartsass-railsは、本家Dart Sassのバイナリ実行ファイルをラップしています。現時点ではLinuxとmacOSでi32版とx64版、Windowsでx64版のバイナリ実行ファイルがあります。今回初めて気づきましたが、Dart Sassは2016年のごく初期からバイナリ実行ファイルをリリースし続けているんですね。

sass/dart-sass - GitHub

参考: Releases · sass/dart-sass

どこかで見たような話ですね。そう、以下の記事にも書いたように、tailwindcss-rails gemもバイナリ版CLIツールをラップしています↓。最近のJSツールチェインはバイナリ版も出すのが流行りなのでしょうか。

Rails 7: importmap-rails + tailwindcss-railsでnode.jsが不要な理由

DHHがtailwind.cssとDart Sassで立て続けにバイナリをサポートしたのは、Rails 7で「Node.jsなしでできる選択肢を増やすため」だと私は考えています。

以下のDHHブログにこんなことが書かれています。

参考: Introducing Propshaft

There’s still more work to be done on Propshaft before it’s fully able to take over the reins from Sprockets as the default asset pipeline in Rails, but we’re fast moving closer. HEY just went into production yesterday with a Propshaft + Dart Sass combination to fit alongside its existing use of import maps. Early Propshaft contributor Breno Gazzola has also already launched production apps on Propshaft.
Introducing Propshaftより

このdartsass-rails gemは、propshaftと組み合わせられるそうです。

これまではRails 7でNode.jsのお世話にならずに使えるCSSフレームワークといえばtailwindだけでしたが、そこにdartsassも加わったわけです。DHHはこのところ「バイナリ実行ファイルによるNode.jsなし路線」をやんわりと推しているように思えます。

dartsass-railsとpropshaftを使ってみる

dartsass-railsのREADMEを見ると、Rails 7でのインストール方法は以下を順に実行するというものでした。

  • ./bin/bundle add dartsass-rails
  • ./bin/rails dartsass:install

1行目は普通にGemfileに自分で追記しても同じですね。

gem "dartsass-rails", "~> 0.3.0"

rails new . -a propshaftで生成したRails 7アプリで上を実行した結果が以下です(関連のなさそうなディレクトリ内のファイルははしょりました)。

.
├── Gemfile
├── Gemfile.lock
├── Procfile.dev
├── README.md
├── Rakefile
├── app
│   ├── assets
│   │   ├── builds
│   │   │   └── application.css
│   │   ├── images
│   │   └── stylesheets
│   │       ├── application.css
│   │       └── application.scss
│   ├── channels/
│   ├── controllers/
│   ├── helpers/
│   ├── javascript
│   │   ├── application.js
│   │   └── controllers
│   │       ├── application.js
│   │       ├── hello_controller.js
│   │       └── index.js
│   ├── jobs/
│   ├── mailers/
│   ├── models/
│   └── views/
├── bin
│   ├── dev
│   ├── importmap
│   ├── rails
│   ├── rake
│   └── setup
├── config
│   ├── application.rb
│   ├── boot.rb
│   ├── cable.yml
│   ├── credentials.yml.enc
│   ├── database.yml
│   ├── environment.rb
│   ├── environments/
│   ├── importmap.rb
│   ├── initializers/
│   ├── locales/
│   ├── master.key
│   ├── puma.rb
│   ├── routes.rb
│   └── storage.yml
├── config.ru
├── db/
├── lib/
├── log/
├── public/
├── storage/
├── test/
├── tmp/
└── vendor
    └── javascript

通常、Sprocketsまたはpropshaftを使う場合は./bin/rails sで起動しますが、dartsass-railsをインストールすると./bin/devで起動するように組み替えられます。

生成されたProcfile.devを見るとこうなっています。

# Procfile.dev
web: bin/rails server -p 3000
css: bin/rails dartsass:watch

app/assets/stylesheets/にはapplication.cssとapplication.scssが両方できてしまいますが、前者は削除して構いません。

これでSassが動くようになり、SassでおなじみのCSSのネストや@importも使えることを確かめました。

ここではpropshaftにしましたが、Sprocketsとの組み合わせでもまったく同じようにdartsass-railsが使えることを別途確かめました。Rails 7ではrails new .のように無指定だと引き続きSprocketsが使われるので、後は上と同じにやれます。

Bootstrapもimportmap-railsで入れてみた

SassがNode.jsなしで動くようになったので、試しにBootstrap 5のJSもimportmap-railsで入れてみました。

./bin/importmap pin popperjs bootstrap

実行後のconfig/importmap.rbは以下のようになりました。

// config/importmap.rb
# Pin npm packages by running ./bin/importmap

pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "bootstrap", to: "https://ga.jspm.io/npm:bootstrap@5.1.3/dist/js/bootstrap.esm.js"
pin "@popperjs/core", to: "https://ga.jspm.io/npm:@popperjs/core@2.11.2/lib/index.js"

ただしこれだけでは動きません。importが必要です。

// 以下を追加
import * as Popper from "@popperjs/core"
import * as Bootstrap from "bootstrap"

自分はとりあえずapp/javascripts/controllers/index.jsに書きましたが、app/javascripts/application.jsに書くのとどちらがよいのでしょう?🤔

まだrails newのオプションではdartsass-railsをインストールできない

執筆時点のRails 7のrails newオプションにはまだdartsass-railsは含まれていませんので、上記の方法で追加インストールする必要があります。今のところプルリクにも見当たりません。

現時点では、rails new -c sass -a propshaftを指定してもpropshaftは無効になり、Node.jsが動き出してyarn add sassでsassがインストールされます。

しかし最近のRails 7の動向を考えれば、そのうちrails newでもdartsass-rails+propshaftのセットアップを指定できるようになるのではないかと期待しています。たとえば-c sassを指定すると、-a propshaft(または無指定でSprockets)の場合はdartsass-rails gemがインストールされ、-j esbuildなどを指定するとyarnでsassがインストールされる、という具合になるのかもしれません。

最後に

Rails 7でのフロントエンドの選択肢、特にNode.jsを使わない選択肢が少しずつ増えていることを実感します。node_modulesディレクトリが膨れ上がってデプロイ時のサイズが1GB近くなったみたいな話もちらほら聞くので、個人的にこの路線は好きです。

これからの課題だと思いますが、こうしたフロントエンドの選択肢についてRailsガイドにまとまったドキュメントが欲しいです。今は情報が周辺gemに散らばっていて追うのが大変ですが、これらが安定してきたらまとまったドキュメントに着手するだろうと期待しています。

関連記事

Propshaft gem README(翻訳)

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

The post Rails 7: dartsass-rails gemはNode.jsなしで使える first appeared on TechRacho.

Rails 7: Active RecordのConnectionPoolsがFiberセーフになった(翻訳)

$
0
0

概要

元サイトの許諾を得て翻訳・公開いたします。

参考: 週刊Railsウォッチ20220228 Active RecordのConnectionPoolをFiberセーフにした

Rails 7: Active RecordのConnectionPoolsがFiberセーフになった(翻訳)

パラレリズムとコンカレンシーは、Ruby 3の大きな焦点のひとつです。この分野の先駆者といえばRubyのFiberです。Fiberは、実行の一時停止、ループ、再開を可能にするコンカレンシーメカニズムであり、コンテキストスイッチのコストが非常に小さくなります。開発者は、スレッドより少ないメモリ消費でコードセグメントの実行を制御できるようになります。

Fiberは、スケーラブルでノンブロッキングのクライアント=サーバーに最適のソリューションですが、RailsでFiberを利用できるようになるまでしばらく時間を要しました。

変更前

RailsでスレッドからFiberへの置き換えが進めば、パフォーマンスの向上が見込まれます。そのひとつがActive Recordです。Active Supportのconfig.active_support.isolation_levelで分離レベルを設定したとしても、現在のActive Recordは未だにスレッドに依存しています。分離レベルは、データベースのトランザクションが他のユーザーやシステムに伝搬する方法を決定します。

分離レベルが重要な理由を知るには、Active RecordのConnectionPoolについて知っておく必要があります。コネクションプールは、Active Recordの複数のコネクションを管理します。Railsはこのトランザクションを実行するために、I/O操作をスレッドに逃し、各スレッドが個別のDBコネクションを維持します(Pumaの場合)。そうしないとデータベースとの個別のやりとりが混ざってしまう可能性があります。Active Recordでスレッドよりパフォーマンスがよいものが使えるなら乗り換えたいところですが、悲しいことにそうはいきません。

config.active_support.isolation_level:fiberを設定してみましょう。

module Myapp
  class Application < Rails::Application
    config.active_support.isolation_level = :fiber
  end
end

このとき、ConnectionPoolがデータベースとどのようにやりとりするかを見てみましょう。

> Rails.application.config.active_support.isolation_level
=> :fiber
> ActiveRecord::Base.connection_pool.send(:current_thread)
=> #<Thread:0x00007f9ba485fa88 run>

Active RecordのConnectionPoolには、Active Supportの分離レベルが反映されていません。

変更後

ありがたいことに、ConnectionPoolのやりとり全体がスレッドまたはFiberのいずれかを使うようになりました(#44219)。

それでは早速見てみましょう。

> Rails.application.config.active_support.isolation_level
=> :fiber
> ActiveRecord::Base.connection_pool.send(:current_thread)
=> #<Fiber:0x00007fb5515d5238 (resumed)>

できました!しかしFiberに変わるとどんな意味があるのでしょうか。近い将来、RailsのI/O操作のパフォーマンス向上が期待できます。現在のRailsではIsolatedExecutionStateで実行を抽象化していますが、少なくともこれについてはそうなるでしょう。しかしRailsのコードベースを見渡すと、スレッド中心の実装にハードコードされているコードがまだたくさんあるようです。Railsコアチームの仕事はまだ山積みです。今日にでもコントリビューションしましょう!

関連記事

Ruby 3: FiberやRactorでHTTPサーバーを手作りする(翻訳)

The post Rails 7: Active RecordのConnectionPoolsがFiberセーフになった(翻訳) first appeared on TechRacho.

Rails 7: importmap-railsとjsbundling-railsでのStimulusの扱いの違い

$
0
0

Rails 7でStimulusを書き始めています。Rails 7でimportmap-railsを使うかjsbundling-railsを使うかでStimulusのセットアップが少し違うことに今頃気づいたので、小ネタですがメモします。

ローカルのStimulusコントローラファイル

ここでは、ローカルのapp/javascripts/controllers/以下のStimulusコントローラファイルの扱いについて書きます。

1. importmap-railsの場合r

importmap-railsを使う場合は、app/javascripts/controllers/index.jsが以下のようにeagerLoadControllersFrom()を呼び出しています。

  • importmap利用時のapp/javascripts/controllers/index.js
// Import and register all your controllers from the importmap under controllers/*

import { application } from "controllers/application"

// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)

// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)

そのおかげで、app/javascripts/controllers/以下に置かれたなんちゃら_controller.jsはそのコントローラ名で自動登録されます。

2. jsbundling-railsの場合

jsbundling-railsの場合は、app/javascripts/controllers/以下のコントローラファイルを自動登録しません(後述)。そのため、何らかの形でコントローラファイルを登録する必要があります。以下のHelloControllerのような要領で登録します。

  • jsbundling-rails(つまりnode)利用時のapp/javascripts/controllers/index.js
// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName

import { application } from "./application"

import HelloController from "./hello_controller"
application.register("hello", HelloController)

コントローラを登録する手間を軽減するため、Stimulusコントローラのジェネレータが使えます。

index.jsのコメントにもあるように、jsbundling-railsの場合は原則として以下を実行してコントローラファイルを生成し、それを使うことになります。

./bin/rails generate stimulus コントローラ名

このジェネレータでは./bin/rails stimulus:manifest:updateも実行されてindex.js内のマニフェストも自動更新されます。./bin/rails stimulus:manifest:updateは単独でも実行できます。

$ ./bin/rails g stimulus mycon
create  app/javascript/controllers/mycon_controller.js
rails  stimulus:manifest:update

以下は実行後のindex.jsです。

// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName

import { application } from "./application"

import HelloController from "./hello_controller.js"
application.register("hello", HelloController)

// 以下が自動で追加される
import MyconController from "./mycon_controller.js"
application.register("mycon", MyconController)

ジェネレータを使わずに、app/javascripts/controllers/以下に自分でコントローラファイルを作成したときは、自分でindex.jsにマニフェストを追加する必要があります。

外部のStimulusコンポーネントを利用する場合

importmap-railsの場合は./bin/importmap pin npmパッケージ名で追加します。importmap-railsは、このStimulusコンポーネントをCDNから取り入れて利用します。

# 例
./bin/importmap pin tailwindcss-stimulus-components stimulus-textarea-autogrow stimulus-scroll-to

jsbundling-railsの場合はyarn add npmパッケージ名で追加します。こちらは普通にビルドされます。

# 例
yarn add tailwindcss-stimulus-components stimulus-textarea-autogrow stimulus-scroll-to

しかし外部のStimulusコンポーネントを利用する場合は、どちらの場合もindex.jsでマニフェストを手動で追加する必要があります。

// index.js
// This file is auto-generated by ./bin/rails stimulus:manifest:update
// Run that command whenever you add a new controller or create them with
// ./bin/rails generate stimulus controllerName

import { application } from "./application"

// たとえば以下のような感じで手動で追加する
import { Alert, Autosave, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
application.register('alert', Alert)
application.register('autosave', Autosave)
application.register('dropdown', Dropdown)
application.register('modal', Modal)
application.register('tabs', Tabs)
application.register('popover', Popover)
application.register('toggle', Toggle)
application.register('slideover', Slideover)

import TextareaAutogrow from "stimulus-textarea-autogrow"
application.register("textarea-autogrow", TextareaAutogrow)

import ScrollTo from "stimulus-scroll-to"
application.register("scroll-to", ScrollTo)

最後に

importmap-railsとjsbundling-railsの違いがわかると、ローカルでStimulusコントローラを書いたときにindex.jsのマニフェストを更新しなくてよいimportmap-railsがいいなという気持ちに傾いてきました。

StimulusコンポーネントであればローカルでもCDN上の外部コンポーネントでも問題なくimportmap-railsで扱えるので、自分はimportmap-railsを選び、JavaScriptもすべてStimulusで書くことにしました。

ただし、Stimulusコンポーネント以外の一般的な外部npmパッケージで、変数の依存関係などでビルドが必須になる複雑なものだと、ビルドのないimportmap-railsでは扱えません。

以下の記事にも書いたように、bootstrap.jsとpopper.jsをimportmap-railsでpinして動かせましたが、たとえばBootstrap RFSはSass変数が絡んでくるのでimportmap-railsではインストールできませんでした。

Rails 7: dartsass-rails gemはNode.jsなしで使える

ある程度以上複雑な外部JSライブラリを使うことが事前にわかっている場合は、最初から素直にjsbundling-railsを使うのがよさそうです。

おまけ: jsbundling-rails + webpackならコントローラをオートロードできる

ここまで書いてから、Stimulus公式ハンドブックにひととおり書いてあることに気づきました。

webpackを使っているのであれば、Railsであるかどうかにかかわらず、@hotwired/stimulus-webpack-helpersパッケージを追加したうえで、以下をindex.jsまたはメインのjsファイルに書けばローカルのStimulusコントローラをオートロードできるそうです(esbuildやrollupではオートロードはサポートされないそうです)。

 import { Application } from "@hotwired/stimulus"
 import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers"

 window.Stimulus = Application.start()
 const context = require.context("./controllers", true, /\.js$/)
 Stimulus.load(definitionsFromContext(context))

関連記事

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

Rails 7 : rails newのフロントエンド関連オプションの組み合わせを調べてみた

The post Rails 7: importmap-railsとjsbundling-railsでのStimulusの扱いの違い first appeared on TechRacho.

「巨大プルリク1件vs細かいプルリク100件」問題を考える(翻訳)

$
0
0

概要

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

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

  • 2018/02/07: 初版公開
  • 2022/02/24: 更新

Git Logo by Jason Long is licensed under the Creative Commons Attribution 3.0 Unported License.

「巨大プルリク1件vs細かいプルリク100件」問題を考える(翻訳)

本記事では、昔ながらの問題である「巨大なプルリク1件と超細かいプルリク100件、どっちなら戦う気になれる?」に対する回答を示したいと思います。チームの一員としてよりよいコードを書くためのガイドラインについてもある程度解説します。今回の記事は、すべて以下のツイートから触発されました。

何が問題だったか

私は、Fullscript社で行われているコードレビューが今ひとつ活用され切っていないことに気づきました。featureブランチが長期間取り残されていることもしょっちゅうで、featureブランチのコードは数千行にまで肥大化し、まともなフィードバックを返すことはおろか、レビューに途方もない時間がかかる始末です。心底ゲンナリでした。

開発者は、時間とやる気が満ちてくるまでレビューを先延ばしにしたりするので、開発プロセスが停滞してしまうことがあります。レビュアーは「レビューしなければ」というプレッシャーを肌で感じつつ、コードの表面をさっと眺めて「LGTM!👍」(良さげ)などと書いて終わらせることもよくあります。

たとえレビューがうまくいったとしても、大規模なリファクタリングをかけるにはタイミング的に手遅れになることもしばしばです。初期段階の設計ミスが頑固に根を張り、修正コストはスタートアップ企業が到底負担しきれないほどに跳ね上がってしまいます。ぐらついている基礎の上で何週間も作業を重ねたこともありました。

残念なことに、コードの品質は詳細な検査が必要なレベルにすら達しませんでした。誰もそこから学んでおらず、レビュープロセスは頓挫してしまったのです。

なるほど、ではどうやって改善する?

レビューを依頼するということは、他の誰かに「責任を共有してください🙇」とお願いするということです。依頼された人は問題を理解し、あなたのコードを把握し、問題がなければ(そう願いたいものです)承認しなければなりません。私たちは開発者として、この作業をできる限り軽減すべきです。

「早期」かつ「頻繁に」

フィードバックを依頼するのは、最も重要な時期、すなわち開発プロセスの早い段階で行うようにしましょう。こうすることで、レビュアーは設計上の問題を早い段階で検出する機会を得られますし、あなたが確かな基礎の上にコードを構築していることを担保できるようになります。チームは、開発プロセスが進んでからの書き直しというコストの高い作業や、既知の欠陥を持つコードを時間や予算の制約のせいでそのままマージする事態を回避できます。

粒度を小さくする

「小さく」というのは、限りなくゼロ行に近づけるということです。たった1行の変更のレビューを嫌がるレビュアーはいないでしょう。プルリクのサイズ(=コードの行数)に上限を設けることで、粒度を下げやすくする効果が著しく向上します。レビュアーが1行ずつ精査しやすくなるのはもちろんのこと、レビュー時間も大きく削減できます。コードを定期的にマージできるようになり、品質にも自信を持てるようになります。

作業のスコープを絞る

コードの行数を削減するための重要なコツは、プルリクのスコープ(=機能のセット)を絞り込み、解決するタスクを1つにする(または密接に関連する少数のタスクに絞り込む)ことです。スコープを絞ることで、レビュアーの認知機能にかけられる負荷を大きく軽減できます。1件のプルリクでいくつもの問題をいっぺんに解決しようと欲張ると、ある問題がどのコードと関連しているのかを整理する作業がつらくなります。

機能が未完成でもリリースする(ただし内緒で)

機能が完成するまでリリースを差し止めることは比較的普通に行われます。残念なことに、これは巨大なfeatureブランチがいつまで経ってもなくならない主要な原因のひとつです。mainrブランチでの開発が進むに連れて、マージのコンフリクトやrebaseといった愉快な事件が起きがちです。たとえマージできたとしても、大規模な変更を無事にデプロイするのはチームにとって神経を削る作業です。目玉機能や大きな依存関係のアップグレードをデプロイする場合はなおさらです。

機能を一時的に取り消すツールを使って、未完成の機能をユーザーの目から隠しておくという手があります。これなら、コードのマージやリリースの頻度を落とさずに済みますし、心配の種も減らせます。開発が完了に近づいたら、特定のアカウントやアーリーアダプタ(訳注: 新機能を喜んで使うユーザー)にだけ新機能へのアクセスを許可できるようになります。デプロイ作業の心配も減りますし、機能の成熟度に応じて機能へのアクセスを制御できるようになります。

追伸: Ruby on Railsをお使いの方には、flipper gemを強くおすすめいたします。

jnunemaker/flipper - GitHub

事前の計画

開発者がこうした制約のもとで作業すると、問題を細かな単位に分割して渡すようになります。この制約は、明確なプランがないまま開発を始めたくなる誘惑を払いのけるのにも役立ちます。これには少々経験が必要ですが、そのうちに慣れて、自然に開発プロセスの一部に組み込まれるでしょう。

「ついでのリファクタリング」はしないこと

開発者は、問題に気づくとその場でリファクタリングすることがよくあります。もちろんリファクタリングはよいことですが、別のプルリクで行うべきです。そうすることでリファクタリングを早めにマージできますし、関係のない機能リリースに修正が押し込められることもなくなります。「ついでの改善」は、元々のプルリクのリリースが遅れれば巻き添えで遅れてしまいますし、最悪まったくリリースされなければそのまま失われてしまうでしょう。

まずはやってみよう

コードレビューは、チーム内でのソフトウェア作成になくてはならない作業です。コードレビューは知識を共有する場であり、コードの品質を監視する門番でもあります。上述の制約の元で作業するようになったことで、Fullscript社のコードレビューで次のような成果を得られました。

  • コードレビューが短時間で完了し、品質も向上した
  • リリースのテストが楽になり、デプロイの苦労も大きく軽減された
  • 問題が発生した場合の変更の取り消しやロールバックも楽になった

単純な話のように見えるかもしれません(し、実際そうかもしれません)が、実践のためにはチームが一丸となって経験を積む必要があります。本記事でお伝えしているメッセージに共感いただけましたら、始め方についてチームメンバーと話してみることをおすすめします。


ご意見やご質問がありましたら、元記事のコメント欄かTwitterまでお気軽にどうぞ。

また、本記事は私が地元のミートアップで発表したスピーチを元にしています。このスピーチは元々、大規模チームでのソフトウェア開発のアプローチについてKevin McPhillipsWillem van Bergenと雑談したときに閃いたものです。お二人に感謝します。

関連記事

いい感じのコードには速攻でLGTM画像を貼ってあげよう

Rails: テストのリファクタリングでアプリ設計を改良する(翻訳)

技術的負債を調査する10のポイント(翻訳)

The post 「巨大プルリク1件vs細かいプルリク100件」問題を考える(翻訳) first appeared on TechRacho.


Rails 7: 数値バリデータにonly_numericオプションが追加(翻訳)

$
0
0

概要

元サイトの許諾を得て翻訳・公開いたします。

なお、この改修はRails 7.0.0〜7.0.2には入っていません。現時点ではmainブランチに入っています。

Rails 7: 数値バリデータにonly_numericオプションが追加(翻訳)

Active Recordには、クラス定義内で直接利用できる多くのバリデーションヘルパーが事前定義されています。これらのヘルパーが提供するバリデーションルールは共通しています。つまり、バリデーションが失敗すればオブジェクトのエラーコレクションにエラーが追加され、このエラーはバリデーションが行われる属性に関連しています。

また、コードを1行書けば、さまざまな属性に同じバリデーションを追加できます。これが可能なのは、どのバリデーションヘルパーにも任意の個数の属性名を渡せるからです。

さまざまなヘルパーの中にはnumericalityヘルパーもあります。このヘルパーは、属性が数値であることを保証します。

numericalityヘルパーの動作を理解するために、最初にJSONカラムを持つテーブルを生成します。

create_table :cricket do |t|
  t.jsonb :points
end

変更前のバリデーション

従来のRailsでは、numericalityバリデータに整数ですらでない値を渡してもエラーにならずにパスしていました。以下の例を見ていただければおわかりかと思います。

class Cricketer < ApplicationRecord
  store_accessor :points, %i[scores]
end

>> Cricketer.create!(scores: "30")
#<Cricketer id: 1, points: {"scores" => "30"}, created_at: Sun, 04 Feb 2022 14:09:43.045301000 UTC +00:00, updated_at: Sun, 04 Feb 2022 14:09:43.045301000 UTC >

訳注

上のコード例はたとえば以下のようにonly_integerを指定するつもりだったと思われます。

class Cricketer < ApplicationRecord
  store_accessor :points, %i[scores]
  validates_numericality_of :scores, only_integer: true, allow_nil: true
end

Rails 7.0.2.2 + Ruby 3.1.1で試すと、たとえば"a"Validation failed: Scores is not a number (ActiveRecord::RecordInvalid)エラーになりますが、"30"という文字列はたしかに記事にあるようにパスしてしまいました。

変更後のバリデーション

先ごろRails 7.0にonly_numericオプションが導入され、属性の値に数値のみを許可できるようになりました(#43914)。つまり、数字の文字列を値として渡すと、ちゃんと解析してエラーを発生するようになります。

以下の例を見ていただければおわかりかと思います。

class Cricketer < ApplicationRecord
  store_accessor :points, %i[scores]
  validates_numericality_of :scores, only_numeric: true, allow_nil: true
end
>> Cricketer.create!(scores: "30")
'raise_validation_error': Validation failed: Scores is not a number (ActiveRecord::RecordInvalid)

>> Cricketer.create!(scores: 30)
#<Cricketer id: 1, points: {"scores" => 30}, created_at: Sun, 04 Feb 2022 14:09:43.045301000 UTC +00:00, updated_at: Sun, 04 Feb 2022 14:09:43.045301000 UTC >

通常、JSONカラムのデータは自動的にはシリアライズされないので、データを正しくシリアライズできるよう、このオプションがRailsのnumericalityバリデータに追加されました。

詳しくは#43914をご覧ください。

訳注

Railsのmainブランチ(74ba52e)でonly_numericを使うことで上記の動作を確認できました。

なお、従来からあるonly_integerの挙動は変わっていません。整数は数値の30でも文字列の"30"でもパスし、30なら整数で、"30"なら文字列で登録されます。
"a"などの非数値文字列や小数3.14や小数の文字列"3.14"は以下のエラーになります。

Validation failed: Scores must be an integer (ActiveRecord::RecordInvalid)

関連記事

Rails 7: Active RecordのConnectionPoolsがFiberセーフになった(翻訳)

The post Rails 7: 数値バリデータにonly_numericオプションが追加(翻訳) first appeared on TechRacho.

週刊Railsウォッチ: 英国政府サイトで使われるRailsアプリ、pg-oscとPercona Toolkitほか(20220308前編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

以下のコミットログのうち、Changelogに反映されているものから見繕いました。

🔗 CSPの修正

content_security_policyが無効なディレクティブを返すことがある問題を修正。
lambda呼び出しの結果によるディレクティブで、selfunsafe-evalなどが一重引用符で囲まれないことがあった。

content_security_policy do |policy|
  policy.frame_ancestors lambda { [:self, "https://example.com"] }
end

この修正によって、上で生成されるポリシーが有効になるようになった。
同Changelogより


つっつきボイス:「CSPのヘッダーで'self'selfになってしまうことがあったのを修正したんですね: 明らかにバグ」「frame_ancestorsという属性初めて知りました」

参考: CSP: frame-ancestors – HTTP | MDN

🔗 skip_forgery_protectionの挙動を修正。

フォージェリー保護が有効になってない状態でskip_forgery_protectionを実行してもエラーにならないよう修正。
この修正により、Rails 7.0でdefault_protect_from_forgeryをfalseにした場合にWelcomeページ(/)でArgumentErrorが発生しなくなる。
Brad Trick
同Changelogより


つっつきボイス:「Action Mailboxに修正が入っていますね↓」「ほんとだ」「でもそれ以外にも関連していそう: たまたまActionController::Baseを継承してかつskip_forgery_protectionを使っていたのがここだけだったんでしょうね」「Welcome画面でArgumentErrorが出たらびっくり」

# actionmailbox/app/controllers/action_mailbox/base_controller.rb#L3
module ActionMailbox
  # The base class for all Action Mailbox ingress controllers.
  class BaseController < ActionController::Base
-   skip_forgery_protection if default_protect_from_forgery
+   skip_forgery_protection

    before_action :ensure_configured

🔗 リダイレクトのレスポンスからbodyコンテンツを削除


つっつきボイス:「リダイレクトでbodyコンテンツが返されてたんですか?」「言われてみれば”You are being redirected”みたいなbodyはありましたね↓: テストでrequest specを書いたりするとこういうのが入ってくる」「そういえばあったかも」

# actionpack/lib/action_controller/metal/redirecting.rb#L88
      self.status        = _extract_redirect_to_status(options, response_options)
      self.location      = _enforce_open_redirect_protection(_compute_redirect_to_location(request, options), allow_other_host: allow_other_host)
-     self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
+     self.response_body = ""
    end

「これが削除されると表示が変わるんでしょうか?」「リダイレクトレスポンスなので、ブラウザの挙動上ユーザーに表示されることはありませんね: curlとかでアクセスしたりすると表示される」「たしかに」「テストの修正は必要になるのかな?」「テストでリダイレクトのbodyをチェックしていない限り大丈夫だと思います: リダイレクトのテストでチェックするのは基本的にステータスコードとロケーションですね」「なるほど」「むしろこの修正でテストが落ちるとしたら、そのテストが的はずれな部分をチェックしている可能性があるかも」

「プルリクによると、リダイレクトのレスポンスにbodyがあるとW3Cバリデータ↓でエラーになるんですって」「今のままでも問題は起きなさそうですけど、W3Cバリデータに怒られるなら削除しようという流れなんでしょうね」

# 同PRより
Warning: Consider adding a lang attribute to the html start tag to declare the language of this document.

Error: Start tag seen without seeing a doctype first. Expected <!DOCTYPE html>.

Error: Element head is missing a required instance of child element title.

参考: The W3C Markup Validation Service

🔗 ActionContoller::LiveIsolatedExecutionStateを修正

短命なスレッドでIsolatedExecutionStateをコピーするようActionContoller::Liveを修正。
ActionContoller::Liveは当初から、ミドルウェアで設定されたCurrentAttributesなどをコントローラのアクションで保持するためにスレッドのローカル変数をコピーするようになっていた。
7.0でIsolatedExecutionStateが導入され、ActionContoller::Liveコントローラで一部のグローバルステートが失われていた。
Jean Boussier
同Changelogより


つっつきボイス:「ACってAction Controllerだったのか」「Liveはストリーミング系のコントローラで、例のCurrentAttributesとも関連しているらしい」「IsolatedExecutionStateの一部が共有されていたのをちゃんと分けるようにしたようですね」

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

後で調べると、IsolatedExecutionStateはpublicではないようです↓。

参考: rails/isolated_execution_state.rb at 7-0-stable · rails/rails

🔗 フィクスチャのメモリフットプリントを削減

フィクスチャアクセサのメモリフットプリントを削減。
従来のフィクスチャアクセサはdefine_methodでeagerに定義されていたため、メモリ使用量がフィクスチャやテストスイートの個数に直接依存していた。
フィクスチャアクセサをmethod_missingで実装したことで、メモリやCPUのオーバーヘッドを大幅に削減できた。
Jean Boussier
同Changelogより


つっつきボイス:「これはメモリ使用量の最適化か」「コードもだいぶ減りましたね↓」「チリも積もればというヤツで、こういう改修が積み重なって全体のパフォーマンスがよくなる👍

# activerecord/lib/active_record/test_fixtures.rb#L57
      def setup_fixture_accessors(fixture_set_names = nil)
        fixture_set_names = Array(fixture_set_names || fixture_table_names)
-       methods = Module.new do
+       unless fixture_set_names.empty?
+         self.fixture_sets = fixture_sets.dup
          fixture_set_names.each do |fs_name|
-           fs_name = fs_name.to_s
-           accessor_name = fs_name.tr("/", "_").to_sym
-
-           define_method(accessor_name) do |*fixture_names|
-             force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
-             return_single_record = fixture_names.size == 1
-             fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
-
-             @fixture_cache[fs_name] ||= {}
-
-             instances = fixture_names.map do |f_name|
-               f_name = f_name.to_s if f_name.is_a?(Symbol)
-               @fixture_cache[fs_name].delete(f_name) if force_reload
-
-               if @loaded_fixtures[fs_name][f_name]
-                 @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
-               else
-                 raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
-               end
-             end
-
-             return_single_record ? instances.first : instances
-           end
-
-           private accessor_name
+           key = fs_name.match?(%r{/}) ? -fs_name.to_s.tr("/", "_") : fs_name
+           key = -key.to_s if key.is_a?(Symbol)
+           fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
+           fixture_sets[key] = fs_name
          end
        end
-       include methods
      end

🔗 #attachした添付ファイルの保存に成功するとblobを返すようになった

添付ファイルをレコードに保存するとblobオブジェクトを返すようになる。
従来は、添付ファイルを保存してもblobオブジェクトは返されなかった。
現在は、#attachメソッドで添付ファイルを追加してレコードを保存すると、レコードにアタッチされたblobまたはblobの配列を返すようになった。添付ファイルの保存に失敗した場合はfalseを返す。
Ghouse Mohamed
同Changelogより


つっつきボイス:「これはActive Storageですね」「今まではrecord.saveの結果を返していたのでtrueかfalseしか返さなかったけど、改修後は成功時にblob(binary large object)を返すようになったんですね: この方が扱いやすくてよさそう👍」「Rubyの条件式はnilfalse以外はtrueになるので、既存のロジックも変わらずに済みますね」

# activestorage/lib/active_storage/attached/one.rb#L57
    def attach(attachable)
      if record.persisted? && !record.changed?
        record.public_send("#{name}=", attachable)
-       record.save
+       if record.save
+         record.public_send("#{name}")
+       else
+         false
+       end
      else
        record.public_send("#{name}=", attachable)
      end
    end

🔗 パス名が空("")の場合にのみPathname.blank?がtrueを返すよう修正


つっつきボイス:「言われてみれば、スペース文字だけのファイル名は作ろうと思えば作れますね」「ファイル名にスペースを混ぜるのがありなんだから、スペースだけのファイル名もありということになりますね」「で、今までは" """Pathname.blank?がfalseになっていたけど、" "ならfalseを返して""ならtrueを返すのが正しい、たしかに」「これはバグ修正ですね」

# activesupport/test/core_ext/pathname/blank_test.rb
# frozen_string_literal: true

require_relative "../../abstract_unit"
require "active_support/core_ext/pathname/blank"

class PathnameBlankTest < ActiveSupport::TestCase
  def test_blank
    assert_predicate Pathname.new(""), :blank?
    assert_not_predicate Pathname.new("test"), :blank?
    assert_not_predicate Pathname.new(" "), :blank?
  end
end

「それにしても、名前がスペースだけのファイルを作るという発想が今までなかった」「それに気づくようなコードを書いていた人がいるということなのかな?」「issue #44452を見ると、Pathname.presenceの挙動で驚いたのが修正のきっかけだったらしい」「なるほど、名前がスペースだけのファイルを作りたかったわけではなかった」

🔗Rails

🔗 英国政府のサイトの多くはRailsで構築されている(Ruby Weeklyより)


同記事より(緑はテストでカバーされている部分)


つっつきボイス:「英国政府の公式サイトは70個ほどあって、そのうちRailsアプリがかなりたくさん使われているそうです」「へー、知りませんでした」「上の記事では政府がアプリを採用するときにどういうテストを行うかという基準を示しているそうです」「なるほど、こういうふうに事前に割と細かく仕様を示すところはありますね」「Railsが使われていると聞くと何となくイギリスに親近感を抱いちゃいました」「アプリをオープンソースベースで作っている公的機関は世界的に見れば結構あります」

🔗 pg-osc: PostgreSQLスキーマ変更ツール(Ruby Weeklyより)

shayonj/pg-osc - GitHub


つっつきボイス:「PostgreSQLのスキーマをゼロダウンタイムで変更する、よく話題になるヤツですね」「このpg-online-schema-changeというコマンドでやれるらしい↓」「略してoscなんですね」

# 同記事より
pg-online-schema-change perform \
  --alter-statement 'ALTER TABLE books ADD COLUMN "purchased" BOOLEAN DEFAULT FALSE; ALTER TABLE books RENAME COLUMN email TO new_email;' \
  --dbname "production_db" \
  --host "localhost" \
  --username "jamesbond" \
  --password "" \
  --drop

「このpg-oscは、pt-online-schema-changeにインスパイアされたと記事にあります」「お、pt-online-schema-changeってPercona Toolkitじゃないですか↓」「コマンド名のptをpgに変えたということなのかな」

参考: pt-online-schema-change — Percona Toolkit

「Percona Toolkitといえば、これまでもよく話題に出てきた優秀なツールですね(ウォッチ20201020)」「元々MySQL向けでしたが今はPostgreSQL向けにもなりつつある、歴史も実績もあるツールです: なお昔はmaatkitという名前でした」「2008年からあるみたいなので歴史長いんですね」「Percona Toolkitにはかなり信頼を寄せています👍

参考: Percona Toolkit (ペルコナツールキット) | MySQLチューニング/保守サポート/コンサルティングのスマートスタイル

「このツールにはMySQLのチューニングでかなりお世話になりました: かつてのMySQLはスロークエリを秒でしか指定できなかった時代があったんですよ」「秒だけだとキツい…」「あの頃Percona Toolkitがなかったらmsecで指定するのは無理でしたね」

🔗 その他Rails

つっつきボイス:「ツイートのスレッドを追うと、Railsのデプロイにどんなものを使っているか人それぞれで楽しい」「Herokuだったり、GitHub Actionsだったり、DockerコンテナからFargateだったり、ssh経由でgit pullしたり、いろいろ」「いったんCDを設定したら後は気にしなくなりますけどね」「そうそう」

参考: CD(継続的デリバリー)とは? » CloudBees|テクマトリックス


前編は以上です。

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

週刊Railsウォッチ: Ruby標準のCSVライブラリは優秀、if代入のコーディングスタイル、rambulanceほか(20220301後編)

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

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

Rails公式ニュース

Ruby Weekly

The post 週刊Railsウォッチ: 英国政府サイトで使われるRailsアプリ、pg-oscとPercona Toolkitほか(20220308前編) first appeared on TechRacho.

Railsセキュリティ修正がリリースされました(7.0.2.3、6.1.4.7、6.0.4.7、5.2.6.3)

$
0
0

Ruby on Rails セキュリティ修正7.0.2.3、6.1.4.7、6.0.4.7、5.2.6.3がリリースされました。

英語版Changelogをまとめて見るにはGItHubのリリースタグ↓が便利です。

詳しくは以下のコミットリスト差分をご覧ください。

なお、Rails 5.2.xのサポートは2022年6月1日に終了します。この機会に確認しましょう。

参考: Ruby on Rails のメンテナンスポリシー – Railsガイド

🔗 セキュリティ修正の概要

🔗 Active Storageでコード注入の脆弱性の可能性

詳しくは以下のDiscussionを参照して下さい。

参考: [CVE-2022-21831] Possible code injection vulnerability in Rails / Active Storage – Security Announcements – Ruby on Rails Discussions

該当するバージョンのRailsで、Active Storageモジュールにコード注入の脆弱性の可能性があります(CVE-2022-21831)。image_processingを用いるActive Storageのバックエンドでmini_magickを使っている場合に、この脆弱性の影響を受けます。

minimagick/minimagick - GitHub

脆弱なコードは以下のような感じになります(変換用メソッドやその引数が、信頼できない任意の入力になっている)。

<%= image_tag blob.variant(params[:t] => params[:v]) %>

この問題の影響を受けるリリースを実行しているユーザーは、ただちにアップグレードするか後述の回避方法を適用してください。

影響を受けるRailsバージョン
Rails 5.2.0以降
影響を受けないRailsバージョン
Rails 5.2.0未満
修正済みバージョン
7.0.2.3、6.1.4.7、 6.0.4.7、 5.2.6.3

回避方法

この問題を回避するには、受け取れる変換用メソッドや引数の厳密な許可リストをアプリケーションで実装する必要があります。ImageMagickの以下のセキュリティポリシーも問題の緩和に有用です。

パッチ

ただちにアップグレードできない事情があるユーザー向けに、以下のパッチが提供されています(git-am形式)。


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

関連記事

Railsセキュリティ修正がリリースされました(7.0.2.2、6.1.4.6、6.0.4.6、5.2.6.2)

Rails 7.0.2がリリースされました

The post Railsセキュリティ修正がリリースされました(7.0.2.3、6.1.4.7、6.0.4.7、5.2.6.3) first appeared on TechRacho.

週刊Railsウォッチ: Crystal言語作者がRubyを愛する理由、TypeScript 4.6リリースほか(20220309後編)

$
0
0

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

🔗Ruby

🔗 strings-truncation: 長い文字列を...で省略(Ruby Weeklyより)

piotrmurach/strings-truncation - GitHub


つっつきボイス:「エンコーディングにUTF-8のほかにEUC-JPも使えるというのは今どき珍しいかも」「Rubyのtrみたいなものかと思ったら、長い文字列の後半を省略記号...にするんですね」「truncationの機能にはいくつかありますけど、これはRailsのtruncateメソッドに近いかな」

参考: EUC-JP – Wikipedia

「しかも日本語などの全角文字も1文字が半角文字2つ分という形で指定できるらしい↓」「なるほど、これがやりたかったのかも」

# 同リポジトリより
strings.truncate("おはようございます", 8)
# => "おはよ…"

🔗 Crystal言語の作者のひとりがRubyを愛する理由(Ruby Weeklyより)


つっつきボイス:「記事を書いたAry BorenszweigさんはCrystal言語の作者のひとりだそうです」「この記事はequalityがお題ですね: Rubyの==のデフォルトが常識的で使いやすいなど」「Crystal言語でRubyと同じにしたかった部分を解説してますね、読みやすくてよさそう👍」「この記事も翻訳したいです」

crystal-lang/crystal - GitHub

「この記事はシリーズものみたい」「全部で7回なんですね↓」「これも翻訳したいです」

「シリーズ記事にもありますけど、改めてRubyのブロックは設計がいいなと思います」「構文としてのブロックは1個までという制限をつけたのがうまいですよね」「もしブロックを複数渡せるようにしていたらカオスになる未来しか今となっては想像できない」


別人ですが、Aryさんの記事を見ていて、RubyistからElixirの人になったDave Thomasさんをちょっと連想しました↓。

参考: Dave Thomas (programmer) – Wikipedia

🔗 システム内のRubyをモジュラーパッケージでインストールする(Ruby Weeklyより)


つっつきボイス:「システムのRubyを、コンパイルとかせずにパッケージでインストールしようという記事のようですね」「dnfって何だろうと思ったらCentOSのパッケージマネージャ(yumの後継の)でした↓」「へ〜こんなの出てるんですね」「Red Had系のLinuxを使わなくなって久しい」「Debian系のaptか、せいぜいAlpineのapkぐらいしか使ってないかも」「Amazon Linuxを使っているのでyumは使う」「使う使う」

参考: 【dnf】コマンド(基礎編)――ソフトウェア(パッケージ)をインストールする:Linux基本コマンドTips(368) – @IT
参考: 【yum】コマンド(基礎編)――ソフトウェア(パッケージ)をインストールする/アンインストールする:Linux基本コマンドTips(42) – @IT

「Amazon Linuxのyumはいつdnfに変わるんでしょうね?」「Amazon Linuxは今v2だから、次のv3の安定版あたりかな〜」

🔗 Amazon Linux Extras

「ところで、Amazon Linux Extrasに用意されているパッケージは何気に進化を繰り返していて優秀: Dockerも常に新しくなっているし、Emacsも入ってるし」

参考: Amazon Linux – Amazon Elastic Compute Cloud — Amazon Linux Extras
参考: Emacs – Wikipedia

「たまにAmazon Linux Extrasのパッケージをチェックすると、ちょくちょく増えたり入れ替わったりしているので楽しい↓」「ちゃんと管理されているんですね」「tomcatという文字見えた😆」「GIMPやSquidまで入ってる」「MATEやfirecracker、nginxやPostgreSQLやGo言語なんかも入っていていろいろ助かります👍」「Rubyのバージョンはもっと新しくして欲しいけど」「リストの連番に空きがあるのはパッケージが削除された部分」「なるほど歯抜けの部分がありますね」

参考: Apache Tomcat – Wikipedia
参考: GIMP – Wikipedia
参考: Squid (ソフトウェア) – Wikipedia
参考: MATE (デスクトップ環境) – Wikipedia

firecracker-microvm/firecracker - GitHub

🔗 RubyやRailsでお気に入りのツールは?(Ruby Weeklyより)

つっつきボイス:「Shopifyのツイッターで”RubyやRailsを使うときにどんなツールが好き?”という問いかけにいっぱいレスがついていました」「いきなりruby/debug来ましたね」「よくみたら@st0012さんだ」「ruby/debugは触り心地最高👍

「自分はbrakemanかな〜」「brakemanも優秀かつちゃんとメンテされていますね👍

presidentbeef/brakeman - GitHub

🔗DB

🔗 trigramによるPostgreSQL全文検索を最適化する(Ruby Weeklyより)


つっつきボイス:「この記事で紹介されているtrigramという手法は、全文検索の分野でよく使われますね: 記事では"hello"{" h"," he","hel","ell","llo","lo "}のように分解していますが、何文字ずつに分解するかも含めていくつか戦略があります」「3文字だからtrigramなんですね」

参考: PostgreSQL 13.1文書 F.31. pg_trgm

F.31.1. トライグラム(またはトリグラフ)の概念
トライグラムは文字列から3つの連続する文字を取り出したグループです。 共有するトライグラムの個数を数えることで、2つの文字列の類似度を測定することができます。 この単純な考えが、多くの自然言語における単語の類似度を測定する際に非常に効率的であることが判明しています。
注記
pg_trgmは、文字列からトライグラムを抽出する時に単語以外の文字(英数字以外)を無視します。 文字列内に含まれるトライグラム集合を決める際、文字列の前に2つの空白、後に1つの空白が付いているものとみなされます。 例えば、「cat」という文字列のトライグラム集合は、「c」、「ca」、「cat」、「at」です。 「foo|bar」という文字列のトライグラム集合は、「f」、「fo」、「foo」、「oo」、「b」、「ba」、「bar」、「ar」です。
F.31. pg_trgmより

2文字のはbigramと言うそうです↓。汎用的にはn-gramなんですね。

参考: bigramとは バイグラム: – IT用語辞典バイナリ

「trigramの特徴は言語を選ばないこと」「この原理ならたしかに漢字でも何でも使えそうですね」「MeCabのような品詞分解や単語分かち書きが不要になります」「英語や韓国語はスペース区切りだけど、日本語のような言語は単語を区切るところから始めないといけなくなる」「もちろんtrigramの戦略が要件や言語に合ってこそですね」

参考: MeCab – Wikipedia

「記事を見ていて、昔にMySQLでSennaとかをコンパイルしたことを思い出しました」「そうそう、ありましたねSenna」「これも昔にNamazuという全文検索エンジンを使ったことがあります(trigramかどうかはわかりませんが)」

参考: MySQLを通じた全文検索エンジンSenna/groongaの利用について
参考: Namazu – Wikipedia

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

🔗 OAuth2とOpenID Connect

元記事: Why you probably don’t need OAuth2 / OpenID Connect!


つっつきボイス:「OAuth2とOpenID Connectがおそらく不要な理由か🤔」「OAuth2とOpenID Connectは違うものなんでしょうか?」「同じではなかったと思います: OAuth2の上にOpenID Connectが追加されたんだったかな」「こんなことも書かれている↓」「OAuth2は認証ではありません、アクセストークンはセッションではありません、たしかに」「OAuth2をファーストパーティなアカウント登録&ログイン手段として使うとつらくなるともあるけど、OAuth2プロバイダを立ち上げるのでもなければ通常は外部サービスを使いますね」

OAuth2 is not Authentication. Access tokens are not sessions.
同記事より

「記事長いですね」「眺めた感じでは、IDの管理方法は事前によく考えておこうということでしょうね: たとえばOAuth2にすればユーザー管理は不要になるけど、その代わり将来自分たちでアカウントを管理しようとしたときにOAuth2だけでできないことが増えて困ったりすることは考えられる」「なるほど」「今はメールアドレスとパスワードによる認証に加えてOAuth2での認証も選べるようにすることも可能です」「たしかによく見かけますね」

参考: OAuth2.0の流れをまとめてみる

「認証周りでつらいことがあって書いた記事なのかも: ちゃんと読んでみるとよさそう👍

🔗 その他インフラ


つっつきボイス:「今まで無制限じゃなかったのかと驚いた人も社内でいましたね」「さすが太っ腹」「無制限っていい響き」

🔗JavaScript

🔗 TypeScript 4.6がリリース


つっつきボイス:「社内でも喜びの声が聞こえてきました」「TypeScript 4.6で何がよくなったんだろう?」

「お〜、super()より前にコードを書けるようになったらしい↓」「今までできなかったんですか」「super()の前にコードを書けない言語はそこそこありますね: たしかJavaではメソッドの先頭にしか書けなかった覚えがあります」「たしかそうですね: 自分はsuper()の前に何か書いた記憶がまったくないです」「普通は継承してから何か書きますよね」

// 同記事より
class Base {
    // ...
}

class Derived extends Base {
    someProperty = true;

    constructor() {
        // error!
        // have to call 'super()' first because it needs to initialize 'someProperty'.
        doSomeStuff();
        super();
    }
}

参考: java – コンストラクタの this() super()はなぜ先頭にしか記述出来ないか – スタック・オーバーフロー

「Javaでsuper()を先頭でしか呼べないのは、初期化やデータ構造やメモリアロケーションで不都合があるからなのかも」「Javaのsuper()はオブジェクトを生成するし、最終的にJavaのバイトコードにまで遡るから厄介そうですよね」「super()に対応するバイトコード命令があるんですか?」「そうそう、コンストラクタを呼び出すとか何かをしてたと思います」「とにかくTypeScriptではそうした制約を回避できるようにしたようですね」「TypeScriptはJavaScriptへのトランスパイラだから、JSの世界でやりようがあるのかも🤔

「へ〜、TypeScriptってこういうunion型も書けるのね↓」「GraphQLにもありますし、最近union型が流行っている感ありますね」

// 同記事より
type Action =
    | { kind: "NumberContents", payload: number }
    | { kind: "StringContents", payload: string };

function processAction(action: Action) {
    if (action.kind === "NumberContents") {
        // `action.payload` is a number here.
        let num = action.payload * 2
        // ...
    }
    else if (action.kind === "StringContents") {
        // `action.payload` is a string here.
        const str = action.payload.trim();
        // ...
    }
}

参考: Unions – The Rust Reference
参考: union – Kotlin Programming Language
参考: PHP 8の正式版が登場、JITやunion型を実装 – Computerworldニュース:Computerworld

「unionというとついC言語伝統のunion(共用体)を思い出す」「そうそう、今もunionと聞いて一瞬脳が止まっちゃいました😆」「わかる」「SQLのUNIONを思い浮かべる人もいるでしょうね」「そして最近は上のようなunion型もあると」「3つのうちどれを思い浮かべるか人によって違いそう」「SQLかな(きっぱり)」

参考: 共用体 – Wikipedia
参考: SELECT文を統合する「UNION」:SQL実践講座(9) – @IT


「話がそれますけど、最近TailwindやDart Sassのバイナリ版が出てRailsでも採用されているみたいに↓、TypeScriptもバイナリ版のトランスパイラを出してtypescript-railsみたいなgemがリリースされたらいいなとちょっとだけ思いました」「それあったら使いたいかも」

Rails 7: importmap-rails + tailwindcss-railsでnode.jsが不要な理由

Rails 7: dartsass-rails gemはNode.jsなしで使える

「TypeScriptの単体バイナリ版トランスパイラは原理的には可能だと思いますけど、TypeScriptが動くところには必ずJavaScriptの実行環境がありますし、TypeScriptのコードはwebpackみたいなバンドラーで他のライブラリと一緒に使うことが多いので、TypeScriptのバイナリ版を作るモチベーションとしては弱いかもしれませんね」「それもそうなんですよね…StimulusのライブラリがTypeScriptで書かれているのをGitHubでちょくちょく見かけるので、RailsでもNode.jsなしでStimulusのコードをTypeScriptで書けたらいいなと思ったのでした😅」「素のTypeScriptを書きたい人がどのぐらいいるかでしょうね」

🔗言語/ツール/OS/CPU

🔗 Make(旧Integromat): ZapierやIFTTTのオルタナツール

参考: Integromat(インテグロマット)とは?使い方・料金を徹底解説! | ノーコードデータベース|NoCode DB


つっつきボイス:「ZapierやIFTTTのオルタナだそうです」「コードを書かずにサービスを連携して自動化できる感じですか」「Zapierはちょっと使ってた」「Makeはオペレーション1000個/月までユーザー数制限なしだけどステップ数は2までか…」「Zapierの無料プランはステップ数無制限なので相当作り込める」「Makeはノンエンジニアをターゲットにしていそうな感じですね」「プログラマーはIFTTTを使っていて、Zapierはノンエンジニアが使っていることが多い印象あるかな」

参考: Zapier | The easiest way to automate your work
参考: IFTTT | Every thing works better together

「どのツールでもいいんですが、この種の自動化ツールは使ってみるといろいろ便利👍」「ですね」「使ったことがない人は一度触ってみるといいと思います」


後編は以上です。

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

週刊Railsウォッチ: 英国政府サイトで使われるRailsアプリ、pg-oscとPercona Toolkitほか(20220308前編)

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

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

Ruby Weekly

Publickey

publickey_banner_captured

The post 週刊Railsウォッチ: Crystal言語作者がRubyを愛する理由、TypeScript 4.6リリースほか(20220309後編) first appeared on TechRacho.

Rails: Webpacker v5からShakapacker v6へのアップグレードガイド(翻訳)

$
0
0

概要

MITライセンスに基づいて翻訳・公開いたします。

表記は「Webpacker」「webpack」「Shakapacker」で統一しました。

shakacode/shakapacker - GitHub

Rails: Webpacker v5からShakapacker v6へのアップグレードガイド(翻訳)

Shakapacker v6では、Webpacker 5からの移行に伴う大幅な変更点がいくつかあるため、手動での対応が必要です。本ガイドはそうした作業を支援するためのドキュメントです。

🔗 Webpacker/Shakapackerは「Webpackのスリムなラッパー」になった

デフォルトのWebpacker 6は、JavaScriptのコンパイルとバンドルに重点を置いています。これは、CSSや静的画像をSprocketsでトランスパイルするRailsの既存のアセットパイプラインと組み合わせる形になります。ほとんどの開発者にはこの組み合わせが推奨されますが、CSSや静的アセットもWebpackerで扱いたい場合は、READMEの『integrations』を参照してください。

以前のWebpacker は、webpackを間接的に設定する形を取っていたため、複雑な二次設定プロセスが発生していました。これは元々、人気の高いフレームワークのデフォルト設定を提供することが狙いでしたが、結果的には解決される問題よりも複雑さの方が増してしまいました。そのため現在のWebpackerは、すべての設定をwebpackのデフォルト設定に直接委ねています。さらに、webpackやbabelなどの主要な依存関係は「ピア依存関係」になっているので、自由にアップグレードできます。

フレームワークとの統合は自分で設定する必要がありますが、webpack-mergeが役に立ちます。詳しくは本記事のv5からv6への移行例にあるVueの移行例を参照してください。

🔗 Webpacker v6.0.0.rc.6をShakapacker v6.0.0に移行する

移行例については、shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refreshの#27を参照してください。

🔗 v6.0.0.rc.6からv6.0.0に移行する手順

: Webpacker v5を利用している場合は、最初に後述の『Webpacker v5をv6.0.0.rc.6にアップグレードする』手順に沿ってv6.0.0.rc.6にアップグレードしておいてください。

  • 1. gem名をwebpackerからshakapackerに、NPMパッケージを@rails/webpackerからshakapackerに変更します。
  • 2. 以下を実行して、ピア依存関係をインストールします。

yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime babel-loader compression-webpack-plugin terser-webpack-plugin webpack webpack-assets-manifest webpack-cli webpack-merge webpack-sources webpack-dev-server
  • 3. bin/webpackbin/webpack-dev-serverを呼び出すスクリプトを更新して、それぞれbin/webpackerbin/webpacker-dev-serverに変更します。

  • 4. webpack の設定を更新して、単一の設定ファイル config/webpack/webpack.config.js を作成します。

    以前のようにNODE_ENVごとに個別のファイルを使う方法にしたい場合は、config/webpack/webpack.config.jsで以下のshimを利用できます。

警告: 以前は、NODE_ENV を設定しない場合のNODE_ENV のデフォルトはdevelopmentでした。そのため、以前config/webpack/development.jsが実行されることを期待していた部分では、代わりにconfig/webpack/RAILS_ENV.jsをお使い下さい。

const { env, webpackConfig } = require('shakapacker')
const { existsSync } = require('fs')
const { resolve } = require('path')

const envSpecificConfig = () => {
  const path = resolve(__dirname, `${env.nodeEnv}.js`)
  if (existsSync(path)) {
    console.log(`Loading ENV specific webpack configuration file ${path}`)
    return require(path)
  } else {
    console.log(`WARNING: Using default webpack configuration. Did not find a Env specific file at path ${path}`)
    return webpackConfig
  }
}

module.exports = envSpecificConfig()
  • 5. JSXのサポートが必要な場合は、babel.config.jsを更新してください。詳しくはShakapackerの『Customizing Babel Config』ドキュメントを参照してください。

🔗 Webpacker v5をv6.0.0.rc.6にアップグレードする

  • 1. gitで新しいブランチを作成します(このブランチですべてのファイルを上書きし、不要な変更を元に戻すことになります)。
  • 2. webpacker.ymlの以下のsource_entry_path をv5のデフォルトから変更することを検討します。

  source_path: app/javascript
  source_entry_path: packs

上のv5のパスを、以下のようにv6に変更することを検討します。

  source_path: app/javascript
  source_entry_path: /

続いて、app/javascript/packs/*にあるファイル(application.jsも含む)をapp/javascript/ディレクトリに移動してから設定ファイルを更新することも検討します。

なお、このファイル移動は必須ではなくオプションなので、packsentriesと呼ばれる別のディレクトリに引き続きエントリを保持できます。このディレクトリはsource_pathの中で定義されます。

  • 3. source_entry_pathにネストしたディレクトリを置かないようにします。
    エントリポイント用のファイルをsource_entry_pathのサブディレクトリに置いていないかどうかを必ず確認してください。サブディレクトリ内のエントリポイント用ファイルは、shakacode/shakapacker v6ではサポートされていません。それらのファイルをトップレベルに移動し、それらのファイルのimportを調整してください。

    Shakapacker v6の新しい設定ではネストを許可していません。その代わり、JavaScriptのルートディレクトリにエントリポイントを置けます。この変更について詳しくはWebpackerの#3156で参照できます。

  • 4. WebpackerのgemとNPMパッケージをアップグレードします。

    注意: gemのページで最新バージョンを確認し、shakapacker gemとパッケージのバージョン番号が同じになるようにインストールしてください(メインのバージョン番号とベータ版の間の表記は、gemではハイフンが使われ、パッケージではドットが使われる点にご注意ください)。

以下は、特定のバージョンにおける例です。

# Gemfile
gem 'shakapacker', '6.0.0.rc.13'
bundle install
yarn add shakapacker@6.0.0-rc.13 --exact
bundle exec rails webpacker:install

以下を実行してすべてのファイルを上書きし、変更内容を確認します。

yarn add @babel/core @babel/plugin-transform-runtime @babel/preset-env @babel/runtime babel-loader compression-webpack-plugin terser-webpack-plugin webpack webpack-assets-manifest webpack-cli webpack-merge webpack-sources webpack-dev-server

なお、webpacker:installはピア依存関係をインストールします。

  • 5. 新しいデフォルトの webpacker.yml への変更点を確認します。特に、source_entry_pathsource_path のトップレベルになるように変更されていることをチェックしてください。

Webpacker v5では、以下のようにsource_entry_pathにデフォルトでpacksディレクトリが使われます。

source_path: app/javascript
source_entry_path: packs

Webpacker v6では、以下のようにデフォルトでトップディレクトリが使われます。

source_path: app/javascript
source_entry_path: /

この設定にしたい場合は、app/javascript/packs/*application.jsも含む)をapp/javascript/に移動し、設定ファイルを更新してください。

なお、このファイル移動は必須ではなくオプションなので、packsentriesと呼ばれる別のディレクトリにエントリを引き続き保持することもできます。このディレクトリはsource_pathの中で定義されます。

  • 6. source_entry_pathにネストしたディレクトリを置かないようにします。

    エントリポイント用のファイルがsource_entry_pathのサブディレクトリに置かれていないかどうかを必ず確認してください。サブディレクトリ内のエントリポイント用ファイルは、shakacode/shakapacker v6ではサポートされていません。それらのファイルをトップレベルに移動し、それらのファイルのimportを調整してください。

    Shakapacker v6の新しい設定ではネストを許可していません。その代わり、JavaScriptのルートディレクトリにエントリポイントを置けます。この変更について詳しくはWebpackerの#3156で参照できます。

  • 7. webpack-dev-serverを最新バージョン(4.2より上)にアップデートし、package.jsonをアップデートします。

  • 8. ビューで使われているAPIを更新します。具体的には、javascript_packs_with_chunks_tagヘルパーをjavascript_pack_tagヘルパーに変更し、stylesheet_packs_with_chunks_tagヘルパーをstylesheet_pack_tagヘルパーに変更します。

注意: 変更の際は、レイアウトやビューで使うjavascript_pack_tag呼び出しとstylesheet_pack_tag呼び出しはそれぞれ最大1回までにしてください。これらのビューヘルパーメソッドにはバンドルを複数渡せるようになっています。この変更を忘れてヘルパーメソッドを複数置いてしまうと、パフォーマンスの問題や、Reactの重複読み込みに関連する他のバグが発生する可能性があります(#2932など)。

注意: app/javascript/application.jsファイル内で、import $ from "expose-loader?exposes=$, jQuery!jquery"のようにexpose-loaderでjQueryをグローバルに公開している場合は、javascript_pack_tagdefer: falseというオプションを渡してください。

  • 9. csspostcssReactTypeScript などの統合機能を使っている場合。それらがShakapacker v6でどう機能するかについては、READMEの『integrations』を参照してください。
  • 10. config/webpack/environment.jsのインポート方法はconfig/webpack/base.jsに変更され、ネイティブのwebpackコンフィグをエクスポートするようになったので、toWebpackConfigを呼び出す必要はありません。変更には以下のようにmergeをお使い下さい。

// config/webpack/base.js
const { webpackConfig, merge } = require('@rails/webpacker');
const customConfig = {
  module: {
    rules: [
      {
        test: require.resolve('jquery'),
        loader: 'expose-loader',
        options: {
          exposes: ['$', 'jQuery']
        }
      }
    ]
  }
};

module.exports = merge(webpackConfig, customConfig);
  • 11. .browserslistrcファイルにカスタムのブラウザリスト設定がある場合は、その設定をpackage.json"browserslist"キーにコピーして、.browserslistrcファイルを削除します。

  • 12. babel.config.jsファイルをまったくカスタマイズしていない場合は削除します。babelのデフォルト設定を使うには、package.jsonで以下のように設定します。

"babel": {
  "presets": [
    "./node_modules/shakapacker/package/babel/preset.js"
  ]
}

Reactの設定については、『Customizing Babel Config』ドキュメントのカスタマイズ例を参照してください。

  • 13. webpacker.ymlファイルからextensionsが削除されました。カスタム拡張子は、以下のようにオブジェクトをマージする形で自分の設定ファイルに移動してください。詳しくはREADMEの『Webpack Configuration』ドキュメントを参照してください。
{
  resolve: {
    extensions: ['.ts', '.tsx', '.vue', '.css']
  }
}
  • 14. webpacker.ymlファイルにwatched_pathsがある場合はadditional_pathsに置き換えます。
  • 15. #3056で一部の依存関係が削除されました。

    Error: Cannot find module 'babel-plugin-macros'などのエラーが表示された場合は、yarn add <依存パッケージ> を実行する必要があります(<依存パッケージ>にはbabel-plugin-macroscase-sensitive-paths-webpack-plugincore-jsregenerator-runtimeなどが含まれます)。これらのパッケージへの依存を取り除くことを検討してもよいでしょう。

  • 16. webpacker.ymlファイルとconfig/webpackに適用された設定の新しいデフォルトを確認します。特に、source_entry_pathsource_pathのトップレベルにするなど、本ガイドで提案されている変更点を注意深く検討してください。

  • 17. bin/webpackを実行して、エラーが発生しないことを確認します。

  • 18. RAILS_ENV=production bin/rails assets:precompileを問題なく実行できることを確認します。確認後、必ずbin/rails assets:clobberを実行して、生成されたアセットをクリーンアップすること。

  • 19. bin/webpackbin/webpack-dev-serverを呼び出すスクリプトを更新して/bin/webpackerbin/webpacker-dev-server に変更します。

  • 20. NODE_ENVが設定されていない場合、bin/webpackerおよびbin/webpacker-dev-serverNODE_ENVはデフォルトでRAILS_ENVになります(従来はNODE_ENVが設定されていない場合はデフォルトでdevelopmentになるため、以前のbinstubでは、config/webpack/development.jsに対応するwebpack設定が使われていました)。

    変更後は、たとえばRAILS_ENVtest の場合はNODE_ENVtestになります。Shakapacker 6.0の最終リリースでは、1つのwebpack.config.jsを使うように変更されました。

  • 21. ここまで完了したら、上述のWebpacker v6.0.0.rc.6をShakapacker v6にアップグレードする手順を実行します。

v5からv6への移行例

  1. React on Rails Project with HMR and SSR
  2. Vue and Sass Example

関連記事

Rails: Webpacker→jsbundling-rails+webpackアップグレード手順(翻訳)

The post Rails: Webpacker v5からShakapacker v6へのアップグレードガイド(翻訳) first appeared on TechRacho.

Viewing all 1831 articles
Browse latest View live