RubyKaigi Takeout 2020 に登壇します

RubyKaigi Takeout 2020 に『Road to RuboCop 1.0』というタイトルで登壇します。

rubykaigi.org

私の登壇は初日の 2020年9月4日(金) 14:00-14:25 です。

RuboCop 1.0 に向けた最新情報でまとめています。RuboCop のライトユーザーから拡張 Cop を作っている RuboCop マニアまで、よければテイクアウトしに来てください。

また、勤務先のブログの方で少しだけ講演について触れています。

blog.agile.esm.co.jp

本講演でのスライド作成にあたっては英文レビューに @yahonda さん に多大なるご協力をいただきました。オンラインで遅い時間まで本当にありがとうございました。

Thor x Rails 6.0での注意点

Rails アプリケーションを構築する際に、バッチ処理や便利タスクなどで Thor (現在の最新バージョンは 1.0.1) を使っている機会があると思うのですが、Rails 5.2 から Rails 6.0 で挙動が変わっていた点とその解決方法をシェアしておきます。

対象

Thor タスクのなかで options.except(:key) を使っているコードが対象です。

class Foo < Thor
  option ...
  def foo
    options.except(:key) # これ
  end
end

言い換えると Thor で options.except を使いその引数がシンボルでなければ、この問題はありません。

現象

options のクラスである Thor::CoreExt::HashWithIndifferentAccessexcept メソッドの挙動が変わっています。

Rails 5.2 まで

h = Thor::CoreExt::HashWithIndifferentAccess.new(foo: 1, bar: 2)
h.except(:foo) #=> {"bar"=>2}

Rails 6.0

h = Thor::CoreExt::HashWithIndifferentAccess.new(foo: 1, bar: 2)
h.except(:foo) #=> {"foo"=>1, "bar"=>2}

なので options.except の引数にシンボルでキー指定しても except されていない結果が返ります。

原因

https://github.com/rails/rails/pull/35771 のパフォーマンスチューニングの影響を受けてしまっていたようです。

解決

3つ例示しておきます。

1つめは場所が限られているようであれば、options.except('key') のように引数をシンボルから文字列にするのが分かりやすいワークアランドです。ただ upstream で予期されていないであろう挙動の影響でプロダクトコードに手入れするのがちょっとくやしい。

2つめは例えば config/initializers/thor.rb のようなファイルで以下のようなモンキーパッチを適用する手段です。

class Thor
  module CoreExt
    class HashWithIndifferentAccess
      def except(*keys)
        dup.tap do |hash|
          keys.each { |key| hash.delete(convert_key(key)) }
        end
      end
    end
  end
end

3つめ。以下のパッチを開いているのでマージされてリリースされるまで Gemfile に github 指定するなどできます。

github.com

昨晩直して先ほど PR を開いたばかりなので、これから動きがあることを期待しています。

Rails アップグレード時などの参考にどうぞ。

不正なバイトを置換文字で置き換えるCSV.openオプション

CSV 3.1.6 がリリースされました。

github.com

CSV 3.1.6 には、CSV.open に不正なバイトを置換文字で置き換えるオプションを追加するのに送ったパッチが取り込まれているのでその紹介です。

ユースケースとして、MS Excel 向けのエンコーディング (有名な CP932) に変換して CSV 出力するに際して、例えば Rails アプリケーションのバリデーション不足などで含まれてしまった不正なバイトをハンドリングしたいケースに活用できるオプションです。

CSV.open(..., invalid: :replace)

指定しているエンコーディング (ここでは CP932) へ変換できないバイトが含まれている場合、不正なバイトを置換文字で置き換えるという:invalid => :replace オプションが File.open にあります。CSV 3.1.5 までは CSV.open では受け付けないオプションだったため、例えば以下のように指定する必要がありました。

File.open(filename, 'w', encoding: Encoding::CP932, invalid: :replace, undef: :replace) do |file|
  csv = CSV.new(file, encoding: Encoding::CP932)

  csv << ...

  csv.close
end

CSV 3.1.6 から CSV.open:invalud オプションを受け付けるようになったため、以下のように書くことができます。

CSV.open(filename, 'w', encoding: Encoding::CP932, invalid: :replace) do |csv|
  csv << ...
end

対応したパッチは以下です。

github.com

また、Encoding::InvalidByteSequenceError が起きるようなエンコーディングに対して invalid: :replace オプション指定した場合、期待に反して ArgumentError が起きる以下のようなコードの問題についても解消されています。

require 'csv'

filename = 'foo.csv'

File.open(filename, 'w', encoding: Encoding::CP932, invalid: :replace) do |file|
  CSV.open(filename, encoding: Encoding::CP932) do |rows|
    rows << ["\x82\xa0"]
  end
end

これは上記のようなコードを書いていたとしても CSV 3.1.5 までは以下のエラーになっていたものです。

% ruby /tmp/csv.rb
Traceback (most recent call last):
        10: from /tmp/csv.rb:5:in `<main>'
         9: from /tmp/csv.rb:5:in `open'
         8: from /tmp/csv.rb:6:in `block in <main>'
         7: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv.rb:658:in `open'
         6: from /tmp/csv.rb:7:in `block (2 levels) in <main>'
         5: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv.rb:1230:in `<<'
         4: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:46:in `<<'
         3: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:46:in `collect'
         2: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:47:in `block in <<'
         1: from /Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:159:in `quote'
