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

Lefthookでチームのコードをゼロコンフィグで引き締めよう(翻訳)

$
0
0

概要

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

※日本語タイトルは内容に即したものにしました。画像は元記事からの引用です。

まえがき

最速を誇るポリグロットGitフックマネージャ、Lefthookの登場です。自由すぎて手に負えないコードをproductionレベルに持っていきましょう。Lefthookのインストールは驚くほど簡単で、最近だとDiscourseLoguxOpenstaxも採用しています。ほとんどのフロントエンド環境やバックエンド環境で、チームの開発者全員が単一の柔軟なツールを頼りにできるようになります。Lefthookにはこんな絵文字🥊もあります。

訳注: ポリグロット(polyglot)は本来「数か国語を話せる人」といった意味ですが、本記事では言語やフレームワークや環境などを複数扱えるという意味で捉えています。

Lefthookでチームのコードをゼロコンフィグで引き締めよう(翻訳)

かつて、数百万人ものユーザーが頼りにしている単独のソフトウェアといえば、象牙の塔にこもった1人の開発者が作り上げるものでしたが、それも今や遠い昔の話です。GitですらLinux Torvaldsが一人で独創性を発揮してこしらえたものであると広く信じられていますが、これもやはり多くのコントリビュータの助けがあってのことであり、何十人ものチームによって今もメンテナンスされています。

あなたが世界を股にかけたオープンソースのプロジェクトに取り組んでいようと、プロプライエタリな商用ソフトウェアの閉じた庭園で花を咲かせていようと、チームで仕事をしていることに変わりはありません。そして、プルリクやコードレビューがどれほど巧妙に組み上げられていようと、多くのコントリビュータが参加している巨大なコードベース全体に渡ってコードの品質を担保するのは、容易なことではありません。

フックを一発お見舞い

Gitフックは、コミットやプッシュといった特定の重要な操作が発生したときにカスタムスクリプトを発動させるしくみのことであり、Gitに組み込まれています。あなたが、Bashの操作と世界で最も広く使われているバージョンコントロールシステム(つまりGit)の内部に通じているのであれば、ツールをまったく追加せずにやれます。./.git/hooks/pre-commitを編集し、形式の整った手頃なスクリプトを配置すれば、たとえばコミットする前にファイルにlintをかけたりできるようになります。

しかし、今あなたが携わっているプロジェクトでの関心事は、もっぱらプロジェクトのコードを書くことであり、コードをチェックするコードの方ではないでしょう。現代のWeb開発ツールの世界にはそれこそ何でも揃っていますが、このように無数のツールが存在する理由はたったひとつ、オーバーヘッドと複雑性を削減することです。Gitフックも例外ではありません。JavaScriptのコミュニティで好んで使われる武器といえば、HuskyWebpackBabelを組み合わせるとか、Nodeベースのツールに依存するcreate-react-appです。しかしRailsを中心に据えるバックエンド世界はというと、Ruby gemとして提供されるOvercommitにほぼ支配されています。

Lefthookとその他のツールの詳しい比較については、プロジェクトのWikiをご覧ください。

どちらのツールもそれぞれよくできていますが、Evil Martiansのようにフロントエンドとバックエンドの混成チームともなると、フロントエンドのlintとバックエンドのlintがRubyとJavaScriptに分断され、それぞれ独自の方法でlintをかけるはめになることもしょっちゅうです。

Lefthookなら同じことを二度も考える必要はもうありません。Lefthookは単独のGoバイナリで、JavaScriptRubyそれぞれのラッパーを内包しています。しかもその他の環境でも単独のツールとして使えます。

ポイント: Lefthookはほとんどのユースケースでセットアップなしでやれます。

LefthookはGo言語のおかげで驚異的な速さを誇り、すぐに使えるスクリプトのコンカレント実行をサポートします。実行ファイルが単独のマシンコードバイナリなので、外部の依存性にわずらわされることもなくなります(Husky + lint-stagedでは500個ほどの依存ファイルがnode_modulesに追加されます)。しかも、開発環境が更新されるたびに依存ファイルを再インストールするという頭の痛い作業からも解放されるのです(試しにグローバルにインストールされたgemを別バージョンのRubyで動かしてみればわかります)。

プロジェクトのルートディレクトリ(後述の例を参照)のpackage.jsonGemfileのいずれか、そしてlefthook.ymlをLefthookに認識させればこれらのツールがインストールされ、次回のgit pull、次回のyarn installや bundle install、次回の git addgit commitで自動的にコードに対して実行されます。プロジェクトに新たに参加するコントリビュータを一切わずらわせません。

詳細番のREADMEに、あらゆる利用シナリオが記載されています。設定の構文は素直で、Lefthookによって実際に実行されるコマンドを隠蔽しません。「うっかり金的攻撃」のようなハプニングもなくなります。

Discourseのハートにもクリーンヒット

Discourseといえば、フォーラムスタイルの議論を行えるオープンソースプラットフォームとしてとても有名ですが、先ごろ不退転の決意でOvercommitからLefthookに完全に移行しました。700人近いコントリビューターが34,000件ものコミットを扱うので、新しいコントリビューションすべてにlintをかけるのは優先順位が高くなります。しかしOvercommitの場合だと、チームのメンバーが新規参入者に必要なツールをインストールするよう毎回リマインドしなければなりませんでした。

@arkweid/lefthookがプロジェクトのpackage.jsonでめでたくdev dependencyとなったことにより、新規コントリビューターのセットアップは不要になりました。

ポイント: Lefthookを使うことで、localhostで実行されるpre-commitスクリプトの実行時間が半分になります。

Discourseのプルリク#7826では、必要なGitフックマネージャが.overcommit.ymlからlefthook.ymlに変更されました。両者の設定を比較してみれば、Overcommitの設定の多くがプラグインのマジックに依存していますが、Lefthookの設定はずっと明示的な書式になっていることがわかります。

DiscourseでLefthookを導入する前と後のCI出力
単に出力方法が変わっただけではありません。Lefthookが行ったことすべてがいい感じにサマリーとして出力されています。Lefthookを使うことで、localhostで実行されるpre-commitスクリプトの実行時間が半分になり、CIの実行速度が20%増加します(パラレル実行のサポートがより手厚いCI環境であればさらによい結果になるでしょう)。

うれしいボーナス

「第1ラウンド」

lefthookバイナリがシステムのどこか(ローカルでもグローバルでもよい)にインストールされていて、プロジェクトのルートディレクトリにlefthook.ymlが配置されていれば、Lefthookが機能するのに必要なものはすべて揃います。

Lefthookのバイナリはグローバルにインストールする(macOSならHomebrew、Ubuntuならsnappy、Arch LinuxならAUR、Go言語のgo getを使えばどの環境でもOKです)ことも、RubyのGemfileやNode.jsのpackage.jsonで開発用の依存関係リストに加えることもできます。

Lefthookをプロジェクトで最初に設定する場合は、好みに応じていくつかのオプションの中から選択する必要があります。

Gemfilelefthookを追加したり@arkweid/lefthookpackage.jsonに追加する場合の主なメリットは、コントリビューターのシステムレベルにlefthookをインストールしなくても済むようにできることです。bundle installまたはyarn installを実行すればバイナリが配置されます。

lefthookをシステムレベルでインストールした後で、プロジェクトのルートディレクトリでlefthook installを実行してlefthook.ymlを生成し、Lefthookのリポジトリで構文の使い方などを学びます。完全なサンプルlefthook.ymlはここにあります。

pre-commitのたびに行うアクションを記述するコードの例は以下のような感じになります(pre-commitは、git commit -m "new feature"を入力した直後、かつそれがコミットされる直前のタイミングとなります)。

pre-commit:
  commands:
    stylelint:
      tags: frontend style
      glob: "*.{js}"
      run: yarn stylelint {staged_files}
    rubocop:
      tags: backend style
      glob: "*.{rb}"
      exclude: "application.rb|routes.rb"
      run: bundle exec rubocop {all_files}
  scripts:
    "good_job.js":
      runner: node

続いてlefthook.ymlをリポジトリにコミットしてお気に入りのリモートリポジトリにプッシュすればあら不思議、チームのどのメンバーもコードをコミットするたびに動くようになります。

もっと詳しく

訳注: 上の見出しの「blow by blow」は「詳しく説明する」というイディオムで、「ボクシングの1打1打を中継する」が起源です。

Lefthookをデモプロジェクトで急いでチェックしたい方にはevil_chatリポジトリをgit cloneすることをおすすめします。このリポジトリは、Evil Martiansの以下の人気記事に関連して構築したプロジェクトです。

新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)

新しいRailsフロントエンド開発(2)コンポーネントベースでアプリを書く(翻訳)

新しいRailsフロントエンド開発(3)Webpackの詳細、ActionCableの実装とHerokuへのデプロイ(翻訳)

このプロジェクトではLefthookでpre-commitフックを設定することでJavaScriptファイルやCSSファイルをPrettierで整形し、ESlintstylelintでlintをかけています。

Lefthookが動くところをざっと見てみましょう。まず、上のリポジトリをgit cloneしてパッケージマネージャを実行します。

$ git clone git@github.com:demiazz/evil_chat.git
$ bundle && yarn

それでは.pcssファイルや.jsファイルを適当にぶっ壊してみてください。

$ git add . && git commit -m "我は世界を滅ぼすものなり"

後は待つだけ!

うまくいけば(=うまくコードをぶっ壊せば)以下が表示されるでしょう。

ダメなコミット
失敗が発生したスクリプトの出力にはボクシンググローブの絵文字が表示されます。あなたに本当の左フックを食らわして注意を引きつけるわけです。

今のlint出力はアプリのフロントエンド部分しかカバーしていません。皆さんご存知のRubocop出力も追加してみたらどうなるでしょうか?

lefthook.ymlを編集して以下を追加しましょう。

# lefthook.yml

pre-commit:
  parallel: true # tell Lefthook to utilise all cores
  commands:
    js:
      glob: "*.js"
      run: yarn prettier --write {staged_files} && yarn eslint {staged_files} && git add {staged_files}
    css:
      glob: "*.{css,pcss}"
      run: yarn prettier --write {staged_files} && yarn stylelint --fix {staged_files} && git add {staged_files}
    # Add these lines
    rubocop:
      glob: "*.{rb}"
      run: rubocop {staged_files} --parallel

なお{staged_files}は、現在のコミットでstagedになっているファイルだけを対象にする便利なショートカットです。

それではさっきぶっ壊したJSファイルやCSSファイルを元に戻し、続いて適当なRubyファイルに「許されないスタイルを含むコミット」をかましてみましょう(私たちが許しますので存分にやってください)。適当なコミットメッセージを付けて、先ほどとは違うファイルタイプの変更をgitに食わせてみます。

おぉっとRubocopの乱入です!

おぉっとRubocopの乱入です!
今度はCSSとJSは問題なしとなり、Rubyの部分にチェックが必要となりました。痛烈な左フックを受けました!

「奴のパンチをかわせ」

ここでは、ワークフローに柔軟性をもたらすLefthookがライバルを上回っている機能を簡単にまとめます。完全なリストについては完全版ガイドをご覧ください。

スピード

Lefthookはコンピュータ(つまりCIサーバーも)の並列性を一滴も余さず活用します。そのために必要な設定はparallel: trueだけです。

以下はさまざまな種類のlintコマンドを記述する設定ファイルであり、コマンドラインでlefthook run lintを入力すれば実行できます。これらは、DiscourseがTravisで使っているものと同じです。

Lefthookは、このようなカスタムタスクを実行することもできます。pre-commitpre-pushpost-checkoutpost-mergeなど、Gitフックで利用可能などのフックにも同じコマンドを設定できます。

# lefthook.yml

lint:
  # parallel: true
  commands:
    rubocop:
      run: bundle exec rubocop --parallel
    prettier:
      run: yarn prettier --list-different "app/assets/stylesheets/**/*.scss" "app/assets/javascripts/**/*.es6" "test/javascripts/**/*.es6"
    eslint-assets:
      run: yarn eslint --ext .es6 app/assets/javascripts
    eslint-test:
      run: yarn eslint --ext .es6 test/javascripts
    eslint-plugins-assets:
      run: yarn eslint --ext .es6 plugins/**/assets/javascripts
    eslint-plugins-test:
      run: yarn eslint --ext .es6 plugins/**/test/javascripts
    eslint-assets-tests:
      run: yarn eslint app/assets/javascripts test/javascripts

自分のシステムではparallel: trueをコメントアウトしているので、このタスクの実行には30秒そこそこかかります。

ポイント: このparallel: trueをオンにすると15.5秒で終わります — 倍速です!

柔軟性

  • ダイレクトコントロール

Gitアクションを待たずにフックを直接実行したい場合は以下を使います。

$ lefthook run pre-commit
  • 柔軟なファイル指定

{staged_files}{all_files}といった組み込みのショートカットを使うことも、特定のファイルを選択する独自のリストを定義することもできます。

pre-commit:
  commands:
    frontend-linter:
      run: yarn eslint {staged_files}
    backend-linter:
      run: bundle exec rubocop {all_files}
    frontend-style:
      files: git diff --name-only HEAD @{push}
      run: yarn stylelint {files}
  • ワイルドカードや正規表現によるフィルタ

ファイルリストをその場でワイルドカード(glob)や正規表現でフィルタする場合は以下のようにします。

pre-commit:
  commands:
    backend-linter:
      glob: "*.{rb}" # glob filter
      exclude: "application.rb|routes.rb" # regexp filter
      run: bundle exec rubocop {all_files}
  • 独自スクリプトの実行

ワンライナーで足りなくなったら、Lefthookでカスタムスクリプトを実行することもできます。

commit-msg:
  scripts:
    "good_job":
      runner: bash
  • タグ付けやローカル設定でさらに柔軟に

タスクをタグでグループ化すれば、フックをローカル実行するときにグループを除外できます(バックエンド開発者なのでフロントエンド用のタスクを回したくない、など)。

Lefthookではプロジェクトのルートディレクトリにlefthook-local.ymlを作成して、メインのlefthook.ymlにあるあらゆる設定をオーバーライドできます(lefthook-local.yml.gitignoreに追加しておくことをお忘れなく!)。こうすることで、次のように別の一連のコマンドにタグを割り当てられます。

# lefthook.yml

pre-push:
  commands:
    stylelint:
      tags: frontend-style # a tag
      files: git diff --name-only master
      glob: "*.{js}"
      run: yarn stylelint {files}
    rubocop:
      tags: backend-style # a tag
      files: git diff --name-only master
      glob: "*.{rb}"
      run: bundle exec rubocop {files}

次のようにローカルでの実行から除外することもできます。

# lefthook-local.yml

pre-push:
  exlude_tags:
    - frontend-style

そしてK.O.

皆さんはローカル開発でDockerを使っていますか?既に使っている方はもちろん、そうでない方にとってもチームでDockerをローカル開発に使うまたとないチャンスです。開発作業全体を完全にDockerコンテナに閉じ込めるのが好みの人もいますし、ローカル環境をキレイに整頓する目的でDockerを用いるのが好みの人もいます。

あなたがメインで使うlefthook.ymlに以下が含まれているとします。

post-push:
  scripts:
    "good_job.js":
      runner: bash

さて、これと同じタスクをDockerコンテナの中でも実行したい、しかし他の人のセットアップをぶっ壊したくないとしたらどうしますか?そこで、バージョン管理システムにチェックインしないようにしたlefthook-local.ymlファイルを用いれば、このコマンドを{cmd}ショートカットで次のようにちょっぴり改変してローカル専用のセットアップにできます。

# lefthook-local.yml

pre-commit:
  scripts:
    "good_job.js":
      runner: docker exec -it --rm <container_id_or_name> {cmd}

{cmd}は、メインの設定のコマンドで置き換えられます。

その結果、コマンドは以下のような感じになります。

docker exec -it --rm <container_id_or_name> node good_job.js

エイト、ナイン、テン…ノックアウトです🥊!


私たちは、Lefthookが銀河系で最速かつ最も柔軟性に富んだGitフックマネージャであると自負しています。皆さんにもぜひ、Discourseの事例に学んでLefthookを業務プロジェクトに追加するなり、自分の愛するオープンソースリポジトリにプルリクを送るなりしていただければと思います。

Lefthookは本質的にポリグロットなので、「純粋なフロントエンド開発チーム」「純粋なバックエンド開発チーム」「フロントエンドとバックエンドの混成フルスタックチーム」を問わず利用できる開発用セットアップであり、Windowsなどのあらゆる主要なOS環境で共通に使えます。

最適なインストール方法を皆さんの要件に応じてお探しいただけます。ぜひお試しください!私たちがLefthookをCrystalballと組み合わせて回している商用プロジェクトについては、Dev.toのこちらの記事でお読みいただけます。

Lefthookを見て「また絵に書いたような車輪の再発明かよ?」とうんざりしかかっている方も、ぜひお試しください。Lefthookが単なる新手のジェットパックではなく、Gitフック管理におけるもうひとつの「古き良き車輪」であることにお気づきいただけるかと思います。

GitやGitHubワークフローを自動化する戦いが終わる前に、決して、決してタオルを投げないでください!

関連記事

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)


PHP / Ruby / Python: 住所から正規表現で都道府県を抽出してみる

$
0
0

BPSの福岡拠点として一緒にお仕事させて頂いてます、株式会社ウイングドアのアリタです。

サイト開発を行なっていると、ユーザー情報や会社情報などで入力された住所データを扱う事があると思います。
今回は正規表現を使って住所から「都道府県」を抽出する方法をご紹介します。

前提

今回は下記の前提のもとで進めていきます。

  • 「都道府県」より下の市区町村などは今回スコープ外
  • 住所の文字コードはUTF-8を想定
  • PHP / Ruby / Pythonの各言語で住所から都道府県を抽出する

PHPで都道府県を抽出してみる

さくっと試すため、PHPのインタラクティブモードを利用します。

$ php -a
Interactive shell

住所を$addressという変数に代入します(ここではBPSさんの住所を引用)。

php > $address = "東京都新宿区西新宿6-20-7コンシェリア西新宿TOWER’S WEST 2F";

正規表現を利用して、preg_match_allにてマッチした結果を抽出します。

php > $regex = '/東京都|北海道|(?:京都|大阪)府|.{6,9}県/';
php > preg_match_all($regex, $address, $matches);

結果をvar_dump。 無事、配列の1番目のインデックスに「東京都」が抽出できました!

php > var_dump($matches);
array(1) {
  [0]=>
  array(1) {
    [0]=>
    string(9) "東京都"
  }
}

念のため、他の住所も試してみます。(ウイングドアの住所を引用)

php > $address = "福岡県福岡市中央区天神4丁目1−28 天神リベラ 3F";
php > $regex = '/東京都|北海道|(?:京都|大阪)府|.{6,9}県/';
php > preg_match_all($regex, $address, $matches);
php > var_dump($matches);
array(1) {
  [0]=>
  array(1) {
    [0]=>
    string(9) "福岡県"
  }
}

配列の1番目のインデックスに「福岡県」が抽出されています。大丈夫そうですね。

Rubyで都道府県を抽出してみる

rails consoleを起動。

$ rails c

住所をaddressという変数に代入。(ここではBPSさんの住所を引用)

[1] pry(main)> address = "東京都新宿区西新宿6-20-7コンシェリア西新宿TOWER’S WEST"
=> "東京都新宿区西新宿6-20-7コンシェリア西新宿TOWER’S WEST"

正規表現を利用して、マッチした結果を抽出します(追記2019/10/18: 正規表現を修正しました🙇)。

[2] pry(main)> address.match(/^.{2,3}[都道府県]/).to_s
=> "東京都"

無事、「東京都」が抽出できました。
・・・というかRubyすごい。簡単すぎる。

念のため、他の住所も試してみます。(ウイングドアの住所を引用)

[1] pry(main)> address = "福岡県福岡市中央区天神4丁目1−28 天神リベラ 3F"
=> "福岡県福岡市中央区天神4丁目1−28 天神リベラ 3F"
[2] pry(main)> address.match(/^.{2,3}[都道府県]/).to_s
=> "福岡県"

無事、「福岡県」が抽出されました。

備考

下記のように 東京都|北海道|(?:京都|大阪)府|.{2,3}県 という正規表現でも取得する事が可能です。

[1] pry(main)> address = "東京都新宿区西新宿6-20-7コンシェリア西新宿TOWER’S WEST"
=> "東京都新宿区西新宿6-20-7コンシェリア西新宿TOWER’S WEST"
[2] pry(main)> address.match(/東京都|北海道|(?:京都|大阪)府|.{2,3}県/).to_s
=> "東京都"
[1] pry(main)> address = "福岡県福岡市中央区天神4丁目1−28 天神リベラ 3F"
=> "福岡県福岡市中央区天神4丁目1−28 天神リベラ 3F"
[2] pry(main)> address.match(/東京都|北海道|(?:京都|大阪)府|.{2,3}県/).to_s
=> "福岡県"

Pythonで都道府県を抽出してみる

インタラクティブモードで起動

$ python3

正規表現操作のライブラリre をimport。

>>> import re

住所をaddressという変数に代入。(ここではBPSさんの住所を引用)

>>> address = "東京都新宿区西新宿6-20-7コンシェリア西新宿TOWER’S WEST 2F"

正規表現を利用して、マッチした結果を抽出します。

>>> matches = re.match('東京都|北海道|(?:京都|大阪)府|.{2,3}県' , address)
>>> print(matches)
<_sre.SRE_Match object; span=(0, 3), match='東京都'>

無事、「東京都」が抽出できました。

念のため、他の住所も試してみます。(ウイングドアの住所を引用)

>>> import re
>>> address = "福岡県福岡市中央区天神4丁目1−28 天神リベラ 3F"
>>> matches = re.match('東京都|北海道|(?:京都|大阪)府|.{2,3}県' , address)
>>> print(matches)
<_sre.SRE_Match object; span=(0, 3), match='福岡県'>

無事、「福岡県」が抽出されました。

まとめ

今回、PHP / Ruby / Python 各言語で住所から「都道府県」を抽出する事が出来ました。
感想としては、Rubyが簡単すぎるにつきます (ただ、市区町村まで考慮すると大変みたいです)。

また、PHP / Ruby・Python で県の箇所の数字が「.{6,9}県」、「.{2,3}県」と異なっているのも面白いなと思いました。PHPの方は(UTF-8のため)1文字3バイト計算で、Ruby・Pythonは文字数で計算されているようです。

正規表現を使えば(使いこなせば)、郵便番号、メールアドレスなどなど
色々な文字列を抽出出来るので、是非色々試してみてください!



株式会社ウイングドアでは、Ruby on RailsやPHPを活用したwebサービス、webサイト制作を中心に、
スマホアプリや業務系システムなど様々なシステム開発を承っています。

関連記事

はじめての正規表現とベストプラクティス#1: 基本となる8つの正規表現

Ruby: 文字列マッチは正規表現より先に専用メソッドを使おう

$
0
0

正規表現は文字列メソッドより「遅い」

新しい話ではなくて恐縮です。
Rubyに限らず、一般に正規表現は言語の文字列マッチメソッドより低速になります。

複雑なパターンを調べたい場合は正規表現を使うことになりますが、特に「開始文字列」「終了文字列」とのマッチを単純にチェックするだけなら、String#start_with?String#end_with?でマッチを取る方が可読性の上でも速度面からもおすすめです。

本記事ではtrue/falseを返す文字列マッチメソッドについてのみ言及していますが、文字列の取り出しや置換といった操作についても、専用メソッドの方が正規表現よりも一般に高速なので、「正規表現は次なる手段」と考えるようにしています。

正規表現が前提の文字列マッチメソッド(高機能、低速)

正規表現を使う場合も、=~よりmatch?の方が高速かつ可読性が高まりますので、マッチするかどうかをチェックするだけならmatch?にしましょう。自分も、=~~が前だったか後だったか毎回忘れてしまいます😅

match?は、以下の記事にあるRubyの特殊変数を更新しません。おそらくその分速いと思われます。

[Ruby] Kernelの特殊変数をできるだけ$記号なしで書いてみる

文字列マッチ専用メソッド(単機能、高速)

これらの文字列マッチ専用メソッドには、一応正規表現を引数として与えることもできますが、速度面では文字列を引数として与える方が有利です。

さらに、String#start_with?String#end_with?の引数には文字列を複数与えられます。

string = 'test_some_kind_of_long_file_name.rb'
string.start_with?('empty', 'void', 'test_') #=> true
string.end_with?('useless', 'missing', 'rb') #=> true

配列に入れた文字列もsplat演算子*を使えば渡せます。

array = %w(empty void test_)
string = 'test_some_kind_of_long_file_name.rb'

string.start_with?(*array)    #=> true

なおString#include?は引数を1つしか渡せません😢。速度的にもString#match?と大差ないようです。

ベンチマーク

Ruby 2.6.5を使いました。なおRubocop 0.75.0にperformance copも併用してかけてみましたが、特に何も言われませんでした。

参考: fast-ruby

# frozen_string_literal: true

require 'benchmark/ips'

SLUG = 'test_some_kind_of_long_file_name.rb'

def slower
  SLUG =~ /^test_/
end

def slow
  SLUG.match?(/^test_/)
end

def fast_start
  SLUG.start_with?('test_')
end

def fast_end
  SLUG.end_with?('rb')
end

def fast_include
  SLUG.include?('_long_')
end

Benchmark.ips do |x|
  x.report('String#=~')          { slower }
  x.report('String#match?')      { slow } if RUBY_VERSION >= '2.4.0'
  x.report('String#start_with?') { fast_start }
  x.report('String#end_with?')   { fast_end }
  x.report('String#include?')    { fast_include }
  x.compare!
end

結果

$ ruby start_string_checking_match_vs_start_with.rb
Warming up --------------------------------------
           String#=~   245.277k i/100ms
       String#match?   404.641k i/100ms
  String#start_with?   501.627k i/100ms
    String#end_with?   490.709k i/100ms
     String#include?   414.337k i/100ms
Calculating -------------------------------------
           String#=~      3.736M (± 2.4%) i/s -     18.886M in   5.057521s
       String#match?      8.825M (± 3.0%) i/s -     44.106M in   5.003011s
  String#start_with?     15.313M (± 2.1%) i/s -     76.749M in   5.014165s
    String#end_with?     14.849M (± 3.7%) i/s -     74.588M in   5.031067s
     String#include?      9.559M (± 3.4%) i/s -     48.063M in   5.034701s

Comparison:
  String#start_with?: 15313477.8 i/s
    String#end_with?: 14848814.2 i/s - same-ish: difference falls within error
     String#include?:  9558764.0 i/s - 1.60x  slower
       String#match?:  8824832.6 i/s - 1.74x  slower
           String#=~:  3736485.1 i/s - 4.10x  slower

関連記事

はじめての正規表現とベストプラクティス#1: 基本となる8つの正規表現

週刊Railsウォッチ(20191021)Rails 6でhas_many関連の修正やSprockets 4.0対応、Shrine 3.0がリリース、Minitestスタイルガイドほか

$
0
0

こんにちは、hachi8833です。スマホで確定申告できるようになるそうです。


つっつきボイス:「スマホで確定申告したい人っているんでしょうか?😆」「スマホとかタブレットでやらないと間に合わないシチュエーションはあるかもですね☺

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

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

今回はコミットリストから見繕いました。has_many関連の修正が目に付きました。

⚓(master)belongs_toからhas_manyへのinverseをサポート

# activerecord/lib/active_record/associations/belongs_to_association.rb#L111
-       # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
-       # has_one associations.
        def invertible_for?(record)
+         inverse = inverse_reflection_for(record)
+         inverse && inverse.has_one?
+         inverse_reflection_for(record)
        end
# activerecord/lib/active_record/associations/collection_association.rb#L288
+     def target=(record)
+       case record
+       when Array
+         super
+       else
+         add_to_target(record)
+       end
+     end

つっつきボイス:「belongs_toからhas_manyへのcollectionのinverseはありそうでなかったんですね😳」「ドキュメントの更新と実際の挙動が合ってなかったので修正したそうです」

「ところでこのr?という「合ってる?」表現↓、ちょっと便利かなと思いました😋」「right?をそこまで略すのはどうかと😆」「SNSのチャット感覚というか😆」「そのぐらいフルで書いてもいいのでは😆」「了解を『り』とか『りょ』と書くみたいな😆


#34533より

⚓(master)inverse_of:を指定したhas_manyのレコード追加で関連付けのコールバックが発火しないように修正

has_manyinverse_of:を追加した場合に関連付けのコールバックが走るのを止める。
上の#34533に関連して、has_manyリレーションでinverse_of:を静かに設定したい、つまり新たに追加したレコード用のロジックをトリガしたくない。
has_manyのinverseを設定すると既存のアプリケーションが壊れる可能性が非常に高いが、これが正しい動作。挙動を選べるようにするプルリクを別途投げて、アプリ側を調整する時間を取れるようにする。
同PRより大意

修正はわずか1箇所です↓。

# activerecord/lib/active_record/associations/collection_association.rb#L293
      def target=(record)
        case record
        when Array
          super
        else
-         add_to_target(record)
+         add_to_target(record, true)
        end
      end

つっつきボイス:「今回はhas_many関連の修正が多くて、特にこれは修正は1箇所だけですがbreaking changeになってますね」「ははぁ😳、こういう変更をバシッと入れるのがすごいな〜って」「さすがに影響大きそうなので、デフォルトではオフにするそうです↓」「使う側はこういう変更追いかけるの大変そう😅

以下がその後に入った、挙動を選べるようにする修正ですね。config.active_record.has_many_inversingという設定(デフォルトはfalse)が追加されています。


なお、以下の記事では「Rails 4.1以降ではinverseの自動検出機能がある」「inverseの自動検出はhas_manyhas_onebelongs_toでのみ効く」「関連付けにそれ以外のオプションを付けると自動検出されなくなる」ともあります。APIのActiveRecord::Associations::ClassMethodsの「Setting Inverses」にも同じ記述がありました。

参考: Rails 4.1+ automatically detects the :inverse_of an association - makandra dev

⚓Sprockets 4.0に合わせてテストスイートを修正


つっつきボイス:「先週取り上げたSprocketsのアップデート(ウォッチ20191015)に合わせてRails側も修正したようです」「ほほぉ〜😋

  • application.cssapplication.css.erbを両方使うのは適切ではなくなった(ナイス変更!)
  • //= link_directory ../javascripts .jsでJavaScriptをデフォルトで再追加することでリンクするのを廃止
    • (これはデフォルトにしたい気がするが、今はWebpackerが望ましいのでJSのテストのためだけに一応足しておいた)
  • アセットのデバッグモードが変わった
    同PRより大意

「ついでにこんな記事↓も見つけたんですが、Rails 6がSprockets 4.0に対応する前にSprocketsを使ってみて、上の修正と同じような箇所でハマったようです😇」「急ぎすぎ😆」「ついでにSprockets 3と4の違いについても追っていますね☺

⚓has_manyのeager loadingのエッジケースを修正

eager loadingの実行結果(のレコード)は重複解除される。
これはhas_manyのeager loadではできているが、できていない組み合わせのケースがあった。
同PRより大意

# activerecord/lib/active_record/relation/finder_methods.rb#L381
      def apply_join_dependency(eager_loading: group_values.empty?)
        join_dependency = construct_join_dependency(
          eager_load_values + includes_values, Arel::Nodes::OuterJoin
        )
        relation = except(:includes, :eager_load, :preload).joins!(join_dependency)

-       if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
+       reflections = join_dependency.reflections + joins_values.map { |joins_value| reflect_on_association(joins_value) }.reject(&:blank?)
+       if eager_loading && !using_limitable_reflections?(reflections)
          if has_limit_or_offset?
            limited_ids = limited_ids_for(relation)
            limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
          end
          relation.limit_value = relation.offset_value = nil
        end
        if block_given?
          yield relation, join_dependency
        else
          relation
        end
      end
# activerecord/test/cases/finder_test.rb#1339
  def test_eager_load_for_no_has_many_with_limit_and_joins_for_has_many
    relation = Post.eager_load(:author).joins(:comments)
    assert_equal 5, relation.to_a.size
    assert_equal relation.limit(5).to_a.size, relation.to_a.size
  end

つっつきボイス:「またhas_many😆」「まただ〜😆」「SQLの結果では複数行になるけどinstantiateするときに1つになるのが本来で、エッジケースでそうならないバグがあったということか: テストコード↑と#37356の再現手順↓を見る方がわかりやすいかも🤔

# #37356より
class Post < ActiveRecord::Base
  has_many :comments
  has_one :author
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

class Author < ActiveRecord::Base
    belongs_to :post
end

class BugTest < Minitest::Test
  def test_in_batches_corner_case
    posts = 5.times.map do
      post = Post.create!
      post.comments << Comment.create!
      post
    end
    multiple_comments = posts[2]
    multiple_comments.comments << Comment.create!

    post_count = 0
    unbatched_query = Post.eager_load(:author).joins(:comments)
    unbatched_query.find_each(batch_size: 2) do |post|
      post_count += 1
    end
    assert_equal unbatched_query.to_a.count, post_count
  end
end

「eager loadingってこれですね↓」「deduplicateって口で言うの大変😅」「デデュプリケート😆

