Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ(20200225前編)RubyのShellwordsライブラリは知っておくべき、VCRはやはり有能、copを自作、Hix on Rails記事ほか

こんにちは、hachi8833です。次回の技術書典は残念ながら中止になりました。型システム祭りも延期だそうです。


つっつきボイス:「今日は1名Zoomで臨時リモートつっつき参加です」「はい聞こえま〜す」「five nineでつながってますね」「それは?」「無線用語😆: 了解度5で信号強度9📡」「Zoomやっぱりいいわ〜❤️」

参考: シグナルレポート(RSレポート)

「今やひととおりのイベントが中止ですし😇」「今度こそ技術書典行けるかと思ったのに😢」「常連ですが残念です😭」「代わりにオンラインでの開催「技術書典 応援祭」なんてのをやるみたい」「今回の出展者は次回優遇措置適用ですって」「出展者も本印刷したりしてますし」「主催者側の負担も半端ないはず」「出展料返却なしとあるけど会場費考えたら無理もない」「いろいろ大変だ...」

以下のツイートはつっつき後に見つけました。

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

お知らせ: 3月の「公開つっつき」はお休みします

これまで毎月第一木曜に休まず開催してまいりました週刊Railsウォッチ「公開つっつき会」ですが、昨今のコロナウイルス流行を鑑みて3月は初の「開催なし」といたします🙇。

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

公式の更新情報がなかったのでコミットリストから見繕いました。

今更ですが@kamipoさんのコントリビューション数がダントツですね。

参考: Rails Contributors - This year

つっつきボイス:「この週は久々に@kamipoさんの修正が少なめでした」「たまには一息入れないと☺️」

attributes=assign_attributesのエイリアスにした

# activerecord/lib/active_record/attribute_assignment.rb#L13
+   alias attributes= assign_attributes
+

つっつきボイス:「修正はシンプルですね」「2つのメソッドが別々でもよかったんでしょうけど、今後また挙動が変わったときに合わせるのが面倒だからエイリアスにしたのかなと想像☺️」「引用されてる#38401の方がメインだったのかも」

#38401まではassign_attributesattributes=もActive Modelで定義されていて引数をコピーしていた。assign_attributesがActive Record内でオーバーライドされるようになってそこではコピーが発生するが、attributes=がオーバーライドされていなかった。
つまりattributes=に代入する引数がネストまたはマルチパラメータだと引数が改変され、引数がコピーされていなかったということになる。
同PRより大意

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の方がラク😋」

シェルいろいろありすぎ

「ところで引数の扱いってシェルによって全然違ったりしますよね😇: 今はだいたいみんな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で『速くなるのはいいんだけど他の人が読みづらくなるコードはつらい』みたいなのを見かけましたね😆」「😆😆」「それ拾いました😋↓」「ライブラリコードだしいいのではという考え方もありますが、書くならコメントにも書いて欲しい気持ち😆」

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で&nbsp;が出てくることはありますね☺️」「Excelとかから自動生成するとちょくちょく混じってくる😆」

「ローカライズ時代の経験ですけど、ラテン系ヨーロッパの人がすごくNBSPを使うんですよ: Orquesta De La Luzみたいな一続きの固有名詞を途中で改行したくないときにOrquesta●De●La●Luzみたいに●のところにNBSPを置く慣習がありまして😅」「へぇ〜😳」「スタイルガイドでNBSPを禁止してあっても、フランスやスペインの人が隙を見てNBSPをちょくちょく入れてくるので仕方なく普通のスペース文字に置換したりしてました😭」

参考: ノーブレークスペース - Wikipedia
参考: エンジニア・Webデザイナー必読: アプリケーションを国際化・多言語展開する前に知っておくべきこと - Qiita -- 昔Qiitaに書いた記事です

rails statsでTypeScriptファイルもカウントするようになった

# 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が見つからない場合に正しくフォールバックするようにした

# 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.06.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は使わないかも🤔」「それもそうですね😅」


hanamirb.orgより

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をどうにかする

RSpecで役に立ちそうないくつかのヒント(翻訳)

開発速度と品質の落とし所は


つっつきボイス:「今回は割とThoughtBotのブログから拾いました」「ベロシティとクォリティ😆」「開発速度と品質のバランスをどうするかみたいな話ですね」「そもそもクォリティとは🤣」「🤣」

拾い読み:

  • コードの品質とは
    • 「斧を振り回す凶暴なメンテナーがお前の家の場所を知ってると思ってコードを書け」とよく言われますが
    • 品質の高いコードは時間がかかり、顧客からメリットが見えにくい
  • 重要なのはチーム内コミュニケーション
    • 互いの意図を正しく推測できるレベルに持ち込む
    • プロダクトオーナーは設計上の決定の背景や理由を示す
  • 技術的負債を意識すること
  • コードの品質をいかに維持するか
    • 定期的なペアプロとコードレビュー
  • まとめ: 開発者とプロダクトオーナーが最初から忌憚なくやりとりできるようにするのが大事

その他Rails


つっつきボイス:「guardは使いたい人が使うでいいかな〜☺️」「チーム全員の環境にguardを入れるのは好きじゃない😆: guardって思わぬときに動き出したりすることがありますし、Docker環境の人もいればそうでない人もいたりすると邪魔になることもありますし😅」「たしかに🤔」「もちろん自分の環境を良くするために使うのは全然OK😋」


前編は以上です。

おたより発掘

バックナンバー(2020年度第1四半期)

週刊Railsウォッチ(20200218後編)rubyapi.orgで高速検索、RuboCopとJUnitFormatter、AWS Organizationsでの管理ほか

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

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

Rails公式ニュース

Hacklines

Hacklines


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。