/Users/koic/.rbenv/versions/2.7.1/lib/ruby/2.7.0/csv/writer.rb:159:in `match?': invalid byte sequence in UTF-8 (ArgumentError)

対応したパッチは以下です。

github.com

CSV.open(..., undef: :replace)

こちらも CSV 3.1.6 で以下のように File.openundef: :replace オプションを CSV.open に指定することができるようになったので、簡潔に対応できるようになりました。

CSV.open(filename, 'w', encoding: Encoding::CP932, undef: :replace) do |csv|
  csv << ...
end

対応したパッチは以下です。

github.com

CSV gem は Gemify されているため、Ruby のアップデートと独立して bundle update できます。詳しくは以下のエントリを参照してください。

koic.hatenablog.com

ruby/csv にパッチを送った際にスピーディーなレビューをしていただいた須藤さんありがとうございました。

パーフェクトRuby on Rails【増補改訂版】をレビュアー献本いただいた

パーフェクトRuby on Rails【増補改訂版】をレビュアー献本いただいた。出版おめでとうございます & 献本ありがとうございました。

以前、Ginza.rb のおりに netwillnet さんから「パ Rails の改訂版を書くにあたってレビュアーお願いできますか?」と声をいただいて、執筆パート担当の igaiga555 さんからのパスでレビュアーに加わらせていただいていた。

全体については仕事の速い tatsuoSakurai さんが書いている書評を見ていただくとして、RuboCop に関わるパートで出版後の変化について記しておこうと思う。

本編9章で rubocop -a を実行すると Style/FrozenStringLiteralComment cop によるマジックコメントが適用されるとあるのですが、RuboCop 0.87 以降では -a オプションでは適用されなくなりました。rubocop -a は安全な cop のみ自動修正するオプションという意味合いに変わったためです。

そのため安全でないとマークされている Style/FrozenStringLiteralComment cop を使った自動修正をするには rubocop -A (大文字の A ) を実行する必要があります (この変更が取り込まれたのは、おそらく脱稿して原稿が印刷所に行っているころ) 。

安全でない自動修正とは、例えば 'foo' << 'bar' のようなコードがあった場合に、frozen string リテラルのマジックコメントなしからありになると、FrozenError (can't modify frozen String) という挙動に変わりますが、このように互換性がない auto-correct になりうるものです (このケースで互換性を維持するためにはレシーバーを dup などする必要がある) 。

rubocop -arubocop -A について詳しくは以下のエントリを参照してください。

koic.hatenablog.com

技術を扱う書籍についてツールがアップデートされていくのは常ですが、レビュー時点ではこの変更への PR 自体も出ていなかったので 1ヶ月くらい時期がずれていれば ...といったライブラリアップデートに関する補完となります。

本書に戻ると Rails 6.0 対応で Docker や GitHub Actions にも触れられているので、これからの業務への知識のステップアップを目指す方などに適した一冊になるのではと思います。改めて執筆お疲れ様でした!

rubocop -a と rubocop -A オプション

RuboCop 0.87 がリリースされた。

github.com

今回の目玉は rubocop -a コマンドラインオプションへの非互換変更となる。

これまでは rubocop -a オプション (rubocop --auto-correct も同義) を使った場合に、自動修正を備えたすべての Cop が適用されたていた。そのため Safe でないと見なされる Cop の自動修正も適用されるというのがデフォルトの振る舞いだった。

今回のリリースからは rubocop -a は Safe とマークされている Cop のみが自動修正の対象となり、従来どおり自動修正を備えた Unsafe を含むすべての Cop を適用する場合は rubocop -A (rubocop --auto-correct-all) を実行することになる。つまり RuboCop 0.59 で導入された rubocop --safe-auto-correct が自動修正のデフォルトの振る舞いになる (そして rubocop --safe-auto-correct オプション自体は非推奨になりました) 。

RuboCop に導入されている Safe というメタデータについては過去の日記を参照してください。

koic.hatenablog.com

GitHubの新デザインを先取りして使う

GitHub がユーザー全体のデザインを変えるより前に、Feature preview を使うと先駆けで新デザインを使うことができる。

先行して新デザインが使えるときは、以下の Feature preview に通知がきているので、それを有効にする。

f:id:koic:20200624175208p:plain:w400

過去に自分が見たプレビュー機能には、"Design updates" と "Repository refresh" があった。

"Design updates" はユーザープロフィールページを新デザインにするものだった。

"Repository refresh" はリポジトリのトップページを新デザインにするものだった。

それぞれ "Give feedback" のリンクからフィードバックできるようになっている。

"Repository refresh" について、リポジトリのトップに表示されている最新のコミットで CI の結果 (✔︎or❌) が分からくなっていて不便だったのを以下の画像添付と一緒に GitHub にフィードバックしていた (たしか昨日 (2020年6月23日) か一昨日くらいのこと) 。

f:id:koic:20200624174907p:plain:w400

この日記を書いている 2020年6月24日現在、リポジトリのトップで master ブランチへのマージ後の最新のコミットへの CI の結果がわかるようになっていた。GitHub への自分のフィードバックか、誰か他の人も同一のフィードバックをしていたかは分からないが、便利になって良かった (というかもともとのデザインで見れていたものなので、ないと不便だった) 。

いまは "Feature preview" が空になっているので、また次のプレビューが楽しみですね。

GitHub が落ちた時に復旧情報を受け取る

Twitter のタイムラインが賑わう GitHub のサービスダウンに関する tip です。

f:id:koic:20200619184749p:plain:w400

GItHub のサービスの状態については https://status.github.com/ で確認できます。

f:id:koic:20200619184810p:plain:w400

さらに右上の Subscribe から復旧状態をメールなどで通知を受け取ることができます。

f:id:koic:20200619184827p:plain:w400

たとえばメール通知にすると、復旧したらこんな感じでメールが来ます。

f:id:koic:20200619185820p:plain:w400

実践的には GitHub に繋がらなければ繋がらないでできる仕事にスイッチして進めている間にだいたい復旧しているので、UX 最高とまではいかなかったりですが参考までに。