参考: eager loadingって何? - おもしろwebサービス開発日記

参考: ActiveRecordのincludes, preload, eager_load の個人的な使い分け | Money Forward Engineers’ Blog

Rails: JOINすべきかどうか、それが問題だ — #includesの振舞いを理解する(翻訳)

⚓キーが多数の場合のread_multi_entriesのパフォーマンスを改善

ActiveSupport::Cache::Store#read_multi_entriesを以下の3点について少々リファクタリングし、フェッチしたキーが増加したときのパフォーマンスを若干改善した。個人的には読みやすさも向上したと思う。

  • eacheach_with_objectに変更。これによってハッシュを冒頭で宣言して最後に返す必要がなくなった。
  • キャッシュエントリが見当たらない場合にローカル変数versionの算出を回避した。
  • 何もしない場合の条件を削除した。
    同PRより大意
================================== A few keys ==================================

Warming up --------------------------------------
          read_multi    12.831k i/100ms
     fast_read_multi    14.510k i/100ms
Calculating -------------------------------------
          read_multi    146.288k (±26.0%) i/s -    654.381k in   5.010593s
     fast_read_multi    172.428k (±25.9%) i/s -    783.540k in   5.023852s

Comparison:
     fast_read_multi:   172427.5 i/s
          read_multi:   146288.2 i/s - same-ish: difference falls within error


================================== Many keys ===================================

Warming up --------------------------------------
          read_multi   196.000  i/100ms
     fast_read_multi   279.000  i/100ms
Calculating -------------------------------------
          read_multi      1.984k (± 6.7%) i/s -      9.996k in   5.062818s
     fast_read_multi      2.823k (± 7.6%) i/s -     14.229k in   5.072824s

Comparison:
     fast_read_multi:     2823.1 i/s
          read_multi:     1984.3 i/s - 1.42x  slower
# activesupport/lib/active_support/cache.rb#L585
        def read_multi_entries(names, **options)
-         results = {}
-         names.each do |name|
-           key     = normalize_key(name, options)
+         names.each_with_object({}) do |name, results|
+           key   = normalize_key(name, options)
+           entry = read_entry(key, **options)
+
+           next unless entry
+
            version = normalize_version(name, options)
-           entry   = read_entry(key, **options)
-
-           if entry
-             if entry.expired?
-               delete_entry(key, **options)
-             elsif entry.mismatched?(version)
-               # Skip mismatched versions
-             else
-               results[name] = entry.value
-             end
+
+           if entry.expired?
+             delete_entry(key, **options)
+           elsif !entry.mismatched?(version)
+             results[name] = entry.value
            end
          end
-         results
        end

つっつきボイス:「割と読みやすいリファクタリングかなと思いました☺」「names.each do |name|names.each_with_object({}) do |name, results|に変えたことでresults = {}を書かなくてよくなったと、なるほど😋」「余分な条件も削除した」「そして1.42倍速くなって読みやすくなった🎉」「冒頭にresults = {}みたいな空の変数初期化を置くのって何となく悔しいですよね😆」「たしかに😆

⚓Rails

⚓Capistranoに対話処理を取り入れる(Hacklinesより)

# 同記事より
namespace :rails do
  desc "Start a rails console"
  task :console do
    exec_interactive("rails console")
  end

  desc "Start a rails dbconsole"
  task :dbconsole do
    exec_interactive("rails dbconsole")
  end

  def exec_interactive(command)
    host = primary(:web).hostname
    env = "RAILS_ENV=#{fetch(:rails_env)}" # add other ENV variables
    command = "cd #{release_path}; #{env} bundle exec #{command}"

    puts "Running command on #{host}:"
    puts "  #{command}\n\n"

    exec %(ssh #{host} -t "sh -c '#{command}'")
  end
end

つっつきボイス:「CapistranoはRuby製の自動化・デプロイツールでお馴染みですが、それに対話的処理を加えてみたという短い記事です」「Capistranoまだ使ったことなくて😅」「morimorihogeさんはCapistranoいいよって言ってました(ウォッチ20181210)」「お〜見てみます😋

「バッチでえいやする代わりに対話的にやりたいときもあるんでしょうか?」「デプロイってだいたいバッチでえいやが多い気もしますけど😆

「Rubyのは知りませんが、デプロイツールはいろいろ使いました☺」「ちなみにどんなのをお使いでした?」「古いところではmavenとか↓」「おぉ知りませんでした😳」「Apache Antもそうかなと思ったらこっちはビルドツールだった😆」「どちらもJava方面なんですね」「何しろ昔からあるので古いといえば古いかな〜👴」「mavenって辞書見ると『物知り、専門家、達人、玄人、通、目利き、大御所』とか強そうな意味が並んでる😆

参考: Apache Maven - Wikipedia


maven.apache.orgより

参考: Apache Ant - Wikipedia


ant.apache.orgより

⚓Railsのビューでstrftimeを直書きするのはたぶん間違い

# 同記事より: config/initializers/time_formats.rb
Date::DATE_FORMATS[:stamp] = "%Y%m%d" # YYYYMMDD
Time::DATE_FORMATS[:stamp] = "%Y%m%d%H%M%S" # YYYYMMDDHHMMSS

つっつきボイス:「この記事ではstrftimeをビューに直書きする代わりに、書式をグローバル定数に置いてるみたいなんですけど、それもどうなんだろうと思って」「お、最初自分もグローバル定数かと思ったけど、このDATE_FORMATSはどうやらRails組み込みの機能↓のようで、そこに:stampという独自の書式を追加していますね」「あ、そうでしたか😅」「グローバル定数で書くのは止めた方がいいけど、機能としてあるなら使っていいと思います😋

DATE_FORMATS    =   { short: "%d %b", long: "%B %d, %Y", db: "%Y-%m-%d", number: "%Y%m%d", long_ordinal: lambda { |date| day_format = ActiveSupport::Inflector.ordinalize(date.day) date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" }, rfc822: "%d %b %Y", iso8601: lambda { |date| date.iso8601 } }
DATE_FORMATS    =   { db: "%Y-%m-%d %H:%M:%S", number: "%Y%m%d%H%M%S", nsec: "%Y%m%d%H%M%S%9N", usec: "%Y%m%d%H%M%S%6N", time: "%H:%M", short: "%d %b %H:%M", long: "%B %d, %Y %H:%M", long_ordinal: lambda { |time| day_format = ActiveSupport::Inflector.ordinalize(time.day) time.strftime("%B #{day_format}, %Y %H:%M") }, rfc822: lambda { |time| offset_format = time.formatted_offset(false) time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") }, iso8601: lambda { |time| time.iso8601 } }

参考: Railsで日付/時刻のフォーマットを設定するTips - Rails Webook
参考: RailsのTime::DATE_FORMATS[:default]は変更しないほうがいい - Qiita

↑2番目の記事ではI18nの機能を使う方法も紹介されています。

⚓スクリーンキャスト: 13分でわかるRails tipsいろいろ(Ruby Weeklyより)


つっつきボイス:「Railsのちょっとした便利ワザ集なんですが、スクリーンキャストって見ます?」「いや〜自分は見ませんけど☺」(少し流し見)

「これが10分あまり続くんですね😅」「まあ10分ならまだ短い方かも: 1時間とかかかるのもざらにありますし😆

「ところでparameterってプラミターとかプウァミターみたいに発音してるんですね😳」「割とそんな感じですね: 「ラ」を強く言うのがポイント」「果物のプラムかと思っちゃいました😅」「あとSQLもあっちの人はだいたいスィークォー(sequel)と言ってます😆」「エスキューエルって言うのは日本人ぐらい?」「たまに言う気もしますけどsequel多いですね☺

なお一般的な意味のsequelは「(ドラマなどの)続編」です。


「スクリーンキャストで思い出したんですけど、昔大学の先輩がライブコーディングをやってみせたときになかなかうまくいかなくて、あれはある意味特殊な才能が必要なんじゃないかって話になりました😆」「あ〜たしかに」「本番でいろいろハプニングが起きうることを見越してライブコーディングするのはホント大変😭」「録画を流す方がまだやりやすいかも: 前にも紹介しましたけど、jnchitoさんは銀座Railsのときに録画を倍速再生して見せてくれました↓」「お〜」

⚓Shrine 3.0がリリース(Awesome Rubyより)


shrinerb.comより

もう3.0.1になっていますね。
コアの再設計以外はほとんどがプラグイン関連の変更のようです。

  • Shrine::Attacherを再設計
  • versionsプラグインを書き直し
  • mirroringプラグインを追加
  • ROMやHanamiとの統合を強化

つっつきボイス:「お、Shrineのアップグレードって前にもつっつきで見たような覚えありますね」「あ、アップグレード予告を取り扱ったような😅

↓こちらでした。

週刊Railsウォッチ(20190902)Ruby 2.6.4セキュリティ修正リリース、スライド「All About Ruby in 2019」、Shrine gem 3.0に入る新機能ほか

「ともあれShrineが3.0でだいぶ強力になったようです🎉」「サイトデザインが新しくなったという見出しがトップ😆」「Attacherというクラスの設計をActive Recordから切り離して他でも使えるようになったり、プラグインを追加更新廃止したり」「ところでなぜShrineという名前なんでしょうね😆」「どの辺が神社仏閣なのかわかりませんし😆

# 同記事より: 単独でも使えるように設計見直し
attacher = ImageUploader::Attacher.new
attacher.attach(file)
attacher.file #=> #<Shrine::UploadedFile>
attacher.url  #=> "https://my-bucket.s3.amazonaws.com/path/to/image.jpg"

shrine: 遺骨・遺物をおさめた箱、櫃(ヒツ)が原義


「Algolia?」「ShrineのサイトがAlgoliaで検索できるようになったと記事にありますね↓: AlgoliaについてはTechRachoにも記事がありますのでどうぞ😋

Rails: 高速リアルタイム検索API「algolia-search-rails」gem README(翻訳)

⚓その他Rails

⚓Ruby

⚓Minitestスタイルガイドが登場(Ruby Weeklyより)


minitest.rubystyle.guideより


つっつきボイス:「rubocopチームが作ってくれたそうですが、シンプルなのがいいなと思って😋」「たしかにあれっと思うぐらい短いですね」「RSpecのスタイルガイドは@willnetさんが日本語にしてくれています↓が、これと比べても短い」

参考: willnet/rspec-style-guide: 可読性の高いテストコードを書くためのお作法集

なお以下は中の人Batsovさんのブログです。rubocop-minitestは進行中のようです。

私はRSpecが大好きですが、Minitestのシンプルな設計にも大いに敬意を払ってます。
私たちRuboCopチームが現在rubocop-minitestに取り組み中であることをお知らせします。
同記事より大意

⚓active_hash: ハッシュをActive Recordモデルっぽくリードオンリーアクセス

ruby-jp Slackで見かけました。


つっつきボイス:「RubyのハッシュをActive Recordみたいにするgemで★700超えてますが、どんな人が使うのかな?🤔

こちらの記事では、都道府県のように更新されないデータをactive_hashで扱ってますね↓。

参考: active_hash[gem]でデータの入ったテーブル作成 - Qiita

ActiveHash::Baseを継承すると、ハッシュのキーがメソッドで生えてくるという」

# 同リポジトリより
class Country < ActiveHash::Base
  self.data = [
    {:id => 1, :name => "US"},
    {:id => 2, :name => "Canada"}
  ]
end

country = Country.new(:name => "Mexico")
country.name  # => "Mexico"
country.name? # => true

「クラスメソッドやインスタンスメソッドもいかにもActive Record風」「おほ、なるほどなるほど☺

# クラスメソッド
Country.all                    # => returns all Country objects
Country.count                  # => returns the length of the .data array
Country.first                  # => returns the first country object
Country.last                   # => returns the last country object
Country.find 1                 # => returns the first country object with that id
Country.find [1,2]             # => returns all Country objects with ids in the array
Country.find :all              # => same as .all
Country.find :all, args        # => the second argument is totally ignored, but allows it to play nicely with AR
Country.find_by_id 1           # => find the first object that matches the id
Country.find_by(name: 'US')    # => returns the first country object with specified argument
Country.find_by!(name: 'US')   # => same as find_by, but raise exception when not found
Country.where(name: 'US')      # => returns all records with name: 'US'
Country.where.not(name: 'US')  # => returns all records without name: 'US'
# インスタンスメソッド
Country#id          # => returns the id or nil
Country#id=         # => sets the id attribute
Country#quoted_id   # => returns the numeric id
Country#to_param    # => returns the id as a string
Country#new_record? # => returns true if is not part of Country.all, false otherwise
Country#readonly?   # => true
Country#hash        # => the hash of the id (or the hash of nil)
Country#eql?        # => compares type and id, returns false if id is nil

# 以下はメタプロで生える
Country#name        # => returns the passed in name
Country#name?       # => returns true if the name is not blank
Country#name=       # => sets the name

「こういう書き方ってしたい方ですか?」「いやぁ〜、今見たときはふむふむと思いましたけど、このgemが入ってきたらまた書き方覚えないといけないのかなって😅」「まあ業務コードに入れるなら確認取ってからでしょうね😆

「Rubyだとハッシュのキーを.で呼べないのが悲しいって言う人をそこそこ見かけたりしますけど、Ruby自身にこういうのは入れないのかな?🤔」「どうでしょう😆、求められているなら入るかもしれませんけど、私は.でハッシュキー呼べなくても別にいいじゃんって思いますし☺

後で探すと、frozen_recordという少し似た感じのgemがありました。

⚓Ruby 2.7のArrayにintersectionuniondifferenceが追加(RubyFlowより)

# 同記事より
[ "a", "b", "z" ].intersection([ "a", "b", "c" ], [ "b" ])  # => [ "b" ]
[ "a", "b", "c" ].union( [ "c", "d", "a" ] ) #=> [ "a", "b", "c", "d" ]
[ 1, 4, 7, 8, "a", :t ].difference([ 4, :t ]) #=> [ 1, 7, 8, "a" ]

つっつきボイス:「Ruby 2.7のArrayに集合論っぽいメソッドが入るそうです」「RubyのSet的なことがArrayでもできるようになったと🤔」「しょっちゅうは使わなくても、たまに欲しくなりそう😆

参考: class Set (Ruby 2.6.0)

⚓parallel: Rubyで並列処理(Ruby Weeklyより)

# 同記事より
# 2 CPUs -> work in 2 processes (a,b + c)
results = Parallel.map(['a','b','c']) do |one_letter|
  expensive_calculation(one_letter)
end

# 3 Processes -> finished after 1 run
results = Parallel.map(['a','b','c'], in_processes: 3) { |one_letter| ... }

# 3 Threads -> finished after 1 run
results = Parallel.map(['a','b','c'], in_threads: 3) { |one_letter| ... }

つっつきボイス:「parallelっていうgemは見たことありそうでなかったので」「このgemは前からありそうな雰囲気ですけど既に標準だったりしません?」「標準ライブラリのリストにはありませんでした」

後で調べると、parallel gemは2009年からありました。

「Rubyで並列的なことをやる方法って他にもいろいろありそうですけど?」「標準ライブラリにThreadとかFiberとかありますね↓😋」「なるほど」

参考: class Thread (Ruby 2.6.0)
参考: class Fiber (Ruby 2.6.0)

「そういえばちょっと前のQuoraで『現状のThreadは直したい』というMatzからの回答↓があったのを思い出しました」「並列系って自分もそもそもほとんどやってこなかったし、ちゃんと勉強してからやらないときっとハマりそう😭」「私もGo言語のgoroutimeまだ自分ではやれてないです😅

参考: Rubyを作り直すとしたら、変更を加える箇所はありますか? - Quora

「Active Recordでもやれるようです↓」「これでテストを速くできるならいいかも😋」「parallel gemのコントリビューターもめちゃ多いですね😳

# reproducibly fixes things (spec/cases/map_with_ar.rb)
Parallel.each(User.all, in_processes: 8) do |user|
  user.update_attribute(:some_attribute, some_value)
end
User.connection.reconnect!

# maybe helps: explicitly use connection pool
Parallel.each(User.all, in_threads: 8) do |user|
  ActiveRecord::Base.connection_pool.with_connection do
    user.update_attribute(:some_attribute, some_value)
  end
end

# maybe helps: reconnect once inside every fork
Parallel.each(User.all, in_processes: 8) do |user|
  @reconnected ||= User.connection.reconnect! || true
  user.update_attribute(:some_attribute, some_value)
end

後で調べると、同じ作者のparallel_testsというgem↓はだいぶ前にウォッチで取り上げたことがありました(ウォッチ20171117)。gemspecでもparallel gemがruntime dependencyになっています。

⚓その他Ruby



つっつきボイス:「川合史朗さんはScheme系言語のGaucheの作者ですね」「ガウシュ?ゴーシュ?😆」「セロ弾きのゴーシュとスペル同じですしゴーシュなのかも🤔」「この方はハワイ在住で俳優もやっていて、いくつか映画にも出演してるそうです🏝」「ソフトウェア開発者で俳優…なんと幸せな人生😊

参考: Gauche - Wikipedia
参考: 17. Gauche Schemeの基本デザインの選択理由、オブジェクトデータベース、浮動小数点数の落とし穴 (川合史朗) — 映画出演の話にも触れられています

⚓DB

⚓Oracleから独自RDBへ


つっつきボイス:「Oracleから自社製RDBMSへの乗り換えニュースを2つ連続で見かけました」「これスゴいですよね〜、自社製のRDBMSがどんなものか触ってみたい気がしますけど」「お、アリババの方はソース公開されてるって記事にありますね😳」「お〜、このoceanbaseというのがそれですか↓: 誰か動かしてみないかな😋」「Amazonの方は名前もわからず😢

ちなみにoceanbaseのドキュメントは中国語かつdocxのようです😭

「oceanbaseについて探してたら、dbdb.io↓というサイトが出てきた」「いろんなRDBMSを一覧できるカタログサイトのようですね☺


dbdb.ioより


以下はつっつき後に見つけました。

参考: 世界1位になったアリババの独自開発DB OceanBaseとは何者か? - ブログなんだよもん

⚓言語・ツール

⚓言語人気の移り変わり


つっつきボイス:「こういうのって単純に楽しいですね😊」「どこ由来のデータかは知りませんけど😆」「大昔はFortranとCobolが人気だったまではわかるんですが、Pascalが1位だった時代ってあったんですか?」「Mac OS 9までのMacだとTurbo Pascalが割と多かった気がします」「そうこうしているうちにC言語トップになって2000年過ぎたあたりからJavaが1位に」「PHP元気だな〜😆」「Rubyもトップテンに」「直近はPythonがトップ」

参考: Turbo Pascal - Wikipedia

⚓その他

⚓キーボードの方がでかい


つっつきボイス:「ぱっと見Wi-Fiアンテナ付きダムハブみたいですけど😆」「あ、これPCですか😳: このスペック(Core i7-8665U 1.9GHz、メモリ16GB)で18万…やばい猛烈に欲しくなってきた❤」「ハートに突き刺さる音が聞こえました😆」「NASつないじゃえば容量問題にならないし、う〜こっち買えばよかったか😭」「もう少しスペック下でもいいから軽くて安いのがあればDocker専用機にして持ち歩きたい😆」(以下延々)

⚓番外

⚓自然言語も移り変わる


つっつきボイス:「最後は言語学ネタで😆: 羽柴秀吉がファシンバ・フィンデヨシだったとか」「何を基準にしてたのかしら😅」「安土桃山時代のポルトガル人宣教師たちが作った日本語-ポルトガル語辞書で当時の発音がキャプチャされてたそうで、は行がɸ音(ph)だったとかかなり違ってますね」「ほぇ〜😳」「ついでに、日本語の濁点や半濁点も、その宣教師たちが必要に迫られてこしらえたのが後に日本語でも定着しちゃったんだそうです」「逆輸入😆

参考: 日葡辞書 - Wikipedia

「そうなるとポルトガル人の発音も今と昔で違ってるでしょうから、そういうフィルタの影響を取り除いて研究するのは大変そう😆」「たしかに😆」「日本人もかつてイギリスをエゲレスとかアメリカンをメリケンとか言ってましたし、外人が日本人の名前を呼ぶときに訛るのと似てるようにも見えますけどね😆」「英語圏の人は『こんにちは』を『コニーチワ』って発音しがちですけど、その辺に通じるかも」「n音が連続するとつながるんでしょうね☺」(以下延々)

江戸時代の長崎出島の通詞(=通訳)が幕末にオランダに渡ったとき、現地の人から「まるでうちのおじいさんが話しているようなオランダ語だ」と妙に感激されたという話を思い出しました。鎖国のせいでタイムカプセル的に保存されてたんでしょうね。

「自然言語もこんなに変わるんだからプログラミング言語も長年の間にがらっと変わったりするかなと」「いやいやプログラミング言語は発音関係ありませんから😆


今回は以上です。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191015)スライド「Rails Performance issues and Solutions」を見る、dirtyに*_previously_was が追加、Sidekiq 6.0.1ほか

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

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

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Publickey

publickey_banner_captured

Ruby 2.7.0-preview2がリリース

$
0
0

10/22に2.7.0-preview2がリリースされました🎉
機能の多くは週刊Railsウォッチでも報じてきましたが、改めて覗いてみました。「Experimental」と銘打たれた機能が割と目に付きます。誤りがありましたら@hachi8833までお知らせください🙇

Compaction GC

GC.compactでヒープを明示的に圧縮できます。

irb(main):006:0> GC.compact
=> {:considered=>{:T_NONE=>5736, :T_OBJECT=>258, :T_CLASS=>197, :T_MODULE=>23, :T_FLOAT=>0, :T_STRING=>5605, :T_REGEXP=>131, :T_ARRAY=>760, :T_HASH=>61, :T_STRUCT=>13, :T_BIGNUM=>0, :T_FILE=>0, :T_DATA=>177, :T_MATCH=>0, :T_COMPLEX=>0, :T_RATIONAL=>0, 16=>0, :T_NIL=>0, :T_TRUE=>0, :T_FALSE=>0, :T_SYMBOL=>8, :T_FIXNUM=>0, :T_UNDEF=>0, 23=>0, 24=>0, 25=>0, :T_IMEMO=>3979, :T_NODE=>0, :T_ICLASS=>30, :T_ZOMBIE=>0, :T_MOVED=>0}, :moved=>{:T_NONE=>0, :T_OBJECT=>258, :T_CLASS=>197, :T_MODULE=>23, :T_FLOAT=>0, :T_STRING=>5605, :T_REGEXP=>131, :T_ARRAY=>760, :T_HASH=>61, :T_STRUCT=>13, :T_BIGNUM=>0, :T_FILE=>0, :T_DATA=>177, :T_MATCH=>0, :T_COMPLEX=>0, :T_RATIONAL=>0, 16=>0, :T_NIL=>0, :T_TRUE=>0, :T_FALSE=>0, :T_SYMBOL=>0, :T_FIXNUM=>0, :T_UNDEF=>0, 23=>0, 24=>0, 25=>0, :T_IMEMO=>3979, :T_NODE=>0, :T_ICLASS=>30, :T_ZOMBIE=>0, :T_MOVED=>0}}

GC.compactは以下を実行します。

  1. フルGC
  2. オブジェクトを移動
  3. 参照更新
  4. フルGC

パターンマッチング(Experimental)

case構文でinを使えるようになりました。関数型言語で広く用いられている手法です。現在はwarningが表示されます。

case {a: 0, b: 1}
in {a: 0, x: 1}
  :unreachable
in {a: 0, b: var}
  p var #=> 1
end

# warning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!

REPL(irb)の改良

これも既に有名ですね。色付けやオートインデント、Tabキー2度押しでAPIドキュメント表示、履歴をブロック単位で遡る機能などが追加されました。irbを終了しても履歴が保存されるようになったのがかなりありがたいです😂

positional引数とキーワード引数の分離

Ruby 3.0でのbreaking changeに備え、Ruby 2.7で各種warningを表示します。まだ自分の中で未消化なので別記事にしようと思います🙇

参考: Ruby 2.7 deprecates automatic conversion from a hash to keyword arguments – Saeloun Blog

パフォーマンス改善

  • JITの改善(Experimental)
  • 以下の結果はfrozen stringになる
# シンボルのto_s
:symbol.to_s.frozen?  #=> true

# Module.name
module Bar ;end
Bar.name.frozen?      #=> true

# trueやfalseのto_s
true.to_s.frozen?     #=> true
false.to_s.frozen?    #=> true

その他追加された機能追加

メソッド参照演算子.:の追加(Experimental)

#method(:メソッド名)と同等です。メソッドを呼び出すのではなく、Methodオブジェクトを返します。

[1,2,3].map do |x| Math.method(:sqrt) end
# 上と下は同等
[1,2,3].map do |x| Math.:sqrt end

#=> [#<Method: Math.sqrt>, #<Method: Math.sqrt>, #<Method: Math.sqrt>]

これを用いて、ブロックとmethodなしで以下のように短く書けます。

[1,2,3].map(&Math.:sqrt)
#=> [1.0, 1.4142135623730951, 1.7320508075688772]

warningは表示されません。

numbered parameterの導入(Experimental)

デフォルトのブロックパラメータをショートハンドで表せます。表記法について議論の末、_1_2といった表記に落ち着いたようです。

[1, 2, 10].map do |i| i.to_s(16) end
# 上と下は同等
[1, 2, 10].map do _1.to_s(16) end

#=> ["1", "2", "a"]

warningは表示されません。

range ..の開始値の省略記法(Experimental)

Ruby 2.6で..の終了値の省略記法が導入されたのに続き、開始値も省略可能になりました。さすがに開始値と終了値を両方省略すると構文エラーになりました。

ary = %w(1, 2, 3, 4)
ary[..3]    # ary[0..3]と同等
#=> ["1,", "2,", "3,", "4"]

warningは表示されません。

Enumerable#tallyの追加

要素ごとの個数をハッシュで得られます。当初は#count_byという名前でした。

["a", "b", "c", "b"].tally
#=> {"a"=>1, "b"=>2, "c"=>1}

tally: (競技の)得点

レシーバーにselfを指定してprivateメソッドを呼べるようになった

インスタンスメソッドからprivateメソッドを呼び出すときにレシーバーにselfを指定してもエラーにならなくなりました。

# 従来
class Foo
  def foo
    self.baz  # レシーバーは指定できないのでエラー
  end
  def bar
    baz       # 呼べる
  end

  private

  def baz
    42
  end
end
Foo.new.foo
#=> NoMethodError (private method `bar' called for #<Foo:0x000055b4b7980258>)
Foo.new.bar
#=> 42
# 2.7
class Foo
  def foo
    self.baz  # selfならレシーバーとして指定できるので呼べる
  end
  def bar
    baz       # 呼べる
  end

  private

  def baz
    42
  end
end
Foo.new.foo #=> 42
Foo.new.bar #=> 42

従来のprivateメソッドは「関数形式でしか呼び出せない」つまり「呼び出しでレシーバーを指定できない」という概念でしたが、「呼び出しでself以外のレシーバーを指定できない」みたいに変わることになりそうですね。

参考: クラス/メソッドの定義 (Ruby 2.6.0)
参考: [Ruby] privateメソッドの本質とそれを理解するメリット - Qiita

Enumerator::Lazy#eagerを追加

Enumerator::LazyからEnumeratorを生成します。

a = %w(foo bar baz)
e = a.lazy.map {|x| x.upcase }.map {|x| x + "!" }
e.class #=> Enumerator::Lazy

e = a.lazy.map {|x| x.upcase }.map {|x| x + "!" }.eager
e.class #=> Enumerator

メソッドに渡されたブロックを(ブロックなしの)Proc.newprocで受けるとwarningを表示

これができていたこと自体初めて知りました😳

def foo
  proc.call # またはProc.new.call
end
foo { puts "Hello" }
#=> warning: Capturing the given block using Proc.new is deprecated; use `&block` instead
#=> Hello

メッセージにあるようにブロック引数(&blockなど)か、yieldで従来どおりに受けられます。

# 従来どおり
def foo(&block)
  block.call
end
foo { puts "Hello" }  #=> Hello

def foo
  yield
end
foo { puts "Hello" }  #=> Hello

メソッドに渡されたブロックを(ブロックなしの)lambdaで受けるとArgumentErrorを表示

def foo
  lambda.call
end
foo { puts "Hello" }
#=> ArgumentError (tried to create Proc object without a block)

従来は以下のwarningが表示されていましたが、2.7で完全に禁止になります。

# warning: tried to create Proc object without a block
#=> Hello

Unicode v12の絵文字対応

Unicode version 12で追加された大量の絵文字に対応しました。Ruby 2.6系も対象になるとあります。

参考: Emoji List, v12.0 — 1,719個あります。

令和対応

なお#15195は2.6.3以降にも導入済みです。

"\u32FF"
#=> "㋿"

Date.new(2019, 5, 1).jisx0301
#=> "R01.05.01"

コンパイラをC99必須に、C90サポートを廃止

参考: C99 - Ruby master - Ruby Issue Tracking System

関連記事

Ruby2.5.xのパラメータの制約についてまとめてみた

Ruby 2.4〜の`#clamp`で最大・最小値をまとめて制約(翻訳)

$
0
0

概要

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

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

Ruby 2.4〜の#clampで最大・最小値をまとめて制約(翻訳)

以前私が書いたValue Object記事initializeメソッドでは、整数に最大値や最小値の制約をかける例を扱いました。Ruby 2.4から、こういうときに便利な#clampメソッドが導入されました。ドキュメントによるとComparableモジュールにあります。

以下のように書くよりも

Array#minArray#maxで値の範囲に制約をかける。

value = 1000
[[0, value].max], 255].min
#=> 255

value = -100
[[0, value].max], 255].min
#=> 0

以下のように書こう

Rubyの#clampメソッドを使う。

value = 1000
value.clamp(0, 255)
#=> 255

value = -100
value.clamp(0, 255)
#=> 0

そう書く理由

Comparableモジュールは応用範囲の広いエレガントな標準ライブラリです。

私が解決しようとしてた問題にどんぴしゃりはまった以上、これより冗長な書き方をする必要があるでしょうか?

そう書かない理由があるとすれば

#clampメソッドを使わない理由はありません。[ ]をネストした従来の紛らわしい書き方がお好きなら別ですが。

謝意

Ruby 2.4より前の私のコードについて指摘いただいたJustinに感謝します。

関連記事

Ruby 2.7.0-preview2がリリース

週刊Railsウォッチ(20191028前編)RailsにSTI用メソッドsti_class_forとpolymorphic_class_forが追加、RuboCopを変更箇所だけにかけるgem、strftime書式生成サイトほか

$
0
0

こんにちは、hachi8833です。Blawn言語が盛り上がってますね。


つっつきボイス:「15歳でプログラミング言語を作ったという話題で、早速Qiitaにもやってみた記事が出てました」「しかもC++で😳」「少し追ってみたところでは、生まれたての言語らしく小さなバグがちょこちょこある様子で、今後を見守っていきたい感じですね☺」「言語を作る人が増えてきっとMatzは内心とても嬉しかったと思います😋

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は第一木曜日ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンスです!発言も自由です。皆さまのお気軽なご参加をお待ちしております🙇

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

今回はmasterのコミットリストから見繕いました。

⚓ActiveSupport::SafeBufferが6.0で動かない問題を修正

# activesupport/lib/active_support/core_ext/string/output_safety.rb#L298
      def set_block_back_references(block, match_data)
        block.binding.eval("proc { |m| $~ = m }").call(match_data)
+     rescue ArgumentError
+       # Can't create binding from C level Proc
      end

#34405with_indexメソッドと互換性がないらしい。
37422より大意

# #37422より
ActiveSupport::SafeBuffer.new('aaa').gsub!(/a/).with_index {|m,i| i }

# 期待する出力: 012
# 実際の出力: ArgumentError

つっつきボイス:「このwith_indexは、each_with_indexとまた別のメソッドでしたね」「こんなメソッドがあったんですね😳」「indexのオフセットも指定できるけど指定しないとゼロから始まるとかそんな感じで☺」「Enumeratorでインデックスを使いたいときに便利そうですね☺」「修正はrescue ArgumentErrorを追加して終了」

参考: instance method Enumerator#with_index (Ruby 2.6.0)
参考: with_indexが便利だという話とstable_sort_by - Qiita

⚓新機能: STI用メソッドをActiveRecord::Inheritanceのpublic APIに追加

追加したメソッドを使ってSTIやポリモーフィック関連付けをextendできる。
クラスをリネームしてクラス名がデータベース内のデータとマッチしなくなったときに有用。
自分のモデルに以下のメソッドを実装すると、既に存在しなくなったクラスの名前でレコードを読み込めるようになる。以下はシンプルな実装例。
同PRより

# 同PRより
class Animal < ActiveRecord::Base
  @@old_names = {
    "Lion" => "BigCat"
  }
  def self.sti_name
    name = super
    @@old_names[name] || name
  end

  def self.sti_class_for(type_name)
    @@old_names.inverse[type_name]&.constantize || super
  end
end

つっつきボイス:「STI関連の機能追加だそうです」「extendできるとはエグい😆」「STIでクラス名が変わっても読み込めると: テーブルは変わらない前提ですねなるほど ☺

参考: 5 シングルテーブル継承 (STI)– Active Record の関連付け - Rails ガイド

「上の実装例を見るとsti_class_forというAPIが追加されたようなので、例でやっているように継承して使うという意図でしょうね☺」「self.sti_nameもオーバーライドしておく必要があるのか、へ〜」「sti_nameは前からあったみたい」「これが追加されたメソッドのコード↓」

# activerecord/lib/active_record/inheritance.rb#173
+     # 指定されたtype_nameに対応するクラスを返す
+     #
+     # 継承カラムに保存された値に対応するクラスを探索するのに使われる
+     def sti_class_for(type_name)
+       if store_full_sti_class
+         ActiveSupport::Dependencies.constantize(type_name)
+       else
+         compute_type(type_name)
+       end
+     rescue NameError
+       raise SubclassNotFound,
+         "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
+         "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
+         "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
+         "or overwrite #{name}.inheritance_column to use another column for that information."
+     end

「実装例のLionBigCat、どっちがoldでどっちがnewだろう?🤔」「これだけだとわかりにくいけど、たぶんLionがnewでBigCatがoldなのかな〜」「constantizeは文字列からクラスやモジュールを生成するメソッドでしたね」

参考: constantize — ActiveSupport::Inflector

「なるほど、こう書けばクラス名が変わった場合にデータベースに入っている前のクラス名をアップデートしなくてもよくなると」「さすがに変えてからそのまま放置はしないでしょうね😆」「それやったらカオス確定😆: リネーム前のクラスが存在しなくなってるから後でコード読んだ人がパニクるし😇」「たとえば、いったんこの書き方でしばらく運用して、うまくいくようなら本格的にクラス名を移行するという使い方ができそうですね☺」「なるほど!」

「あるいは、クラスからクラス名を取れるAPIが前からあるなら、その逆にクラス名からクラスを取れるAPIもあった方がいいよね、という発想で作られたのかもしれないと今思いました☺」「そういう見方もあるのか😳」「コードのコメントに『このconstantizeはZeitwerkとコンパチなの?』『大丈夫、decorateしてある』というやり取りもありますね」

「これが2つ目に追加されたAPI↓: polymorphic_class_for

# activerecord/lib/active_record/inheritance.rb#L192
+     # 指定されたnameに対応するクラスを返す
+     #
+     # ポリモーフィックtypeカラムに保存された値に対応するクラスを探索するのに使われる
+     def polymorphic_class_for(name)
+       name.constantize
+     end

「そして既存のfind_sti_classsti_class_forを使う形に変わっている↓」「つかsti_class_forに切り出して委譲した形か」「たしかにfind_sti_classはオーバーライドしたくないし😆」「切り出したことで柔軟になった感じですね😋

# activerecord/lib/active_record/inheritance.rb#L251
        def find_sti_class(type_name)
          type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
-         subclass = begin
-           if store_full_sti_class
-             ActiveSupport::Dependencies.constantize(type_name)
-           else
-             compute_type(type_name)
-           end
-         rescue NameError
-           raise SubclassNotFound,
-             "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
-             "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
-             "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
-             "or overwrite #{name}.inheritance_column to use another column for that information."
-         end
+         subclass = sti_class_for(type_name)
+
          unless subclass == self || descendants.include?(subclass)
            raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
          end
+
          subclass
        end

⚓Active Storageのコンパイル済みJSをソースと同期

# activestorage/test/javascript_package_test.rb
+# frozen_string_literal: true
+
+require "test_helper"
+
+class JavascriptPackageTest < ActiveSupport::TestCase
+  def test_compiled_code_is_in_sync_with_source_code
+    compiled_file = File.expand_path("../app/assets/javascripts/activestorage.js", __dir__)
+
+    assert_no_changes -> { File.read(compiled_file) } do
+      system "yarn build"
+    end
+  end
+end

Active Storageのコンパイル済みJSがソースコードと同期しない問題を追った。
これを修正するためにActive Storageのコンパイル済みJSバンドルとソースを強制的に同期するようにした。同期していればテストはパスし、していなければfailする。
同PRより大意


つっつきボイス:「これはテストコードの割と小さな修正ですね☺

⚓ActiveModel::Error訳文参照でインデックス付き属性探索をサポート

このPRの意図は、ActiveModel::Errorのインスタンスでattributeがインデックス付きattributeの場合にインデックス付きsuffixなしでメッセージを探索できるようにすること。これはindex_errors: trueを使うActiveRecordモデルによくある。
#37447より大意

# 同PRより
class Manager < ActiveRecord::Base
  has_many :reports, index_errors: true
end

class Report < ActiveRecord::Base
  belongs_to :manager
  validates_presence_of :name
end

manager = Manager.new
invalid_report = Report.new
manager.reports = [invalid_report]
manager.save

error = manager.errors.first
error.attribute
# => :"reports[0].name"
error.type
# => :presence

上のようにattributeがインデックス化されているとする。これはReportレコードの場合はよいが、カスタム訳文メッセージの場合にはインデックスがあるためにうまくいかない。このPRでは探索キーからインデックスを削除する探索エントリを追加することで、インデックスなしで訳文を探索できるようになる。
#37447より大意


つっつきボイス:「エラーのI18n関連の修正だそうで、2つPRがあります」「Reportだとreports[0]みたいなメッセージ参照が生成されるけど、カスタムメッセージのときにインデックスが邪魔だったのね」「1つめのPRの修正は以下のように[インデックス]を削除しただけでした↓」「お〜、attributeからインデックス指定を削るだけでやれちゃうのか😳」「これでインデックスを気にせず書けるようになる😋

attribute = attribute.to_s.remove(/\[\d\]/)

「で2つ目のPRではさらに上の修正の不足部分が修正されていました↓」「おっと+が漏れてた😆」「あやうく1桁インデックスしか処理できないところだった〜😆」「プルリクが2つ並んでたので気づきました☺

# activemodel/lib/active_model/error.rb#L20
-       attribute = attribute.remove(/\[\d\]/)
+       attribute = attribute.remove(/\[\d+\]/)

⚓関連付けリレーションでのインスタンス作成でunscopeが効くように修正

#35868の意図は、関連付けの読み込みを一貫させることにある。関連付けのリレーションでのインスタンス作成は副作用を伴うべきではない。
同PRより大意

上の#35868はウォッチ20191015でも話題にしました。

# activerecord/lib/active_record/association_relation.rb#L18
-   def build(*args, &block)
+   def build(attributes = nil, &block)
      block = _deprecated_scope_block("new", &block)
-     scoping { @association.build(*args, &block) }
+     @association.enable_scoping do
+       scoping { @association.build(attributes, &block) }
+     end
+   end
    alias new build

-   def create(*args, &block)
+   def create(attributes = nil, &block)
      block = _deprecated_scope_block("create", &block)
-     scoping { @association.create(*args, &block) }
+     @association.enable_scoping do
+       scoping { @association.create(attributes, &block) }
+     end
    end

-   def create!(*args, &block)
+   def create!(attributes = nil, &block)
      block = _deprecated_scope_block("create!", &block)
-     scoping { @association.create!(*args, &block) }
+     @association.enable_scoping do
+       scoping { @association.create!(attributes, &block) }
+     end
    end

つっつきボイス:「@kamipoさんによる修正です」「これだけだとわかりにくいので、issue #37138を見てみると、unscopeしたはずの関連付けでunscopeが無視されるという現象が起きてたそうです」「うひゃぁ😱」「そもそもunscope使うなと言いたいけど😆

期待される動作: new_comment = post.comments.unscope(where: :visible).newでデフォルトスコープがunscopeされるので、new_comment.visibleはfalseになる(そのフィールドのデータベースデフォルト値)
実際の動作: new_comment = post.comments.unscope(where: :visible).newのunscopeが効かず、default_scopeによってnew_comment.visibleがtrueになる

「以前訳した記事↓でも『default_scopeは使うな』というのがありました」「まあdefaultのに限らずunscopeしたくなることはまれになくもないんですけど、unscopeするぐらいならもう一回ビルドし直す方がいいんじゃね?って思いますし😆」「😆

Railsのdefault_scopeは使うな、絶対(翻訳)

「再現コードでdefault_scopeは…あるか↓😇」「出たな😆」「visibleをVisibleCommentみたいにラップしちゃうなら、そっちの方でdefault_scopeを使うのは自分は別にいいと思いますし」「ふぅむ🤔

# #37138より
class Comment < ActiveRecord::Base
  belongs_to :post
  default_scope -> { where(visible: true) }
end

「そして修正↓はというと、attributes = nil…?」「従来の*argsは実はattributesで、*は付いてたけどarrayじゃなくてhashを取るという前提だったらしい🤔

# activerecord/lib/active_record/association_relation.rb#L18
-   def build(*args, &block)
+   def build(attributes = nil, &block)
      block = _deprecated_scope_block("new", &block)
-     scoping { @association.build(*args, &block) }
+     @association.enable_scoping do
+       scoping { @association.build(attributes, &block) }
+     end
+   end
    alias new build

「元の*argsという引数名はふわっとしてて少々雑な感じではありますね😆」「引数名をattributesに変えつつ、何も渡されなかった場合に明示的にnilが渡るようにしたのか」「ここは意味は変わらないはずだからリファクタリングですね☺」「修正後はscoping { @association.create!(*args, &block) }enable_scoping↓のブロックで囲んでいて、これがキモかな」「難しいコードや😭」「さすが@kamipoさん」

# activerecord/lib/active_record/associations/association.rb#L46
+       @enable_scoping = false
...
+     def enable_scoping
+       @enable_scoping = true
+       yield
+     ensure
+       @enable_scoping = false
+     end

「以下がテストコード↓」「unscopeしてbuildしたらbulb.nameがnilになるのが正しいと」

# activerecord/test/cases/associations/has_many_associations_test.rb#241
+ def test_build_and_create_from_association_should_respect_unscope_over_default_scope
+   car = Car.create(name: "honda")
+
+   bulb = car.bulbs.unscope(where: :name).build
+   assert_nil bulb.name
+
+   bulb = car.bulbs.unscope(where: :name).create
+   assert_nil bulb.name
+
+   bulb = car.bulbs.unscope(where: :name).create!
+   assert_nil bulb.name
  end

「引数に*argsとか書くとレビューでツッコまれます?」「そうとも限らなくて、昔のRailsでは割と見かける書き方でしたし、引数を触らずに委譲したいコードだと*argsって書いたりしてましたね😋」「おぉ」

⚓Rails

⚓Rails 6のAction Cableテスト機能

# 同記事より
require "test_helper"

class PublishCommentaryJobTest < ActionCable::Channel::TestCase
  include ActiveJob::TestHelper

  # `assert_broadcast_on` asserts exact message sent on a channel stream.
  test "publishes commentary" do
    perform_enqueued_jobs do
      assert_broadcast_on(CommentaryChannel.broadcasting_for('match_1'), comment: "Hello and welcome everyone!!") do
        PublishCommentaryJob.perform_later(1, "Hello and welcome everyone!!")
      end
    end
  end

  # `assert_broadcasts` asserts the number of messages sent to stream
  test "asserts number of messages" do
    perform_enqueued_jobs do
      PublishCommentaryJob.perform_later(1, "Hello and welcome everyone!!")
      assert_broadcasts CommentaryChannel.broadcasting_for('match_1'), 1
    end
  end

  # `assert_no_broadcasts` asserts no messages sent to stream
  test "no comment published if invalid match id" do
    perform_enqueued_jobs do
      PublishCommentaryJob.perform_later(-1, "Hello and welcome everyone!!")
      assert_no_broadcasts CommentaryChannel.broadcasting_for('match_1')
    end
  end
end

つっつきボイス:「上の記事に出てくるaction-cable-testingはたしかEvil Martiansの人が作ったgemでした」「ほほぅ☺」「このgemはRails 5で入りそこなったけどRails 6でマージされたと以下の翻訳記事にあります😋

Rails 6のB面に隠れている地味にうれしい機能たち(翻訳)

「Action Cableのテスト書いたことないんで、そもそも書けるのか?ってちょっと思いましたけど😆」「テストを自力で書こうとすると難しそうですね🤔

  • 接続テスト
  • チャネルのテスト
  • ブロードキャストのテスト

参考: ActionCable::Connection::TestCase

⚓レガシープロジェクトでRuboCopを使う

抜粋:

  • .rubocop_todo.ymlを使わない方針で進める
  • 過去のコードよりも今後のコードチェックを重視
    • 古いコードベースをずっとそのままにするということではない
  • 古いコードベースで当面わずらわされないようにするgemの紹介

つっつきボイス:「レガシーコードになるべく触らずにコードチェックする系の記事で、以下のgemも紹介されています」

⚓Pront

「その中でprontoというgemは変更部分だけをRuboCopとかでチェックできるようにするそうです↓」「GitHubにレビューコメント付けてくれる😋」「つまり修正のプルリク作るところまでやってくれるってことですよね😋」「prontoは自体はランナーを呼ぶようですが、RuboCopやさまざまなlintのランナーがずらりとありますね😍


prontolabs/prontoより

「GitLabでも動くのかな?」「お、GitLabCIもやれるとあります❤: 割と試しやすそうですし社内のGitLabで使ってみません?」

# GitLabCI用config例
lint:
  image: ruby
  variables:
    PRONTO_GITLAB_API_ENDPOINT: "https://gitlab.com/api/v4"
    PRONTO_GITLAB_API_PRIVATE_TOKEN: token
  only:
    - merge_requests
  script:
    - bundle install
    - bundle exec pronto run -f gitlab_mr -c origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME

参考: Prontoでソースレビュー自動化 - アクトインディ開発者ブログ

⚓Overcommit

「overcommit gemはコミット時にローカルでGit Hooksを起動して必須の処理を回すんだったかな」「社内でも誰か使ってた覚えあります」「設定を欲張り過ぎるとコミットのたびにうるさく鳴き出しそう😆

overcommitでmasterへのプッシュを禁止するとかもできるそうです↓。

参考: git hooksをovercommitで管理して作業効率の底上げを狙う - LCL Engineers’ Blog

⚓rubocop_lineup

「rubocop_lineupは新しいせいか日本語記事まだないですね」「ブランチでmasterとの差分行だけをRuboCopでチェックする拡張だそうです」

rubocop_lineupになぜこの写真↓?と思って調べると、lineupには『面通しのために並ばせた容疑者の列』という意味もあるんだそうです(米国のみ)。毎度邪魔になるメッセージを常習犯に見立てた感じですね👮🏼‍♀️


mysterysci/rubocop_lineupより

⚓active_merchant: 支払いサービスの抽象化ライブラリ(Ruby Weeklyより)

# 同サイトより
# ゲートウェイのテストサーバーにリクエスト送信
ActiveMerchant::Billing::Base.mode = :test

# クレジットカードオブジェクトの作成
credit_card = ActiveMerchant::Billing::CreditCard.new(
  :number     => '4111111111111111',
  :month      => '8',
  :year       => '2009',
  :first_name => 'Tobias',
  :last_name  => 'Luetke',
  :verification_value  => '123'
)

if credit_card.valid?
  # TrustCommerceへのゲートウェイオブジェクトを作成
  gateway = ActiveMerchant::Billing::TrustCommerceGateway.new(
    :login    => 'TestMerchant',
    :password => 'password'
  )

  # 10ドル(1000セント)を認証
  response = gateway.authorize(1000, credit_card)

  if response.success?
    # 金額をキャプチャ
    gateway.capture(1000, response.authorization)
  else
    raise StandardError, response.message
  end
end

Active MerchantはeコマースシステムShopifyの抜粋です。このライブラリの主な設計原則は、ShopifyのシンプルAPIや統合APIの要件を用いて、内部APIが大きく異るさまざまな支払いゲートウェイにアクセスすることです。
同リポジトリより

TechRachoにも記事がありました↓。

ActiveMerchant を使ってPayPal Express Checkout の与信取得と回収機能を導入する


つっつきボイス:「支払いを抽象化するという触れ込みのactive_merchantはかなり前からあるみたいで、今も熱心にメンテされているようです」「使ったことなかった😆」「対応している支払いサービスもたくさんあって、当然のようにStripeにも対応してますね😋↓」「StripeにJP含まれてませんけど😆」「なかなか使い所が難しそう: 多言語展開するような全世界向けシステムで、国によって決済業者が違う、みたいなケースでマッチするんだろうけど🤔


同リポジトリより

Stripe決済を自社サービスに導入してわかった5つの利点と2つの惜しい点

⚓foragoodstrftime.com: strftimeの書式生成サイト


つっつきボイス:「strftimeの書式をさっと調べられるサイトです」「これは覚えられないヤツ😆」「要素をドロップして並べられるとか、随分凝ったサイトですね😆」「まあそこまでせんでも😆」「作ってみたかったんですよきっと☺

[Ruby/Rails] strftimeのよく使うテンプレート

なお、以下は以前もウォッチで紹介した同様の趣旨のサイトです。


前編は以上です。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191021)Rails 6でhas_many関連の修正やSprockets 4.0対応、Shrine 3.0がリリース、Minitestスタイルガイドほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20191029後編)Ruby 2.7.0-preview2、tapping_device gemとhumanize gem、平成Ruby会議ほか

