Faker のコミット権をもらった

昨年くらいに Faker の org メンバーになっていたけれど、リポジトリの PR がたまっていている状態をどうにかしたかったので、そこのチームにコミット権を付与してもらった。

github.com

アクティブなチームメンバーがマージできるようになったのではと思う。

GW にやったこと (2020年)

GW にやったことについて、GitHub から辿れることを中心に書き残しておく。

.rubocop.yml で ERB を書けるようになるパッチをレビューしてマージした。

こんな書き方ができるようになる。

AllCops:
  Exclude:
  <% `git status --ignored --porcelain`.lines.grep(/^!! /).each do |path| %>
    - <%= path.sub(/^!! /, '') %>
  <% end %>

github.com

Active Record Oracle enhanced adapter に対する upstream の日次動向チェックで動きがあったので追随した。

github.com

以前から気にはなっていた Active Record Oracle enhanced adapter のテスト時の警告を抑制した。

github.com

.rubocop.yml で無効になっている部署に対して、一部の cop を有効にできるようにするパッチをレビューしてマージした。べんり。

github.com

Layout/ConditionPosition cop の auto-correct を実装した。

github.com

以下のようにマルチバイト文字への振る舞いが等価でないため Performance/Casecmp cop を unsafe にした。

'äöü'.casecmp('ÄÖÜ').zero? #=> false
'äöü'.casecmp?('ÄÖÜ')      #=> true

github.com

レガシー APIcontent_tagtag にする Rails/ContentTag cop をレビューしてマージした。PR の根拠となるリソースなど示されているためレビューの手間が少なくなっていたのでよかった。

ドッグフーディングとして実 Rails アプリケーションに適用してみて良い感じだった。

2020年5月7日追記

偽陽性がありそうだったので、調べるつもり。

github.com

Style/GuardClause cop の誤ったメッセージを修正した。

github.com

Lint/EnsureReturn cop の auto-correct を実装した。

github.com

RailsConf 2020.2 COUCH EDITION の視聴をはじめた。往年の Kent Beck ファンとして濃密な 15min の講演がよかった。

railsconf.com

rubocop-hq/rubocop#7925 が複合要因のイシューで未解決部分があった Style/GuardClause cop の偽陽性を修正した。

github.com

Performance/DeletePrefix cop と Performance/DeleteSuffix cop を実装した。

bad ケースと good ケースは以下。

# bad
str.gsub(/\Aprefix/, '')
str.gsub!(/\Aprefix/, '')
str.gsub(/^prefix/, '')
str.gsub!(/^prefix/, '')

# good
str.delete_prefix('prefix')
str.delete_prefix!('prefix')

ベンチマークは以下。

% ruby -v
ruby 2.5.8p224 (2020-03-31 revision 67882) [x86_64-darwin17]

% cat bench.rb
require 'benchmark/ips'

Benchmark.ips do |x|
  str = 'foobar'

  x.report('gsub')           { str.gsub(/bar\z/, '') }
  x.report('gsub!')          { str.gsub(/bar\z/, '') }
  x.report('delete_suffix')  { str.delete_suffix('bar') }
  x.report('delete_suffix!') { str.delete_suffix('bar') }
  x.compare!
end

% ruby bench.rb
Warming up --------------------------------------
                gsub    46.814k i/100ms
               gsub!    46.896k i/100ms
       delete_suffix   211.337k i/100ms
      delete_suffix!   208.332k i/100ms
Calculating -------------------------------------
                gsub    546.500k (± 1.3%) i/s -      2.762M in 5.054918s
               gsub!    551.054k (± 1.2%) i/s -      2.767M in 5.021747s
       delete_suffix      4.780M (± 1.1%) i/s -     24.092M in 5.040850s
      delete_suffix!      4.770M (± 1.1%) i/s -     23.958M in 5.022823s

Comparison:
       delete_suffix:  4780060.8 i/s
      delete_suffix!:  4770419.3 i/s - same-ish: difference falls within error
               gsub!:   551054.2 i/s - 8.67x  slower
                gsub:   546500.1 i/s - 8.75x  slower

ドッグフーディングとして rails/rails リポジトリと、実 Rails アプリケーションで実行してみて良い感じの首尾だった。

github.com

