昨年くらいに Faker の org メンバーになっていたけれど、リポジトリの PR がたまっていている状態をどうにかしたかったので、そこのチームにコミット権を付与してもらった。
アクティブなチームメンバーがマージできるようになったのではと思う。
昨年くらいに Faker の org メンバーになっていたけれど、リポジトリの PR がたまっていている状態をどうにかしたかったので、そこのチームにコミット権を付与してもらった。
アクティブなチームメンバーがマージできるようになったのではと思う。
GW にやったことについて、GitHub から辿れることを中心に書き残しておく。
.rubocop.yml で ERB を書けるようになるパッチをレビューしてマージした。
こんな書き方ができるようになる。
AllCops: Exclude: <% `git status --ignored --porcelain`.lines.grep(/^!! /).each do |path| %> - <%= path.sub(/^!! /, '') %> <% end %>
Active Record Oracle enhanced adapter に対する upstream の日次動向チェックで動きがあったので追随した。
以前から気にはなっていた Active Record Oracle enhanced adapter のテスト時の警告を抑制した。
.rubocop.yml で無効になっている部署に対して、一部の cop を有効にできるようにするパッチをレビューしてマージした。べんり。
Layout/ConditionPosition
cop の auto-correct を実装した。
以下のようにマルチバイト文字への振る舞いが等価でないため Performance/Casecmp
cop を unsafe にした。
'äöü'.casecmp('ÄÖÜ').zero? #=> false 'äöü'.casecmp?('ÄÖÜ') #=> true
レガシー API の content_tag
を tag
にする Rails/ContentTag
cop をレビューしてマージした。PR の根拠となるリソースなど示されているためレビューの手間が少なくなっていたのでよかった。
ドッグフーディングとして実 Rails アプリケーションに適用してみて良い感じだった。
2020年5月7日追記
偽陽性がありそうだったので、調べるつもり。
Style/GuardClause
cop の誤ったメッセージを修正した。
Lint/EnsureReturn
cop の auto-correct を実装した。
RailsConf 2020.2 COUCH EDITION の視聴をはじめた。往年の Kent Beck ファンとして濃密な 15min の講演がよかった。
rubocop-hq/rubocop#7925 が複合要因のイシューで未解決部分があった Style/GuardClause
cop の偽陽性を修正した。
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 アプリケーションで実行してみて良い感じの首尾だった。
Rails 6.0.3 がリリースされたので、ホームページを更新した。
GitHub 社の Codespaces のベータの waitlist にならんだ。
まだ PR にしていないセルフレビュー中のパッチや、仕掛かりや手戻りのパッチなど手元にあるので、それらは今後引き続きといった感じ。
カンファレンスとしては、去年のゴールデンウィークは RailsConf 2019 のためミネアポリスに行っていたけれど、RailsConf 2020 はオンラインで聞くゴールデンウィークだった。
Active Recordでのヒント句の書き方について。
クエリの実行計画の最適化を RDBMS のオプティマイザ (プランナ) に任せずに、アプリケーション側で指定するのにヒント句というのがあります。通常 RDBMS のオプティマイザに任せたりしていますが、DBA からチューニングのアドバイスがあったりしたときに使ったりできます。
Oracle、MySQL 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 ドキュメントから抜粋。
Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)") #=> SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)") #=> SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
Rails 5.2 以下であれば、select
メソッドを使って指定するといった形式になると思います。
以下は select
の振る舞いを利用して、カラムの前にコメントとしてヒントを足す方法ですが、既存の select
メソッドの引数に手を加える必要があるのが難点です。
Topic.select("/*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.*")
他にも方法はあると思いますが、Rails 6.0 以上でのoptimizer_hints
メソッドが使えれば流れるようなインタフェースにできてクールでしょう。
2020-04-21 追記
PostgreSQL へのサポートについて kamipo さんからのアドバイスを追記しておきます。
アクティブレコードのoptimizer hints、ポスグレでも動いてそうなテスト書いてるけど手元にpg_hint_plan入れても全く動かなくてヒントが効こうが効かまいが結果に影響がないヒントをわざと書いてそれっぽい感じを演出してるだけなので、動作は君の目で確かめてほしい。
— Ryuta Kamizono (@kamipo) 2020年4月21日
bundle install --clean
を実行するときの注意点として、バッドノウハウを書き残しておきます。
これは、以前 pocke さんが CircleCI の実行を速くするのに bundle install --clean
を使う方法を記されていたことで思い出したことです。
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 に記しておきましょうという話でした。
Active Record でサポートしているコアアダプターの MySQL, PostgreSQL, SQLite 以外のサードパーティーの RDBMS に対する Arel visitor がコアから外されることになった。
SQL Server は以前から独自で梱包していたようで、Oracle は今回対応した PR を Acitive Record Oracle enhanced adapter のリポジトリに開いている。
この変更によってメンテナンスされているアダプターのユーザーに対して直接的な影響はないが、アダプター開発者は Rails リポジトリの activerecord/lib/active_record/connection_adapters に加えて、activerecord/lib/arel の変更もウォッチ対象になるだろう。
余談だが今回 Arel visitor を Oracle enhanced adapter に輸入する際の手順で、過去に書き残しておいた記事がちょっと役立った。
git status -s | grep '^DU ' | cut -d ' ' -f 2 | xargs git rm
が思い出せなかった。べんり。
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!
TL;DR 公開 gem の配布先は従来どおり rubygems.org のみで良いかなといまのところ思っています。
GitHub では GitHub Packages がリリースされて、gem を含めていくつかのパッケージに対してサポートされている。
gem に対する GitHub Packages への現時点での考え。
OSS など公開 gem のリリース先については、従来どおり rubygems.org のみで良いかなといまのところ思っています。理由としては、rubygem.org ではダウンロード数などの数字によって、その Gem やリリースがユーザーに与える影響への指標のひとつとして中央集権先として見ることができるためです。将来的に複数のホスティングに分散するメリットが明確になったらその時は改めて考えてみたいところ。
一方で、gem server
を使った社内 Gem サーバーなどを運用している場合は、GitHubパッケージをホスティング先にすると便利そうです。その他、リリース時に rubygems.org が死んでいたら死ぬみたいな状況を回避したいようなシステムの冗長構成先として使えるかもしれません (DNSimple が死んで rubygems.org が引けなかったことが数年前にあったけれど、滅多にないと思う) 。
このエントリの出典は、RuboCop リポジトリに以下のような GitHub Packages 向けに Gem をリリースするタスク追加の PR が送られてきたやりとりの際に考えたことでした。
備忘を兼ねてここに書いておこしておきます。