$
0
0

こんにちは、hachi8833です。久しぶりに腰を痛めてしまいました😇

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンス!発言も自由です。皆さまのお気軽なご参加をお待ちしております🙇

⚓Ruby

⚓Ruby 2.7.0-preview2がリリース(Ruby公式ニュースより)

Ruby 2.7.0-preview2がリリース


つっつきボイス:「先に記事出しちゃいましたが、Ruby 2.7.0-preview2がリリースされて詳細がだいぶ明らかになりました😋」「めでたい🎉


「今回はExperimentalと銘打った新機能が目立ってましたが、気になった機能はあります?」「rangeの開始を省略できるようになった↓とありましたけど、個人的には冗長でもフルで書きたい方ですね〜😆」「実は私も😆」「一抹の気持ち悪さがあるといえばあるかも😆

ary = %w(1, 2, 3, 4)
ary[..3]    # ary[0..3]と同等
#=> ["1,", "2,", "3,", "4"]

「従来のRubyだと、ブロックなしのprocProc.newがメソッドのブロックをキャッチしていた↓というのが一番驚きでした😳」「そうそう☺」「ある意味バグに近い感じ☺」「こんな書き方できてたとは😅」「面白がって使う人とかいそう😆」「知らずに書いてハマる人の方が多いかも😇」「普通はyieldで受けるかブロック変数で受けますよね」

def foo
  proc.call # またはProc.new.call
end
foo { puts "Hello" }
#=> warning: Capturing the given block using Proc.new is deprecated; use `&block` instead
#=> Hello

「上は2.7でwarningになって、lambdaにもあった同様の挙動は一足先に2.7でエラー扱いになります↓」

def foo
  lambda.call
end
foo { puts "Hello" }
#=> ArgumentError (tried to create Proc object without a block)

「今まで他の言語やってたから思うのかもしれませんけど、こうやってブロックを食わせて処理するって慣れないと見えにくいところがあるかもですね☺」「でもRubyのブロックはやっぱり便利〜↓」

参考: Rubyのブロックは本当に素晴らしい発明だと思いますが、他の言語はそれを追随していますか?に対するYukihiro Matsumotoさんの回答 - Quora

なお、以下はつっつきの後で見つけたNoah Gibbsさんのベンチマーク記事です。

参考: Ruby 2.7preview2, a Quick Speed Update — Appfolio Engineering

⚓MJITのC生成を追う(Ruby Weeklyより)


つっつきボイス:「上はRubyのベンチマークやコアな部分を追っているNoah Gibbsさん↓の記事で、MJITがどんなCのコードを吐いているかを調べたそうです」「これはハードそう😅」「今追いきれないので後でサマリー追加しておきますね😋

Ruby 3 JITの最新情報: 現状と今後(翻訳)


MJITはほとんどの場合、コールスタックやCレベル関数呼び出しなどのシンプルな記録を最適化しているにすぎない。関数が再定義されたかどうかのチェックなどによるオーバーヘッドを含む、Rubyのほとんどの操作については基本的に従来と変わらない。
何らかのbreaking changesなしに言語レベルで最適化するのはきわめて困難なので、これは理解できる。MJITは言語のセマンティクスを一切変更しないようにしているので、MJITはほとんどの場合インタプリタがこれまで行っていたようにシンプルかつ機械的に変換している。
同記事末尾より大意

ご存知のとおりRuby 2.6から--jitオプションが使えます。自分でも2.7-preview2でごく簡単なベンチを回してみたところ、速度的には--jitありなしでほぼ違いはない雰囲気でした。

⚓humanize: 数値をさまざまな言語でスペルアウト(Ruby Weeklyより)

# 同リポジトリより
2.humanize  # => "two"
4.humanize  # => "four"
8.humanize  # => "eight"
15.humanize # => "fifteen"
16.humanize # => "sixteen"
23.humanize # => "twenty three"
42.humanize # => "forty two"

つっつきボイス:「個人的に面白そうなgemを見つけました😋」「RailsのActive Supportに似たようなものがありましたっけ?」「Active Supportのこれ↓とか少し近いかな」「あとモデル名や属性名を変換するヤツとか」

number_to_human(123)                         # => "123"
number_to_human(1234)                        # => "1.23 Thousand"
number_to_human(12345)                       # => "12.3 Thousand"
number_to_human(1234567)                     # => "1.23 Million"
number_to_human(1234567890)                  # => "1.23 Billion"
number_to_human(1234567890123)               # => "1.23 Trillion"
number_to_human(1234567890123456)            # => "1.23 Quadrillion"
number_to_human(1234567890123456789)         # => "1230 Quadrillion"
class BlogPost
  extend ActiveModel::Naming
end

BlogPost.model_name.human # => "Blog post"

「このhumanize gemで("9" * 156).to_i.humanizeを実行するとこんなの出ます↓」「うは🤣」「これは🤣」「quinquagintillionなんて数詞見たことありませんし😆」「阿僧祇恒河沙無量大数的な😆

=> “nine hundred and ninety nine quinquagintillion, nine hundred and ninety nine novenquadragintillion, nine hundred and ninety nine octoquadragintillion, nine hundred and ninety nine septenquadragintillion, nine hundred and ninety nine sesquadragintillion, nine hundred and ninety nine quinquadragintillion, nine hundred and ninety nine quattuorquadragintillion, nine hundred and ninety nine trequadragintillion, nine hundred and ninety nine duoquadragintillion, nine hundred and ninety nine unquadragintillion, nine hundred and ninety nine quadragintillion, nine hundred and ninety nine novemtrigintillion, nine hundred and ninety nine octotrigintillion, nine hundred and ninety nine septentrigintillion, nine hundred and ninety nine sextrigintillion, nine hundred and ninety nine quintrigintillion, nine hundred and ninety nine quattuortrigintillion, nine hundred and ninety nine trestrigintillion, nine hundred and ninety nine duotrigintillion, nine hundred and ninety nine untrigintillion, nine hundred and ninety nine trigintillion, nine hundred and ninety nine novemvigintillion, nine hundred and ninety nine octovigintillion, nine hundred and ninety nine septenvigintillion, nine hundred and ninety nine sexvigintillion, nine hundred and ninety nine quinvigintillion, nine hundred and ninety nine quattuortillion, nine hundred and ninety nine trevigintillion, nine hundred and ninety nine duovigintillion, nine hundred and ninety nine unvigintillion, nine hundred and ninety nine vigintillion, nine hundred and ninety nine novemdecillion, nine hundred and ninety nine octodecillion, nine hundred and ninety nine septendecillion, nine hundred and ninety nine sexdecillion, nine hundred and ninety nine quindecillion, nine hundred and ninety nine quattuordecillion, nine hundred and ninety nine tredecillion, nine hundred and ninety nine duodecillion, nine hundred and ninety nine undecillion, nine hundred and ninety nine decillion, nine hundred and ninety nine nonillion, nine hundred and ninety nine octillion, nine hundred and ninety nine septillion, nine hundred and ninety nine sextillion, nine hundred and ninety nine quintrillion, nine hundred and ninety nine quadrillion, nine hundred and ninety nine trillion, nine hundred and ninety nine billion, nine hundred and ninety nine million, nine hundred and ninety nine thousand nine hundred and ninety nine”
同リポジトリより

参考: 無量大数 - Wikipedia

「humanizeというよりto_english的つーか😆」「お、ロケール指定できるみたい↓」「じゃいいか😆」「小数点もやれますね」

# 同リポジトリより
Humanize.configure do |config|
  config.default_locale = :en  # [:en, :es, :fr, :tr, :de, :id], default: :en
  config.decimals_as = :digits # [:digits, :number], default: :digits
end

0.42.humanize(decimals_as: :digits) # => "zero point four two"
0.42.humanize(decimals_as: :number) # => "zero point fourty-two"

「これ日本語もやろうと思えばやれるんでしょうね😆」「誰かがやろうと思えば😆」「やってみちゃおうかな😋」「ひらがな読みも出せたらいいのに」「『ひとつふたつみっつ』とか😆」「『にひゃく』『はっぴゃく』とか活用形が地獄化しそう😇

「漢数字gemといえばこちらも話題になった気がしますね↓」「おぉ既にあるんですね😳

⚓tapping_device: RubyのTracePointで手軽にデバッグ

Goby言語の@st0012さんがRubyのTracePointを用いて作ったデバッグ用gemです。オレオレRailsアプリのdevelopmentに入れて動かしてみました。言わずもがなですが、tapping deviceは「盗聴器」になぞらえてますね😆

参考: class TracePoint (Ruby 2.6.0)

⚓その他Ruby

平成Ruby会議の登壇募集は本日締め切りだそうです。お早めにどうぞ。

⚓DB

⚓SQLで日時をフォーマットする(Postgres Weeklyより)

見出しより:

  • Dateのフォーマットは多種多様
  • Dateの表現はデータ型によって異なる
  • DateをStringに変換する
    • TO_CHAR()
    • EXTRACT()
    • DATE_TRUNC()
  • StringをDateに変換する
    • CAST()
    • TO_DATE()
  • PostgreSQLのDate関数リストとサンプル


同記事より

つっつきボイス:「初心者向けSQL日付フォーマットガイドということで」「EXTRACT()にCENTURYとかMILLENNIUMとかいろいろありますね↑」「SQL日時変換はRDBMSに相当依存するので、当該RDBのドキュメント読むのがいいでしょうね☺: 汎用性が高いのはEXTRACT()かな」「おぉ」

「SQLの日付フォーマットってストアドプロシージャとかでたまに必要になったりするのかも☺」「ストアドプロシージャはRDBMSごとに違いがありすぎると教わりました😆」「いつだったか、ストアドプロシージャを全部シェルスクリプトに書き換えたことありますけど🤣」「え?😅」「今さらっとすごいこと言った🤣」(以下延々)

参考: ストアドプロシージャ - Wikipedia

「このDOW(day of week)という略語は日本語で言う『曜日』なんですけど、どういうわけか英語圏ではさっぱり定着しないんですよ」「そういえば英語に1単語で曜日の概念を表す用語ってありませんね😳」「表示スペースがどうしても足りないときにしぶしぶDOWと表記するみたいです」

WikipediaにすらDOWという略語は載っていません↓。

参考: Names of the days of the week - Wikipedia

⚓その他DB


blog.aquameta.comより


つっつきボイス:「aquametaはPostgreSQLだけでWeb開発するIDEなんだそうで、★も800超えてますね」「シェルスクリプトでWebシステムを組んたことならありますけど、それに匹敵するキワモノ感ですね〜🤣」「え?😅」「さらっとまたスゴいこと言った🤣」「そこまでひとつの技術にこだわらなくても、今は便利なものがいろいろありますからそっちを使いましょうよって思いますし☺」「aquametaはかれこれ20年近く続いているプロジェクトだそうです☺」「趣味としてならとても理解できる☺」「変態やなーとも思いつつも、内部的にストアドプロシージャ使ってるならまあ確かに何でもできるかも😆: Web IDEまで作り込むその愛がすごい」

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

⚓Rails向けAWS S3 IAMポリシーセキュリティ設定(Ruby Weeklyより)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::another-test-bucket"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::another-test-bucket/*"
            ]
        }
    ]
}

見出しより:

  • デフォルトのS3設定の問題点
  • IAMポリシーの概要
  • S3のsudoアクセスを使う
  • セキュアなS3 IAMポリシーを設定するには

つっつきボイス:「Rails向けのS3 IAMポリシーについての記事です」「RailsアプリでIAM設定に関する話となると、Instance Roleで設定するというのも一つの手段ですが、EC2インスタンスが絡む実行でしか使えません」「おぉ」「記事のようにBucket Policyで設定するのはいいんですが、『特定のBucketアクセスだけできる』ちゃんとしたIAMポリシを書くのは結構難しいです」「そうでしたか!」「ListAllMyBucketsがないとaws s3 ls できないとか地味に色々問題が出ますし😇

AWSアカウントを新規作成した直後はrootユーザーのアクセスキーだけが利用できる状態になるが、これをそのままRailsアプリで使うと深刻なセキュリティ問題につながるので避けなければならない。
同記事より抜粋

「さすがにこんな初歩的な間違いをする人は今どきいないのでは😆: 確かAWS公式でもrootアカウントは初期利用だけにして、AdministratorAccessのIAMアカウントを作ったら後はIAMアカウントを使おうね、ということになってたはずですし、ちゃんと堅い運用しているところならAWS rootアカウントはMFA有効にした上で、MFAキーを金庫に入れて運用します」


参考: The Technical Side of the Capitol One AWS Security Breach

同記事の末尾からリンクされている上の記事も興味深い内容です。

参考: 米金融大手Capital One情報漏えいの容疑者、さらに30社超からデータ盗難の疑い - ZDNet Japan

⚓サーバーレスのキャッシュについて知っておきたいこと(Serverless Statusより)

見出し:

  • キャッシュは現在も「極めて」重要
  • キャッシュをどこに実装すべきか
    • クライアントアプリ
    • CloudFront
    • API Gateway
    • Lambda function
    • DAX(DynamoDB Accelerator)
  • まとめ

つっつきボイス:「サーバーレスでもキャッシュは重要」「そこはきっとそうでしょうね☺」「ほとんどのリージョンではコンカレント実行が1000までというソフトリミットがあるけど、その先に1分あたりのコンカレント実行が500までというハードリミットがあって、以下みたいにイベント開催中や飯どきにハードリミットに突き当たることがある↓、と」「ふむふむ」


同記事より

「こういう図が出てくるとちょっとほっとしますね」「それでも5つありますけど😆」「キャッシュ多いの〜」「それぞれ特性の異なるキャッシュが可能でうまく使えば効果的だけど、気を付けないと費用もかさむと」「実際には使用メモリの増加でしょうね💸」「CloudFrontはいいけど制約が多いとか、API Gatewayは強力だけどユースケースが少なかったともあります」「DAXはDynamoDB Acceleratorか」

参考: Amazon DynamoDB Accelerator (DAX) | AWS

参考: Amazon ElastiCache(インメモリキャッシングシステム)| AWS

DynamoDBを使わない場合や、複数の異なるデータソースからのデータをキャッシュしたい場合はElasticacheの導入も検討しよう。
元記事より抜粋

「サーバーレスでElasticache使うのかー: まあ安定・高パフォーマンス運用したいならわからなくもないけど、Elasticacheはインスタンス運用になるので時間課金の要素ができちゃうのが難点ですね」「そこが違うんですね😳」「その他のものは全部(ほぼ)完全従量課金ができます☺

⚓CSS/HTML/フロントエンド/テスト

⚓2019年のWeb動向


つっつきボイス:「少し長めの記事ですが、普段あまり目にしてないWebコーディングやデザイン方面の動向を大雑把に見るのにいいかなと思って😋

⚓SVG

「SVG使ってる人が7割」「この辺は元記事の母集団の構成にもよりそうですけど😆」「最近ファビコンとかアイコンでSVG使ってるサイト増えてる印象」

参考: Scalable Vector Graphics - Wikipedia

⚓WebP

「WebPって?」「割と最近にGoogleが作った画像フォーマットだそうです」「今の時代にまた新しい画像フォーマットが出るとは」「このアンケートではまだ8%しか使ってないようです」

「WebPにまだ対応してないブラウザもあるんですって😆」「IEですねわかります😆」「Safariも😆」「日本での普及は大変そうかな😅


developers.google.comより

参考: WebP - Wikipedia

「…WebPは10年近く経っててめっちゃ使われてるのではないかと🧐」「やや、そうでしたか😅」「最近むしろHEIFがすごい勢いで普及してて怖いんですけど」「こちらですか↓」「iPhoneのデフォルトフォーマットです🧐

参考: High Efficiency Image File Format - Wikipedia

⚓CSSプリプロセッサ

「CSSプリプロセッサはこのアンケートではSassが強いですね〜」「何も使ってない人も3割😆」「SassはもともとRubyで実装されてましたけど今年ruby-sassはディスコンになりましたね」


sass-lang.comより

⚓仕様のチェック

「HTMLやCSSの仕様をどこで確認するか」「W3Cが35%」「WHATWG HTML Standardは15%というのは意外に少ないかも」「仕様書確認しない派😆」「今年半ばにW3CとWHATWGがついに合意したというニュース↓があったから、今ならWHATWGで仕様を確認するのが正式ということになりますね」「MozillaのMDNが見やすいというのもわかる☺