Rails 6.0.3 がリリースされたので、ホームページを更新した。

github.com

GitHub 社の Codespaces のベータの waitlist にならんだ。

github.com

まだ PR にしていないセルフレビュー中のパッチや、仕掛かりや手戻りのパッチなど手元にあるので、それらは今後引き続きといった感じ。

カンファレンスとしては、去年のゴールデンウィークは RailsConf 2019 のためミネアポリスに行っていたけれど、RailsConf 2020 はオンラインで聞くゴールデンウィークだった。

Active Recordでのヒント句の書き方

Active Recordでのヒント句の書き方について。

クエリの実行計画の最適化を RDBMSオプティマイザ (プランナ) に任せずに、アプリケーション側で指定するのにヒント句というのがあります。通常 RDBMSオプティマイザに任せたりしていますが、DBA からチューニングのアドバイスがあったりしたときに使ったりできます。

OracleMySQL 5.7.7 以上 (MariaDB 除く) 、PostgreSQL だと pg_hint_plan が使えるものあたりがサポートしているようです。ヒント句は既存のクエリ自体は書き換えることなく、コードコメントで指示を出せるのが売りのようです。

SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`

Active Record で書く際もなるべく既存のクエリ API に手を入れない形で書けると嬉しいところ。

Rails 6.0 以上だと、kamipo さんのパッチによって optimizer_hints メソッドを使った指定ができます。メソッドチェーンで足す形で書けるのが便利。以下 API ドキュメントから抜粋。

Example (for MySQL):

Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
#=> SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`

Example (for PostgreSQL with pg_hint_plan):

Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
#=> SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"

github.com

Rails 5.2 以下であれば、select メソッドを使って指定するといった形式になると思います。 以下は select の振る舞いを利用して、カラムの前にコメントとしてヒントを足す方法ですが、既存の select メソッドの引数に手を加える必要があるのが難点です。

Topic.select("/*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.*")

他にも方法はあると思いますが、Rails 6.0 以上でのoptimizer_hints メソッドが使えれば流れるようなインタフェースにできてクールでしょう。

bliki-ja.github.io

2020-04-21 追記

PostgreSQL へのサポートについて kamipo さんからのアドバイスを追記しておきます。

`bundle install --clean`を実行するときの注意点

bundle install --clean を実行するときの注意点として、バッドノウハウを書き残しておきます。

これは、以前 pocke さんが CircleCI の実行を速くするのに bundle install --clean を使う方法を記されていたことで思い出したことです。

pocke.hatenablog.com

bundle install --clean は Bundler 管理下で不要な gem とそのバージョンを削除します。pocke さんの伝える CI 上の gem のキャッシュを削除する用途以外に、イメージ化していないデプロイ環境で bundle update している環境なんかでも同一 gem に対して複数バージョンがインストールされていたりします。そういった環境で bundle install --clean を実行すると、不要バージョンを削除してガツっと容量を空けることができたりします。

以上を踏まえて、本題の注意点です。問題となるのは Gemfile に記載せずにサーバーに直接 gem install passenger とか gem install unicorn といった感じでデプロイ環境に直接サーバーをインストールして、Gemfile / Gemfile.lock にサーバーの gem が記されていないケースです。その状態で bundle install -clean するとサーバーが消えます。文字列に起こすといかにも間抜けですが、やらかすと悲しいことになるというか、かつてなったことがあるのでここに記しておきます。Unicorn で書き忘れはない気がしますが、Passenger なんかは Apache モジュールとしてコンパイルしたりした後で、うっかり Gemfile に書き忘れていたりしないか気をつけると良いです。

そうそう踏む人はいないと思いますが、サーバー含めてインストールする gem はきちんと Gemfile に記しておきましょうという話でした。

Rails 6.1でサードパーティーのArel visitorが外される

Active Record でサポートしているコアアダプターの MySQL, PostgreSQL, SQLite 以外のサードパーティーRDBMS に対する Arel visitor がコアから外されることになった。

github.com

SQL Server は以前から独自で梱包していたようで、Oracle は今回対応した PR を Acitive Record Oracle enhanced adapter のリポジトリに開いている。

github.com

