- Ruby / Rails関連
週刊Railsウォッチ(20200225前編)RubyのShellwordsライブラリは知っておくべき、VCRはやはり有能、copを自作、Hix on Rails記事ほか
こんにちは、hachi8833です。次回の技術書典は残念ながら中止になりました。型システム祭りも延期だそうです。
【#技術書典 8 開催中止のお知らせ】
誠に残念ですが、先週末の新型コロナウイルス感染症に関わる状況の急激な変化を鑑み、2月29日(土)、3月1日(日) に予定しておりました技術書典8の中止、およびオンライン開催「技術書典 応援祭」への変更をお知らせいたします。https://t.co/MXWhI4DPFm— 技術書典 公式アカウント (@techbookfest) February 17, 2020
- イベント: 型システム祭り - connpass -- 延期
つっつきボイス:「今日は1名Zoomで臨時リモートつっつき参加です」「はい聞こえま〜す」「five nineでつながってますね」「それは?」「無線用語😆: 了解度5で信号強度9📡」「Zoomやっぱりいいわ〜❤️」
参考: シグナルレポート(RSレポート)
「今やひととおりのイベントが中止ですし😇」「今度こそ技術書典行けるかと思ったのに😢」「常連ですが残念です😭」「代わりにオンラインでの開催「技術書典 応援祭」なんてのをやるみたい」「今回の出展者は次回優遇措置適用ですって」「出展者も本印刷したりしてますし」「主催者側の負担も半端ないはず」「出展料返却なしとあるけど会場費考えたら無理もない」「いろいろ大変だ...」
以下のツイートはつっつき後に見つけました。
面倒かもしれんが、中止になってしまったテック系 / デザイン系のイベントは、募金ができる「支援ページ」作って欲しいわ。中止になって資金的大打撃 → その結果次ができなくなりました ... というのは避けて欲しいので支援したい。
— ヤスヒサ 🗑 (@yhassy) February 20, 2020
- 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- 毎月第一木曜日に「公開つっつき会」を開催しています: お気軽にご応募ください
⚓お知らせ: 3月の「公開つっつき」はお休みします
これまで毎月第一木曜に休まず開催してまいりました週刊Railsウォッチ「公開つっつき会」ですが、昨今のコロナウイルス流行を鑑みて3月は初の「開催なし」といたします🙇。
⚓Rails: 先週の改修(Rails公式ニュースより)
公式の更新情報がなかったのでコミットリストから見繕いました。
今更ですが@kamipoさんのコントリビューション数がダントツですね。
参考: Rails Contributors - This year
つっつきボイス:「この週は久々に@kamipoさんの修正が少なめでした」「たまには一息入れないと☺️」
⚓attributes=
をassign_attributes
のエイリアスにした
- PR: Copy argument in AttributeAssignment#attributes= by eugeneius · Pull Request #38473 · rails/rails
# activerecord/lib/active_record/attribute_assignment.rb#L13
+ alias attributes= assign_attributes
+
つっつきボイス:「修正はシンプルですね」「2つのメソッドが別々でもよかったんでしょうけど、今後また挙動が変わったときに合わせるのが面倒だからエイリアスにしたのかなと想像☺️」「引用されてる#38401の方がメインだったのかも」
#38401までは
assign_attributes
もattributes=
もActive Modelで定義されていて引数をコピーしていた。assign_attributes
がActive Record内でオーバーライドされるようになってそこではコピーが発生するが、attributes=
がオーバーライドされていなかった。
つまりattributes=
に代入する引数がネストまたはマルチパラメータだと引数が改変され、引数がコピーされていなかったということになる。
同PRより大意
- PR: Do not stringify attributes in assign_attributes by vinistock · Pull Request #38401 · rails/rails
⚓rails generate
の改善
# railties/lib/rails/generators/actions.rb#L250
def rails_command(command, options = {})
- execute_command :rails, command, options
+ if options[:inline]
+ log :rails, command
+ command, *args = Shellwords.split(command)
+ in_root do
+ silence_warnings do
+ ::Rails::Command.invoke(command, args, options)
+ end
+ end
+ else
+ execute_command :rails, command, options
+ end
end
つっつきボイス:「PRの内容がタイトルのshell out(=不意の出費を払う) とどうも意味がつながらなくて悩んだんですが、修正でrequire shellwords
が追加されているので、それにちなんでshell outってもじったのかなと思いました🤔」「ああShellwordsってライブラリあるある😆」
⚓Rubyでシェル操作するならShellwordsを使おう
「Shellwordsって、コマンドラインの引数をいい感じに配列に分解したりエスケープしたりしてくれるっぽいですね」「そうそう、Shellwordsはシェルのコマンドライン引数をクォートしたりできます🧐」
参考: Rubyから外部コマンドを実行するときはShellwordsモジュールが便利 - ブログのおんがえし
「Shellwordsはいわゆるコマンドインジェクションを防止するライブラリで、コマンドライン引数を扱うなら基本これを使うべきです😎」「そうでしたか!😳」「それをRailsコマンドの処理に使うようにしたと」
OSコマンドインジェクションの仕組みとその対策 | セキュリティ対策 | CyberSecurityTIMES
「Shellwords使わないと引数処理マジつらいですよ〜😭」「おぉ」「コマンド引数が完全に固定されてるとか自分で作った引数しか渡さないならいいんですが、ユーザー入力を含む文字列とかを引数に渡すのであれば、Shellwords使わないときっとインジェクション作り込んじゃいます💀」
「一番安全なのはfork()
自体に引数を渡すやり方」「おぉ?」「SQLで言うPrepared Statementみたいなもので、コマンドと引数をひとつの文字列にまとめるんじゃなくて引数を分離して渡す: これならどんなinfected stringぶち込まれても大丈夫💪」「あ〜なるほど」
参考: prepareStatementの使用 - データベース接続 - サーブレット入門
「とにかくShellwordsはRubyでシェルを操作するならぜひ知っておくべき🧐」「単に便利というレベルじゃなくてマストなんですね」「Shellwords使わないなら、自分のコードがインジェクションされない理由を全部説明できないといけない🧐」「Shellwordsって今まで知らなかったんですが、重要だったんですね」「まあ忙しいときはString#shellescape
で引数ごとにちまちまやることもありますけど、と思ったらこれも内部でShellwordsを呼んでた😆」「引数をまとめて処理するならShellwordsの方がラク😋」
- ドキュメント:
String#shellescape
(Ruby 2.7.0 リファレンスマニュアル) - ドキュメント:
Shellwords.#shellescape
(Ruby 2.7.0 リファレンスマニュアル)
⚓シェルいろいろありすぎ
「ところで引数の扱いってシェルによって全然違ったりしますよね😇: 今はだいたいみんなbash使ってますけど、たまにtcsh使う人がいたりしますし」「私はずっとbash一筋ですけど、AIX(IBMのUnix)を使うはめになったときは強制的にkshでした😆」「ksh😆」「名前しか見たことない😆」「kshってKorn Shellの略でしたっけ?」「はい、kshマジわからなかった😆」「某大学のデフォルトシェルが何か変だなと思ったらtcshで絶句しました🤣」
参考: tcsh - コマンド (プログラム) の説明 - Linux コマンド集 一覧表
参考: ksh
「macOSもCatalinaからデフォルトがzshになっちゃいましたね😢」「zshでかくてforkが重いからやめた方がいい気がしますけど😆」「🤣🤣」「ぜとしぇをデフォルトにするぐらいならfishあたりの方がよかったのでは😆」「同意です😆」
参考: Z Shell - Wikipedia
参考: Friendly interactive shell - Wikipedia
⚓expand_cache_key
のアロケーションを削減
# activesupport/lib/active_support/cache.rb#L80
def expand_cache_key(key, namespace = nil)
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{prefix}/"
end
expanded_cache_key << retrieve_cache_key(key)
expanded_cache_key
end
つっつきボイス:「expand_cache_key
的なものをこの間も見かけた気がしますね☺️(ウォッチ20200203)」「dup
をやめて+
演算子に変えてますね」「frozenの文字列に+
するとduplicateするヤツか😆」「まだ慣れない😅」「+
ってdup
の倍以上速いそうです」「解説がないとわかんなさそう😆」
参考: String#+@
(Ruby 2.7.0 リファレンスマニュアル)
「そういえば最近Twitterで『速くなるのはいいんだけど他の人が読みづらくなるコードはつらい』みたいなのを見かけましたね😆」「😆😆」「それ拾いました😋↓」「ライブラリコードだしいいのではという考え方もありますが、書くならコメントにも書いて欲しい気持ち😆」
もう一つ辛いコードありました
「将来どう要件が変化したらこの巨大な拡張機能使われるの?という無駄な汎用性で複雑になったコード」
— まろ@関数型言語作曲機械学習勉強してない (@_marony) February 18, 2020
⚓NBSPチェックの正規表現を修正
ついでにmatch?
をinclude?
に修正しています。
# activesupport/lib/active_support/configuration_file.rb#L34
File.read(content_path).tap do |content|
- if content.match?(/\U+A0/)
+ if content.include?("\u00A0")
warn "File contains invisible non-breaking spaces, you may want to remove those"
end
end
つっつきボイス:「/\U+A0/
マジか😆」「テストなかったのかしら😆」「本来ならテストと一緒に書いておきたいヤツ」
⚓NBSP文字とラテン系言語
「NBSP(ノーブレークスペース)って日本だと普通使わないかなと思うんですけど、どうでしょう?」「HTMLで
が出てくることはありますね☺️」「Excelとかから自動生成するとちょくちょく混じってくる😆」
「ローカライズ時代の経験ですけど、ラテン系ヨーロッパの人がすごくNBSPを使うんですよ: Orquesta De La Luz
みたいな一続きの固有名詞を途中で改行したくないときにOrquesta●De●La●Luz
みたいに●のところにNBSPを置く慣習がありまして😅」「へぇ〜😳」「スタイルガイドでNBSPを禁止してあっても、フランスやスペインの人が隙を見てNBSPをちょくちょく入れてくるので仕方なく普通のスペース文字に置換したりしてました😭」
参考: ノーブレークスペース - Wikipedia
参考: エンジニア・Webデザイナー必読: アプリケーションを国際化・多言語展開する前に知っておくべきこと - Qiita -- 昔Qiitaに書いた記事です
⚓rails stats
でTypeScriptファイルもカウントするようになった
- PR: Count TypeScript files as JavaScript in `rails stats` by jpcody · Pull Request #38506 · rails/rails
# railties/lib/rails/code_statistics.rb#L41
- def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|coffee|rake)$/)
+ def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|ts|coffee|rake)$/)
stats = CodeStatisticsCalculator.new
Dir.foreach(directory) do |file_name|
path = "#{directory}/#{file_name}"
if File.directory?(path) && !file_name.start_with?(".")
stats.add(calculate_directory_statistics(path, pattern))
elsif file_name&.match?(pattern)
stats.add_by_file_path(path)
end
end
stats
end
つっつきボイス:「お〜rails stats
にJavaScriptの項目があったんだ」「今までtsが入ってなかったという😆」
後でオレオレRailsアプリでrails stats
やってみました↓。
+----------------------+--------+--------+---------+---------+-----+-------+
| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |
+----------------------+--------+--------+---------+---------+-----+-------+
| Controllers | 344 | 232 | 5 | 30 | 6 | 5 |
| Helpers | 64 | 44 | 0 | 3 | 0 | 12 |
| Models | 100 | 39 | 3 | 6 | 2 | 4 |
| JavaScripts | 2874 | 1740 | 0 | 110 | 0 | 13 |
| Libraries | 47 | 44 | 0 | 1 | 0 | 42 |
| Model specs | 91 | 43 | 0 | 0 | 0 | 0 |
| Request specs | 15 | 11 | 0 | 0 | 0 | 0 |
| Routing specs | 47 | 40 | 0 | 0 | 0 | 0 |
| Helper specs | 24 | 2 | 0 | 0 | 0 | 0 |
+----------------------+--------+--------+---------+---------+-----+-------+
| Total | 3606 | 2195 | 8 | 150 | 18 | 12 |
+----------------------+--------+--------+---------+---------+-----+-------+
Code LOC: 2099 Test LOC: 96 Code to Test Ratio: 1:0.0
⚓primary
db configが見つからない場合に正しくフォールバックするようにした
- 元記事: Use correct fallback in schema cache initializer by kytrinyx · Pull Request #38433 · rails/rails
# activerecord/lib/active_record/railtie.rb#L135
- next if db_config.nil?
filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
- db_config.spec_name,
- schema_cache_path: db_config.schema_cache_path,
+ "primary",
+ schema_cache_path: db_config&.schema_cache_path,
)
つっつきボイス:「最近この辺の修正をよく見かけるかも🤔」「元々"primary"を省略していたのを後付けでいろいろ直さないといけなくなった感」「マルチDBってやっぱり大変なんですね」「というより最初の設計で"primary"を省略できるようにすることが明示されてなくて後からやらないといけなくなったのかなと😆」
⚓番外: ドキュメント修正
アソシエーション名が、joinされたテーブル名と対応していない場合、
to_table:
キーを持つHashを指定してそのテーブル名を指定すること。
同PRより
つっつきボイス:「add_reference
のAPIドキュメントに追記が入りました」「to_table:
でやれるのか😳」「これ使ったことなかったけどいつからあったんだろう?」
後で調べるとRails 5.0.0.1でto_table:
が入ったようです。
# activerecord/README.rdoc#L143
class AddSystemSettings < ActiveRecord::Migration[5.0]
class AddSystemSettings < ActiveRecord::Migration[6.0]
「トリビアですが、マイグレーションのサンプルコードの5.0
が6.0
に書き換えられました」「そのままコピペするとハマるヤツ😆」
# activesupport/lib/active_support/inflector/methods.rb#L200
# classify('calculus') # => "Calculus"
# classify('calculus') # => "Calculu"
「意図的なスペルミスがうっかり修正されたのでrevertしたそうです😆」「😆」
⚓Rails
⚓Hix on RailsのRailsチュートリ記事
つっつきボイス:「Railsでよくある感じの手順解説が並んでいて、昔だったらScreencastにされがちなノウハウが記事形式になってるのがいいなと思ったので」「こんだけみっちり書くモチベーションが凄い👍」「記事はどれも2019年12月以降なので新しいのも嬉しいです😋」「一覧表示のレイアウトとかTechRachoで参考にしたい❤️」「TechRachoでやるなら記事もリニューアルしたいですね☺️」
⚓Interactorパターン
Factorial社では、早くから動詞(verb)を第一級市民にすることにした。新しいドメインがやってきたらそこにどんな動詞があるかを自問自答する。モデルのあらゆるCRUD/RESTコンベンションへの誘惑と戦い続け、ドメイン固有の動詞を使うようにした。「HelenはJohnからの最新の休暇リクエストをrejected = falseした」という言い方はしないだろう。「HelenはJohnからの最新の休暇リクエストをrejectした」と言うのが普通だ。
私たちはInteractorという新しい種類のオブジェクトを導入することでこれを実装した。新しくも何ともないコンセプトだが、私たちのアプリはRailsアプリとは似ても似つかないものになった。私たちのActive Recordモデルはきわめて小さくなってコールバックをまったく使っていない。ビジネスロジックのほとんどはInteractorに実装されている。コントローラがActive Recordと直接やりとりしなくなったことで、strong parametersの必要性もほぼほぼなくなった。アプリを動詞中心にしたことで、私たちのアプリは再利用が容易になり、もっと重要なことにコンポジション可能になった。
同記事より抜粋・大意
つっつきボイス:「上は『自分たちはこういう方針で設計している』みたいな記事なんですが、引用した法則1でInteractorパターンが登場していたのが気になりました」「お〜Interactorね☺️」「この人たちはActive Recordのメソッド名が名詞形で単数複数あるのが好きでないらしくて、動詞の命令形で考えることにしたようです」「まあメンテできるならそういう方針でもいいんじゃないでしょうか☺️」「kazzさんも『そこは設計の方針だから好き好きでいいよ☺️』と言ってました」
そういえばinteractorというgemがありました(関連記事)。
InteractorパターンはHanamiに取り入れられているんですね。
参考: あーありがち - Clean ArchitectureとHanamiですっきりしてきた
「ふと最近のHanamiを見てみると、コミットの頻度が思ったより緩やかですね↓」「Hanamiは使ってる人は使ってるでしょうし、おそらくですけどHanami自体はあんまり多機能化の方向は目指してない気がしますね: Railsみたいに何でもやれるフレームワークが欲しい人はあまりHanamiは使わないかも🤔」「それもそうですね😅」
- リポジトリ: Commits · hanami/hanami
⚓RuboCopでコードレビュー支援: Net::HTTPを使わせないcop(Hacklinesより)
# 同記事より
# cop/check_resilient_api_clients.rb
module RuboCop
module Cop
module ExternalServices
class CheckResilientApiClients < Cop
MSG = 'Use a more resilient API client'.freeze
def on_send(node)
add_offense(node, severity: :warning)
end
end
end
end
end
つっつきボイス:「なるほど自分たちでcopを書く話☺️」「先週取り上げたNet::HTTP↓を使わせないためのcop作ったそうです👮♂️」「copを書くとDSL的なものやASTを扱ったりするのでいい勉強になりますね👍」「ああ確かに」「copではRubyを解析しないといけないのでRubyに対する理解が深まります😋 」
参考: Net::HTTP is not your API client - mwallba
# 同記事より
> ruby-parse -e 'Net::HTTP.get_response(uri)'
(send
(const
(const nil :Net) :HTTP) :get_response
(send nil :uri))
参考: RuboCopの新しいルールを追加する方法(Custom Copの作り方) - アジャイルSEの憂鬱
⚓test doubleとVCRでテストを高速化(Hacklinesより)
つっつきボイス:「おぉVCR🥰」「VCRはリポジトリだけ見ててもあまりピンとこないんですけど、確か銀座Railsで実際にVCRを動かしているのを見ておおっなるほど❤️という感じになりましたね: 動作をレコーディングして再生&やり直し可能にできるgem」「動いてるのを横から見るとこうやって使うのかと一発でわかる感😋」
「VCRは、手続き的に『ここまで進んだらここまではできてる』みたいなものを上から下に書き下していくコードをテストするときにうまくマッチしますね☺️」「おぉ」「なのでオブジェクト指向というよりは😆、手続き的な考え方」
「VCRは動作をリプレイできて、しかもそのリプレイをキャッシュしてくれるのがいいですね😋」「カセットというものを入れると1回呼び出したAPIのレスポンスを全部キャッシュしたものを返してくれる」「ふむふむ」「しかもカセットはファイルに保存できるので、カセットも一緒にgitにコミットしておけばテストでそのAPIを叩かなくてもよくなる🔨」「へぇぇ😳」「なのでclosed APIのテストなんかでとっても便利: 自分しかアクセスできないclosed APIに自分の環境でアクセスして取ってきたデータをベースにできる」「おぉ〜😍」
「ほら、APIのスタブを書くのってとっても面倒じゃないですか😆」「はいたしかに😆」「APIのスタブやモックを作るということは、そのAPIの内部仕様を把握しないといけないですし、APIが巨大なXMLを返したりするとひたすらつらいですし、APIの仕様が変わってスタブを再編集とかやってられませんし😇」「ですです😇」
「VCRはそういうAPIコールのレスポンスをファイルにも出せるので、そのカセットファイルがあればAPIにアクセスする権限のない他の人がテストするときにカセットからロードできる」「いいわ〜❤️」「VCRはいろいろ有能💪: スタブとかモックとかAPI内部仕様とかを一切忘れてよくなるので、割り切りの落とし所としてはかなりいいと思いますね☺️」
参考: VCRを使って開発中にあほみたいにリクエストを飛ばさないようにする - かずおの開発ブログ(主にRuby)
参考: 永久保存版!?伊藤さん式・Railsアプリのアップグレード手順 - Qiita
元記事見出しより:
- 特定の部分だけをテストしたい
- VCR gemとカセットでテストを高速化
- test doubleとメソッドスタブでテストを高速化
- 重たいfeature testをどうにかする
⚓開発速度と品質の落とし所は
つっつきボイス:「今回は割とThoughtBotのブログから拾いました」「ベロシティとクォリティ😆」「開発速度と品質のバランスをどうするかみたいな話ですね」「そもそもクォリティとは🤣」「🤣」
拾い読み:
- コードの品質とは
- 「斧を振り回す凶暴なメンテナーがお前の家の場所を知ってると思ってコードを書け」とよく言われますが
- 品質の高いコードは時間がかかり、顧客からメリットが見えにくい
- 重要なのはチーム内コミュニケーション
- 互いの意図を正しく推測できるレベルに持ち込む
- プロダクトオーナーは設計上の決定の背景や理由を示す
- 技術的負債を意識すること
- ThoughtBotはコード監査請け負います
- コードの品質をいかに維持するか
- 定期的なペアプロとコードレビュー
- まとめ: 開発者とプロダクトオーナーが最初から忌憚なくやりとりできるようにするのが大事
⚓その他Rails
つっつきボイス:「guardは使いたい人が使うでいいかな〜☺️」「チーム全員の環境にguardを入れるのは好きじゃない😆: guardって思わぬときに動き出したりすることがありますし、Docker環境の人もいればそうでない人もいたりすると邪魔になることもありますし😅」「たしかに🤔」「もちろん自分の環境を良くするために使うのは全然OK😋」
- リポジトリ: guard/guard: Guard is a command line tool to easily handle events on file system modifications.
前編は以上です。
おたより発掘
RuboCopにはRuboCop自体でのルール違反を検出するInternalAffaiersという部署を持っていて、3rdパーティーCop実装者へのガイドラインとしてどこかに加えて良いかもと思った。https://t.co/cuDZBiv3tV
週刊Railsウォッチ(20200225前編)https://t.co/wnl9t24ccw
— Koichi ITO (@koic) February 25, 2020
知見の宝庫なんだけどこれに全部もってかれた
『「斧を振り回す凶暴なメンテナーがお前の家の場所を知ってると思ってコードを書け」とよく言われますが』週刊Railsウォッチ RubyのShellwordsライブラリは知っておくべき、VCRはやはり有能、copを自作、Hix on Rails記事ほか https://t.co/W9NSxygFkC
— あっきー🍺 (@kuronekopunk) February 25, 2020
バックナンバー(2020年度第1四半期)
週刊Railsウォッチ(20200218後編)rubyapi.orgで高速検索、RuboCopとJUnitFormatter、AWS Organizationsでの管理ほか
- 20200217前編 Railsのオプション引数退治、HSTSのデフォルトmax-ageが1年から2年に変更、semantic_logger gemほか
- 20200212後編 Rubyistが解説するUnicodeとUTF-8、Sorbetが速い理由、CSSの歴史、2019年の脆弱性まとめほか
- 20200210前編 Railsのベンチマークジェネレータ、長いバックグラウンドジョブと戦う、Timestamp切り詰めの謎、Open APIツールほか
- 20200204後編 Ruby3.0の他のbreaking change、Rubyのシリアライザ、GitHubのcode ownersほか
- 20200203前編 Railsの各種高速化コミット、OpenAPIの使い所、パンくずリストgem loaf、Railsビュー最適化ほか
- 20200128後編 もう一つのgemマネージャgel、”Did you mean”の仕組みを追う、DXOpalでブラウザゲームほか
- 20200127前編 Railsでキーワード引数warning退治始まる、ライブラリとフレームワークの違い、ShopifyのRails高速化記事ほか
- 20200121後編 RubyKaigi 2020受付開始、RubyGemsとBundlerの今後、ファイル同期ツールMutagenほか
- 20200120前編 福岡でも公開つっつき会、Railsのconnection_specification_nameでprimaryという名前が非推奨に、structure.sqlとschema.rbほか
- 20200115後編 Ruby 2.7関連情報、Bootstrap 5は今年前半リリースか、PostgreSQLでやってはいけないリストほか
- 20200114前編 config_forのbreaking change、Active Storage variantをDBでトラッキング、SprocketsとWebpackの違いほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp Slackなど)です。