参考: HTML標準仕様の策定についてW3CとWHATWGが合意 今後はWHATWGのリビングスタンダードが唯一のHTML標準仕様に - ITmedia NEWS

⚓CSSフレームワーク

「BootstrapやBulmaなどのCSSフレームワーク」「Bulma知らない〜😆」「フレームワーク使っていないが64%というのが意外ですね😳」「HTML・CSSコーダーが対象だからかも?🤔」「Bootstrapの型にはめられたくないとか?」「型にはまっちゃえば楽なんですけどね☺

参考: Bootstrap · The most popular HTML, CSS, and JS library in the world.


getbootstrap.comより

⚓Slack

「SlackはWebデザイナー・エンジニアともに6割使ってますね」「この世にはSlackの存在を知らない職場もまだまだありますから😆

⚓最近のメッセージサービス

「携帯の+メッセージとRCSも初めて見た😳」「いわゆるSMSみたいなヤツなんでしょうね: +メッセージは自分のスマホにインストールだけはしてますけど☺

参考: +メッセージ(プラスメッセージ) | サービス・機能 | NTTドコモ
参考: Rich Communication Services - Wikipedia

⚓番外: All your base are belong to us

「『All your base are belong to us』を知らない人が8割: もう20年ぐらい前だからレトロネタになっちゃったか〜👴」「…これ何ですか?」「その昔英語圏でめちゃめちゃコピペされた、ゲームのセリフの有名な誤訳ですね」「当時からネット使ってたけど気が付かなかった〜😅」「ニコ動のネットの歴史で見た気がする😆」「ゲーマー界隈では超有名ですね😆

参考: All your base are belong to us - Wikipedia

英語圏でも説明が必要になってるっぽいです↓。

⚓その他

⚓Googleの量子超越性


つっつきボイス:「仮想通貨系が騒がしくなってますね」「ひと月ぐらい前に査読中の論文がリークしたとかで英語圏で先に騒がれてたそうです」「暴落したのがちょっと痛快😆」「実際にどこまで通用するのか謎ですけど🤣」「結果が微妙なまま大々的に発表するとかよくありますし☺」「現実に降りてくるにはまだかかるでしょうね」

以下はつっつきの後で見つけました。

参考: グーグルが主張する「量子超越性の実証」に、IBMが公然と反論した理由|WIRED.jp
参考: 量子超越性実証で世界騒然のGoogle量子ラボに行ってきた | ギズモード・ジャパン

⚓番外

⚓無電源センサネットワーク


同記事より


つっつきボイス:「電波を電力に変換する高効率なダイオードで無電源でセンサーネットワークを作れるかもしれないそうです」「お〜、たぶん一瞬だけ電力を発生させてそれでセンシングして送信して、みたいに動作するのかな?😍」「こういう電源なしで外部マイクロ波や光駆動のセンサは普通に世の中的にありますね」「おぉ、そうでしたか😅」「無線駆動式センサは自分が学生の頃にも既にあったと思います: ちなみに送信onlyのノードなら割と昔から無線給電できるんですが、難しいのは受信の方ですね☺

「Appleウォッチがバッテリーなしで動くようになって欲しいです⌚」「まだ無理😆」「私のスマートウォッチも1日1回充電してますね😆」「電卓ぐらいならソーラーバッテリーで動くのに😅


後編は以上です。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191028前編)RailsにSTI用メソッドsti_class_forとpolymorphic_class_forが追加、RuboCopを変更箇所だけにかけるgem、strftime書式生成サイトほか

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

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

Ruby 公式ニュース

Ruby Weekly

Postgres Weekly

postgres_weekly_banner

Serverless Status

serverless_status_banner

React Status

react_status_banner


Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)

$
0
0

概要

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

なお、コード例には翻訳時に補助的なコメントを追加しています。

Ruby 2.7: ハッシュからキーワード引数への自動変換が非推奨に(翻訳)

Rubyのメソッド定義は極端なまでに柔軟です。それを端的に表す例がMarc-André Lafortuneのブログ記事にありましたので引用します。

class C
  def hi(needed, needed2,
         maybe1 = "42", maybe2 = maybe1.upcase,
         *args,
         named1: 'hello', named2: a_method(named1, needed2),
         **options,
         &block)
  end
end

C.instance_method(:hi).parameters
# => [ [:req, :needed], [:req, :needed2],
#      [:opt, :maybe1], [:opt, :maybe2],
#      [:rest, :args],
#      [:key, :named1], [:key, :named2],
#      [:keyrest, :options],
#      [:block, :block] ]

Ruby 2.7ではキーワード引数の設計の変更に踏み切ります。しかしその前にキーワード引数について理解しておきましょう。

そもそもキーワード引数とは?

キーワード引数はRuby 2.0で導入され、ちょうどオプション引数(つまりハッシュオブジェクト{ })と同じように扱われていました。キーワード引数を受け取るメソッドにオプション引数を渡すこともできますし、逆にオプション引数を受け取るメソッドにキーワード引数を渡すこともできます。

# Ruby 2.6まで
def a_method(k: 1)  # キーワード引数を受け取るメソッド
  puts "k: #{k}"
end

a_method
=> k: 1
a_method(k: 2)       # キーワード引数を渡せる
=> k: 2
a_method({k: 2})     # オプション引数も渡せる
=> k: 2

def a_method(**kw)   # オプション引数を受け取るメソッド
  puts "kw, #{kw}"
end

a_method
=> kw, {}
a_method(k: 2)       # キーワード引数も渡せる
=> kw, {:k=>2}
a_method({k: 2})     # オプション引数を渡せる
=> kw, {:k=>2}

#14183 — “Real” keyword argument」で指摘されているとおり、キーワード引数とオプション引数のどちらを渡してもよいという互換性は、実に多くのバグやエッジケースの温床となっていました。

RubyConf 2017では、Matzが「Ruby 3.0で『本物の』キーワード引数をお見せします」と公式にアナウンスしました(動画↓)。キーワード引数が通常の引数から完全に切り離されるのもそのひとつです。

この変更によって非互換性が生じます。Ruby 3.0でのキーワード引数の設計見直しに向けた準備のため、ハッシュをキーワード引数に自動変換する挙動はRuby 2.7で非推奨となります。

本記事では、この変更によって実際に影響を受ける可能性のあるコードと、Ruby 3.0をサポートするための移行方法について記します。

キーワード引数を受け取るメソッドにハッシュが渡される場合

# Ruby 2.7
def a_method(k: 1)    # キーワード引数を受け取るメソッド
  puts "k: #{k}"
end

a_method({k: 1})      # ハッシュを渡すとwarningが表示される
(irb):4: warning: The last argument is used as the keyword parameter
(irb):1: warning: for 'a_method' defined here
# => k: 1

# warningを回避し、かつRuby 3での互換性を確保するには
# double splat演算子を付けて渡す

a_method(**{k: 1})
# => k: 1

オプション引数とキーワード引数を両方受け取るメソッドに、渡した引数が足りない場合

# Ruby 2.7
def a_method(opts, **kw)
  puts "opts, #{opts}"
  puts "kw, #{kw}"
end

a_method(k: 1)    # キーワード引数だけを渡すとwarningが表示される
(irb):5: warning: The keyword argument is passed as the last hash parameter
(irb):1: warning: for 'a_method' defined here
# => opts, {:k=>1}
kw, {}

# warningを回避し、かつRuby 3での互換性を確保するには
# 引数をハッシュオブジェクトにする

a_method({k: 1})   # ハッシュオブジェクト { } ならOK
=> opts, {:k=>1}
=> kw, {}
# メソッド定義に**nilと書くことで
# メソッドがキーワード引数を受け取らないことを明示できる
def a_method(opts, **nil)
  puts "opts, #{opts}"
end

a_method(k: 1)
#=> ArgumentError (no keywords accepted)

互換レイヤ

互換レイヤとして、オプション引数を受け取るメソッドにキーワード引数を渡すことは引き続き許容されます。

# Ruby 2.6と2.7
def a_method(opts={})   # オプションハッシュを受け取るメソッド
  puts "opts, #{opts}"
end

a_method(k: 1)          # これはOK
#=> opts, {:k=>1}

非シンボルがキーワード引数のキーとして使えるようになる

任意のキーワードを(double splat演算子で)受け取れるメソッドには、キーがシンボルでないキーワード引数を渡すことが許容されるようになります。

Ruby 2.6まで

def a_method(**kw)      # double splatで任意のキーワードを受け取るメソッド
  puts "kw, #{kw}"
end

a_method("k" => 1)      # キーが非シンボルだとエラーになる
#=> ArgumentError: wrong number of arguments (given 1, expected 0)
from (pry):7:in 'a_method'