この変更によってメンテナンスされているアダプターのユーザーに対して直接的な影響はないが、アダプター開発者は Rails リポジトリactiverecord/lib/active_record/connection_adapters に加えて、activerecord/lib/arel の変更もウォッチ対象になるだろう。

余談だが今回 Arel visitor を Oracle enhanced adapter に輸入する際の手順で、過去に書き残しておいた記事がちょっと役立った。

koic.hatenablog.com

git status -s | grep '^DU ' | cut -d ' ' -f 2 | xargs git rm が思い出せなかった。べんり。

Ruby 2.8.0-devビルドのためclangを更新した

TL;DR としては手元の macOS の clang のバージョン更新を怠っていたので、Ruby の head (2.8.0-dev) でコンパイルエラーが起きていた。久しぶりの Ruby 2.8.0-dev でコンパイルエラーが出たら clang を更新してみましょう。

以下ログ込みで記しておく。 Ruby 2.8.0-dev に話題の Endless method definition が入ったこともあり、一昨日あたりに数日ぶりのアップデートをしたら rbenv install 2.8.0-devコンパイルエラーが起きるようになっていたので、ruby/rubyリポジトリで再現するか見たところ期待どおり同様のエラーになった。

% cd path/to/ruby/ruby
% make distclean
(snip)

% make check
        BASERUBY = echo executable host ruby is required.  use --with-baseruby option.; false
        CC = clang -fdeclspec
        LD = ld
        LDSHARED = clang -fdeclspec -dynamiclib
        CFLAGS = -O3 -ggdb3 -Wall -Wextra -Werror=deprecated-declarations -Werror=division-by-zero -Werror=implicit-function-declaration -Werror=implicit-int -Werror=pointer-arith -Werror=shorten-64-to-32 -Werror=write-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Werror=extra-tokens -std=gnu99  -pipe
        XCFLAGS = -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fno-strict-overflow -DRUBY_DEVEL=1 -fvisibility=hidden -DRUBY_EXPORT -fPIE -DCANONICALIZATION_FOR_MATHN -I. -I.ext/include/x86_64-darwin17 -I./include -I. -I./enc/unicode/12.1.0
        CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT
        DLDFLAGS = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -fstack-protector-strong -Wl,-pie -framework Security -framework Foundation
        SOLIBS = -lpthread -lgmp -ldl -lobjc
        LANG = ja_JP.UTF-8
        LC_ALL =
        LC_CTYPE = ja_JP.UTF-8
        MFLAGS =
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
compiling ./main.c
In file included from ./main.c:21:
In file included from ./include/ruby.h:33:
In file included from ./include/ruby/ruby.h:46:
./include/ruby/3/scan_args.h:264:47: error: use of undeclared identifier 'fmt'; did you mean 'fma'?
    RUBY3_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) <  0,    "bad scan arg format",                    "error")
                                              ^~~
                                              fma
./include/ruby/3/scan_args.h:102:57: note: expanded from macro 'rb_scan_args_count'
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
                                                        ^
./include/ruby/3/scan_args.h:98:29: note: expanded from macro 'rb_scan_args_count_lead'
    (!rb_scan_args_isdigit((fmt)[ofs]) ? \
                            ^
./include/ruby/3/scan_args.h:67:62: note: expanded from macro 'rb_scan_args_isdigit'
#define rb_scan_args_isdigit(c) (RUBY3_CAST((unsigned char)((c)-'0'))<10)
                                                             ^
./include/ruby/3/cast.h:31:28: note: expanded from macro 'RUBY3_CAST'
# define RUBY3_CAST(expr) (expr)
                           ^
./include/ruby/3/attr/diagnose_if.h:34:36: note: expanded from macro 'RUBY3_ATTR_DIAGNOSE_IF'
    __attribute__((__diagnose_if__(_, __, ___))) \
                                   ^
/usr/include/math.h:554:15: note: 'fma' declared here
extern double fma(double, double, double);
              ^
In file included from ./main.c:21:
In file included from ./include/ruby.h:33:
In file included from ./include/ruby/ruby.h:46:
./include/ruby/3/scan_args.h:264:28: error: subscript of pointer to function type 'double (double, double, double)'
    RUBY3_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) <  0,    "bad scan arg format",                    "error")
    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./include/ruby/3/scan_args.h:102:33: note: expanded from macro 'rb_scan_args_count'
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
                                ^
