RubyKaigi Takeout 2020 に登壇した

RubyKaigi Takeout 2020 に登壇した。

本編登壇は RubyKaigi 2018 以来 2 回目。去年 RubyKaigi 2019 では LT 登壇で TracePoint を使うことによって仕込んでしまったバグの話をしていた

今回のスライドは以下です。

RuboCop 1.0 に向けた話

本編から RuboCop 1.0 に向けた話をいくつかピックアップしておきます。後述の収録への問題があったため、特に終盤テンポが上がっていることからテキストとして少し補足しておきます。

拡張 API の変更

拡張 Cop の開発者向けの API は以下のように変更になります。

これまでの API:

class CustomCop < Cop
  def on_send(node)
  end

  def autocorrect(node)
  end
end

これからの API:

class CustomCop < Base
  extend AutoCorrector

  def on_send(node)
    add_offense(node) do |corrector|
    end
  end
end

従来の RuboCop::Cop::Cop はすぐに削除されるものではない soft deprecated なものですが、今後は RuboCop::Cop::Base を使うことをすすめます。RuboCop 自体もまだ移行中です。依存している Parser 実装から変わっているため、まだ予期していない不具合を持っている可能性があります。何か問題を見つけたら rubocop-hq までご連絡ください。

Safe / SafeAutoCorrect

RuboCop には SafeSafeAutoCorrect という概念があります。

Safe偽陽性を起こすことなく検出できるかどうかによって、避けることが難しい偽陽性が目立ったものは安全でない Safe: false がマークされています。

SafeAutoCorrect は振る舞いの互換性を維持できるかどうかです。こちらも RuboCop の提案による変更で非互換を起こすものは SafeAutoCorrect: false が設定されています。また偽陽性を持ったままの変更したことで壊れることを防ぐため Safe: false とされているものは SafeAutoCorrect: false です。

とりわけ最近の重要な変更点として、rubocop -a オプションの挙動変更と rubocop -A オプションの導入です。前者は安全な Cop でのみ自動修正を行うもので、後者は安全でない Cop も含めて自動修正を行うものです。

RuboCop 0.86 以前では、frozen stirng magic comment を追加する場合は rubocop -a でしたが、frozen string になったことで破壊的メソッドへの呼び出しがあるような場合など非互換の変更になる可能性があります。このような安全でない Cop での自動修正を実行したい場合は rubocop -A コマンドを使うことになります。

以下のエントリも参考にしてください。

koic.hatenablog.com

pending ステータス

もしいま使っている RuboCop がちょっと古くてアップグレードしづらいといった問題は、Cop への pending ステータスの導入での軽減を図っています。

ここ何バージョンかで RuboCop に追加された新しい Cop は 1.0 までは pending というステータスで、デフォルトでは有効になっていません。ユーザーが明示的に .rubocop.yml への Enabled: trueEnabled: false を指定してどうするかを決めるようになっています。

pending ステータスのものは RuboCop 1.0 で有効になりますので、もしそれまでにデフォルトで有効になるべきでない Cop を見かけたら RuboCop HQ までお知らせください。

本編収録にあたって

音楽 CD でライブ盤とスタジオ盤があるように、これまでふだんをライブだったとすると、今回はじめてのスタジオ盤の収録となってなかなか思うように行かなかった。その点を書き残しておく。

スライドと録音を同期させる方法として RubyKaigi チームから紹介があり、普段使いのプレゼンテーションツールが Keynote なので、Keynote での録音が手速かろうと進めていた。

実はそこでもいろいろとあって、手元の環境が古すぎて録音機能がない Keynote 6.0 (2013年) からのアップグレードというのが最初に必要だった。そしてそもそもの macOS が古いため OS のアップグレードも必要でとなり、そのあたりのセットアップに時間が使われたりした。

そのあとはスライドへの音録りになるけれど Keynote の録音機能の使い方を調べつつ覚えていった。ページ切り替えのタイミングによってやや音質が変わるのは、うまく録音できていた箇所まで戻してやり直しかひと呼吸取るためのものだった。

そしてインターネットの向こうにも人がいない PC に向かって話すということができなくて、全編テキスト起こしをするということをしていた。筋肉少女帯の『詩人オウムの世界』のギターソロはふだんアドリブで弾いているところ、譜面に起こしたという逸話をもとにしたものだったけれど、以下のようにうまくいった部分とそうでない部分があった。

  • 話し始めるにあたり、何から話していくかの流れができているのは良い
  • 急いでテキスト起こしをしたツケで、バグっているテキストをそのまま読み上げてしまっている箇所がある (Opal への説明ミスとか)

はじめての試みっぽいバグとしてテキストを読みつつもライブ感を作りたくて、流れに乗ったタイミングでテキストから外れるというものだった。どっちつかずの弊害でした。

こういったことをやっていると、タイムキーピングが疎かになって終盤、畳み掛けるようなスピードで収めに行くことになった。結果として 25分程度というオファーのところ 25分54秒だったので、最後は編集で少しカットできないかやってみようとしたけれど、ソフトの選定や使い方を覚えるまで辿り着けず断念。これは時間の中でこなせるスキルを予め獲得しているかという経験がものをいう世界線だった (そしてそのスキルは現状持ちあわせていなかった) 。 いちおう覚えたこととして、あとで編集しやすいようにまとまりのあるスライドごとに音声を収めておくと良いという知識を得ることができた。ページ切り替えのタイミングで話しているとそのパートだけを削除したいという編集ができなくなるのが理由。

あと MacBook Pro の内蔵マイクで収録していたけれど、途中音質がガラッと変わる部分があった。機材自体は変えていないので姿勢やマイク距離が変わったなどで、音の当て方が変わっていたのではと思う。このあたり音質を担保するのであればそれようの機材を使った方が良さそう (今現在は持っていないのですが) 。

以上のような失敗経験やノウハウを得る良い機会にもなったのが今回のスライド作成だった。次回はもう少しうまくできるんじゃないかなと思っている。

RubyKaigi Takeout 自体は、リモートながら RubyKaigi 感があって良かった。収録済みの配信であるもののタイムテーブルがあったのが良い舞台装置になっていたのではと思っている。勤務先ではタイムテーブルを見る会を実施したり、当日はどちらのトラックを聞こうか悩んだりできていつもながらのアクティビティを起こすきっかけにできたのは大きかったと思う。コンテンツとしては Parser を使ったトランスパイルの話がいくつかあったり、それに伴って見慣れた S 式が出てきたりと、普段の OSS 活動で備えている特異技術とリンクしたトピックなんかはとりわけ興味深く聞いていました。

楽しかったです。RubyKaigi Takeout 2020 の関係者のみなさん、ありがとうございました。

f:id:koic:20200908145328j:plain

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" が空になっているので、また次のプレビューが楽しみですね。