a_method(**{"k" => 1})  # { }で囲んでも**を付けてもダメ
#=> TypeError: wrong argument type String (expected Symbol)
from (pry):11:in `__pry__'

Ruby 2.7

def a_method(**kw)  # double splatで任意のキーワードを受け取れる
  puts "kw, #{kw}"
end

a_method("k" => 1)  # 非シンボルキーを渡せる
=> kw, {"k"=>1}

まとめ

Ruby 2.7ではハッシュからキーワード引数への自動変換が非推奨になります。さまざまなシナリオからわかるように、アプリケーションでこの非推奨の用法から移行することをまず検討しましょう。今後も改善が続く部分につき、今後のRubyではさらに変更が行われるかもしれません。

関連記事

Ruby 2.7.0-preview2がリリース

Ruby2.5.xのパラメータの制約についてまとめてみた

Ruby: 数値をスペルアウトするhumanize gemに日本語ロケールを追加しました

$
0
0

週刊Railsウォッチ(20191029)で取り上げたhumanize gemに日本語ロケールを追加して数値を漢数字読みに変換できるようにしてみました。

humanize gemについて

レシーバーの数値を読み上げ可能な文字列に変換します。デフォルトロケールは英語です。

require 'humanize'
(1000..1050).to_a.map(&:humanize)
=> ["one thousand", "one thousand and one", "one thousand and two", "one thousand and three", "one thousand and four", "one thousand and five", "one thousand and six", "one thousand and seven", "one thousand and eight", "one thousand and nine", "one thousand and ten", "one thousand and eleven", "one thousand and twelve", "one thousand and thirteen", "one thousand and fourteen", "one thousand and fifteen", "one thousand and sixteen", "one thousand and seventeen", "one thousand and eighteen", "one thousand and nineteen", "one thousand and twenty", "one thousand and twenty-one", "one thousand and twenty-two", "one thousand and twenty-three", "one thousand and twenty-four", "one thousand and twenty-five", "one thousand and twenty-six", "one thousand and twenty-seven", "one thousand and twenty-eight", "one thousand and twenty-nine", "one thousand and thirty", "one thousand and thirty-one", "one thousand and thirty-two", "one thousand and thirty-three", "one thousand and thirty-four", "one thousand and thirty-five", "one thousand and thirty-six", "one thousand and thirty-seven", "one thousand and thirty-eight", "one thousand and thirty-nine", "one thousand and forty", "one thousand and forty-one", "one thousand and forty-two", "one thousand and forty-three", "one thousand and forty-four", "one thousand and forty-five", "one thousand and forty-six", "one thousand and forty-seven", "one thousand and forty-eight", "one thousand and forty-nine", "one thousand and fifty"]

小数点や無限大も使えますが、ロケールによってはまだできてないものもあります。

日本語ロケール

この間プルリクを投げていましたが、ちょうど今朝マージされました😂。実はrubygemをいじったのはこれが初めてです😅

使い方

通常のgemと同様に、以下を実行するかGemfileに書いてbundle installしてインストールすれば、require 'humanize'することで使えます。

$ gem install humanize

以下のように負数や小数やFloat::INFINITYも扱えます。

require 'humanize'

2019.humanize(locale: :jp)
#=> "二千十九"

-123422223.48948753.humanize(locale: :jp)
#=> マイナス一億二千三百四十二万二千二百二十三・四八九四八八

Float::INFINITY.humanize
#=> "無限大"

(Float::INFINITY - Float::INFINITY).humanize
#=> "未定義"

毎回(locale: :jp)を書くのがだるければ、Humanize.config.default_locale = :jpでロケールを設定できます。

require 'humanize'
Humanize.config.default_locale = :jp

puts "#{5000000000000000.humanize}円欲しい"
#=> 五千兆円欲しい

なお、(decimals_as: :number)を指定すると小数以下が桁表示になります。他のロケールではともかく、日本語では意味はないでしょうね。

3.1415926535897932384626.humanize(decimals_as: :number)
#=> "三・十四兆一千五百九十二億六千五百三十五万八千九百七十九"

めいいっぱいにやってみました。

require 'humanize'
Humanize.config.default_locale = :jp

('9' * 72).to_i.humanize

結果が長いので以下に置きます。

九千九百九十九無量大数九千九百九十九不可思議九千九百九十九那由他九千九百九十九阿僧祇九千九百九十九恒河沙九千九百九十九極九千九百九十九載九千九百九十九正九千九百九十九澗九千九百九十九溝九千九百九十九穣九千九百九十九𥝱九千九百九十九垓九千九百九十九京九千九百九十九兆九千九百九十九億九千九百九十九万九千九百九十九

参考: 同趣旨のgem

なお、兆までの数値は以下のgemで漢数字に変換できます。

年号の漢数字であればwareki gemで対応できます。「10月」「十月」「一〇月」「拾月」「什月」といったバリエーションにも対応しています😳

:jpロケールのコード

lib/humanize/locales/constants以下にロケールの定数を定義します。この定数はロケール以外にメインのlib/humanize.rbからも参照され、小数点や無限大やゼロ値の場合に用いられます。

# lib/humanize/locales/constants/jp.rb
module Humanize
  class Jp
    INFINITY = '無限大'.freeze
    UNDEFINED = '未定義'.freeze
    NEGATIVE = 'マイナス'.freeze
    POINT = '・'.freeze
    LOTS = ['', '万', '億', '兆', '京', '垓', '𥝱', '穣', '溝', '澗', '正', '載', '極', '恒河沙', '阿僧祇', '那由他', '不可思議', '無量大数'].freeze
    SUB_ONE_GROUPING = %w[〇 一 二 三 四 五 六 七 八 九 十 十一 十二 十三 十四 十五 十六 十七 十八 十九 二十 二十一 二十二 二十三 二十四 二十五 二十六 二十七 二十八 二十九 三十 三十一 三十二 三十三 三十四 三十五 三十六 三十七 三十八 三十九 四十 四十一 四十二 四十三 四十四 四十五 四十六 四十七 四十八 四十九 五十 五十一 五十二 五十三 五十四 五十五 五十六
    # (略)
 九千九百九十九].freeze

SUB_ONE_GROUPING定数のリストがゼロから9999までと長大ですが、リスト化することでアルゴリズムがシンプルになり、ロケールごとの違いもここで吸収できます。

以下は:jpロケール処理のコアです。:enロケールをコピペして「, and」の追加処理を除去した程度です。

# lib/humanize/locales/jp.rb
require_relative 'constants/jp'

module Humanize
  class Jp
    def humanize(number)
      iteration = 0
      parts = []
      until number.zero?
        number, remainder = number.divmod(10000)
        unless remainder.zero?
          add_grouping(parts, iteration)
          parts << SUB_ONE_GROUPING[remainder]
        end

        iteration += 1
      end

      parts
    end

    private

    def add_grouping(parts, iteration)
      grouping = LOTS[iteration]
      return unless grouping

      parts << "#{grouping}"
    end
  end
end

変換の主な仕様

命数は「無量大数」まで

「不可説不可説転」までやってみたかったのですが、系列が合わない(10^4で進行しない)ので断念しました。

参考: 命数法 - Wikipedia

1000は「一千」に変換する

10や100や1000を漢数字で表す場合には以下の慣習を考慮する必要があります。

  • 10を「十」と書くことはあっても「一十」と書くことはない
    • なお20以上なら「二十」などと書く
  • 100を「百」と書くことはあっても「一百」と書くことはない
    • 同じく200以上なら「二百」などと書く
  • 1000は上と異なり、「千」とも「一千」とも書ける
    • 特に「一千万」「一千億」は必ず「一」を付けると考えてよい

このように、1000については「千」「一千」という2とおりの表記が考えられ、どちらを取るべきか考えました。

その結果、「一千」なら「一千万」「一千億」とも整合するので「一千」に統一することにしました。「一千」をどうしても「千」にしたいのであれば後から独自に除去する手もあります。

(参考)ボツにした:jpロケールのコード

実は、当初のコードはhumanizeのフレームワークに乗っかっていませんでした。以下のように最小限の定数を定義して、アルゴリズムでどこまでやれるかやってみたかったのでした。

# lib/humanize/locales/constants/jp.rb
module Humanize
  class Jp
    POINT = '・'.freeze
    INFINITY = '無限大'.freeze
    UNDEFINED = '未定義'.freeze
    NEGATIVE = 'マイナス'.freeze
    LOTS_ONE = %w[万 億 兆 京 垓 𥝱 穣 溝 澗 正 載 極 恒河沙 阿僧祇 那由他 不可思議 無量大数].freeze
    LOTS_TWO = %w[十 百 千].freeze
    SUB_ONE_GROUPING = ['〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'].freeze
  end
end

そして漢数字への変換をアルゴリズムで処理してみると、以下のようなおぞましいコードになってしまい、メンテできそうになかったので最終的に放棄しました😇

# lib/humanize/locales/jp.rb
require_relative 'constants/jp'

module Humanize
  class Jp
    def humanize(number)
      parts = []
      group = 0
      target = number.digits
      target.each_with_index do |digit, index|
        group = index % 4
        case
        when zero_on_first_3_digits?(digit, index)
          next
        when zero_on_lower_3_digits_in_higher_group?(digit, group)
          next
        when all_zeroes_on_4_digits_in_group?(digit, group, index, target)
          add_grouping(parts, (index / 4), Jp::LOTS_ONE)
        when one_thousand?(digit, group)
          add_grouping(parts, group, Jp::LOTS_TWO)
          parts << Jp::SUB_ONE_GROUPING[digit]
        when two_or_more_thousands?(digit, group)
          add_grouping(parts, group, Jp::LOTS_TWO)
          parts << Jp::SUB_ONE_GROUPING[digit]
        when none_thousand?(digit, group)
          add_grouping(parts, group, Jp::LOTS_TWO)
        when any_zeroes_on_4_digits_in_group?(digit, group, index, target)
          add_grouping(parts, (index / 4), Jp::LOTS_ONE)
          parts << Jp::SUB_ONE_GROUPING[digit]
        when nonzero_on_highest_digit_of_group?(digit, group)
          parts << Jp::SUB_ONE_GROUPING[digit]
        end
      end
      parts
    end

    private

    # returns true in the cases like 0, 0 of 202, 0 of 2_000
    def zero_on_first_3_digits?(digit, index)
      digit.zero? && index < 4
    end

    # returns true in the cases like 0 of 3000_9999
    def zero_on_lower_3_digits_in_higher_group?(digit, group)
      digit.zero? && group.nonzero?
    end

    # returns true in the cases like 0 of 1_1000_9999, 0 of 1_0100_9999
    def all_zeroes_on_4_digits_in_group?(digit, group, index, target)
      digit.zero? && group.zero? && target[index, 4] != [0, 0, 0, 0] && index.nonzero?
    end

    # returns true in the cases like 1 of 1000_9999
    def one_thousand?(digit, group)
      digit == 1 && group == 3
    end

    # returns true in the cases like 2 of 2000_9999
    def two_or_more_thousands?(digit, group)
      digit != 1 && group.nonzero?
    end

    # returns true in the cases like 1 of 9111_9999
    def none_thousand?(digit, group)
      digit == 1 && group.nonzero?
    end

    # returns true in the cases like 0 of 1_0000_9999
    def any_zeroes_on_4_digits_in_group?(digit, group, index, target)
      digit.nonzero? && group.zero? && target[index, 4] != [0, 0, 0, 0] && index.nonzero?
    end

    # returns true in the cases like 1, 1 of 21, 1 of 321, 1 of 4321
    def nonzero_on_highest_digit_of_group?(digit, group)
      digit.nonzero? && group.zero?
    end

    def add_grouping(parts, iteration, lots)
      grouping = lots[iteration - 1]
      return unless grouping

      parts << grouping
    end
  end
end

当初はifcaseの階層が5つぐらいになってしまったので、条件をメソッドに切り出して階層を浅くしてみたものの、全然読みやすくなりませんでした😇

しかもフレームワークにちゃんと乗っていなかったので小数点が扱えていなかった…

とはいえかろうじて動いたので、この時点で作ったspecが最終的なコードを書くときに役立ちました😋

今度はひらがな読みやってみようかな。

関連記事

Ruby: CSVでヘッダとボディを同時に定義するやり方

週刊Railsウォッチ(20191105前編)Rails 6のデフォルト設定解説、DHHも消したいaccepts_nested_attributes_for、スライド『実践Railsアプリケーション設計』ほか

$
0
0

こんにちは、hachi8833です。今年の3連休は昨日のでおしまいだそうです。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、来週11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇

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

今週は平和に公式情報からです。

⚓Active Storage blobのパーマネントURLを公開可能に

blob向けのパーマネントURL。
configurations.ymlでサービスのキーにpublic: true | falseを設定することで、サービスのblobをpublicまたはprivateにできる。publicなサービスは常にパーマネントURLを返すようになる。
Blob#service_urlは非推奨になり、Blob#urlが推奨される。
changelogより

# activestorage/test/service/s3_public_service_test.rb
+# frozen_string_literal: true
+
+require "service/shared_service_tests"
+require "net/http"
+require "database/setup"

+if SERVICE_CONFIGURATIONS[:s3]
+  class ActiveStorage::Service::S3PublicServiceTest < ActiveSupport::TestCase
+    SERVICE = ActiveStorage::Service.configure(:s3_public, +SERVICE_CONFIGURATIONS)
+
+    include ActiveStorage::Service::SharedServiceTests
+
+    test "public URL generation" do
+      url = @service.url(@key, filename: +ActiveStorage::Filename.new("avatar.png"))
+
+      assert_match(/.*\.s3\.amazonaws\.com\/.*\/#{@key}/, url)
+
+      response = Net::HTTP.get_response(URI(url))
+      assert_equal "200", response.code
+    end
+  end
+else
+  puts "Skipping S3 Public Service tests because no S3 configuration was supplied"
+end

つっつきボイス:「issue #31419の、Active StorageのService APIからファイルへのアクセスも許可したいという流れで入ったPRだそうです」「ほほう😋」「パーマネントURLって何を指してるのかな?🤔」「#31419にいいね👍が48個もついてるのでみんな欲しがってるっぽい😆

ActiveStorage::Serviceの現時点のAPIではurlメソッドでしかリンクを取得できず、返されるpublic URLはほとんどのサービスでは同じタイムフレーム内で期限切れになる。サービスからファイルオブジェクトにもアクセスできるfileメソッド的なものがあれば、ファイルの公開方法をより柔軟にできて便利になると思われる。
#31419より大意

「#36729を見ると(ストレージ)プロバイダのpublic URLでは一般にファイル名をカスタマイズできないので、現状はpublic bucketとprivate bucketのディレクトリ構造が違ってしまっている: この修正ではconfigで設定すればS3やAzureやGCSで/キー/ファイル名の固定URLで統一的にアップロードして後でそのURLでダウンロードできるようにする、という感じのようです🤔」「まだActive Storageちゃんと使ってないけど、あるとうれしい機能らしいということはわかった😆

⚓has_manyのinverseが設定可能になった

この間取り上げた#34533の続きだそうです(ウォッチ20191021)が、#37413は取り上げてませんでした。

# activerecord/lib/active_record/railtie.rb#L29
    config.active_record.use_schema_cache_dump = true
    config.active_record.maintain_test_schema = true
+   config.active_record.has_many_inversing = false

つっつきボイス:「先週はhas_many関連が豊作でしたけど😆#34533で入ったhas_manyのinverseを利用できるようにするかどうかを設定で選べるようになったそうです(デフォルトはfalse)」「has_manyでinverseできるのが本来だけどbreaking changeになるから設定を増やしたのね☺

⚓inflectorが:zeitwerkモードでオーバーライド可能になった

# activesupport/lib/active_support/dependencies/zeitwerk_integration.rb#L56
      module Inflector
+       # Concurrent::Map is not needed. This is a private class, and overrides
+       # must be defined while the application boots.
+       @overrides = {}

-       def self.camelize(basename, _abspath)
+         basename.camelize
+         @overrides[basename] || basename.camelize
+       end
+
+       def self.inflect(overrides)
+         @overrides.merge!(overrides)
+       end
      end

つっつきボイス:「Active SupportのinflectorはRailsで名前の単数形複数形みたいな活用形(inflection)を制御するヤツですね☺」「@overridesというハッシュがあればオーバーライドできると」「特殊な活用形をここに入れられる感じですね😋」「basenameがあればよし、なければcamelizeすると」

「Railsガイドの更新↓を見るとautoloaderでこうやって活用形を定義できるとありますね」「html_parserHTMLParserに変換するヤツわかる〜😆」「HtmlParserだと違う感ありますね😆」「SslErrorも違う感😆」「Htmlは現実に使われちゃってるところもあったりするのでワンチャンありな気もしなくもないけど😆」「HTMLは大文字にしたいです〜😭

以下のようにすることでActive Supportの活用形がグローバルに効く。アプリケーションによってはこれでもよいが、Active Supportでデフォルトのinflectorにオーバーライドのコレクションを渡してbasenameを個別にcamerizeすることもできる。
ガイド更新分より大意

# guides/source/autoloading_and_reloading_constants.md#L289
# config/initializers/zeitwerk.rb
-inflector = Object.new
-def inflector.camelize(basename, _abspath)
-  basename == "html_parser" ? "HTMLParser" : basename.camelize
-end
-
Rails.autoloaders.each do |autoloader|
-  autoloader.inflector = inflector
+  autoloader.inflector.inflect(
+   "html_parser" => "HTMLParser",
+   "ssl_error"   => "SSLError"
+ )
end

「inflectionってときどきinfection(感染)と間違えそうになります😆

⚓ルーティングのマッパーでHTTPのOPTIONSをサポート

# actionpack/lib/action_dispatch/routing/mapper.rb#L752
+       # Define a route that only recognizes HTTP OPTIONS.
+       # For supported arguments, see match[rdoc-ref:Base#match]
+       #
+       #   options 'carrots', to: 'food#carrots'
+       def options(*args, &block)
+         map_method(:options, args, &block)
+       end

つっつきボイス:「何とHTTPのOPTIONS verbがルーティングマッパーで初めてサポートされたそうです」「あれ〜今までなかった?」「OPTIONSってそういえばあったわ😆

参考: OPTIONS - HTTP | MDN

curl -X OPTIONS http://example.org -i

HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Thu, 13 Oct 2016 11:45:00 GMT
Expires: Thu, 20 Oct 2016 11:45:00 GMT
Server: EOS (lax004/2813)
x-ec-custom-error: 1
Content-Length: 0
developer.mozilla.orgより

「新機能にしてはテスト無しでズコっと入ってますね😆」「今更ですけどmapper.rbのコード↓めちゃ長い〜😇」「2300行😇

「#37370のプルリク↓見ると、今まではmatchを使わないと書けなかったのか」「シンタックスシュガーというか😳」「OPTIONS何に使うんだろう😆」「要るのかどうかは知らないけど😆」「クローラーとかで使いそうですけど、業務だとあまり使わないかな?」「とも言い切れなさそう😆

# 同PRより
# before
match 'bar', to: 'foo#bar', via: :options

# after
options 'bar', to: 'foo#bar'

「ルーティングといえば、以前つっつきでmorimorihogeさんが『Railsのルーティングは組み合わさったときにどう動くのかがわからなくてつらい』って言ってましたね(ウォッチ20180406)」「ほんにそれ: ひとつひとつのAPIにはドキュメントがあるけど組み合わせたときがマジむずいし、しょうがないからルーティングを切ってはアクセスしてみて動いた〜とか動かない〜とかやってますし😭

Railsのルーティングを極める(前編)

⚓番外: RailsはまだSameSite=Noneパッチがマージされていない


つっつきボイス:「前回のウォッチのレビュー中に教わった情報で、cookieにSameSite=Noneを設定していないサイトは来年2月からChromeでSameSite=Laxとみなすぞということだそうです」「Laxって『ゆるい』ってことか😆」「Railsではそれに対応する2017年の#28297がまだマージされてないそうです😳

参考: Chrome で SameSite=None に関する Cookieについての警告が表示される | ラボラジアン
参考: CookieのSameSite属性 NoneとLaxの違い - Qiita

#28297の最新のコメントを見ると『現在のメンテナンスポリシーによるとこれが入るのは早くてRails 6.1で、バックポートはされないかも』とあります。

⚓Rails

なお、今週のRuby Weeklyの末尾のICYMIがなかなかよさそうなエントリでした。


つっつきボイス:「ICYMIを調べたら『In case you missed it』の略で『もしご存知なければ』という感じですね☺

「その中でこの記事↓はリードオンリーのRailsコンソールを使う方法の解説です」「productionのデータをぶっ壊さずにコンソール使いたいときはあるかも☺」「dry run的な」「saveやめろぉぉみたいなことがなくなるのはよさそう😆」「ローカルでもリードオンリーコンソールをやりたいことはあったりしますね: 頑張って作ったテストデータをうっかり壊したくないときとか😋

参考: How to Setup a Readonly Rails Console - DEV Community 👩‍💻👨‍💻

⚓Rails 6の新しいデフォルト設定の意味と、安全にコメント解除する方法(Ruby Weeklyより)

同記事より(長いのでRails.application.config.は略しました):

  • action_view.default_enforce_utf8 = false
  • action_dispatch.use_cookies_with_metadata = true
  • action_dispatch.return_only_media_type_on_content_type = false
  • active_job.return_false_on_aborted_enqueue = true
  • active_storage.queues.analysis = :active_storage_analysis
  • active_storage.queues.purge = :active_storage_purge
  • active_storage.replace_on_assign_to_many = true
  • action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
  • active_record.collection_cache_versioning = true
  • config.autoloader = :zeitwerk

つっつきボイス:「この記事はRails 6で追加された新しい設定のいくつかを詳しく解説して、移行時に設定のコメントを安全に外す方法も書かれています」「おほ😍

「バージョンアップのたびにconfigの項目って増えますよね😆」「それはしゃーない😆」「RailsのconfigについてはRailsガイド↓にもありますけど最小限しか書かれていないことが多いので😅

「たとえばaction_dispatch.use_cookies_with_metadata = trueを有効にするとpurposeフィールドをcookieに追加してから署名・暗号化する、その代わり一度有効にしたらRails 5.xにダウングレードできなくなる、という具合」「へぇ〜😳」「引き返せない設定😇

action_dispatch.return_only_media_type_on_content_type = falseも長いですけど😆、有効にするとcontent_typeでmedia type以外の値(charset=utf-8など)も含まれるようになる」「最近のcontent_type周りの修正に関連してそう🤔ウォッチ20190902)」

active_job.return_false_on_aborted_enqueue = trueはActive Jobですね」「Active Jobでthrow(:abort)できる↓って知らなかった〜😳」「そこの挙動を変えられるんですね」

# 同記事より
class MyJob < ApplicationJob
  before_enqueue { |job| throw(:abort) if job.arguments.first }
  def perform; end
end

job1 = MyJob.perform_later(false)
job2 = MyJob.perform_later(true) 

active_storage.queues.analysis = :active_storage_analysis」「Active Storageで何か分析してくれるのかな?😆」「あ、画像のheightwidthを取ってmini_magickで使ったりできるのか」

[Rails] MiniMagickでPDFのページ数を取得するときはフォントエラーに注意!

「きりがないのでこの辺で止めますけど、Railsガイドだけだとわからない情報があってよさそうですね😍」「configできれば触りたくないけど😢」「必要になったらこの記事を泣きながら読むことになるんでしょうね😆」「ありそう😆」「この記事翻訳したいです😋

⚓Rails 6でビューヘルパーのimage_altが削除

たしかに最新のapi.rubyonrails.orgから消えています。

# api.rubyonrails.orgより
image_alt('rails.png')
# => Rails

image_alt('hyphenated-file-name.png')
# => Hyphenated file name

image_alt('underscored_file_name.png')
# => Underscored file name

つっつきボイス:「image_altヘルパーがRails 6から消えたそうです」「そういえば最近は自分でaltを設定しないといけないことになってた気がするけどそれかな😎」「image_altがファイル名から適当に推測して生成するaltテキストがスクリーンリーダーなどでいろいろ具合がよくなかったそうです」「やはりaltぐらい自分で書けと」「書きたくないけど😆

後で調べると、Rails 5.2でimage_altが非推奨になっていました↓。image_tagからの呼び出しも削除されたそうです。

⚓DHHも消したがっているaccepts_nested_attributes_for


#26976より

上はDiscourseのclean-railsで知りました↓。


つっつきボイス:「以前から評判のよろしくないaccepts_nested_attributes_forですけど(ウォッチ20180820)、少なくとも2016年の時点ではDHHも殺したいと思っていることを上で知りました😆」「これは殺していいと思う🙋‍♀️」「使ったことあるけど心底つらかった〜😇」「Discourseでjoker1007さんも滅びるべきと書いてますね」「自分もjoker1007さんに全面同意🙋‍♀️

「DHHもコメントで『新しいAPIとして推奨すべきでない』『むしろコントローラで手動でやる方法を示すべき』と書いてますね」「わかる〜😂

「joker1007さんのコメントでも言及されているけど、こういうのはむしろJSON構造から攻略するのがいいんじゃないかって自分も思いますし☺」「なるほど!」「動的にフォームの項目を増やすんなら結局JavaScriptのお世話になりますし、そうやってJavaScript使ったのに結局素のフォームだったら意味ないので、素直にAPI叩けばええやんって思いますし😆

⚓accepts_nested_attributes_forはデモ用なのか?

「ここは自分の推測でしかないんですけど、accepts_nested_attributes_forってもしかすると『Railsなら15分でアプリを書けまっせドヤァ😎』みたいなデモ用なんじゃないかって今思いました」「あ〜何だかわかる気がします😳」「ほとんど何も書かなくてもよしなにやれるあたりとか、そういう用途だと有用なんですよ」「ところがそれを真に受けて業務で使うと途端に破綻するという🤣」「🤣」「モデルもビューも結構癒着しますし😇、これって単純に追加して保存できるだけなんじゃね?って」

「こんな例えが合ってるかどうかわかりませんけど、楽器のキーボードについているデモ演奏ボタン↓にちょっと似ているかもですね😆」「それそれっ🤣」「そのデモ演奏ボタンを本番のライブで無理やり使おうとしているみたいな😆」「デモ演奏だとテンポも変えられませんし😆

「まあそういう感じのデモ機能って15年ぐらい前に流行りましたよね☺」「ボタン一発でブログサイトを作れますとか😆」「そういうデモでhas_many周りを一気にやれるのを見せるのはとってもインパクトあるんですけど、実際には使えないという🤣」「deleteってどうすんの?みたいなレベルで既に悩む」「で建て増しを繰り返すうちに結局詰んだり😇

「idがついてないとcreateだし、idがついていればupdateだし、deleteフラグが立ってればdestroyするし、というのを一見同時にやれそうな気がするんですよ: でも誰もコントロールできない😆」「スレッドセーフとかも大丈夫かどうかよくわからないし😅」「歴史調べてないのであくまで推測&印象😆」「自分一人しか使わない管理画面で、かつ重要じゃないデータを手軽に出したいみたいなユースケースならaccepts_nested_attributes_forはまだワンチャンあるかなって思います☺

「なおaccepts_nested_attributes_forはRails 2.3からあるそうです↓」「割と古くからあった気はします」「さすがに最初期からではなかった😆

⚓Meetup for Rails engineersのスライド3つ


connpass.comより


つっつきボイス:「こちらのイベントを見逃してて、終わってから気づきました😅」「3つは追いきれないので、とりあえず『実践Railsアプリケーション設計』のスライドを見てみましょうか😋

⚓『実践Railsアプリケーション設計』

つっつきではかなり盛り上がりましたが、記事にすると多すぎるので間引いています🙇

「『実装とテストは資料が多いけど、設計の書籍は抽象的な内容が多い』と」「これホントにそう!外部設計と内部設計の本ってたいてい抽象論になっちゃう😭」「なるべく具体的な設計の過程を知りたいですよね」「ところが設計を具体的に書くと、今度は分量が増える割には価値が薄くなっちゃうという😇」「読んだ人にとっては自分の業務に合わないとかが続出しちゃうんですよ😢」「言語が違うだけでも大きく変わりますし」「設計論を具体的に書くと一般性が損なわれちゃうんですね😅

「しかも本当に具体的な設計ってビジネス上の機密に直結しちゃうから、そういうものほど本にできない😆」「そうそう😆

「このスライドでは以下に絞って話を進めていますね」

「要件から重要な名詞と動詞を抽出して、概念を固めたうえで関係をまとめる↓」「つまりエンティティに適切な名前を与える」「そこが超重要👍」「設計って実はものすごく日本語力を要求されるんですよ」「誰が見ても誤解しない名前をつけるのが大事」

「最近自分が設計するときは、いきなり英語名を付けないように注意してますね☺

「これが実際の名前か↓」「ReassociatedRequestだと受動態か完了形か迷っちゃうので、個人的にはReassociationRequestとしたい気がしますけど😆」「実際の業務を見ないとどっちが適切か判断難しいですね😅」「やっぱり名前むずい😭

「このスライドいいですね👍」「ここに書ききれないぐらいいい話がいっぱい出ました😂」「こういうスライドを元に強い人が解説するのがよい気がしました😋

⚓でかい画像を正しく扱う


つっつきボイス:「お馴染みEvil Martiansの記事で、Railsに限定せずにでかい画像を適切に扱う方法を解説しています」「こんな図も↓」「かなり長い記事…😅


同記事より

⚓TimeWithZoneクラス


つっつきボイス:「ベストマサフミさんの短い記事です」「:dbto_timeで変換しないとUTCになっちゃうのか😳

⚓その他Rails

つっつきボイス:「RAILS_ENV=stagingはたしかに悪い文化!」「productionとstagingで環境を分ける意味ってあんまりなくて、stagingはデータが本物ならproductionになれるようにするのが正確ですね☺」「でないとif stagingみたいなのができてだんだんつらくなるし」「挙句の果てにstagingはよくてもproductionでコケたりしますし😇」「そしてproductionはデプロイしないとどうなるかわかりません、になって本末転倒になると😆


つっつきボイス:「まだbetaだそうですが気になりますね😋」「Railsとフロントエンドってそんなに相性悪くないと自分は思うんですけどね☺」「仲良くなれないという思い込みもあるのかも?」「たぶんね」「でもたぶん仲良くはなれない🤣」「🤣


前編は以上です。

おたより発掘

こちらこそありがとうございます🙇

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191029後編)Ruby 2.7.0-preview2、tapping_device gemとhumanize gem、平成Ruby会議ほか

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

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

Rails公式ニュース

Ruby Weekly

週刊Railsウォッチ(20191106後編)holiday_japan gemで日本の祝日判定、小さい関数が有害になるとき、Gitブランチのファジー検索ほか

$
0
0

こんにちは、hachi8833です。明日から始まるRubyWorld ConferenceにBPSのmorimorihogeも参加いたします。本日現地入りしていますので見かけたらお気軽にお声掛けください🙇(Twitter: @morimorihoge)。

RubyWorld Conference 2019にスポンサー登録しました


  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、来週11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇

⚓Ruby

⚓「小さい関数は有害」か?


つっつきボイス:「Rubyに限らない設計話で、少々長いです」「あえて逆説的な煽りタイトル付けてる感じ☺」「小さい関数有用ですよ何言ってるんですか😆」「普通そう思いますよね😆」「メソッドを切り分けないでやれると思っている人がいるのがいつも不思議でしょうがないですよもう😆

「たぶん関数は小さければいいというものではないということを言ってそう🤔」「記事の雰囲気としては、メソッドの大きい小さいというより設計や抽象化の間違いを問題視してるのかなと☺」「大きい小さいが問題ではないと自分も思いますし」「ネーミングがあかんとかね😆

後でざっと中身を追いかけてみました。classitisという見慣れない造語が出てきたので仮訳にしてみました。

見出しより:

  • 小さい関数のメリットとされている点
    • 1つのことだけをやる
  • DRY絶対視に潜む誤り
  • 名前を付けるのは難しい
  • コードの局所性が失われる
  • クラスの「汚染」
  • 引数を減らすと依存関係が見えにくくなる
  • 小さい関数が増えると読みづらくなる
  • 「薄っぺらなモジュール」と「クラス増殖症(classitis)」の問題
  • 小さい関数に意味がある場合
    • ネットワークI/O
    • プロパティベースのテスト

Sandi Metz氏もAll The Little Thingで「誤った抽象化より、コード重複の方が遥かにコストが低い」「ゆえに誤った抽象化より重複が望ましい」と述べています。
同記事トップハイライトより大意



同記事で引用されている「The Tower of Abstraction」スライドより

同記事で引用されている「The Tower of Abstraction」というスライドは惜しくもSpeakerDeckから失われているので、動画だけ貼っておきます。

⚓holiday_japan: 日本の祝日判定gem


つっつきボイス:「ruby-jp Slackで見かけました」「今年来年あたりの日本の祝日はざわついてますから😆」「ざわついてる😆」「前の天皇誕生日は消えるんですよね?」「名前変えて復活するかもですし」「そうやって祝日が増えていくと」「10万年も経てば365日全部祝日になる😆」「んなこたーない😆

「おお、天皇の即位の日がちゃんと出てる↓❤」「即位礼正殿の儀も😋」「人力でメンテしてるんでしょうね」「そりゃそうですよ🤣」「コンピュータには無理」

# 同リポジトリより
$ ruby -r holiday_japan -e 'HolidayJapan.print_year 2019'
listing year 2019...
2019-01-01 Tue 元日
2019-01-14 Mon 成人の日
2019-02-11 Mon 建国記念の日
2019-03-21 Thu 春分の日
2019-04-29 Mon 昭和の日
2019-04-30 Tue 国民の休日
2019-05-01 Wed 天皇の即位の日
2019-05-02 Thu 国民の休日
2019-05-03 Fri 憲法記念日
2019-05-04 Sat みどりの日
2019-05-05 Sun こどもの日
2019-05-06 Mon 振替休日
2019-07-15 Mon 海の日
2019-08-11 Sun 山の日
2019-08-12 Mon 振替休日
2019-09-16 Mon 敬老の日
2019-09-23 Mon 秋分の日
2019-10-14 Mon 体育の日
2019-10-22 Tue 即位礼正殿の儀
2019-11-03 Sun 文化の日
2019-11-04 Mon 振替休日
2019-11-23 Sat 勤労感謝の日

⚓小賢しいコードは書いてくれるな(Ruby Weeklyより)

# 同記事より: 小賢しくないコード例
class LogController
  def initialize(progname = nil)
    logger_backends = {
      'ELASTICSEARCH' => EsLog.new(progname),
      'POSTGRES'      => PgLog.new(progname),
      'SQLITE'        => SqlLog.new(progname),
    }
    @loggers = Settings.log_backend_priorities.split(',').map { |k| logger_backends[k] }
  end

  def where(query)
    @loggers.each do |logger|
      results = logger.where(query)
      return results if results.present?
    end
  end

  def process(log)
    @loggers.each do |logger|
      return if logger.process(log)
    end
  end
end

つっつきボイス:「短い記事ですが、clever codeは『小賢しい』『猪口才な』コードかなと」「これはいい表現かも😍」「要はトリッキーなコードに走るなということでしょうね☺」「ひねらずに上みたいに素直に書いてくれと」「これはまあ麻疹みたいなもので、一度はこういう道を通るんですっ😆」「悪例みたくrecallとかwhereとか書いてこじらせたくなる時期はある😆」「結構いい年になってもやってる人、いますよ😆

「小賢しくなっちゃったコードって、書いた本人としては愛着が湧いてしまってプッシュしたくてしょうがなくなりがち😆」「そこをうまく分離できるといいかも☺」「翻訳でも同じ現象ありますね: 訳した直後だと愛着が湧いて変えたくない気持ちになるんですけど、翌日頭を冷やしてもう一度見直すとあれ?となったりします😆

⚓DB

⚓クラウドネイティブの地理分散SQLアプリを低レイテンシーで構築するテクニック9つ(DB Weeklyより)


同記事より

見出しより:

  • 地理分散SQLはRDBMSの未来
    • マルチゾーン
    • マルチリージョン
    • マルチクラウド
  • 広域ネットワークのレイテンシを懸念する理由
  • シングルロー(single-row)トランザクションのレイテンシを下げる4つの手法
  • マルチロー(multi-row)トランザクションのレイテンシを下げる5つの手法

つっつきボイス:「地理的に分散しているSQLアプリのレイテンシを下げる手法の解説記事です」「クラウドベンダーを複数使うとか大変そう😅」「leaderとかfollowerとあるのはプライマリとセカンダリに相当するのかな🤔」「普段なかなかお目にかかれない大規模な話がいっぱい😳」「リードオンリーレプリカなら何とか😅」「オリンピックのサイト構築みたいに世界中から大量アクセスされるシステムだとこういうノウハウが必要になるんでしょうね」

「クォーラム(quorum)って何でしたっけ?」「用語としてはWikipediaのこれですね↓」「一種の多数決ですか」「手元の辞書によるとやっぱりラテン語由来でした😳

quorumとは分散システムにおいて、分散トランザクションが処理を実行するために必要な最低限の票の数である。quorumベースの技術は分散システムにおいて、処理の整合性をとるために実装される。
Wikipediaより

参考: Quorum - Wikipedia

quorum: 〔議決に必要な〕定足数◆イギリスで治安判事(justices of the peace)を任命するとき、多くの判事の中から(任命状の「その中で(of whom)」にあたるラテン語がquorumだった)執行に必要不可欠な判事を指名し、これらの判事がjustices of quorumと呼ばれたことから。「定足数」の意味で使われるようになったのは1616年から。


「話は逸れますけど、こういうクォーラム的なアイデアを最初に見たのは小松左京の『果しなき流れの果に』でした: 静止軌道上の複数の衛星にそれぞれコンピュータが搭載されていて、それらの結果の多数決を取って使うというものですが、何しろ当時は大型コンピュータの時代だったのでそれだけでびっくりできました😅

参考: 果しなき流れの果に - Wikipedia

「私はこっち〜↓😆」「エヴァだ😆」「バルタザール、メルキオール、キャスパーで合議するヤツ✝」「昔みんなで力を合わせて作ったLinuxサーバーのホスト名がその3つでした😆」「もろに誰もが通る道ですね〜、4つ目のホスト名が早速足りなくなりますけど😆」「ホスト名から機能がわからないし名前足りなくなるしで、命名体系を考える反省材料になりました😅

参考: 新世紀エヴァンゲリオンの用語一覧 - Wikipedia

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

⚓GoogleのBorgとは


つっつきボイス:「はてブに上がってた記事ですがこちらもデカい話😳」「GKE(Google Kubernetes Engine)やGoogle App Engineの実行基盤だから完全にクラウドのバックエンドですね」「絶対ダウンしてはならないとか、もう自分らから直接見えない世界😅」「日本語で読み解いてくれている方に感謝🙏


同記事より

「2015年にBorgの資料が公開されていたんですね↓」「Googleも草創期に比べてだいぶ落ち着いてきたからこの辺の情報を出してもいいかという感じになったのかも☺

参考: GoogleがBorgの詳細を公開

Borgで開発を行っていた技術者の多くは,現在はKubernetesに移行している。
infoq.comより

⚓その他クラウド


つっつきボイス:「Unixの偉い人であるカーニハンがKindle本出したそうです」「2,300円はそこそこ高いか😅」「memoir?」「思い出(memory)をフランス語でオシャレっぽく言ってみるテストでしょうね😆

参考: ブライアン・カーニハン - Wikipedia

Kindle Unlimitedで読めることに後から気づきました😋。200ページ足らずです。



つっつきボイス:「自分もこういうこと言ってみたくなるお年頃〜😅: プログラミングやるならアセンブラからとか基盤から作ろうとか」「WebやるならCGIからとか、いくらでも遡れちゃう😆」「個人差はたしかにありますね☺

⚓JavaScript

⚓Electron 7.0.0リリース(JavaScript Weeklyより)

主な変更は「Arm(64bit)のWindows対応」「TypeScript Definitionsジェネレータに移行」「非推奨APIの削除」などです。


つっつきボイス:「ElectronはChromiumエンジンでクロスプラットフォームなデスクトップアプリを作る基盤で、自分は間接的にですが以下のEpichrome↓とか結構使ってます」

Mac: Chromeブラウザの大量のタブをEpichrome.appですっきりさせよう


electronjs.orgより

「ElectronはChromiumエンジンを丸呑みしているのでお手軽にクロスプラットフォームやれます😋」「JS/HTML/CSSでブラウザアプリっぽくデスクトップアプリを作れると、なるほど😋」「その代わりエンジン丸呑みなのでアプリのサイズが結構でかくて😅

「今どきデスクトップアプリを作りたいとかあります?😆」「自分はChromeのタブがすぐ数百個とかになりがちだったので、タブを減らしたくてEpichromeで別アプリに逃がすことが多いです」「気持ちはわかります☺


「何年か前に、Go言語で書いたHTTPアプリを無理やりElectronに埋めてデスクトップアプリを作ってみたことがあって一応動いたんですが、Appストアに登録する手続きとか証明書の埋め込みとかが面倒でそれきりです😇」「それは審査通らなさそうな雰囲気😆」「当時は前例のない形態のアプリでしたし😅」「キワモノアプリ😆

今はこういうのもあるんですね↓。#194を見ると証明書周りは未実装でした。

参考: [go-astilectron]D&DでGoモジュール側にファイルパスを受け渡す - Qiita

⚓CSS/HTML/フロントエンド/テスト

⚓デザインと目の錯覚


つっつきボイス:「角の丸いレクタングルの縦が揃っているはずなのに揃って見えない、みたいな錯覚について解説している記事ですね」「なるほど、こういうのとかね↓」「並べたアイコンががたぴしして見えるとか」「BPS社内のデザインチームにもSlackで聞いてみたんですが、この記事で扱っている内容はデザイン界隈ではだいたい常識だそうです」


同記事より

optical weightとかジャストロー錯視って初めて見る用語😳」「開発者もこういう記事を一度ざざっと見ておくとよさそう😋」「デザイナーのお気持ちを理解するためにも」

参考: 「ジャストロー錯視」What You Get Is What You See by dotimpact - DMM.make

⚓クロスブラウザテストとデバイスファーム

つっつきボイス:「次の2つはBPS社内Slackで話題になったスライドです」

「クロスブラウザテストは闇しかない、間違いない😇」「BrowserStackという有料サービスはこういうときに有能という話も出ましたね(ウォッチ20190320)」「BrowserStackはエミュレーションとかじゃなくてガチのハードウェア上で動いているヤツ」「スライドにもありますけど海外のサービスなので重いのが難点😅


browserstack.comより

「ついこの間Seleniumをインストールするのに2時間以上かかったんですけど、これは止めた方がいいヤツという心の声に従って引き返しました😆」「きっと止めて正解😆」「掴んだ瞬間にビビッと嫌な予感がしたら当たり牌🀄」「インストールできたところで何が担保できているのか自分的には謎ですし😆

「スライドではBrowserStack以外の海外デバイスファームも紹介されていますね↓😋


causelabs.comより


crossbrowsertesting.comより

⚓スライド: Webデザインドリル


つっつきボイス:「こちらも社内Slackから」「後半はデザイナ向けですが、中盤までは普通にHTMLコーディングしたりシステム開発する人達もおさえておくべきな感じありますね: 穴埋めドリルはともかく資料としては良くまとまってると思います👍」「😍

⚓言語・ツール

⚓Gitブランチのファジー検索


同記事より


つっつきボイス:「Gitブランチをインクリメンタル検索で絞り込むシェル関数が紹介されています」「あ〜これいいかも😍」「シンプルだけど効果大」「後で入れてみよっと😋

「なお上のシェル関数では、以下の記事↓で紹介されていたfzfというpecoに似たツールを使ってますね」

モダンな開発用ターミナル環境のためのツール紹介

「ちょうど今ローカルブランチが死ぬほどあって困ってるんですけど、ブランチ名にissue番号入れろというレギュレーションかけたのも自分なんですよね〜これが😆」「GitHubならサイトのブランチ名コピー機能で少し楽にやれますけど😋」「GitはGUIで使うと決めていたのに、最近何だかCLI操作がじわじわ増えてきてるという😆」「自分は逆にGUIの方が慣れてなくてコワいです😅

⚓その他

⚓昆虫を搭載


つっつきボイス:「昆虫の玉乗りで乗り物を制御させるという試みだそうです」「記事の写真の真ん中辺にある、羽の生えたこのちっちゃいのが虫?!」「オスのカイコガがメスの匂いにめちゃくちゃ敏感なのを利用してるんですって」「玉転がしという進行方向と逆の運動なのに、昆虫があっという間に適応するのがびっくりでした😳」「何だかこえぇ〜😱」「ある意味恐ろしい話😅」「いい悪いは別にして、昆虫ハックはまだまだやれそうな予感😆

⚓手書き数字の地域差


つっつきボイス:「手書き数字の地域差って意外に大きい…」「7にスラッシュ追加するのは何となく前からやってました」「ヨーロッパの手書きの4ってほとんどイナズマっぽく書かれることもあるみたい😳

「欧米のはまだわかるんですけど、アラビア数字の手書き(下の段↓)はさらにスゴいことに」


ameblo.jp/fujisakalughalughaより

参考: 今日のアラビア語。◇文法編◇#12【数字の表記】 | 藤坂託実の語学散策 ~アラビア語とその他諸々~

「オレの知ってるアラビア数字じゃない😇」「9以外ほとんど似てない😆」「2と3は90度回転したようにも見えなくもないけど」「4もちょいイナズマっぽくはあるけど」「アラビア数字とアラビアの数字って別物なんですね…」「どこでどう枝分かれしたのやら😆

参考: アラビア数字 - Wikipedia

⚓番外

⚓PならばQ


つっつきボイス:「以前見に行ったロマンティック数学ナイトというイベントの昔のスライドです」「行けたら行く、は行かないと同義😆」「これ好きっ😆」「身に沁みる😆


romanticmathnight.orgより

参考: 論理包含 - Wikipedia

「推論の論理包含って、ANDやORやXORと違ってベン図や真理値表↓が非対称ですよね」「pが偽だったらqはどっちでもええ😆」「こうすると対偶が取れるんだったかな?🤔

参考: pならばqの真偽


geisya.or.jpより

「『叱られないと勉強しない』の対偶を考える問題も好きです: ついうっかり『勉強すると叱られる』と答えそうになる😆」「😆」「たぶん『勉強しているということは叱られた』みたいに時間の前後関係も考慮しないといけないかも🤔

参考: 【対偶】怒らないと勉強しない?? | 数学美術館


後編は以上です。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191105前編)Rails 6のデフォルト設定解説、DHHも消したいaccepts_nested_attributes_for、スライド『実践Railsアプリケーション設計』ほか

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

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

Ruby Weekly

DB Weekly

db_weekly_banner

JavaScript Weekly

javascriptweekly_logo_captured

週刊Railsウォッチ(20191107前編)Active Recordモデルをprivateで封じ込める、心折れないRailsスキーマ管理、Railsセッションをクロスドメイン共有ほか

$
0
0

こんにちは、hachi8833です。Rails 6.0.1が先週リリースされましたね🎉

Rails 6.0.1がリリース!修正を追ってみました

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、来週11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇

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

公式情報を中心に見繕いました。

⚓マルチDBのマイグレーション後に同じデータベースに再接続するよう修正

標準的なマルチDBセットアップで、2番目のデータベースがレプリカでなく、独立したテーブルセットを持っている状態で以下の非常にシンプルなタスクがあり、rails db:migrate fooを実行したとする。

task foo: :environment do
  puts User.last # or some model with a table that only exists in the primary db
end

実際の振る舞い: establish_connectionがマイグレーションタスクごとに実行されるため、プライマリではなく直前のマイグレーション対象データベースに対してクエリが実行される。
期待される振る舞い: マイグレーションタスクがクリーンアップされ、実行後プライマリ・データベースに再接続される。
#37578より大意


つっつきボイス:「Rails 6のマルチDBがらみの修正ですね」「お、rakeタスクですか」「コネクションをoriginal_configに保存しておいて、終わったらそれを復元しているんですね↓」「コネクションが1つだったら起きなかった問題っぽい」「本番で知らずに切り替わってたらびっくりして目を疑っちゃいそう😇」「実際に使わないと見つけにくそうなバグですね☺

# activerecord/lib/active_record/railties/databases.rake#L81
  desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
  task migrate: :load_config do
+   original_config = ActiveRecord::Base.connection_config
    ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
      ActiveRecord::Base.establish_connection(db_config)
      ActiveRecord::Tasks::DatabaseTasks.migrate
    end
    db_namespace["_dump"].invoke
+ ensure
+   ActiveRecord::Base.establish_connection(original_config)
  end

⚓ローカルキャッシュを改変するときのバグを修正

このテストケースがバグをある意味正しく示してくれると思う。手短に言うと、戻り値の改変からは既に保護されていたにもかかわらず、元の値の改変から保護されていなかった。
同PRより大意

# 同PRより
my_string = "foo"
cache.write('key', my_string)
my_string << "bar"
cache.read('key') # => "foobar"

つっつきボイス:「上のコード例を見る方が早いと思うんですけど、キャッシュの値に入れた元の値を改変するとキャッシュの値まで変わっちゃってるという😳」「これはアカンやつ〜😆」「dupしないでそのまんまキャッシュに入っちゃってたか😇」「それを防ぐためにdup_value!を追加したんですね」

# activesupport/lib/active_support/cache/strategy/local_cache.rb#L60
-         def write_entry(key, value, **options)
-           @data[key] = value
+         def write_entry(key, entry, **options)
+           entry.dup_value!
+           @data[key] = entry
            true
          end

「これに限らずhashやarrayで割とよくあるバグですね☺」「これはあるある」「単純に@data[key] = valueしたら、valueが変わるとキャッシュの値まで変わっちゃう😇」「誰も書き換えなければ問題ないんですけど、キャッシュだから書き換えあるでしょうし😆」「よけてたはずなのにどして?ってなったり😆」「ぱっと見正しそうなだけに見落としそう😅

「まあ誰しもやりそうなバグですから☺」「でもやったらアカン😆」「キャッシュに入れたmy_stringを後生大事に使い回すのがそもそもよくなかったという考え方もあるかも😆」「修正でdup入ったから微妙に遅くなるでしょうね😆

「#37587の場合は同じKeyに対してread / write処理を書いて、かつ元のオブジェクトが参照できるときにしか発生しないので、『同一リクエスト内で同じオブジェクトをwrite / readした』『クラス変数などのリクエストをまたいでメモリにデータを保持する変数でwrite / readした』とかのケースぐらいで、割とレアな気はしますね☺」「おぉ」


「こういうバグが起きにくい言語仕様ってあるのかなって、ついそっちを考えちゃいます😅」「全部値渡しにしたらデカいオブジェクトのコピーが半端ないコストになりますよ😆」「参照で渡したいときとコピーしたいときとありますからね〜☺」「C言語知ってる人にならRubyは基本的にポインタ渡しだよって説明できますけど」「若い人だとポインタ知らなさそう😆

参考: ポインタ (プログラミング) - Wikipedia

なおdup_value!はActiveSupport::Cache::Entryにありますが、なぜかapi.rubyonrails.orgで出てこなかったのでAPIdockを貼ります。

参考: dup_value! (ActiveSupport::Cache::Entry) - APIdock

⚓インラインジョブを別スレッドで実行できるようになった

# activejob/lib/active_job/queue_adapters/inline_adapter.rb#L13
    class InlineAdapter
      def enqueue(job) #:nodoc:
-       Base.execute(job.serialize)
+       Thread.new { Base.execute(job.serialize) }.join
      end

      def enqueue_at(*) #:nodoc:
        raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at https://guides.rubyonrails.org/active_job_basics.html"
      end
    end

つっつきボイス:「インラインジョブを別スレッドで実行できるようにしたそうです」「今までは別スレッドにできなかったと😳」「Thread.newでジョブを実行してからjoinしてますね」「enqueueだし、もしかするとjob.serializeが重いのかも🤔」「joinするということはジョブの完了を待つってことなのかな?」「issueの方を見るとよさそう↓」

「これはThread.newした中でexecuteさせることで直ちにenqueueした処理を解放させて、次のenqueue 待ちをなるべくゼロにしたい、ということだと思います」「おぉ」「これはOSの割り込みハンドラによるコンテキストスイッチ実装とかでもよくある実装方針で、割り込みハンドラはなるべく限界まで小さくする、というやつですね☺(割り込みハンドラの処理中は他の割り込みハンドラを受けられなくなってしまうので、その時間は最小限にするために、割り込みハンドラでは即queueに処理イベントを積むだけ積んでハンドラを脱出する)」「なるほど!」「多分、micro jobが大量に実行されるようなユースケースになってくると、ジョブのスループットに影響が出てくるんだと思われます」

「あ、issueに例のCurrentAttributes↓が登場してます😆」「憎っくきCurrentAttributes😆」「これってグローバルステートだからスレッドからこちょこちょするときに気を付けないといけないんでしょうね☺」「スレッド周り難しくてよくわかんないけど、上の修正は何となくworkaroundっぽい雰囲気🤔」「この修正で切り抜けられるならまあいいのかなと☺」「スレッド生成してもコストはそんなに変わらなさそうではある🤔

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

「上はDHHが入れたCurrentAttributesに反対してた人の記事を翻訳したもので、CurrentAttributesはやりすぎだという主張ですね」「そもそもグローバルステートですし😆」「記事の人はどちらかというと設計として好きになれないみたいです」「わかる😆」「一応CurrentAttributesにはスレッドローカルな変数もあるみたいですけど、それが回り回って今回のissueにつながったんだとしたら何となくわかる気がする☺」「グローバルステートならスレッドセーフであって欲しいですよね」

「こういうCurrentAttributes的な機能って、わかってて使う人にはとっても有用なんですよ😆」「あ〜それはある意味難しい問題😅」「そしてよくわかってない人が飛びつくと詰む、みたいな害の方が大きくなったりしがち😆」「共有情報をスレッドに乗せるみたいなコードをJavaで見たことはあるので、CurrentAttributesがまったくナンセンスということはないんじゃないかとは思いますね☺」「使う人を選ぶ機能😆

⚓GitHub Actionsに対応

# .github/workflows/rubocop.yml
name: RuboCop
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Set up Ruby 2.6
      uses: actions/setup-ruby@v1
      with:
        ruby-version: 2.6.x
    - name: Install required package
      run: |
        sudo apt-get install libmysqlclient-dev libpq-dev libsqlite3-dev libncurses5-dev
+   - name: Cache gems
+     uses: actions/cache@preview
+     with:
+       path: vendor/bundle
+       key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
+       restore-keys: |
+         ${{ runner.os }}-gem-
    - name: Build and run RuboCop
      run: |
+       bundle config path vendor/bundle
        bundle install --jobs 4 --retry 3
        bundle exec rubocop --parallel

つっつきボイス:「これはGitHub向けの改修ですね」「gemのキャッシュと最新のRuboCopに対応」「GitHubがRailsでできてるのは有名ですよね😋」「GitHub Actionsはウォッチでも何度か取り上げましたけど(ウォッチ20190925)結構期待できそう😍

rubocop --parallelって知らなかった😳」「お、これは欲しいかも😋」「Rubocopちゃんで大量修正が必要なプロジェクトなんかでも、1ファイルごとにやれればいいからparallelにすれば速くなりますね😍」「どうせRSpecの方が断然遅いからまあ別にって感じですけど🤣」「🤣

参考: rubocop/basic_usage.md at master · rubocop-hq/rubocop
参考: Rubocop を使っているアナタがするべき2つのこと - Qiita

⚓ActiveStorage blobからrequire_dependencyを排除

# activestorage/app/models/active_storage/blob.rb#L17
class ActiveStorage::Blob < ActiveRecord::Base
- require_dependency "active_storage/blob/analyzable"
- require_dependency "active_storage/blob/identifiable"
- require_dependency "active_storage/blob/representable"
+ unless Rails.autoloaders.zeitwerk_enabled?
+   require_dependency "active_storage/blob/analyzable"
+   require_dependency "active_storage/blob/identifiable"
+   require_dependency "active_storage/blob/representable"
  end

つっつきボイス:「ZeitwerkがRails 6で殺したrequire_dependencyがActiveStorage::Blobにちょっぴり残ってたので排除したという小さい修正です」「サーチアンドデストロイ💀」「一応Zeitwerkなしでもやれるようにしないといけませんし☺」「これで殺戮完了したのかな?」「どうでしょう😆

参考: 定数の自動読み込みと再読み込み (Classic) - Rails ガイド

require_dependencyはRailsで独自に作ったメソッドでしたか〜」「以前のウォッチでも名前空間地獄の話題でrequire_dependencyの話になりましたね(ウォッチ20181022)」「Zeitwerkになってrequire_dependencyが不要になったというかむしろ完全排除される方向に」

Rails 6 Beta2時点のZeitwerk情報(要訳)

⚓ActiveRecord::Baseから不要なrequireを削除

# activerecord/lib/active_record/base.rb#L
-require "yaml"
require "active_support/benchmarkable"
require "active_support/dependencies"
require "active_support/descendants_tracker"
require "active_support/time"
-require "active_support/core_ext/module/attribute_accessors"
-require "active_support/core_ext/array/extract_options"
-require "active_support/core_ext/hash/deep_merge"
-require "active_support/core_ext/hash/slice"
-require "active_support/core_ext/string/behavior"
-require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/introspection"
require "active_support/core_ext/class/subclasses"
require "active_record/attribute_decorators"
require "active_record/define_callbacks"
require "active_record/log_subscriber"
require "active_record/explain_subscriber"
require "active_record/relation/delegation"
require "active_record/attributes"
require "active_record/type_caster"
require "active_record/database_configurations"

つっつきボイス:「Active Record::Baseに要らないrequireが結構残ってたのが削除されてました」「Baseから消えるってなかなかスゴい😆」「core extensionあたりのrequireはいつの間にか冗長になってたんだろうな〜」「あ、requireしなくてもautoloadでモジュールが自動読み込みされるようになってたのか☺」「なるほどね!」

# b2c9ce3より
# activerecord/lib/active_record.rb#58
    autoload :Base
    autoload :Callbacks
+   autoload :Core
    autoload :CounterCache
    autoload :DynamicMatchers
    autoload :DynamicFinderMatch

参考: module function Kernel.#autoload (Ruby 2.6.0)

requireって雑に増えていきがちだからコワくてなかなか消せないのが大変😆」「いつかは消さないといけないんでしょうけど」「かぶっててもfalseが返るだけで害はないのでなかなか消されなさそう😆」「このたびfalseになることが確定したんでしょうね☺

⚓Rails

⚓Active Recordモデルをprivateにして大人しくさせてやった(Ruby Weeklyより)


つっつきボイス:「tameは『飼いならす』ですね😆」「モチベの説明が長いので後で追ってみます」

Account.public_methods.sizeでメソッドが685個出てきた😆」「むちゃくちゃや😆」「これは死にたくなる😇

「これがコンセプトみたいです↓」「むむむ…?Accountクラスを複数形のAccountsモジュールにして、Accounts::Modelnewしてからテーブル名だけ指定し、そしてprivate_constant :Modelでモデルをprivateにした…だと..?うはぁ〜!」「ウケた😆、喜びですか驚きですかあきれてますか?」「いや〜、これは面白い!!🎉

# 同記事より
module Accounts
  # Some important stuff up here, which will get to in a bit

  class Model < ApplicationRecord
    self.table_name = 'accounts'
  end
  private_constant :Model

  # Some important stuff down here, which will get to in a bit
end

「これは最近ActiveModel絡みでよく話している、永続化層切り離しですね〜❤」「おぉ」「上のように書くことでActiveRecordのメソッドのほとんどを殺すことができる: そして以下みたいに欲しいメソッドだけself.fetchとかself.createみたいに書いて単にモデルにdelegateして、かつそのモデルを返している」「いわゆる委譲ですね」

ModelモデルはprivateなのでAccountsモジュールの中なら見えるけど外部からはシャットアウトされる」「Accounts::Modelで外からいじるんじゃねーぞ、と😆」「これ確かに面白〜い!😋」「Rails wayじゃありませんけどね😆

# 同記事より
# app/models/accounts.rb
module Accounts
  def self.fetch(id:)
    Model.find(id)
  end

  def self.create(name:)
    Model.create!(name: name)
  end

  class Model < ApplicationRecord
    self.table_name = 'accounts'
  end
  private_constant :Model
end

「オレが委譲で許した以外の方法でモデルにアクセスするなよと😆」「返すものがモデルじゃなくてリレーションになるとまたちょっと微妙な話になるんですけど☺

「その発展型がこれか↓」「お、最後のAccountは普通のPORO(Pure Old Ruby Object)で、fetchcreateが今度はModelじゃなくてカスタムのAccountクラスを返すようにしたと、ほほぉ〜これはたぶんwhereみたいなリレーションを相手にしたくないと言ってそう😋

# 同記事より
# app/models/accounts.rb
module Accounts

  # --- Public APIs
  def self.fetch(id:)
    db_object = Model.find(id)
    Account.new(
      id: db_object.id,
      name: db_object.name,
    )
  end

  def self.create(name:)
    db_object = Model.create!(name: name)
    Account.new(
      id: db_object.id,
      name: db_object.name,
    )
  end

  # --- Private ActiveRecord model
  class Model < ApplicationRecord
    self.table_name = 'accounts'
  end
  private_constant :Model

  # --- Entity for the outside world
  class Account
    attr_reader :id, :name

    def initialize(id:, name:)
      @id = id
      @name = name
    end
  end
end

「さらにトランザクションもこの形↓でやってるし😳」「業務コードらしくなってきた😋

module Accounts
  def add_seat(id:)
    Model.transaction do
      db_object = Model.find(id)
      db_object.number_of_licenses += 1
      db.object.save!

      if db_object.number_of_licenses == 5
        SalesNotification.create!(account_id: id)
      end
    end
  end

  class SalesNotification
    belongs_to :account
  end
  private_constant :SalesNotification

  # Rest of implementation...
end

「まあこのパターンを既存のRailsアプリでいきなりやるのは無理あるのでそれはおいとくとして、今後はこういうふうに作ってみいやという感じかな〜😆」「自分には、ある意味カプセル化の基本に立ち返ったように見えますね☺」「そう!ちゃんとカプセル化してる」「これを実際にやるかどうかは別としても、ちょっと新鮮ですね😍

「一応記事の末尾にもいろいろ書いてますね: これはRailsのデフォルトのパターンじゃないし、どのActive Recordモデルに適用できるとも限らないと」「そりゃそうだ😆」「機が熟すまでこの設計には飛びつかない方がいいということみたいですね☺」「最初からこう書いていれば無駄なメソッドが600個も生えてこなくて済むでしょうけど😆

「元々Active Recordが継承でやるように作られちゃってるからなんでしょうけど😢」「まあそれはあるかも☺」「この記事を書いた人は、たぶんデータベースに直接触らせたくないマン😆

「このパターンでやれそうな例として『AccountUserをいつも同時に変更しているなら、同じモジュールに入れるべきじゃね?』と思えたときが挙げられてますね」「あ〜わかる!離れているモデルをいつも同時に扱ってわけわからなくなるぐらいだったらモジュールに閉じ込めてprivateにしちまえと😆

「いわゆるPoEAA↓のActiveRecordパターンからきちんとビジネスオブジェクトを切り離す前段階としては悪くなさそうですね: テストコードがあればとりあえず不用意にActiveRecordの呼ばれたくないメソッドを隠蔽できるのはまあ悪くないのかも?(つらそうだけど)」

「それにしても面白いパターンだわ〜😋」「カプセル化としてはとてもキレイではある☺」「これが実際にうまく当てはまる場合って何だろう?ん〜とん〜と🤔」(以下延々)


以下は記事冒頭の「モチベーション」より:

システムが大きくなったらカプセル化を強化すべきである。私たちはマイクロサービスや何ちゃらRailsエンジンでやりたいのではなく、Rubyの基本機能を少々用いることで実現する。
(中略)
そういうわけでモデリングにおいて防衛的なアプローチを始めた。つまりサポートできるものだけをpublicにしようということだ。これによってモデルの表面積が小さくなってサポートしやすくなるし、用途が絞られることで内部変更もしやすくなる。
(中略)
最後に、ROMSequelのようにData Accessパターンでこれに近いことをやれるライブラリはいろいろあるものの、それらに完全に乗り換えるのは簡単ではない。おそらく皆さんのアプリは最初からActive Recordを使っているだろう。本記事では、そうした技術への乗り換えが困難なまでに育ったRailsアプリを前提としている。
「データベースはインターフェイスじゃないんだけど!」とつぶやいてる人にはもしかするとこのパターンが向いているかもしれない。
同記事より大意

⚓Railsのセッションをクロスドメインで共有(RubyFlowより)


つっつきボイス:「1本目はcookieとセッションの基本的な解説で、2本目が本題のようです」「クロスドメインでセッションを共有ぅ〜?」「無茶な😆

「どうやらこの人たちはapp.kittens.iodev.kittens.ioという2つのドメインで認証を共有したいらしいです」「なるほどそっちですか😆」「cookieの仕様でこういうのってやれるんだったかな?🤔」「この記事ではセッションストアをRedisにしてるので、それならやれそう☺

「お、このconfig↓でdomain: :allにするのがポイントらしい😳」「これでドメインが変わってもcookieをよしなに扱えるってこと?」「へぇ〜😳

# 同記事2より
Rails.application.config.session_store :redis_session_store,
  key: '_kittens_session',
  serializer: :json,
  domain: :all,
  redis: {
    expire_after: 1.week,
    key_prefix: 'kittens:session:',
    url: ENV['REDIS_SESSIONS_URL']
}

「この記事みたいにサブドメインの違う複数サーバーでセッションを共有したいことって結構あるんでしょうか?」「サブドメインがwwwとかliveとかloginみたいに分かれてて、loginで認証したら他のサブドメインも見られるようにする、なんてのは普通にやりますね☺」「SSO↓でやると大げさになっちゃうんでしょうか?」「まあそのためだけにSSOは使わないでしょう😆」「これでやれるならサブドメインを気軽に作れますし☺」「昔セッション共有やるべきかどうかについて議論になった気がするけど思い出せない😆

参考: シングルサインオン - Wikipedia

追いかけボイス: 「web書くならにcookieのdomain指定は把握しておいてほしいなあ…大昔にこんなのを書いていました↓」「おぉありがとうございます😂」「まあ今はさらに色々ありますが😆

社内勉強会でSOP (Same Origin Policy) の話をしました

⚓Active StorageはRails 6でどう変わったか


つっつきボイス:「記事はActive StorageがRails 6でどう変わったかというまとめで、このSaeloun Blogは最近グロスで翻訳の許可ももらえました😋


「お、mini_magickが置き換わった?」「そういえばウォッチでもimage_processingというgem↓に置き換わったのを扱ってた覚えが(ウォッチ20180511)」「画像の向きも自動で修正してくれるとか、image_processingよさげ😍


「次は画像のvariantのサポート」「variantはサイズ違いの画像ですね」「carrierwaveとかでやってたのがActive Storageでもできるようになった☺

carrerwave↓のgemspecを見るとimage_processingが入ってますね😋。mini_magickもまだありますが。


「最後がhas_many_attached」「これだけで書けるのはアツい❤」「そういえばRails 6で挙動が変更されたんでした(ウォッチ20190729)」「前はattachしてupdateすると追加されてたのが、Rails 6で更新されるようになって他と挙動を合わせたと」「countの結果↓違う〜😨」「breaking changeなのでオプションで選べるようになったんでした」

# 同記事より
# Rails 5まで
blog = ActiveStorage::Blob.create_after_upload!(filename: "updated_pic.jpg")
user.update(images: [blog])

user.images.count
=> 2

# Rails 6
blog = ActiveStorage::Blob.create_after_upload!(filename: "updated_pic.jpg")
user.update(images: [blog])

user.images.count
=> 1

⚓心が折れないRailsスキーマ管理


つっつきボイス:「Railsスキーマ管理で心が折れないための方法😆」「冒頭で早速例の『マイグレーションでActive Recordモデルを参照するな』が出てきてますね」「morimorihogeさんも口を酸っぱくして言ってるヤツ(ウォッチ20190415)」「babaさんも昔記事書いてました↓」

[Rails 3] 失敗しないmigrationを書こう

「次は使い捨てのスクリプトでデータをインポート」「one-offは『使い捨ての』という意味ですね」「捨てスクリプトを全環境で実行したらもうgitにも登録するなと」「それわかる〜😋」「基本的には残す意味ないヤツ😆」「one-off migrationスクリプトを歴史としてコミット履歴に残すというのは別に悪くはない気もしますね: Wikiとかに書いてもいいんだけど『いつのコードで動かすことを想定していたか』を明らかにするという点ではコミットに挟まっててrevertした履歴があるというのも歴史管理としては一つの戦略だとは思う(これがベストだとは思いませんが)」「おぉ」

「次はどの環境のスキーマを『正』にするかみたいな話」「productionが正に決まっとる😆」「病欠でいない人がスクリプトをローカルで走らせてなくて、しかもスクリプトがもう消されたという状況になったら、production->staging->developmentの順で最新にすると」「考えたくない状況😅」「むか〜しスキーマのインデックス周りが環境ごとにちょっぴりずれてて修復したの思い出した😭」「マイグレーションの定番を押さえるのによさそうな記事ですね😋

見出しより:

  • マイグレーションはスキーマだけを変更する
  • seedやデータインポートを使い捨てスクリプトでやる
  • pruneを徹底して環境を同期する
  • おまけ: いらないマイグレーションファイルを消す
  • 上のやり方から離れるべき場合

⚓その他Rails


前編は以上です。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191106後編)holiday_japan gemで日本の祝日判定、小さい関数が有害になるとき、Gitブランチのファジー検索ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20191112後編)invisible gemで可視性を変えずにパッチ当て、スライド:「型なし言語のための型」、自然言語の言語名を推測ほか

$
0
0

こんにちは、hachi8833です。DHHがAppleクレカの件でTVに出てたようです。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第16回公開つっつき会」(無料)

第16回目公開つっつき会は、今週11月14日(木)19:30〜にBPS会議スペースにて開催されます。今回は月初ではありませんのでご注意ください。

週刊Railsウォッチの記事にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇

⚓Ruby

⚓invisible: privateやprotectedを変えずにモンキーパッチ(Ruby Weeklyより)

# 同記事より
module WithFoo
  extend Invisible

  def public_method
    super + ' with foo'
  end

  def protected_method
    super + ' with foo'
  end

  def private_method
    super + ' with foo'
  end
end
class MyClass < Base
  include WithFoo
end

instance = MyClass.new

MyClass.public_method_defined?(:public_method)       #=> true
instance.public_method                               #=> 'public with foo'

MyClass.protected_method_defined?(:protected_method) #=> true
instance.protected_method                            # raises NoMethodError
instance.send(:protected_method)                     #=> 'protected with foo'

MyClass.private_method_defined?(:private_method)     #=> true
instance.private_method                              # raises NoMethodError
instance.send(:private_method)                       #=> 'private with foo'

「RubyのRubyのModule Builderパターン」記事↓のshioyamaさんの作ったgemです。

RubyのModule Builderパターン #1 モジュールはどのように使われてきたか(翻訳)


つっつきボイス:「publicだろうとprotectedだろうとprivateだろうとモンキーパッチを当てたいようです」「ほぇ〜😆

「そもそもprivateメソッドってパッチ当てられるんでしたっけ?」「たしかできるはず🤔: 普通にクラスをオープンすれば上書きされるはずですし」「README見ると、privateメソッドに普通にパッチを当てるとpublicになっちゃうみたいですね」「あ〜!そうかも!😳: そのとおり、publicメソッドでオーバーライドしたことになります」「そういうことでしたか😅

後でRuby 2.6.5で試してみたところ、確かにvisibilityなしでパッチを当てるとpublicに変わりますね。Bazでprivateを指定するか、invisible gemをインストールしてBazモジュールでextend Invisibleすると変わらなくなりました。

class Foo
  private
  def foo
    99
  end
end

class Bar < Foo
end

Bar.public_method_defined?(:foo)  #=> false(fooはpublicではない)
Bar.private_method_defined?(:foo) #=> true(fooはprivate)

module Baz
  def foo
    88
  end
end

class Bez < Foo
  include Baz
end

Bez.public_method_defined?(:foo)  #=> true(fooがpublicに変わった)
Bez.private_method_defined?(:foo) #=> false(fooはprivateではなくなった)

「メソッドのvisibilityを変えずにパッチを当てられるgemなんですね😳」「元のincludeするモジュールのvisibilityに合わせてパッチを当てると言ってますね☺」「しかもsuper使ってても大丈夫となってるし」

「本当ならパッチを当てる側のモジュールにもpublicとかprivateとか書かないといけないんでしょうけど、メソッドがいくつもあってそれぞれvisibilityが違ってたらモジュールにいちいちvisibility書いてられないみたいなことになりそう😇」「invisible gemを入れると元のvisibilityをチェックしてくれてそれを変えないようにパッチを当てられるのか〜」「まあサンプルコードのdef public_methodみたいな名前は個人的にはメタ過ぎてどうかと思いますけど😆

「もしかしてキワモノかと思ったら結構いいgem!?」「まあキワモノかも😆」「でもこれは面白い!👍」「用途としては、パッチでvisibilityをいちいち書きたくないのでおサボりしたいとか、visibilityが異なるメソッドにまとめてパッチを当てたいときとかですかね〜☺

「このgemはどういう仕組みでやってるんだろう?🤔」「Module#includedあたりで頑張ってるのかな?😆」「ソース↓見ると意外に短いですね😳」「たぶんいったんpublicになったメソッドをまたprivateやprotectedに引き戻してるんじゃないかな〜と予想(ソース読んでないけど😆)」「何となく禁断の地に手を入れてるような気が😅」「わかる😆」「むしろRubyの言語仕様上できるのがビックリ」「Rubyの仕様深い😅

# https://github.com/shioyama/invisible/blob/master/lib/invisible.rbより
module Invisible
  def append_features(base)
    private_methods, protected_methods = methods_to_hide(base)

    super

    base.send(:private, *private_methods)
    base.send(:protected, *protected_methods)
  end

  def prepend_features(base)
    private_methods, protected_methods = methods_to_hide(base)
    return super if private_methods.empty? && protected_methods.empty?

    mod = dup

    if name
      return if base.const_defined?(mod_name = ['Invisible', *name.split('::')].join('__'))
      base.const_set(mod_name, mod)
    end

    mod.send(:private, *private_methods)
    mod.send(:protected, *protected_methods)
    base.prepend mod
  end

  private

  def methods_to_hide(mod)
    [(instance_methods - private_instance_methods)   & mod.private_instance_methods,
     (instance_methods - protected_instance_methods) & mod.protected_instance_methods]
  end
end

append_featuresprepend_featuresにパッチを当ててるんですね😳

参考: instance method Module#append_features (Ruby 2.6.0)
参考: instance method Module#prepend_features (Ruby 2.6.0)

⚓スライド: 型なし言語のための型

社内Slackで教えてもらいました🙇


つっつきボイス:「富山Ruby会議でsoutaroさんが発表したスライドです」


toyamarb.github.ioより

富山はブリしゃぶが最高だそうです🐟

「これまでも情報は出てましたが、以下の相関図↓がまとまっててうれしいです😂: Level 1型チェッカーが必須で、Level 2型チェッカーは好みで選んでいいという位置づけ」「はぁ〜なるほど!」「RubyKaigiでもSorbetとかいろいろ出てきてよ〜わからんかったけど😆こうやって整理されるとありがたい🙏」「選べるのがいい👍

「Rubyの型チェックはまだまだ難しい点があるようです」「Rubyはいろいろ自由ですし😆」「Array#mapはML型推論が効かないのか😳

「TypeScriptはうまくやってるな〜コイツめっ😆

「『型を書きたくない人はTS使わないから観測できない』、まさしく〜😆」「後でじっくり読み返そう😋

⚓HTTPX: Ruby HTTPライブラリのニューフェース(Ruby Weeklyより)

主な機能:

  • HTTP/2とHTTP/1.xをサポート
  • デフォルトでコンカレントリクエストやれる
  • シンプルかつチェイン可能なAPI
  • プロキシ(HTTP(S)、Socks4/4a/5)
  • シンプルなタイムアウト
  • 軽量(機能を明示的に読み込む)
  • 圧縮(gzip、deflate、brotil)
  • 認証(BASIC、Digest)
  • Cookie
  • HTTP/2 サーバープッシュ
  • H2Cアップグレード
  • リダイレクト

参考: HTTP/2? h2? h2c? って何? - 隙あらば寝る

# 同リポジトリより
response = HTTPX.get("https://nghttp2.org")
puts response.status #=> 200
body = response.body
puts body #=> #<HTTPX::Response .

⚓RubyをRubyで書きたい

つっつきボイス:「Ruby言語をRubyで書こうみたいな動きを最近ちらほら見かけるので」「Cで書かれたものを減らしてRubyで書く、わかる〜😋」「Cを滅ぼしたい気持ちも😆」「Rubyで書き直すのはわかるんですけど、パフォーマンス的にどうなんでしょう?」「それが場合によってはRubyで書いた方がCより速いケースもあるらしいと何かで見かけました😳

どこで見たのかなと思ったらQuoraでした↓。

「Cで書かれたRubyのコアはできたから今後はRubyでRubyを記述しよう、みたいな感じにはなかなかならないんじゃないかなと思ってた😆」「自分は逆に、言語ができたらその言語で記述し直して置き換えるのって言語づくりで割と早い段階でやる印象ありますけどね」「そうなの?😳言語作ったことないからわからないけど😆」「むしろRubyはまだCで書かれてる部分多いのかって思いましたし☺」「そういえばGo言語のコンパイラも最初Cで書かれていたのが、ある時期からへその緒が切れてGo自身でコンパイラが書かれるようになりましたね(セルフホスティング)」

「ツイートの続き↑を見ると、今は一部でCのコードをRubyで生成してるんですね」「それはOKなのか😆」「人間様はC書かなくてよろしい😆


つっつきの後で、RubyでCを書く機能がひっそり入ってらしいことを知りました。

⚓その他Ruby


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

⚓「Kubernetes Patterns」が無料で読める


つっつきボイス:「登録すれば無料で読めると😆」「今手が回らないので後でダウンロードしてみます」「どんなパターンがあるのかだけでもさらっと見れたらいいですね😋」「図があったらうれしい」「パターン本ってクラス図を見るだけでだいたいわかるものとそうでもないものとありますけど、だいたいクラス図でわかりますよね☺

ダウンロードしました😋


同PDFより

⚓スライド: AWSのセキュリティ対策全部盛り


つっつきボイス:「ブクマ1000超えてました」「全部盛りなだけあって内容を追うのは時間かかりそう」「セキュリティの解説は本気でやると全部盛りになるのは仕方なさそうですね: 用途ごとにどこを押さえるかをひととおり網羅しないといけませんし☺」「用途を絞り込めないあたりはフレームワークの解説にも通じるものがあるかも🤔

「スライドでもお馴染み徳丸本が紹介されてますね↓」「新しい版はmorimorihogeさんいわく『9mm弾ぐらいなら止められそう』なぐらいの分厚さ😆」「物理で防御😆」「やっぱり分厚くなる😆

「セキュリティはコストとのバランスも重要って昔教わりました」「鍵閉め忘れみたいな大ポカは論外だとしてもまあそうなりますよね☺」「何を守るかと、セキュリティにいくら投資するかで変わってきますし💰」「上を見たらきりがない」「どんなにセキュリティを高めても米軍に襲いかかられたらアウトでしょうし😇」「セキュリティの後出しジャンケンよくありますし😆『ほれ見たことか』『言わんこっちゃない』とか」「😆

「ソーシャルエンジニアリングとかショルダーハッキングみたいな『まさかそんな手が?!』みたいな手法もあることを知っておくのは大事ですね〜☺」「ですね〜」

参考: ソーシャル・エンジニアリング - Wikipedia
参考: ショルダーハックとは - @IT

「某同人誌で読んだんですが、セキュリティ調査のためにハニーポットを仕掛けたところ、さっそく侵入者がやってきてしめしめ釣れたと思ったら、調査場所のWiFiアクセスポイントから位置情報を抜かれそうになって『やばいっ!』て慌ててケーブル引っこ抜いたという話があったそうです💀」「こえぇ😱」「シビレる😇

参考: ハニーポット - Wikipedia

⚓その他インフラ


nic.ad.jpより


つっつきボイス:「名前はそっけない感じですが、さすがJPNICで『最新データセンターネットワーク・プロトコル動向』とか『サイバーセキュリティ新常識2020』のようなエッジなトピックが盛りだくさんのようです」「『Wi-Fi 今昔物語』😆」「『超高速超低遅延ネットワーク最新動向』強そう💪」「さすがに参加費はお安くなかった💰」「学割なら90%オフだそうです🎓

⚓JavaScript

⚓IntelliCodeがさらに(Publickeyより)


つっつきボイス:「オンライン機能はあってもおかしくない感じですけど、前にも扱った(ウォッチ20180511)IntelliCodeのAIレビューがますますアグレッシブにやってるっぽいですね」「行単位でサジェスチョン😳」「ちょっと試してみたい気が😋」「私もSublimeからVSCodeに移行を企んでます😆」「Matzもこういうのが欲しいって言ってましたね」「元データは結局GitHubのトップ3000リポジトリのソースのようです」「ということはプログラミングに不慣れな人にはあんまり効果ないかも?😆

「あと20年もしたらコードレビューが今より楽になったりするでしょうか?🤔」「たぶんならない😆」「設計やネーミングまでは手が回らなさそう😆」「やっぱりネーミングが一番難しいですよ😭」「よく名前で内部の振る舞いを表すなとか言われますけど、いい名前だったら内部の振る舞い使ったって構わないわけですし😤

⚓その他JS

// 同記事より
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'
const rootEl = document.getElementById('root')
// ReactDOM.render(<App />, rootEl)
const root = ReactDOM.createRoot(rootEl)
root.render(<App />)

つっつきボイス:「Reactが数年をかけてexperimentalでコンカレントモードが入ったそうです」「またエグいものが😆」「コンカレントやりたいんだろうなきっと🤔」「非同期やれるならコンカレントもやりたいとか😆」「非同期でいい思い出ないし😭」「非同期をちゃんと設計するには違う脳が必要🧠」「ぼくたちは手続き脳から脱却できないのかも😅」「自分もやってませんけど、たぶん理論をがっつり勉強しないとだめなんですよきっと😅」「オブジェクト脳とも違うんですよね?」「まったく別😤」「別」

コンカレントモードのissueも賑わってますね。

⚓言語・ツール

⚓Git 2.23に新コマンド


つっつきボイス:「社内Slackで話題になってたヤツで、ちょっとだけタイプ量減りそう」「gitはGUIでやってますが😆」「結局従来の使い方になりそうな予感😅

# 同記事より
git checkout -b hogehoge # 従来
git switch -c hogehoge   # 2.23から

git checkout .          # 従来
git restore .           # 2.23から

「CentOSとかだとサービスの管理にsystemctlを使いますけど、私は未だにそれをガン無視してserviceコマンド使ってますし😆」「自分なんか/etc/init.dあたりのスクリプトを叩いたりしますし🤣」「macOSはまたコマンド違いますし」

参考: serviceコマンドとsystemctlコマンドを調べてみた – koyama’s blog
参考: コマンド/launchctl - MacWiki

Linuxのサービス起動周りとDockerとの関連を理解する#1(社内勉強会)

⚓guesslanguage: 自然言語が何語かを推測

Go言語でやってますが、元はPythonだそうです。

// 同リポジトリより models/ro.go
package models

func init() {
    models["ro"] = map[string]int{
        " de": 0,
        " în": 1,
        "de ": 2,
        " a ": 3,
        "ul ": 4,
        " co": 5,
        "în ": 6,
        "re ": 7,
        "e d": 8,
// 略

つっつきボイス:「個人的な興味なんですが、自然言語を対象に何語の文章かを推測するツールです」「リポジトリは5年前の😆」「日本語はない?」「ないか〜」「ひらがなを検出したら100%日本語だから要らないのかも😆」「東京特許許可局みたいに漢字だけだったら中国語と区別するのは難しいかな?😆」「こういうツールを欲しい人は確実にいるんですけど少なくて😢

「これは基本的にアルファベット語の言語判定用?」「だと思います: キリル文字というだけだとロシア語なのかウクライナ語なのかブルガリア語なのかまではわからないので、その言語に多い文字列を検出してポイントを積んでいく感じで」「へぇ〜」「昔の職場でこれと似たようなツールをperlでこっそり作ったことあったんですけど、そのときにフランス語とルーマニア語がとてもよく似ていて判別が難しいとか、アラビア語とペルシャ語を判定するとかいろいろやってました😆」「😆」「そのときに、アラビア語とペルシャ語の外見的な違いは冠詞のアル(ال)が多めなのがアラビア語で少ないのがペルシャ語だと教わりました」「知らんわ😆

参考: キリル文字 - Wikipedia
参考: アラビア文字 - Wikipedia

「こういう研究してた人が身近にいましたけど、ポイントを積んで『xx語である確率何%』みたいに判定するのは言語解析で割と一般的なようですね☺」「やはりそうでしたか😳」「名前は忘れましたけど手法があって、昔世界中の言語の文字列をかき集めて集計するお手伝いしてました」「そんな楽しいことやってたんですね😋」「そのときもいろいろあってですねゴニョゴニョ(略)」

⚓数式表現がISO規格になってた


Wikipediaより


つっつきボイス:「Kindle無料本のアマゾンレビューを読んでたら数式表現のISO規格があることをたまたま知りました」「キリ番ゲットで縁起がいい😆」「『記号 ÷ は使うべきではない』とは😆」「なぜか数理論理学の記号が最初😆」「三角関数やら写像やらベクトルやらオイラー定数やらあれもこれも😋(以下延々)」

⚓その他

⚓classitisとdivitis


つっつきボイス:「先週のウォッチを制作中にclassitisとdivitisという見慣れないWeb関連用語を見かけたんですが、-tisは肝炎(hepatitis)とか膵臓炎(pancreatitis)みたいな『〜炎』を表す接尾語らしくて、それに引っ掛けた新しい造語のようです」「病名というか医学用語☺

「意味は基本的にネガティブで、classitisはCSSで無駄なクラスをいっぱい作ること」「無駄なクラスなんかYAGNIにしかなりませんし😆」「divitsはHTMLの意味のあるタグじゃなくて何でも<div>タグでやってしまうことだそうです」「どっちも悪い意味だった😆

⚓来年2/29と3/1


techbookfest.orgより


つっつきボイス:「今度こそ技術書典行きたいです😭」「今度は何買おうかな😋

⚓番外

⚓ガラスに刻む


つっつきボイス:「煮ても焼いても喪失しないといっても金槌で叩いたら割れるのでは🔨」「せっかくなのでこのぐらいタフなガラス↓に封入したらいいかなと思って😆

「ガラス破れたら中の札束持ち帰り放題🤣」「誰も成功しなかったみたいです😊」「飛び蹴りは効かないと😆」「ドリルとかポンチで一点突破する方が見込みあるかも🗼」「そこに液体窒素をかけるとか」「銃は社会的にダメか😆


後編は以上です。公開つっつき会でお会いしましょう!

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191111前編)Active Recordモデルをprivateで封じ込める、心折れないRailsスキーマ管理、Railsセッションをクロスドメイン共有ほか

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

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

Ruby Weekly

Publickey

publickey_banner_captured

React Status

react_status_banner

Ruby: RubocopとRufoを最小限に共存させてみた

$
0
0

しばらく使っていなかったrufoフォーマッタを久しぶりに設定してみました。

しかしrufoはデフォルトで引用符をダブルクォート"に揃える方針で、RuboCopはデフォルトではシングルクォート'に揃える方針になっている部分が真っ向からぶつかっています。

以下のCHANGELOGを見ると、Rufo 0.3.0からダブルクォートがデフォルトになっています。

最小限の共存

プロジェクトの現状に応じて、以下のいずれかの設定にすることで、引用符に関してはぶつからなくなります(他の設定の共存についてはここでは考えません)。なお自分はRufoに寄せました。

1. RuboCopに寄せる場合

# .rufo
quote_style :single

この場合式展開#{}を含む引用符はダブルクォート"が保たれます。また、%q()%Q()、エスケープされた引用符を含む文字列リテラル、ヒアドキュメントの中の引用符はquote_styleの影響を受けません。

2. Rufoに寄せる場合

# .rubocop.yml
Style/StringLiterals:
  EnforcedStyle: double_quotes

なお、以下のrubocop-config-rufo gemを使う手もあるようですが、試していません。

おまけ: Rufo 0.7.0の設定一覧

Rufoのsettings.mdが2年前から更新されておらず、不便です😢

現在のmasterブランチで用意されている設定をlib/rufo/settings.rbから拾いました。0.7.0ではどれも効くことを確認しました。後でsettings.mdを更新するプルリク投げてみようかと思います(投げました: #189)。

# rufo/lib/rufo/settings.rb より
    parens_in_def: [:yes, :dynamic],
    align_case_when: [false, true],
    align_chained_calls: [false, true],
    trailing_commas: [true, false],
    quote_style: [:double, :single],

おまけ: Rufoはどこを目指すか

settings.mdの冒頭には「この設定オプションはいずれ消すつもり」という記述があります。今もそのつもりなのかどうかはよくわかりませんが、RufoのReadmeにも、Go言語のgofmtなどのような「唯一の正しいスタイル」に揃えたいという意向も示されていますし、CHANGELOGでも早い段階で多くのオプション設定が削除されているので、いつになるかはわかりませんが、いずれ設定オプションはなくなってデフォルト設定のみになるだろうと推測しています。

議論の流れ次第ですが、Rufoの引用符はこのままダブルクォートに統一されそうな予感がします。個人的には、シングルクォートにはアポストロフィとしての用途もあるので、ダブルクォート統一に賛成したいです。

Rufoのごく初期からあるissue #2↑を見ると、RuboCop作者の@bbatsovさんも議論に参加していますね。

関連記事

RuboCop作者がRubyコードフォーマッタを比較してみた: 前編(翻訳)


週刊Railsウォッチ(20191118前編)ActiveJob引数のログ抑制、RailsガイドProプランお試し、ファイルアップロードのレジュームgemほか

$
0
0

こんにちは、hachi8833です。銀座Rails #15やっぱり行けばよかった😢。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

今回のウォッチは第16回公開つっつき会を元にお送りいたします。ご参加いただいた皆さまありがとうございました!差し入れいただいて感激です😂。

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

今回はコミットリストから見繕いました。


つっつきボイス:「6.0.1リリースから間もないせいか、小さ目のパッチが中心のようです」「明日の銀座Railsのネタ仕込まないといけないの思い出した😅」

新機能: 重要な情報を引数とするジョブでログ出力を抑制するオプションを追加

ジョブがキューに入るときやジョブ実行時に、ジョブの引数のログ出力を無効にするオプションを追加した。
CHANGELOGより大意

class SensitiveJob < ApplicationJob
  self.log_arguments = false

  def perform(my_sensitive_argument)
  end
end

つっつきボイス:「今まではジョブのログに出ちゃってたのか😳」「例のfilter_parameters↓的なことをジョブのログでもやりたいということでしょうね☺️」

参考: 13.1 パラメータをフィルタする — Action Controller の概要 - Rails ガイド

# config/initializers/filter_parameter_logging.rb
config.filter_parameters += :password

「(ゲストの方に)背景を説明しておくと、これはパスワードのような重要な情報をログに出さないための仕組みの話ですね: せっかく他のセキュリティを頑張っても、ログに生のパスワードとかが全部出てたりすると、仮に侵入されたときにログファイルだと楽勝で読めてしまうので、あ…終了😇なんてことがまれによくあったりします😆」「お〜😅」

「フォームに値を入力して送信すると原則としてパラメータ(params)がログ出力されるんですけど、Railsには前述のような問題を防ぐ仕組みとして前からfilter_parametersというものがあって、デフォルトではpasswordという文字をパラメータ名に含む場合は、ログに出すときに値をXXXXみたいに伏せ字にしてくれます」「なるほど!」「今回の改修はそれをジョブの引数についても同じようなことをやれるようになったということですね☺️」

「この改修って割と重要ですよね😆」「一部の人にとっては😆」

スレッドでのdeprecationメッセージ出力を修正

# activesupport/test/deprecation_test.rb#L293
+ def test_silence_threaded
+   barrier = Concurrent::CyclicBarrier.new(2)
+
+   th = Thread.new do
+     ActiveSupport::Deprecation.silence do
+       barrier.wait
+       barrier.wait
+       assert_not_deprecated { ActiveSupport::Deprecation.warn "abc" }
+     end
+     assert_deprecated("abc") { ActiveSupport::Deprecation.warn "abc" }
+   end
+
+   barrier.wait
+
+   assert_deprecated("abc") { ActiveSupport::Deprecation.warn "abc" }
+
+   ActiveSupport::Deprecation.silence do
+     assert_not_deprecated { ActiveSupport::Deprecation.warn "abc" }
+   end
+
+   assert_deprecated("abc") { ActiveSupport::Deprecation.warn "abc" }
+
+   barrier.wait
+   th.join
+ ensure
+   th.kill
+ end

つっつきボイス:「ActiveSupport::Deprecation.silenceがスレッドローカルになってなかったのでConcurrent::ThreadLocalVar↓を使うようにしたのかなと☺️」「そういえばちょっと前にもConcurrentを使った改修がありましたね😳(ウォッチ20190909)」

# activesupport/lib/active_support/deprecation.rb#L38
    def initialize(deprecation_horizon = "6.2", gem_name = "Rails")
      self.gem_name = gem_name
      self.deprecation_horizon = deprecation_horizon
      # By default, warnings are not silenced and debugging is off.
      self.silenced = false
      self.debug = false
+     @silenced_thread = Concurrent::ThreadLocalVar.new(false)
    end

RailsアプリでConcurrent Rubyを使う(翻訳)

「でもdeprecation warningなんだし、そうまでして直さなくてもいいような気もしますけど😆」「この修正がないととても困るという気はあまりしない😆」「マルチスレッドで一部についてだけdeprecation warningを出して、それ以外では抑制したいときとか?」「特定IPのときだけdeprecation warningを出したいとか?😆」「どうしても欲しいシチュエーションがあんまり思いつかない〜🤣」「欲しい人がいるのはわかるけど😆」

「たとえばRailsの移行中とかで、warningを承知のうえで抑制したいけど全部抑制すると後で追えなくなるから欲しくなったとかですかね?☺️」「移行中に自分たちでwarningをある程度制御しておきたいみたいな」「Pumaとかでマルチスレッドで回しているときにこの問題に気づいたとかはあるかも🤔」「せやなと言うしかない😆」

従来はActiveSupport::Deprecation.silence { ... }がすべてのスレッドのdeprecationメッセージを抑制するので、マルチスレッド環境でびっくりする人もいると思う。
ActiveSupport::Deprecation.silenced=はグローバルオプションに設定を残すので少々紛らわしい可能性もあるが、これにはさまざまな用法があるのでこの方法が最もうまく一般的な用法を変えずに済むと思われる。silence {}はコードの小さなセクションのメッセージを抑制し、silenced=はイニシャライザやテストヘルパーでデフォルト設定に用いることが多い。
自分としてはActiveSupport::Deprecation.silenced=を非推奨にして代わりにActiveSupport::Deprecation.behavior = :silenceを使うべきかもしれないとふんわり思っている(絶対そうすべきとまでは思っていないし、このプルリクの趣旨から外れる)。
同PRより大意

新機能: travel_backテストヘルパーがブロックを取れるようになった

# activesupport/lib/active_support/testing/time_helpers.rb#L42
+     def stubbed?
+       !@stubs.empty?
+     end
...
      def travel_back
+       stubbed_time = Time.current if block_given? && simple_stubs.stubbed?
+
        simple_stubs.unstub_all!
+       yield if block_given?
+     ensure
+       travel_to stubbed_time if stubbed_time
+     end

# 同コミットより
Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00

travel_back do
  Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
end

Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00

つっつきボイス:「travel_backは時間を巻き戻すヤツでしたね」「ブロックの最終評価値を使ってtravel_backを設定できるようになった😋」「これはわかる〜😋」

参考: travel_back — ActiveSupport::Testing::TimeHelpers

# api.rubyonrails.orgより
Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel_to Time.zone.local(2004, 11, 24, 01, 04, 44)
Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
travel_back
Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

「Railsでテストとかで現在時刻を違う時間に設定するのにtravel_toというメソッドを使うのがRailsらしいですよね☺️」「列車の比喩で『旅』『時間旅行』にかけた感じ😆」

参考: travel_to — ActiveSupport::Testing::TimeHelpers

# api.rubyonrails.orgより
Time.current     # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel 1.day
Time.current     # => Sun, 10 Nov 2013 15:34:49 EST -05:00
Date.current     # => Sun, 10 Nov 2013
DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500

Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
travel 1.day do
  User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
end
Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00

「これはテストコードで使うんでしょうか?」「時刻に依存するテストを書きたいときなんかに使いますね☺️」「travel_toで現在時刻を別の時間に変えるんですけど、それを元に戻したいときにtravel_backします⏱」「なるほど〜」「業務アプリを書いていると時刻系はよくハマります🧐」

travel_backは以下の記事にも登場してますね。

Rails: Timecopを使わなくても時間を止められた話

やはりというか原田真二の「タイム・トラベル」という懐メロを思い出しました👨。

「そういえばtimecopってありましたね」「以前はtimecopでやってましたが今はActiveSupportでやれます👍」

時間警察はドラえもんで知りましたが、古くはポール・アンダースン「タイムパトロール」、最近だと仮面ライダーG電王なんですね😳。

参考: タイムパトロール - Wikipedia
参考: 仮面ライダー×仮面ライダー×仮面ライダー THE MOVIE 超・電王トリロジー - Wikipedia

HTTPトークン認証の解説をガイドに追加

# rails/actionpack/lib/action_controller/metal/http_authentication.rb#L405
    module Token
      TOKEN_KEY = "token="
      TOKEN_REGEX = /^(Token|Bearer)\s+/
      AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
      extend self
...

つっつきボイス:「RailsガイドにBASIC認証とダイジェスト認証の説明はあったのにトークン認証の説明がなかったので追加したそうです」「機能は前からあったんですね😳」

トークン認証

HTTPトークン認証は、HTTP AuthorizationヘッダーでBearerトークンの利用を有効にするスキームです。さまざまな形式のトークンを利用可能ですが、詳細は本ドキュメントの範疇を超えます。
たとえば、以下のように事前発行された認証トークンを使って認証やアクセスを実行したいとします。Railsでのトークン認証の実装はauthenticate_or_request_with_http_tokenというメソッド1つでやれるのでとても簡単です。

class PostsController < ApplicationController
  TOKEN = "secret"
  before_action :authenticate
  private
    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
      end
    end
end

authenticate_or_request_with_http_tokenは上述の例のように2つの引数「トークン」「Hash(HTTP Authorizationヘッダーから取り出したオプションを含む)」を取ります。認証が成功したらこのブロックはtrueを返します。falseまたはnilが返ると認証は失敗です。
同PRより大意

「トークン認証って何だろう?🤔」「Authorizationにトークンを突っ込むだけで使える🧐」「SSOサーバーを自分で実装するときとかに使うのかなと推測🤔」「Authorizationヘッダーでサポートされてるなら大丈夫そう」「Railsガイドに記載されていない機能って結構ありますよね😆」「こういうのが増えるのはありがたい🙏」

参考: RFC 6750 - The OAuth 2.0 Authorization Framework: Bearer Token Usage — トークン認証の仕様
参考: トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた - Qiita

トークンはtoken68の仕様↓に沿っていればよいようです。

参考: token68

class_method_defined_withinをリファクタリング

# activerecord/lib/active_record/attribute_methods.rb#L117
      def dangerous_class_method?(method_name)
-       RESTRICTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
-     end
+       return true if RESTRICTED_CLASS_METHODS.include?(method_name.to_s)

-     def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
-       if klass.respond_to?(name, true)
-         if superklass.respond_to?(name, true)
-           klass.method(name).owner != superklass.method(name).owner
+       if Base.respond_to?(method_name, true)
+         if Object.respond_to?(method_name, true)
+           Base.method(method_name).owner != Object.method(method_name).owner
          else
            true
          end
        else
          false
        end
      end

つっつきボイス:「シンプルなリファクタリングのようです」「1箇所でしか呼んでないメソッド要らんだろということでインライン化した感じですね☺️」

match?starts_withends_withに変更


つっつきボイス:「最近も似たような修正あったような🤔」「こういうふうにmatch?start_with?に置き換えたり↓」「地道なマイクロ最適化ですね☺️」

# actionpack/test/dispatch/exception_wrapper_test.rb
    setup do
      @cleaner = ActiveSupport::BacktraceCleaner.new
      @cleaner.remove_filters!
-     @cleaner.add_silencer { |line| !line.match?(/^lib/) }
+     @cleaner.add_silencer { |line| !line.start_with?("lib") }
    end

「正規表現は一般にパフォーマンスがあまり出ないので『〜で始まる文字』みたいなチェックは専用の文字列メソッドの方がわかりやすいし速いですね😋」「特に前方一致は明らかに違い出ますね🧐」

「2つ目はto_jsonstart_with?が効かなかった問題を修正したそうです↓」「こういうのってたまに踏む😆」「ありがたい🙏」「OptionMergerは暗黙で使われるから知らないうちに踏んでたかも😅」

# activesupport/lib/active_support/option_merger.rb#L5
module ActiveSupport
  class OptionMerger #:nodoc:
    instance_methods.each do |method|
-     undef_method(method) unless method.start_with?("__", "instance_eval", "class", "object_id")
+     undef_method(method) unless method.to_s.start_with?("__", "instance_eval", "class", "object_id")
    end

Ruby: 文字列マッチは正規表現より先に専用メソッドを使おう

たぶんこちらも関係してそうです↓。

ポリモーフィック関連付けで:polymorphic:as:foreign_typevalid_optionsに追加した

# activerecord/lib/active_record/associations/builder/belongs_to.rb#L9
    def self.valid_options(options)
-     super + [:polymorphic, :counter_cache, :optional, :default]
+     valid = super + [:counter_cache, :optional, :default]
+     valid += [:polymorphic, :foreign_type] if options[:polymorphic]
+     valid
    end

つっつきボイス:「@kamipoさんの修正です」「Active Recordのこの辺のコードを単体で見てもまるでわからん😆」「😆」「何ができるようになるのやら😆」

y-yagiさんのブログに、これを含めたvalid_options関連のkamipoさんの修正がまとまっていました。

参考: rails commit log流し読み(2019/11/10) - なるようになるブログ

「今Active Record周りを勉強し始めているところなんですけど、上のコードはActive Recordの中身の話なんでしょうか?」「そうですね、Active Recordはこう書かれているというヤツです」「こういうところを読んでくとActive Recordがどう振る舞うかわかるようになるんでしょうか?」「この辺の改修を眺めるというのはどちらかというと、Railsでアプリを開発していてたまたまRailsのバグを踏んだり、うっかりRailsが想定していない書き方をして謎のエラーを出してしまったときなんかに役に立つこと『も』あるという感じです」「あ〜そうでしたか😳」「普通にRailsアプリのコードを書いている分には9割必要ないと思います😆」「😆」

「それでもどういう改修があったかを追っていくことには一定のメリットがありますね: 新しい機能が入ったりバグが修正されたということは、安定版のRailsにはまだ入っていない機能なので、自分たちのところでバグが発現する可能性があるわけです」「ふむふむ」「改修を追っていると、自分がそういうバグを踏んだときに『あ、そういえばこのバージョンのRailsにはこんなバグがあった気がする』という既視感につながっていくので、そういうのがごくまれに役に立つことは、ある🤣」「🤣」「🤣」

「この修正は安定版にはまだ入ってないんですね😳」「改修はmasterブランチにまず入るんですけど、これはまだリリースされていない状態のブランチです: 今だと最新安定版(6-0-stableブランチ)は6.0.1なんですけど、masterはそれより進んだ状態になっていて、今後時期が来たらたとえば6.0.2というタグが打たれて次の最新安定版に入るという具合です」「なるほど😋」「今はrubygems.org↓でRailsを見ると6.0.1になっているので、rails newすると6.0.1が最新として入るようになってます」

参考: RubyGems.org | コミュニティのGemホスティングサービス

新機能: コネクションを管理するRoleManagerを導入

こちらはつっつきの後で気づきました😅。コードが長いので引用はしません。

このプルリクで追加したRoleManagerは複数のRolesを管理できる。各Roledb_configを持つ。
これによりpublic APIの改変を回避し、ハンドラやロールの概念を扱えるようにし、各クラスのハンドラごとに複数のコネクションを内部で確立するのに必要なものを達成する。
同PRより大意

Rails

RSpec 3.9がリリース


つっつきボイス:「いつの間にか3.9になってた😳」「rspec-railsだけ本体とバージョンが合わなくなる?」「rspec-railsは足並み揃えるよりどんどん改修入れる方を優先しようということですね☺️」

なお、私が今転生作業中のオレオレRailsアプリはこっそりminitestにしてます😆。

HerokuがSorbetをやってみてわかったこと(Ruby Weeklyより)


よかった点:

  • 結果を保存せずに同じメソッドを2回呼んでいたのを指摘
  • nullableなカラムを公開してたのを指摘
  • 雑にnilまたはtrueを返していたのを指摘

つっつきボイス:「HerokuがSorbetを使ってみたと」「4人チームで実験的にやってみた段階だそうですが、上のような点を見つけてくれたそうです😆」

「Sorbetはウォッチでも何度か取り上げましたが、RailsというよりRubyやってる人たちの間でアツい静的型チェッカーです😆」「😆」「Railsアプリを普通に分にはSorbetまだ知らなくても大丈夫かなと思いますが😆、とてもミッションクリティカルなアプリを書くときには必要になるかもしれませんね☺️」

「とりあえずアイスクリームアイコン🍨↓がかわいい❤️」


sorbet.orgより

「sorbet-rails試してみたかったんですが、なぜか今日のbundle installがえらく遅くて、しかも今見たらnot publicly available yetとか出てた😇」「😆」


その後うなすけさんの以下のスライドに今頃気づきました。

Basecampが無料個人版をリリース


basecamp.comより


つっつきボイス:「Basecampが個人版を?へぇ〜」「法人版だと価格的に見合わないので出したそうです」「completely freeって書いてますね」「マジで?」「見落としてた😅」「機能制限はあるけど無料!」「ストレージ1GBか〜😅」「個人なら十分かも」

「一応背景を説明すると、BasecampというのはRuby on Railsフレームワークの作者であるDHH(David Heinemeier Hanson)↓がいる会社で、一種のプロジェクト管理ツール的なサービスであるBasecampはもちろんRailsで提供しています」「日本ではあまり知られていませんけど😆」「日本だと(聞き取れず)が使ってるらしいけどネ」

「Basecampのサービスを何に例えたらいいかな🤔」「GitHubプロジェクトからソースコード管理を引いたような感じですかね〜☺️」「タスク管理とかチャットとかスケジュールとか」

「そしてBasecampに新しい機能が入ると、いずれその機能がRailsにも入ってくるということが割とあったりします😆」「へぇ〜」「たとえばRailsのAction CableはRails 4.xで入ったんですけど、それはBasecampにチャット機能が入ったからだろう、なんてことがよく言われます🤣」「😆」「Basecampを見てれば直近の未来がわかるかもしれないと」「BasecampはフルにRailsで書かれているはずなので知っておくといいと思います☺️」

参考: Action Cable の概要 - Rails ガイド

Skunk: 「コードの臭い」を数値化(Ruby Weeklyより)


つっつきボイス:「コードがどのぐらい臭ってるかを数値化するgemだそうです💩」「説明すると、『コードの臭い(code smell)』はプログラミングの世界ではダメなコードの書き方に対して使われます」

# 同リポジトリより
New critique at file:////Users/etagwerker/Projects/fastruby/skunk/tmp/rubycritic/overview.html
+-----------------------------------------------------+----------------------------+----------------------------+----------------------------+----------------------------+----------------------------+
| file                                                | stink_score                | churn_times_cost           | churn                      | cost                       | coverage                   |
+-----------------------------------------------------+----------------------------+----------------------------+----------------------------+----------------------------+----------------------------+
| lib/skunk/cli/commands/default.rb                   | 166.44                     | 1.6643999999999999         | 3                          | 0.5548                     | 0                          |
| lib/skunk/cli/application.rb                        | 139.2                      | 1.392                      | 3                          | 0.46399999999999997        | 0                          |
| lib/skunk/cli/command_factory.rb                    | 97.6                       | 0.976                      | 2                          | 0.488                      | 0                          |
| test/test_helper.rb                                 | 75.2                       | 0.752                      | 2                          | 0.376                      | 0                          |
| lib/skunk/rubycritic/analysed_module.rb             | 48.12                      | 1.7184                     | 2                          | 0.8592                     | 72.72727272727273          |
| test/lib/skunk/cli/commands/status_reporter_test.rb | 45.6                       | 0.456                      | 1                          | 0.456                      | 0                          |
| lib/skunk/cli/commands/base.rb                      | 29.52                      | 0.2952                     | 3                          | 0.0984                     | 0                          |
| lib/skunk/cli/commands/status_reporter.rb           | 8.0                        | 7.9956                     | 3                          | 2.6652                     | 100.0                      |
| test/lib/skunk/rubycritic/analysed_module_test.rb   | 2.63                       | 2.6312                     | 2                          | 1.3156                     | 100.0                      |
| lib/skunk.rb                                        | 0.0                        | 0.0                        | 2                          | 0.0                        | 0                          |
| lib/skunk/cli/options.rb                            | 0.0                        | 0.0                        | 2                          | 0.0                        | 0                          |
| lib/skunk/version.rb                                | 0.0                        | 0.0                        | 2                          | 0.0                        | 0                          |
| lib/skunk/cli/commands/help.rb                      | 0.0                        | 0.0                        | 2                          | 0.0                        | 0                          |
+-----------------------------------------------------+----------------------------+----------------------------+----------------------------+----------------------------+----------------------------+
StinkScore Total: 612.31
Modules Analysed: 13
StinkScore Average: 0.47100769230769230769230769231e2
Worst StinkScore: 166.44 (lib/skunk/cli/commands/default.rb)

「どうやらこのSkunkはRubyCritic(ウォッチ20180223)っていうgemの拡張として作られたみたい☺️↓」「skunkっていう名前が気に入ってエイヤで作ったんじゃないかな〜😆」「😆」「よくskunkっていう名前のgem名取れたなって」「ホントだ😳」「名前の奪い合いとかありますし😆」「自分だったらとりあえずリポジトリ取っちゃうかな😆」「最近だと使ってないリポジトリは譲渡しなさいって迫られたりするみたいですけど😆」


同リポジトリより

参考: RubyCritic + CircleCI + Slack でプロジェクトの Ruby コードを継続的に採点しよう | Engineer’s Base Camp

「gemの名前としてstinkとかskunkとかを使うのはまだわかりますけど、CIでこれが走って『オマエのコードはこれだけ臭いゾ』とか出てきたらバトルになるんじゃないかと😆」「ですよね😆」「もうちょいオブラートにくるむとか😆」

「なおソフトウェアの世界だと、これみたいにコードに何らかの形で点数をつけるツールというのは昔からいっぱいあります」「ふむふむ」「上で言うとchurnとかcostみたいな項目はたぶん昔からあって、そういう値を元にstink_scoreとやらを算出するとかそういう感じかなと☺️」「Code Climate↓も似たようなことをやっていますね」「カバレッジみたいなメジャーな項目については算出方法が確立しているんですけど、項目をずらりと出しただけだとわかりにくくて、知りたいのは『要するに何点なの?』というところなので、このgemとかCode Climateとかはそういうところをやってくれます☺️」


codeclimate.comより

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

RailsガイドProプランのお試しでクレカが不要に


同記事より


つっつきボイス:「最近始まったRailsガイドProプランは、横断検索がかなりいい感じらしいのでRailsを書き始めて間もない人には結構いいんじゃないかと思います👍」「ほほぉ😋」「本家のRails Guidesにもない機能です」「新しい人が開発中に詰まったときに簡単に調べられるのは便利でしょうね☺️: 自分は脳内にマップできてるのでたぶん使わないけど😆」「Proプランでは例のAlgolia↓でインクリメンタル検索やってるそうです」

Rails: 高速リアルタイム検索API「algolia-search-rails」gem README(翻訳)

activestorage-resumable: レジューム機能をサポート(RubyFlowより)

<!-- 同リポジトリより -->
<%= form.file_field :attachments, multiple: true, resumable_upload: true %>

つっつきボイス:「おぉ?Active Storageでレジューム機能?」「しかもアップロードで?」「レジュームは、アップロード中に回線切れても後で再開する機能ということですね」「まだ★2つ(その後14★に)ですが刺さりました?」

「そもそもHTTPの仕様ではPOSTアップロード中にレジュームできるのか?🤔」「アップロードのレジュームって難しそうですね☺️」「ダウンロードはレジュームできるんでしょうか?」「昔からありますね🧐」「Stackoverflow↓を見た感じではやっぱりアップロードのレジュームはなさそう😇」

参考: Standard method for HTTP partial upload, resume upload - Stack Overflow

「resumable gemのREADMEをざっと見ると、chunk単位でアップロードしてプログレスをLocalStorageに保存することでやってるみたいっすよ😎」「あぁ、そのレベルから頑張ってるのか😆」「これはがんばり屋さんだな〜😆」「LocalStorageって20MBぐらいしか入らないんだっけ?」「プログレス情報だけなら問題なさそう😋」

HTML5のLocal Storageを使ってはいけない(翻訳)

「ちなみにchunkという言葉はコンピューターネットワークの世界でよく使われます」「辞書を見ると『厚切りの一切れ』みたいな感じ」「HTTPの仕様レベルではアップロードのレジューム機能は提供されていないはずなので、このgemはそういうchunk単位で小分けにしたファイルを何回もアップロードすることでレジュームを実現してるということでしょうね☺️」「は〜なるほど!」「何も読んでないけどたぶんそういう実装だと思います😆」「後は使ってみないと😆」

その他Rails



前編は以上です。

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191112後編)invisible gemで可視性を変えずにパッチ当て、スライド:「型なし言語のための型」、自然言語の言語名を推測ほか

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

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

Rails公式ニュース

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

週刊Railsウォッチ(20191119後編)メソッド参照演算子が廃止、GitHub新機能続々、平成Ruby会議、GitHub OAuthバイパスほか

$
0
0

こんにちは、hachi8833です。うらやましさに身を焦がしてます。


つっつきボイス:「この間出たMacbook Proの16インチね☺」「買った人or買いたい人…はさすがにいないか😆」「税金抜きでこの値段はやっぱ高すぎ💸」「メモリ64GBはたしかに欲しいけど」「つかそこだけ欲しい😭」「Escキーがやっと物理に戻ったし」「名前がProなのにプロ仕様じゃなかったとは😆

「お集まりのMacな皆さんにはEsc付いてます?」「私のは付いてます😋」「いいな〜😢」「Touch Barタイプは物理Escキーありませんし」「ここ1年ぐらいはMacからWindowsに移行してるんですけど↓、たまにMacに戻ると物理Escキーないのは不便ですね〜😢

Web開発環境をMacBook ProからWindows機に移行してみた話


  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

後編も引き続き第16回公開つっつき会を元にお送りいたします。ご参加いただいた皆さまありがとうございました!🙇

⚓Ruby

⚓メソッド参照演算子.:が廃止に

ruby-jp Slackで見かけました。以下の記事でも報じたメソッド参照演算子.:が廃止となりました。

Ruby 2.7.0-preview2がリリース

その少し前には.:は重いという記事も出ていました↓。

その後、廃止に関連するissueを見つけました。


つっつきボイス:「2.7preview1から実験的に入っていたメソッド参照演算子.:が先週廃止になってました😳」「Rubyゴルフに使えそうなシンタックス消えた😆」「#16275長いな〜」「コロン:の意味をこれ以上増やしたくないという意見が見えますね」「Matzの締めくくりコメントを読めばだいたいわかりそう」「.:は将来別の機能を足すときに使うかも😋」「ありそう」「今日出席できなかったkazzさんもちょっぴり残念そうにしてました☺

近年は関数型プログラミングにインスパイアされた機能の追加や議論が続いていたが、それらは大局的な設計に基づいていたのではなくアドホックな追加だった。今のところRubyには関数型プログラミングに関する「大きな絵」がないが、今後はより関数型的なRubyの「大きな絵」に基づいて設計すべきと思う。あと点字みたいな.:演算子は自分の好みでない。
#16275コメント#26より大意

「nobuさんがコメント#1で『.:は関数型のためだけではなくて、元々はobj.methodがオーバーライドされててもメソッドを正確に抽出するためのものだった』って書いてますね☺」「え😅」「obj.methodをオーバーライドするケースってあんまり思いつかないけど😆」「同じく😆」「まあRubyだとそういうことがまったくないとは言えませんし😆

⚓そろそろrakeやめる?(RubyFlowより)

時間がなくてつっつきで追えなかったので、rakeの代替として記事にリストアップされていた以下の2つのgemだけピックアップします🙇



whatisthor.comより

# http://whatisthor.com/より
class MyCLI < Thor
  desc "hello NAME", "say hello to NAME"
  def hello(name, from=nil)
    puts "from: #{from}" if from
    puts "Hello #{name}"
  end
end

# dazuma/toysより
tool "greet" do
  desc "My first tool!"
  flag :whom, default: "world"
  def run
    puts "Hello, #{whom}!"
  end
end

その他にコメント欄でrunfileというgemも挙がっていました。


runfile.dannyb.coより

⚓GitHubのOAuthをバイパスしてみた

ruby-jp Slackで見かけました。


同記事より


つっつきボイス:「そんなことできちゃったんですね😳」「この記事書いた人270万円ばかし賞金ゲットしてるな🥇」「おぉ?」「セキュリティのコンペ?😅」「というよりこの人は仕事ではないところでアプリケーションの脆弱性を見つけるという高尚な趣味をお持ちらしくて😆、GitHub Enterpriseのトライアル版をダウンロードしてHEADリクエスト周りをゴニョゴニョしたらOAuth突破できたのでGitHubに報告したら25000ドルをゲットした、という感じっすね☺」「すげぇ〜😳」「よく見つけたな〜」「日本だとあんまりありませんけど、自社のアプリの脆弱性を報告してくれたら内容に応じて賞金を出しますみたいな企画は最近見かけますね☺

⚓prism: mrubyとWebAssemblyでフロントエンドWebアプリ(Ruby Weeklyより)

試しにgem install prism-cliしてprism initすると以下のapp.rbが生成され、prism serverでブラウザのlocalhost:3042/app.rbにアクセスすると動きました。

class HelloWorld < Prism::Component
  attr_accessor :name

  def initialize(name = "World")
    @name = name
  end

  def render
    div(".hello-world", [
      input(onInput: call(:name=).with_target_data(:value)),
      div("Hello, #{name}")
    ])
  end
end

Prism.mount(HelloWorld.new)
<!DOCTYPE html>
<html lang=en-us>

<head>
  <meta charset=utf-8>
  <meta content="text/html; charset=utf-8" http-equiv=Content-Type>
</head>
  <body>
    <div id="root"></div>
  </body>
  <script type="text/javascript" src="/prism-assets/prism.js"></script>
  <script type="text/javascript" async src="/prism-assets/bundle.js"></script>
  <script type="text/javascript">
    Prism.run("#root", "/app.rb");
  </script>
</html>

つっつきボイス:「とりあえず手元で試したらするっと動いてくれました😋」「皆さんWebAssemblyでいろいろ遊んではる😆

mrubyをWebAssemblyで動かす(翻訳)

⚓その他Ruby


つっつきボイス:「平成Ruby会議よさそうですよね👍」「まだ参加できます?」「つっつきの時点であと7人なので今のうちですよ」「2スロットありますね」「ドリコムさんの会場だから例によってあの2つの部屋でやるんでしょうね☺」「『TextbringerでつくるTextbringer』って😆」「Rubyの話もRailsの話もありますね😋」「割とRuby寄りかなとは思いますけど☺」「12/14は忘年会シーズンですけど😆」「こういう濃いイベントに参加すると脳みそがめっちゃ疲れますけど😆」「期待できそう!」



こちらはつっつき後のツイートです↓。

⚓DB

⚓PostgreSQL 12のクエリパフォーマンス第一印象;(DB Weeklyより)


つっつきボイス:「PostgreSQLも12か〜」「そういえばPostgreSQLは年1でメジャーバージョンアップされるポリシーに変わってましたね☺」「タイトル通りファーストインプレッション😆」「軽くベンチ回してますけど↓だいたい誤差の範囲かと😆


同記事より

⚓シェアと用途

「質問なんですけど、日本国内でPostgreSQLを使ってる大手のサービスってありますか?」「大手になると1種類のデータベースだけということはあまりないですね: ぶっちゃけて言えばWebアプリではMySQLもPostgreSQLも使われてます」「なるほど😋

「自分はWebアプリを長くやってきたのでその範囲で言うと、(クリティカルでない)エンドユーザー向けのWebアプリではMySQLを使っていることがやっぱり多いと思います」「お〜」「主な理由は、単純なクエリであればMySQLの方が速いことが多いから🚄: 今でもぽすぐれより速い」「おぉ〜」

参考: PostgreSQLとMySQL、使うならどっち? データベース専門家が8つの視点で徹底比較! - エンジニアHub|若手Webエンジニアのキャリアを考える!

「対照的にPostgreSQLは複雑なクエリをちゃんと書けるという特徴があります: なのでシンプルなWebアプリだとMySQLで十分なことが多いんですけど、複雑なビジネスロジックをクエリにする場合はたとえばMySQLだと通らなくてPostgreSQLだと通るということがあります😭」「ふぅむ🤔

「MySQLとPostgreSQLは使い始めてみるといろいろ違いが出てくるんですよ: どちらかというとPostgreSQLの方が標準SQLと呼ばれる規格に近くて、MySQLは標準から外れているところがちょくちょくあるという😆」「訛り強そう😆」「MySQLで通るクエリがPostgreSQLだと通らないというのはよくあります、しかもめちゃくちゃよくあります💀」「ひぇ〜😅

⚓移行について

「困るのは、たとえば最初はMySQLにマッチする要件だったのに、アプリが育ってきてPostgreSQLの機能を使いたくなったときとかですね: 自分の経験上、MySQLからPostgreSQLへの移行は死ぬほどつらかったことしかなかった😇」「うぅ😅」「後から別のものにお引越しするのは本当に本当に大変😢

「逆はどうでしょう?」「PostgreSQLからMySQLへの移行が発生することはめったにありませんね☺: 単純なクエリを速くしたい以外のモチベーションがありませんし、今ならmemcachedやRedisで高速化する方法などもありますし」「なるほど!」「そういうわけで、RDBMSの選択で悩むぐらいならぽすぐれにしとけば?って自分なら思いますし😆」「😆」「もちろん異論は認めます😆

⚓RDBMSの違い

「質問ですけど、MySQLとPostgreSQLの書き方はそんなに大きくは変わらないと思ってもいいんでしょうか?」「単純なクエリであればざっくり8割ぐらいは同じに書けますけど、一方でしか通らないクエリも書けます: そもそも引用符の使い方からして違いがありますし↓😢」「そうでしたか😳

Web+DB Pressの特集を自分でまとめたメモから引用します↓。

「まあOracleでもMicrosoft SQL Serverでもみんなそれぞれ方言ありますし、どれが正しいという話でもありませんし、まずは1つに決めてみっちり学習して、それから他のものについて違いを学んでいくのがいいと思います🧐」「なるほど!」

⚓その他DB


つっつきボイス:「Rubyコミッターのk0kubunさんが作ったツールをどなたかがWebAssemblyでブラウザで動くようにしてくれたそうです」「なるほど、スキーマを別のRDBMS用に更新するCREATE TABLE構文を生成してくれるのね😋」「上の話題とシンクロした感😆

参考: k0kubun’s blog


sqldef.github.ioより

ALTER TABLE user ADD COLUMN created_at datetime NOT NULL AFTER name

「ちょうど今表示されてますけど、MySQLだとAFTER nameって書けるんですよ↑」「そういえば!」「MySQLなら作成済みのカラムの間に新しいカラムをこれで挿入できるんですが、ぽすぐれはできない😇」「PostgreSQLはカラム順変えられないんでしたっけ?」「CREATE TABLEレベルではできません😆」「テーブル作り直しか😢」「とまあこういうところにもRDBMSの違いがあったりします: 両方経験すれば『こういうカラム順変更は簡単じゃないゾ』ってわかりますけど、片方しか知らないとハマったりします😆」「😆


「質問ですが、RDBMSを別のものに変えて欲しいみたいな注文を受けたときに、できるできないを見分けるコツってあるんでしょうか?」「自分もさんざんこの種のワナを踏んできたことで、だんだん鼻が利くようになってきたかなと思います😆」「妖怪アンテナが立つというか😆」「逆にこれは実は簡単とか、この機能は移行先にもきっとあるはずだと確信を持ったりするというのもだんだん見えるようになってきました☺」「やはり経験なんですね😀

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

⚓最近のIaas PaaSクラウド動向


同記事より


つっつきボイス:「Synergy Research Groupの調査結果だそうです」「ともするとポジショントークになりがちですけど😆、Alibabaがランクインしてるということは一応世界をカバーしているかな🤔」「ちょっと前に、Office 365の売上を足すとMicrosoftの方が売上高いみたいな報道が流れて、Amazonがクラウドで負けたのかと勘違いする人が続出したりしてましたけど😆」「それ足したらあかん😆」「この記事はIaaS+PaaSで比較してるから割と正しそうかな🤔」「この種の記事はどう売りたいのかという意図まで読んでおきたいですね☺

「Azureが割と伸びてる」「オンプレのWindows Serverからの移行が多そう🤔」「あ、オンプレ(オンプレミス)というのは機材を自社内に物理的に置くことを指します」「クラウドと対になる概念なんですね」

参考: オンプレミス - Wikipedia

「オンプレではなくAWSみたいなクラウドに置く場合、クラウド側のハードウェア置き換えの都合などで『今度何月何日何時にインスタンスをシャットダウンして再起動します』みたいに強制的に再起動させられたりしますが、AWSは契約上そうしていいことになっています😆」「😆」「その代わりクラウドはハードウェアの老朽化とかについて利用者側は考えずに済むというメリットがあります☺

「実機を構築したことがあるとわかるんですけど、実機ハードウェアの部品って5年後にも同じものがあるかどうかわからないんですよ」「😱」「特にハードディスクはロットによって微妙な違いがあったりして組み合わせると具合が悪いみたいな一種の神話があったりするので、近いロットで揃えるようにしたりしますね」「逆にロットを合わせない方がいいみたいな神話もあったりしますけど🤣」「そうそう、どっちもあるんですよ🤣」「ロットが同じだと同時に壊れやすいらしいとか😆」「工場が違うとダメらしいとか😆」「RAID 5で1個壊れて差し替えたらリビルド中にまた1個壊れて、それを差し替えたらさらに壊れて…なんて話も聞きますし」「それはひどい😆

「でクラウドだとそういうつらい目に遭わなくて済むので広く使われるようになったという感じです☺」「なるほど!」「物理サーバーはほこり溜まるからキライ😆」「Azureが伸びているというのも、おそらくそういうオンプレのWindows Serverをクラウド化したいという需要に乗ってるのかもしれない🤔

⚓GitHubの新機能


つっつきボイス:「ついさっき上が流れてきました🎉」「GitHubで定義ジャンプできるようになったか🎉」「さしあたってRubyとGoとPython」「定義ジャンプは先々週ぐらいからできてたかな〜、GitHub Universe↓でいろいろ新しいものが発表されてましたし」「あ、GitHubのイベントですね」


githubuniverse.comより

「PHPの定義ジャンプはまだ難しいんだろうな😆」「Rubyの方が難しそうですけど😆」「候補リスト出すぐらいならやれそう」「method_missing使ってると無理かも😆」「今Active Supportあたりで定義ジャンプやってみると、インスタンス変数なのにメソッド引いてきたりしてるし😆」「それでもないよりうれしい😂」「ソース取ってくるのめんどいときとかね😆


「スマホアプリでプルリクやマージできるとか何とか😆」「スマホでまでやりたくないな〜🤣


「GitHub Actionsも来ましたね🎉」「今のところ/actionsの管理方法がまだバタバタしてる感じかな〜」「その辺の運用はこれからでしょうね☺」「GitHubでやれることがいろいろ増えるのはいい👍


「これもさっき流れてきました↑」「Rosetta Project↓みたい?😆」「砂漠にアーカイブデータを置くプロジェクトってのもあった覚えが🤔」「砂漠も極地も環境が安定してるからでしょうね」「南極の氷はそろそろ溶け始めてるんじゃ?😆」「Git自身は分散管理だから本来こういうプロジェクトはなくてもいいはずで、その意味ではGitではなくてGitHubのプロジェクトなんだな〜ってちょっと思ったり😆


rosettaproject.orgより

⚓その他クラウド

⚓JavaScript

⚓TypeScript 3.7リリース(JSer.infoより)

breaking changesもビシビシ入っているようです。


つっつきボイス:「TypeScript書いてる人います?」「ほい🤚」「おぉやってますか」「まあTSもどこまで厳しくやるかで原理主義派とそうでない派の紛争が勃発してて面倒ですが😆」「any許すかどうかとか😆

参考: TypeScriptのanyは嘘? - Togetter

「CoffeeScriptを使っている方は?」「rails newするときに真っ先に外すgem😆」「さすがに使う人減ったか」「実は数年前にCoffeeScript 2↓ってのが出てるみたいですけど😆」「おや😆

参考: CoffeeScript 2という新たなる希望 - Qiita

⚓その他JS

// 同記事より: 見苦しくなる例
// Compiled with faster.js
const arr = [1, 2, 3];
const results = new Array(arr.length);
const _f = (e => 2 * e);
for (let _i = 0; _i < arr.length; _i++) {
  results[_i] = _f(arr[_i], _i, arr);
}

つっつきボイス:「いわゆる『早すぎる最適化はよくない』という話☺」「よくある記事ですけどJavaScriptベースですね」「もうJITに任せればいいんじゃね?😆

⚓CSS/HTML/フロントエンド/テスト

⚓h2とh2c

ということで、h2 も h2c も HTTP/2 のこと、TLSによって暗号化がされているものがh2、暗号化されていないものがh2cという事だった。
同記事より


つっつきボイス:「先週のウォッチ制作中に見つけた記事です」「DevToolsのプロトコル欄にh2とかh2が出ると」「暗号化してないのがh2c😳」「知らんかった〜別に困らんけど😆」「ちょい短すぎかと😆」「まだ広まってないし名前また変わるかもしれませんし😆」「そのうちHTTP/3出ちゃうでしょうし🤣」「最近はバージョンをカジュアルに上げるの流行ってますし🤣

⚓言語・ツール

⚓Test Anything Protocol(TAP)とは


testanything.orgより

# testanything.orgより
TAP version 13
1..N
ok 1 Description # Directive
# Diagnostic
  ---
  message: 'Failure message'
  severity: fail
  data:
    got:
      - 1
      - 3
      - 2
    expect:
      - 1
      - 2
      - 3
  ...
ok 47 Description
ok 48 Description
more tests....

つっつきボイス:「最初Ruby Weeklyでrspec-tap-formatters↑を見つけたのですが、TAPが何なのかが気になって」「どれどれ、1から6までテストするとして、以下だと1と3がfailして、6番目のテストもないから6もfailするという感じ↓」「言語に依存しないでテストを書けるようにするものみたい」

1..6
not ok
ok
not ok
ok
ok
FAILED tests 1, 3, 6
Failed 3/6 tests, 50.00% okay

「rspec-tap-formattersはspecをそのTAP形式↓で出せるのね」「他のテストツールもTAP扱えるようになればテストを共通化できるだろうという夢の世界😆」「そういえばcucumberも言語非依存なんで、複数言語向けのライブラリでcucumberでテスト書いてるところもあったりするけど、そういうふうに1段階上がるノリなのかも🤔」「それはそれでわかりますね☺

TAP version 13
# test: String {
  # group: #present? {
    # group: when whitespaces and other characters {
      ok 1 - returns true
      1..1
      # tests: 1, passed: 1
    }
    # group: when nil {
      not ok 1 - returns false
        ---
        location: "./resources/string_spec.rb:8"
        error: |-
          Failure/Error: expect(string.present?).to eq(false)
          NoMethodError:
            undefined method `present?' for nil:NilClass
        backtrace: "./resources/string_spec.rb:9:in `block (4 levels) in <top (required)>'"
        ...
      1..1
      # tests: 1, failed: 1
    }
    # group: when whitespaces only {
      ok 1 - returns false
      1..1
      # tests: 1, passed: 1
    }
    1..3
    # tests: 3, passed: 2, failed: 1
  }
  1..3
  # tests: 3, passed: 2, failed: 1
}
1..3
# tests: 3, passed: 2, failed: 1
# duration: 0.026471 seconds
# seed: 27428