./include/ruby/3/scan_args.h:98:28: note: expanded from macro 'rb_scan_args_count_lead'
    (!rb_scan_args_isdigit((fmt)[ofs]) ? \
                           ^
./include/ruby/3/scan_args.h:67:62: note: expanded from macro 'rb_scan_args_isdigit'
#define rb_scan_args_isdigit(c) (RUBY3_CAST((unsigned char)((c)-'0'))<10)
                                                             ^

(snip)
./include/ruby/3/scan_args.h:102:57: note: expanded from macro 'rb_scan_args_count'
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
                                                        ^
./include/ruby/3/scan_args.h:99:29: note: expanded from macro 'rb_scan_args_count_lead'
     rb_scan_args_count_var(fmt, ofs, vari) : \
                            ^
./include/ruby/3/scan_args.h:89:31: note: expanded from macro 'rb_scan_args_count_var'
     rb_scan_args_count_trail(fmt, ofs, vari) : \
                              ^
note: (skipping 2 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
./include/ruby/3/scan_args.h:75:29: note: expanded from macro 'rb_scan_args_count_block'
     rb_scan_args_count_end(fmt, (ofs)+1, (vari)+1))
                            ^
./include/ruby/3/scan_args.h:70:7: note: expanded from macro 'rb_scan_args_count_end'
    ((fmt)[ofs] ? -1 : (vari))
      ^
./include/ruby/3/attr/diagnose_if.h:34:36: note: expanded from macro 'RUBY3_ATTR_DIAGNOSE_IF'
    __attribute__((__diagnose_if__(_, __, ___))) \
                                   ^
/usr/include/math.h:554:15: note: 'fma' declared here
extern double fma(double, double, double);
              ^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.
make: *** [main.o] Error 1

自分の環境のことなので、clang のバージョンが古かろうと見てみたところ version 9.0.0 だった。

% clang --version
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

LLVM を更新する。

% brew upgrade llvm
(snip)

==> llvm
To use the bundled libc++ please add the following LDFLAGS:
  LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"

llvm is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have llvm first in your PATH run:
  echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.zshrc

For compilers to find llvm you may need to set:
  export LDFLAGS="-L/usr/local/opt/llvm/lib"
  export CPPFLAGS="-I/usr/local/opt/llvm/include"

clang のバージョンが 10.0.0 になる。

% clang --version
clang version 10.0.0
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm/bin

rbenv install 2.8.0-dev でふたたびインストール可能になった。

% ruby -v
ruby 2.8.0dev (2020-04-12T05:58:13Z master a07cbacd23) [x86_64-darwin17]

話題の Endless method definition を動かすとこんな感じ。

% ruby -e 'def greet(name) = puts("Hello, #{name}!"); greet("John Smith")'
Hello, John Smith!

ついでに右代入してみる。

% ruby -e '1 + 1 => x; puts x'
2

Enjoy!

GitHub Packages と Gem の配布先

TL;DR 公開 gem の配布先は従来どおり rubygems.org のみで良いかなといまのところ思っています。


GitHub では GitHub Packages がリリースされて、gem を含めていくつかのパッケージに対してサポートされている。

help.github.com

gem に対する GitHub Packages への現時点での考え。

OSS など公開 gem のリリース先については、従来どおり rubygems.org のみで良いかなといまのところ思っています。理由としては、rubygem.org ではダウンロード数などの数字によって、その Gem やリリースがユーザーに与える影響への指標のひとつとして中央集権先として見ることができるためです。将来的に複数のホスティングに分散するメリットが明確になったらその時は改めて考えてみたいところ。

一方で、gem server を使った社内 Gem サーバーなどを運用している場合は、GitHubパッケージをホスティング先にすると便利そうです。その他、リリース時に rubygems.org が死んでいたら死ぬみたいな状況を回避したいようなシステムの冗長構成先として使えるかもしれません (DNSimple が死んで rubygems.org が引けなかったことが数年前にあったけれど、滅多にないと思う) 。

このエントリの出典は、RuboCop リポジトリに以下のような GitHub Packages 向けに Gem をリリースするタスク追加の PR が送られてきたやりとりの際に考えたことでした。

github.com

備忘を兼ねてここに書いておこしておきます。