⚓その他

⚓これもソリューション

ボヘカラ on Twitter: “これやると、その店だけ原価率が如実に上がるので、SVがお店に張り付く事になります。最初はオーバーポーションが疑われますが、暫くするとSVが居る時だけ原価率が落ち着くので、抜いてるなと判明します。そうなると監視カメラとマイクとPOS… https://t.co/V6NXqfPBqm”

台湾のレシートは宝くじ https://www.ouchi.link/entry/taiwan-receipt /薬を飲んだら宝くじチャンス。ただし薬を飲まなかったら当選金は渡さない。これで98%が薬を飲むようになった。https://ameblo.jp/gawayo508/entry-11969395639.html

2019/11/12 10:34

⚓番外

⚓英語のお気持ち


つっつきボイス:「そういえばpriorityって文脈でちょい変わるなと思って」「どっちもありますね〜☺

「footgunは翻訳中に調べて見つけたんですが普通の辞書にはまだ載ってませんでした」「新しい言葉っぽい🤔」「これ単語になるのか😆」「『自分の足を撃ち抜く』がどんどん短くなってついに1語になった感😆」「略しすぎ😆」「プログラマーならわかる😆」「たぶんIT業界限定☺


後編は以上です。

おたより発掘

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191118前編)ActiveJob引数のログ抑制、RailsガイドProプランお試し、ファイルアップロードのレジュームgemほか

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

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

Ruby Weekly

RubyFlow

160928_1638_XvIP4h

DB Weekly

db_weekly_banner

Publickey

publickey_banner_captured

Serverless Status

serverless_status_banner

JSer.info

jser.info_logo_captured

週刊Railsウォッチ(20191125)Ruby 3.0は2020年12月にリリース決定、Rails 5.2.4rc2とRuby 2.7.0-preview3がリリースほか

$
0
0

こんにちは、hachi8833です。先週発熱してしまいました💊。今週のウォッチはいつもより短くなっています🙇

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください

⚓お知らせ: 週刊Railsウォッチ「第17回公開つっつき会」(無料)

第16回目公開つっつき会は、来週12月05日(木)19:30〜にBPS会議スペースにて開催されます。

週刊Railsウォッチの記事やここだけの話にいち早く触れられるチャンス!発言・質問も自由です。引き続き皆さまのお気軽なご参加をお待ちしております🙇

⚓Rails

⚓Rails 5.2.4rc2がリリース(Ruby公式ニュースより)

Changelogを見た限りでは、修正この間の6.0.1よりも少なめで、2017年などやや古い修正も含まれています。セキュリティ関連の修正は見当たりません。

PR: Make ActiveSupport::Logger Fiber-safe by cmrd-senya · Pull Request #36753 · rails/rails

Rails 6.0.1がリリース!修正を追ってみました

⚓Rails 6のActive Record新機能10(Ruby Weeklyより)

目次より:

  1. rails db:prepare
  2. rails db:seed:replant
  3. データベース自動切り替え
  4. Enumのnot_*スコープ
  5. #extract_associated
  6. #annotate
  7. #touch_all
  8. #destroy_by#delete_by
  9. #whereでのエンドレスレンジ..
  10. implicit_order_column

⚓Ruby

⚓Ruby 2.7.0-preview3がリリース

preview2↓からの変更点はそれほどありませんので、違いのみピックアップします。

Ruby 2.7.0-preview2がリリース

キーワード引数について

  • preview3リリースの主な目的は、キーワード引数の互換性確認用。
    • キーワード引数関連のwarningが冗長という指摘があり、「deprecation warningをデフォルトで無効にする(#16345)」か「deprecation warningを減らす(#16289)」の2つのソリューションを議論中。まだ決定は下されていないが、最終リリースでは修正の予定。

つまりpreview3で互換性を開発者にどしどし確認して欲しいということですね。

取り消された機能

  • experimentalのメソッド参照演算子.:Feature #12125, Feature #13581, Feature #16275)が取り消し
  • Symbol#to_sModule#nametrue.to_sfalse.to_snil.to_sが常にfrozenを返す変更(Feature 16150)が取り消しに
  • regexp#match?にnilを渡した場合に(StringやSymbolと同様に)TypeErrorを返す変更(Feature #13083)が取り消し

デフォルトgem

上はすべて11/06から公開されています。

  • 以下のデフォルトgemはruby-coreに昇格のみ、rubygems.orgでは未公開
    • monitor
    • observer
    • timeout
    • tracer
    • uri
    • yaml

その他

  • MonitorクラスとMonitorMixinモジュールのパフォーマンス改善(Feature #16255

⚓RubyConf 2019に参加した

先週ナッシュビルで開催されたRubyConf 2019のNoah Gibbsさんによる記事です。

抜粋:

  • Ruby 3.0の来年12月リリースが決定したことが公式にアナウンスされた
  • Fiberを用いたasyncなgemがRubyコアに多数入りそう
  • 個人的にArtichokeが面白そう
  • rubyfmtは発展途上だがアツい(以下の記事参照)

Matzのキーノート動画(54m52s)を覗いてみたところ「よほどのことがない限り来年12月にRuby 3.0をリリースする」「期限切ったことがとても重要」「もしかしたら機能をいくつか落とすかもしれないけど」「頑張って締め切り守る」とのことでした。

RuboCop作者がRubyコードフォーマッタを比較してみた: 前編(翻訳)



rubyconf.orgより

⚓その他Ruby


今週は以上です。

おたより発掘

バックナンバー(2019年度第4四半期)

週刊Railsウォッチ(20191119後編)メソッド参照演算子が廃止、GitHub新機能続々、平成Ruby会議、GitHub OAuthバイパスほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly

docker-composeを便利にするツール「dip」を使ってみた

$
0
0

dipとは

dipは、Rails開発会社のEvil Martiansのメンバーが作った、docker-composeでの作業を能率的に行えるツールです。dipはDocker Interaction Processの略だそうです。

Rubyで書かれていますが、Rubyがなくても使えます。元々Rails向けに作られたツールのようですが、Evil MartiansのReactアプリにもdip.xmlがあることからわかるように、Railsに限らず一般のdocker-composeでも使えます。私の場合はローカル開発環境でのみ使っています。

Evil Martiansはdipの記事を書くと言ってましたが、未だに出ていないので自分で記事を書いてみました😆

dipのREADMEにはシェルと統合してdipを省略する方法も書かれていますが、ちょいやりすぎ感あったので自分はやっていません。

必要なもの

  • Ruby 2.3以上(Rubyを使う場合)
  • Dockerとdocker-composeがインストールされていること
  • docker-compose.ymlファイル

インストール

以下のいずれかの方法でdipをインストールし、dip.ymlをdocker-compose.ymlと同じ階層に置きます。

1. homebrewでのインストール

brew tap bibendi/dip
brew install dip

2. gemコマンドでのインストール

gem install dip

3. コンパイル済みバイナリのインストール

curl -L https://github.com/bibendi/dip/releases/download/4.2.0/dip-`uname -s`-`uname -m` > /usr/local/bin/dip
chmod +x /usr/local/bin/dip

dip.ymlの設定

リポジトリにある以下のdip.ymlはEvil MartiansのRails案件に寄った設定なので、自分の用途に応じてカスタマイズが必要です。interaction:の下のサブコマンドを適宜カスタマイズします。

version: '4'

compose:
  files:
    - docker-compose.yml

interaction:
  bash:
    description: Open the app container bash shell
    service: app
    command: /bin/bash

  dev:
    service: app
    command: exit
    subcommands:
      console:
        description: Open the gem console
        command: ./bin/console
      clean:
        description: Clean dependencies
        command: rm -rf Gemfile.lock

  bundle:
    description: Run Bundler commands
    service: app
    command: bundle

  rspec:
    description: Run Rspec commands
    service: app
    command: bundle exec rspec

  rubocop:
    description: Run Rubocop commands
    service: app
    command: bundle exec rubocop

provision:
  - dip app clean
  - dip bundle install

なおprovision:というコマンドは固定されているらしく、他の名前にできませんでした。

自分のカスタマイズ例

これは以下の記事で自分が使ったdip.ymlです。

Rails 6のDocker開発環境構築をEvil Martians流にやってみた

dip bashdip rubocopは長いのでdip shdip copにしてみました。

なおtest:というサブコマンドは予約語なのか認識できなかったので、minitest:としました。

version: '4'

environment:
  RAILS_ENV: development

compose:
  files:
    - docker-compose.yml

interaction:
  sh:
    description: Open the app container bash shell
    service: backend
    command: /bin/bash
    compose_run_options: [no-deps]

  bundle:
    description: Run bundler command
    service: backend
    command: bundle
    compose_run_options: [no-deps]

  rake:
    description: Run rake command
    service: backend
    command: bundle exec rake

  rails:
    description: Run rails command
    service: backend
    command: bundle exec rails
    subcommands:
      s:
        description: Start rails server
        service: rails
        compose_run_options: [service-ports]

  yarn:
    description: Run yarn command
    service: backend
    command: yarn

  minitest:
    description: Run minitest
    service: backend
    environment:
      RAILS_ENV: test
    command: bundle exec rails test

  cop:
    description: Run rubocop
    service: backend
    default_args: -a
    command: bundle exec rubocop

  psql:
    description: Run psql console
    service: postgres
    command: psql -h postgres -U postgres -d postgres

  inspect_all:
    description: Run all checkers
    service: backend
    command: bundle exec license_finder
    command: bundle exec rails test
    command: bundle exec rails_best_practices .
    command: bundle exec bundle-audit
    command: bundle exec brakeman

provision:
  - dip compose down
  - dip compose up -d postgres
  - dip yarn install
  - dip sh -c "./bin/setup 2> /dev/null; exit 0"
  - dip sh -c ./bin/setup
  - dip sh -c "RAILS_ENV=test ./bin/setup"

使い方

dip.ymlのあるディレクトリでdip lsすると、dip.ymlで設定したサブコマンドの一覧が表示されます。dip helpとするとさらに詳しいヘルプが表示されます。

$ dip ls
sh           # Open the app container bash shell
bundle       # Run bundler command
rake         # Run rake command
rails        # Run rails command
rails s      # Start rails server
yarn         # Run yarn command
minitest     # Run minitest
cop          # Run rubocop
psql         # Run psql console
inspect_all  # Run all checkers

あとはdip サブコマンドを実行するだけです。

サブコマンドにはdip bundle installdip rails consoleのようにオプションも追加できます。

dip sshdip nginxなど凝った機能がいろいろあるようですが、その辺りはまだ追求していません。

dipの便利な点

Dockerが公式にこういうツールを出せばいいのにと思っちゃいました。

1. コンテナにログインせずに作業できる

bundle installrails dbconsoleyarn install --check-filesといった決まりきったコマンドを実行するのに、いちいちシェルでログインするのは面倒です。また、Dockerのシェルの安定性にもちょっと不安があります。

dipならdip bundle installdip rails dbconsoledip yarn install --check-filesで実行できます。

なおGemfileがない状態でのrails newはさすがにログインが必要でした。

2. コンテナの起動状態を気にしなくてよい

docker-composeでコンテナに入る場合、コンテナが起動していなければdocker-compose run app bash、コンテナが起動していればdocker-compose exec app bashとするなど、面倒な使い分けが必要になります。

そもそもdocker-composeというコマンドが長すぎですよね。自分はdcomというbashエイリアスを作っているほどです。

dipなら、たとえば自分の設定したdip shdip rails sはコンテナが起動していなくても使え、dipが終了すればコンテナも終了してくれます。コンテナの二重起動を気にしなくてよくなるのは本当にありがたいです😂

その分起動は少々遅くはなると思います。

まあ自分は何か作業した後はdcom downを実行する癖が付いてしまいましたが。

3. 複数のサブコマンド実行が書きやすい

docker-composeの中でシェルコマンドを複数実行しようとすると、たとえば以下のように&&でつなげてbash -cで実行するというダルい書き方になってしまいます。

command: bash -c "elasticsearch -d && elasticsearch --http.port=9250"

コマンドを5つも6つもつなげると美しくありませんし、シェルスクリプトに追い出すのも何だか敗北感があります。

dipなら自分のサブコマンドにcommand:を複数書けます。ただしこの方法はドキュメントに見当たらないので、サポート外機能のつもりで使っています。

  inspect_all:
    description: Run all checkers
    service: backend
    command: bundle exec license_finder
    command: bundle exec rails test
    command: bundle exec rails_best_practices .
    command: bundle exec bundle-audit
    command: bundle exec brakeman

4. Dockerfileやdocker-compose.ymlの変更が基本的に不要

dip.ymlはDockerfileやdocker-compose.ymlと別に設定できるので、うかつにこれらを変更してチームの人に怒られずに済みます。dip.ymlを~/.gitignore_globalに加えておけば自分用に心ゆくまでカスタマイズできます。

試していませんが、さすがにsshログイン機能などを使う場合はdocker-compose.ymlの変更も必要そうです。

services:
  web:
    environment:
      - SSH_AUTH_SOCK=/ssh/auth/sock
    volumes:
      - ssh-data:/ssh:ro

volumes:
  ssh-data:
    external:
      name: ssh_data

おまけ

以下のprovision:コマンドは自分のRails開発環境の初期設定用にbin/setupなどを実行します。

provision:
  - dip compose down
  - dip compose up -d postgres
  - dip yarn install
  - dip sh -c "./bin/setup 2> /dev/null; exit 0"
  - dip sh -c ./bin/setup
  - dip sh -c "RAILS_ENV=test ./bin/setup"

4つ目のdip sh -c "./bin/setup 2> /dev/null; exit 0"は本来不要なはずなのですが、自分の環境ではなぜか./bin/setupが1回目で必ずデータベース接続に失敗し、2度目だと成功するので、仕方なく加えています😅。そのうち直したいです。

おたより発掘

関連記事

クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)

Goby: Rubyライクな言語(7)Ripperライブラリを追加した

$
0
0

こんにちは、hachi8833です。今年もアドベントカレンダーの季節がやってまいりました。だいぶ間が空いてしまいましたが、Goby記事です。

Goby: Rubyライクな言語(2)Goby言語の全貌を一発で理解できる解説スライドを公開しました!

Ripperクラス

2018年9月に、GobyにRipperライブラリを追加しました。Rubyのライブラリと同じ名前で恐縮です。

RubyのRipperはクラスメソッドとインスタンスメソッドを両方備えていますが、Gobyのは今のところクラスメソッドのみです。require 'ripper'して使う点はGobyも同じです。

RubyのRipper

RubyのRipperは、Rubyの標準ライブラリに含まれているパーサーです。Rubyの構文解析を追ったり静的解析を行ったりするときに使われます。なおRubocopではある時期からRipperを使わなくなったそうです(関連記事)。

参考: class Ripper (Ruby 2.6.0)

# Ruby 2.6.5
require 'ripper'
Ripper.lex "[1, 2, 3].map do |i| i * i end"
# 出力(改行を追加しています)
[
  [[1, 0], :on_lbracket, "[", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>], 
  [[1, 1], :on_int, "1", #<Ripper::Lexer::State: EXPR_END>], 
  [[1, 2], :on_comma, ",", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 3], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 4], :on_int, "2", #<Ripper::Lexer::State: EXPR_END>],
  [[1, 5], :on_comma, ",", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 6], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 7], :on_int, "3", #<Ripper::Lexer::State: EXPR_END>],
  [[1, 8], :on_rbracket, "]", #<Ripper::Lexer::State: EXPR_END>],
  [[1, 9], :on_period, ".", #<Ripper::Lexer::State: EXPR_DOT>],
  [[1, 10], :on_ident, "map", #<Ripper::Lexer::State: EXPR_ARG>],
  [[1, 13], :on_sp, " ", #<Ripper::Lexer::State: EXPR_ARG>],
  [[1, 14], :on_kw, "do", #<Ripper::Lexer::State: EXPR_BEG>],
  [[1, 16], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG>],
  [[1, 17], :on_op, "|", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 18], :on_ident, "i", #<Ripper::Lexer::State: EXPR_ARG>],
  [[1, 19], :on_op, "|", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 20], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG|EXPR_LABEL>],
  [[1, 21], :on_ident, "i", #<Ripper::Lexer::State: EXPR_END|EXPR_LABEL>],
  [[1, 22], :on_sp, " ", #<Ripper::Lexer::State: EXPR_END|EXPR_LABEL>],
  [[1, 23], :on_op, "*", #<Ripper::Lexer::State: EXPR_BEG>],
  [[1, 24], :on_sp, " ", #<Ripper::Lexer::State: EXPR_BEG>],
  [[1, 25], :on_ident, "i", #<Ripper::Lexer::State: EXPR_END|EXPR_LABEL>],
  [[1, 26], :on_sp, " ", #<Ripper::Lexer::State: EXPR_END|EXPR_LABEL>],
  [[1, 27], :on_kw, "end", #<Ripper::Lexer::State: EXPR_END>]
]

GobyのRipper

GobyのRipperは、今のところ以下のクラスメソッドのみです。

  • tokenize
  • parse
  • lex
  • instruction

なおGobyではシンボルが常にStringクラスなので、requirerequire :ripperのようにシンボル形式でも書けます😋

# Goby 0.1.11
require :ripper
Ripper.lex("[1, 2, 3].map do |i| i * i end")
# 出力(改行を追加しています)
[
  [0, "on_lbracket", "["], 
  [0, "on_int", "1"], 
  [0, "on_comma", ","], 
  [0, "on_int", "2"], 
  [0, "on_comma", ","], 
  [0, "on_int", "3"], 
  [0, "on_rbracket", "]"], 
  [0, "on_dot", "."], 
  [0, "on_ident", "map"], 
  [0, "on_do", "do"], 
  [0, "on_bar", "|"], 
  [0, "on_ident", "i"], 
  [0, "on_bar", "|"], 
  [0, "on_ident", "i"], 
  [0, "on_asterisk", "*"], 
  [0, "on_ident", "i"], 
  [0, "on_end", "end"], 
  [0, "on_eof", ""]
 ]

今のところは、だいぶあっさりした出力です。

importは以下のようになっています。

package ripper

import (
    "fmt"
    "github.com/goby-lang/goby/compiler"
    "github.com/goby-lang/goby/compiler/bytecode"
    "github.com/goby-lang/goby/compiler/lexer"
    "github.com/goby-lang/goby/compiler/parser"
    "github.com/goby-lang/goby/compiler/token"
    "github.com/goby-lang/goby/vm"
    "github.com/goby-lang/goby/vm/classes"
    "github.com/goby-lang/goby/vm/errors"
    "strings"
)

一見してわかるように、Gobyに備わっているコンパイラを直接importして使っています。基本的にGobyのRipperは、コンパイラのparserやlexerやtokenなどの結果を正直に吐き出しているだけです。だからこそ私でもどうにか作れました。

当初のRipperは、StringIntegerなどと同じネイティブクラスとして作り始めたのですが、常駐させることもないと思い、ローダブルなライブラリに変更して必要なものを以下のように読み込んでいます。require :ripperするまでアクティベートされません。

// Imported objects from vm
type Object = vm.Object
type VM = vm.VM
type Thread = vm.Thread
type Method = vm.Method
type StringObject = vm.StringObject
type HashObject = vm.HashObject
type ArrayObject = vm.ArrayObject

GobyのローダブルライブラリのGoコードは、以下のようにinitでクラスメソッドとインスタンスメソッドを登録しておきます。

// Internal functions ===================================================
func init() {
    vm.RegisterExternalClass("ripper", vm.ExternalClass("Ripper", "ripper.gb",
        // class methods
        map[string]vm.Method{
            "instruction": instruction,
            "lex":         lex,
            "new":         new,
            "parse":       parse,
            "tokenize":    tokenize,
        },
        // instance methods
        map[string]vm.Method{},
    ))
}

ただしGobyのローダブルライブラリでは、上の他にダミーのRipperクラスをripper.gbというファイルとして用意しておく必要があります。requireすると、上のinitでripper.gbとripper.goがバインドされます。

# A dummy class for ripper.go
class Ripper
end

Ripperの中身

tokenizeparselexinstructionメソッドは作りとして大差はないので、tokenizeメソッドを例に取ります。

func tokenize(receiver Object, sourceLine int, t *Thread, args []Object) Object {
    if len(args) != 1 {
        return t.VM().InitErrorObject(errors.ArgumentError, sourceLine, "Expect 1 argument. got=%d", len(args))
    }

    arg, ok := args[0].(*StringObject)
    if !ok {
        return t.VM().InitErrorObject(errors.TypeError, sourceLine, errors.WrongArgumentTypeFormat, classes.StringClass, args[0].Class().Name)
    }

    l := lexer.New(arg.Value().(string))
    el := []Object{}
    var nt token.Token
    for i := 0; ; i++ {
        nt = l.NextToken()
        if nt.Type == token.EOF {
            el = append(el, t.VM().InitStringObject("EOF"))
            break
        }
        el = append(el, t.VM().InitStringObject(nt.Literal))
    }
    return t.VM().InitArrayObject(el)
}

lexer.Newのようにコンパイラ配下のパッケージで.Newし、文字列を食わせた結果をarrayとして返しているだけです。

instructionは、階層が2つに固定されているのを利用してArrayやHashに変換しています。

func convertToTuple(instSet []*bytecode.InstructionSet, v *VM) *ArrayObject {
    ary := []Object{}
    for _, instruction := range instSet {
        hashInstLevel1 := make(map[string]Object)
        hashInstLevel1["name"] = v.InitStringObject(instruction.Name())
        hashInstLevel1["type"] = v.InitStringObject(instruction.Type())
        if instruction.ArgTypes() != nil {
            hashInstLevel1["arg_types"] = getArgNameType(instruction.ArgTypes(), v)
        }
        ary = append(ary, v.InitHashObject(hashInstLevel1))

        arrayInst := []Object{}
        for _, ins := range instruction.Instructions {
            hashInstLevel2 := make(map[string]Object)
            hashInstLevel2["action"] = v.InitStringObject(ins.ActionName())
            hashInstLevel2["line"] = v.InitIntegerObject(ins.Line())
            hashInstLevel2["source_line"] = v.InitIntegerObject(ins.SourceLine())

            arrayParams := []Object{}
            for _, param := range ins.Params {
                arrayParams = append(arrayParams, v.InitStringObject(covertTypesToString(param)))
            }
            hashInstLevel2["params"] = v.InitArrayObject(arrayParams)

            if ins.Opcode == bytecode.Send {
                hashInstLevel1["arg_set"] = getArgNameType(ins.Params[3].(*bytecode.ArgSet), v)
            }

            arrayInst = append(arrayInst, v.InitHashObject(hashInstLevel2))
        }

        hashInstLevel1["instructions"] = v.InitArrayObject(arrayInst)
        ary = append(ary, v.InitHashObject(hashInstLevel1))
    }
    return v.InitArrayObject(ary)
}

Ripperのバグ修正

実はマージ後しばらくしてRipper.instructionの動作がおかしくなっていました。この間それを思い出してやっと直しました😅#804)。

最近のGoby

今、GobyにもRubyのような文字列の式展開機能を付けたいと思ってゴニョゴニョ調べています。現在のGobyの構文解析はシンプルなのですが、式展開をやるにはlexerやparserやASTをそこそこ大掛かりに変えないといけなさそうで悩んでます。まあ趣味なのでちびちび進めるつもりです😊

関連記事

Rubyの式展開(string interpolation)についてまとめ: `#{}`、`%`、Railsの`?`

RuboCop作者がRubyコードフォーマッタを比較してみた: 後編(翻訳)

Viewing all 1079 articles
Browse latest View live