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

週刊Railsウォッチ(20200511前編)Rails 6.0.3リリース、rails newに--masterオプションが追加、system specとfeature specの違いほか

こんにちは、hachi8833です。『みんなで筋肉体操』を今頃知って、軌道に乗せようと試行錯誤中です。


つっつきボイス:「筋肉体操一時期流行ってましたね💪」「当時まるで気づいてなくて、『筋肉は裏切らない』の出どころもやっとわかりました😅」「おうちでやる版が出たのね☺️」「今や空前の筋トレブーム」「みんな家にいますし😆」

「ジムによってターゲットの客層がそれぞれ違うんですけど、ゴールドジムはガチの筋トレ勢向けらしい」「ボディビルダーとか」「一般向けとかスタジオレッスン中心ではないと😳」「行ったことありますけどガチ勢多かった😅」(以下延々)

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

今回のつっつき会は、試験的にZoomで外部ゲストをお迎えして小規模に開催しました。ご参加ありがとうございました!🙇

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

4/27の更新情報を中心に見繕いました。

GW中は@kamipoさんによる修正がかなり増えていました。


つっつきボイス:「Railsの公式更新情報ってウィークリーでしたっけ?」「自分はいつもチェックしてますけど平気で2か月とかインターバルあります😆」「ですよね😆、そんなしょっちゅう出てた気がしない」「公式が毎週やってたらRailsウォッチでそんな頑張らなくていいでしょうし😆」「😆」

「@kamipoさん、めっちゃアクティブにやってる」「すげ〜、毎日コミット!」「今の時期ライブが開催されていないからかも😆」「こんなにアクティブになれる秘訣が知りたいです」「あとお知らせにもありますけど、Railsのメーリングリストが以下のDiscourseに統合されたそうです↓」

rails new--masterオプションが追加


つっつきボイス:「--masterとは?」「どうやら--devとか--edgeというオプションは前からあったみたいです」「知らな〜いこれ🤣」「何に使うんだろ🤣」「rails newするときに最新版を取ってきたりできるということ?」「だと思います」

「たぶん業務では直接使わないでしょうね😆」「gemを作ってる人がこれをCIに仕込んで常にedgeでテストできるようにするとか?」「普通にgitでブランチ指定すればよさそうですけど😆」「同じこと思いました😆」「これがどうしても欲しい理由はよくわからん😆」「手っ取り早くmasterでnewしたいとか?」「グローバルgemを汚さずにedgeを取って来たいとか?」「それならrbenvとかでやればよかったりして😆」「rbenvならグローバルgemは汚れないけどCIだとうまく動かなかったりするんですよね😅」「ああたしかに」「この機能がrails newコマンドに入っていれば、不自由な環境でもrailsコマンドさえ動けばやれるから、そういうのが欲しい人もいるのかなと」「まあ--dev--edgeが前からあるなら--master足してもいいかも☺️」

背景

現在のrails newジェネレータでサポートされているオプションは--dev--edge
--devはローカルのRailsセットアップを指し、--edgeはRailsの最新stableブランチを指す。
しかしRailsコミュニティでedgeと言えば最新のmasterだと考えることが多く、Shopifyの以下の素敵な記事で最新のmasterを指す方法が書かれているほど。
Living on the Edge of Rails – Shopify Engineering
本来ならedgeがmasterを指す変更をおすすめするところだが、利用が多すぎて詰まった。

変更

現在のedgeの機能を変えるよりは、欲しいものを足そうかと思う。
このPRは--masterフラグを追加する。
これはまさにedgeに期待される動作を行うもので、Railsの最新stableブランチではなくmasterブランチを指す。

しかし何でまた?

ほら、開発者が水平シャーディングのようなRailsに最近コミットされた新機能を見たら、とりあえず新しいRailsアプリで試してみたくなるじゃないですか❤️。
このコマンドがあればコンフィグなしでやれるし、普段OSSをサポートする余裕のない自分らみたいな人でもテストコードをコミットしたりもできるので🙏。
同PRより大意

リファクタリング3つ


つっつきボイス:「上の2つはほぼ同じ感じのリファクタリングだそうです」「不必要なwhenを減らしてシンプルにした感じ😋」

# activerecord/lib/active_record/relation/where_clause.rb#L136
        def invert_predicate(node)
          case node
          when NilClass
            raise ArgumentError, "Invalid argument for .where.not(), got nil."
-         when Arel::Nodes::In
-           Arel::Nodes::NotIn.new(node.left, node.right)
-         when Arel::Nodes::IsNotDistinctFrom
-           Arel::Nodes::IsDistinctFrom.new(node.left, node.right)
-         when Arel::Nodes::IsDistinctFrom
-           Arel::Nodes::IsNotDistinctFrom.new(node.left, node.right)
-         when Arel::Nodes::Equality
-           Arel::Nodes::NotEqual.new(node.left, node.right)
          when String
            Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node))
          else
-           Arel::Nodes::Not.new(node)
+           node.invert
          end
        end

「明示的に呼び出すように変えてる↓」

# actionview/lib/action_view/renderer/partial_renderer.rb#L279
      def render_partial_template(view, locals, template, layout, block)
-       instrument(:partial, identifier: template.identifier) do |payload|
+       ActiveSupport::Notifications.instrument(
+         "render_partial.action_view",
+         identifier: template.identifier
+       ) do |payload|
          content = template.render(view, locals) do |*name|
            view._layout_for(*name, &block)
          end
          content = layout.render(view, locals) { content } if layout
          payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
          build_rendered_template(content, template)
        end
      end

「こういうinstrumentationの呼び出しって元々シングルトンというか、Railsのどこからでも呼び出せるものなんですけど、以下のinstrument(name, **options)みたいなメソッドがレンダラーのクラスに中途半端に存在していると呼び出しパスが複数できちゃうから、上みたいにグローバルな呼び出しに統一しようよということだと思います」「ふ〜む」

# actionview/lib/action_view/renderer/abstract_renderer.rb#L173
-     def instrument(name, **options) # :doc:
-       ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
-         yield payload
-       end
-     end
-

「自分もたまにこういうメソッドって書いちゃうんですよね: ActiveSupport::Notifications.instrumentみたいな長ったらしい名前空間を何度も書くのが面倒なときとか😆」「わかります〜」「でも実際このメソッドって無意味ですし、こういうのを残しとくと#instrumentの引数が変わったときに挙動が変わったりして害を生じたりすることもあるのであんまりよくないよってことだと思います🧐」「なるほど!」「今なら長い名前空間の入力はIDEに補完させれば済みますし☺️」

「公式情報には『あえてDRYでない方向にリファクタリングした』という感じで書かれてました」「ここではメソッドに別名を付けているだけなので、たぶんですけどDRYでないようにしたというよりは、以前のショートハンドメソッドがよくなかったよねということかなと思います🧐」「なるほど😅」「メソッド内で別のことをしているならともかく、他に何もしてないので☺️」

PostgreSQLアダプタのresult_as_arraymap_types!に置き換えた

# activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L13
        def query(sql, name = nil) #:nodoc:
          materialize_transactions

          log(sql, name) do
            ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-             result_as_array @connection.async_exec(sql)
+             @connection.async_exec(sql).map_types!(@type_map_for_results).values
            end
          end
        end

つっつきボイス:「map_types!って何だろうと思ったらPGライブラリのメソッドだそうです↓」「なるほど、便利メソッドが元からあるならそれを使いましょうということね☺️」「自分らがPGライブラリを直接触ることはまずなさそう☺️」

find_by_sqlの不必要なループを回避


つっつきボイス:「これも@kamipoさんによる修正ですね」「やらなくていい処理をunlessで囲ったのね☺️」「こういう更新はちょくちょく入ってる」

# activerecord/lib/active_record/querying.rb#L45
    def find_by_sql(sql, binds = [], preparable: nil, &block)
      result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
-     column_types = result_set.column_types.dup
-     attribute_types.each_key { |k| column_types.delete k }
+     column_types = result_set.column_types
+
+     unless column_types.empty?
+       column_types = column_types.reject { |k, _| attribute_types.key?(k) }
+     end
+
      message_bus = ActiveSupport::Notifications.instrumenter

      payload = {
        record_count: result_set.length,
        class_name: name
      }
      message_bus.instrument("instantiation.active_record", payload) do
        if result_set.includes_column?(inheritance_column)
          result_set.map { |record| instantiate(record, column_types, &block) }
        else
          # Instantiate a homogeneous set
          result_set.map { |record| instantiate_instance_of(self, record, column_types, &block) }
        end
      end
    end

アソシエーションを複数回autosaveできるよう修正

# activerecord/lib/active_record/autosave_association.rb#L367
      def before_save_collection_association
-       unless defined?(@new_record_before_save)
-         @new_record_before_save = new_record?
-       end
+       @new_record_before_save ||= new_record?
      end

つっつきボイス:「これはまた面倒くさそうな部分の修正😆」「たしかにdefined?だと1回しかチェックできないし」 「undefしないと戻せない😆」

#38166以来アソシエーションは1回しかautosaveされないようになっていた(レコードをsaveした後は@new_record_before_saveが常にfalseになる)。ここではレコードが永続化に移行するのが1回だけであると仮定しているが、複数回起きるケースが2つある。1つはレコードをsaveするトランザクションがロールバックする場合、もう1つは永続化したレコードが後に複製される場合。
私たちのアプリを6.0.2.2から6.0.3.rc1に進めたときにリグレッションが発生したので、バックポートを希望する。
同PRより大意

番外: ドキュメントの改善

# guides/source/getting_started.md#L612
-we don't specify what the response should be. We just added the `create` action
+we don't specify what the response should be. We added the `create` action

つっつきボイス:「最後のトリビアは、justとかsimpleのような大して意味のない語を削除したそうです」「フォーマルに少し寄せたというか」「軽くポリコレ感😆」「高校生の書くエッセイとかを先生が注意したりするヤツ😆」「言い回しに突っかかる人いますし😆」

「でもjustって便利なんですよ英作文のときとか😆」「言葉に詰まったときにとりあえず書いちゃうヤツ😆」「日本人だと学校英語の影響で始めのうちはonlyを使いがちなんですけど、onlyは文のどこに置くかで限定の対象がデリケートに変わってくるので文が込み入ってくると割と面倒くさくて🤣」「へぇ〜😳」「わかる🤣」「特に会話だとjustって便利😂」

参考: 「Only」の位置で、文の意味は変わりますか? - eigopedia


日本語で言うと「ちゃんと」とか「しっかりと」みたいなあってもなくてもいい語が雰囲気で投入されるのと少し似ているかもと思いました。自分が英文にするときには「ちゃんと」や「しっかりと」みたいな語はだいたい削除しちゃいます😉。

『The Elements of Style』↓という一世紀前からある定番の本は薄くてすぐ読めるのでおすすめです。

参考: 百年前の英文指南書『The Elements of Style』に学ぶ、効果的な文章の書き方11の法則 | ライフハッカー[日本版]

Rails

Rails 6.0.3がリリース

セキュリティリリースでないことがバージョン番号だけでわかるようになって助かります。


つっつきボイス:「そうそう、ちょっと前からRailsのバージョニングが変更されたんですよね😋(ウォッチ20200302)」「たしかにわかりやすくてありがたいです🙏」「セキュリティフィックスのみのリリースには4桁目が付くようになった: 仮に6.0.3にセキュリティリリースが出るときは6.0.3.1みたいになる」「ちょうど今朝rails newしたんですけど見たら6.0.3になってる〜😋」「逆に、たとえば6.0.3.1の次に6.0.4が出たら、それはセキュリティ以外の修正や変更ということですね☺️」

「6.0.3の変更内容は知ってるものも知らないものもありますね」「マルチプルデータベース周りはまだごりごり変わってるっぽい😆」「もうちょい様子見な感じですね😅」

Active Supportのto_sentence

to_sentence↓ってナニコレ知らない〜😆」「"A, B, and C"みたいな英語っぽい形式に変換するのか😆」「あ〜いかにも英語圏のためのメソッド😆」「to_sentenceにはORみたいの他の接続詞も渡せるんですって😆」

参考: Railsで配列の要素から"A, B, and C"形式の文字列を作る時はArray#to_sentenceが便利 - WEB SALAD

# web-salad.hateblo.jpより
%w(one).to_sentence
# => "one"
%w(one two).to_sentence
# => "one and two"
%w(one two three).to_sentence
# => "one, two, and three"

「なんで英語だとand入れるんですかね?」「英語的には列挙するときに最後にandとかorを入れないと許してもらえないところあります😆」「英語圏ではA, B, and Cみたいに書くのが普通だと思いますし😆」「ですです」「日本語なら"AとBとCとD"みたいに全部"と"でjoinすれば済むんですけどね😆」

「ところでこのメソッドってどこで使うんでしょう?」「メッセージじゃないですか?」「あ、そうか😳」

「そういえば工業英語だったか特許英語だったかな、andの前のカンマ,を置くか置かないかで意味が変わるらしいというのを知って英語って面倒くさいな〜って思った覚えがあります😆」「マジで?😆」「自分らはandの前にカンマを置かないと習ったような気がしますけど🤔」


後で本棚から探しだしてみると、"英語ではandの前にカンマを置く(列挙)書き方と、置かない(結合が強まることが多い)書き方の両方があるが、ネストした列挙で誤解を招かないためには、原則として列挙を表すandの前にカンマを置くよう統一するのが望ましい"という趣旨でした。自分なら箇条書きで書きたいところです😆。

  • あいまいな例
A, B, C and D and E
「A」「B」「CとD」「E」なのか「A」「B」「C」「DとE」なのか区別できない
  • よい例
A, B, C, D, and E
「A」「B」「C」「D」「E」で一意に定まる
A, B, C and D, and E
「A」「B」「CとD」「E」で一意に定まる
A, B, C, and D and E
「A」「B」「C」「DとE」で一意に定まる

『続・技術翻訳のテクニック』(富井篤) p145-146より大意

なお自分は日本語なら列挙を「A」「B」「CとD」「E」のように「」の連続だけで表す方法が好きです❤️。ついでながら、bread and butterは全体でひとつの英熟語です。

system specとfeature specの微妙な違い(Ruby Weeklyより)


つっつきボイス:「お気持ち系の記事かな😆」「『オレはこう思う』みたいな😆」

「feature specって一時期流行ったんですよ」「そういえば😳」「turnipとかcucumberとか最近あんまり聞かなくなったと思いません?」「見かけたの結構前だったような😆」「あれですよ、BDD(ビヘイビア駆動開発)が流行ったとき😆」「それですね😆」「feature specもてはやされてましたけど、今思えば夢の世界だったんだろうか🌠」

参考: ビヘイビア駆動開発 - Wikipedia

「一応cucumber-railsとかはメンテはされてるかな👀」「あれ、テスト落ちてる?😆」「ホンマや😆」「cucumber本体の方はちゃんとメンテされてるしテストも通ってる」

「cucumberみたいなツールは、たとえば政府調達系みたいな案件ならむしろマッチするかなという気もしますね🧐」「つまり要件が固いもの?」「そう、めちゃくちゃ要件の固い案件」「ふむふむ」「一次請けの会社がcucumberで要件定義を書いて下請けに発注するみたいな😆」「なるほど〜」


記事見出しより:

  • Railsにある2つのレベルのテスト
    • 高レベルかつ粒度が大きい: feature spec
    • 低レベルかつ粒度が小さい: model spec
  • feature specからsystem specへ
    • 背景
    • system specはsystem testを包含する
  • 構文上の違い
    • feature specの例
    • system specの例
  • まとめ

Docker内でRailsのシステムテストを動かす(Ruby Weeklyより)

# 同記事より
services:
  app:
    build: .
    command: bundle exec rails server -p 3000 -b '0.0.0.0'
    # ... more config ...
    ports:
      - "3000:3000"
      - "43447:43447"
    # ... more config ...
    environment:
      - SELENIUM_REMOTE_HOST=selenium

つっつきボイス:「ハウツー記事ということで」「Dockerの中のテストでSeleniumをぶん回すときの話みたい」


記事冒頭より:

  • Dockerの中でシステムテストを気持ちよく回すために
    • RSpecをシステムテストに使う方法
    • モダンなブラウザ内でヘッドレスモードのテストを回す方法
    • ビルドやデバッグを効率よく行うために、ヘッドレスでないブラウザでテストを回せるか

Railsでライブラリを使わずにReact componentをレンダリングする

// 同記事より
export const mountComponent = (component, componentName) => {
  const nodes = Array.from(
    doc.getElementsByClassName(`react-${componentName}`)
  );
  nodes.forEach((node) => {
    const data = node.getAttribute("data");
    const props = data && data.length > 2 ? JSON.parse(data) : {};
    const el = React.createElement(component, { ...props }, []);
    node.innerHTML = ''
    return ReactDOM.render(el, node);
  });
};
// 同記事より
document.addEventListener("turbolinks:load", () => {
  mountComponent(MessageDisplay, "MessageDisplay");
  mountComponent(AnotherComponent, "AnotherComponent");
  mountComponent(AThirdOne, "AThirdOne");
}

つっつきボイス:「ライブラリってどのライブラリ?😆」「何ともファジーな😆」「サードパーティのライブラリを使わずにReact componentをコンポーネント単位でレンダリングしたりテストしたりしたいということかな」「コンポーネントのテストをRailsで書きたいかというと、データが絡むときとかなら書きたいな〜😋」


見出しより:

  • しくみについて
  • Railsビューの中で特殊なelementをレンダリングする
  • JSを書く
  • mountComponentを呼ぶタイミング
  • まとめ

Rails+StimulusReflex+CableReadyでチャット機能を作る


つっつきボイス:「お、Stimulusか」「こうなるとRailsは単なるWebSocketサーバー同然だったりして😆」

参考: WebSocket - Wikipedia

「CableReadyって最近ちらほらと見かけますけど、Action Cableを使いやすくするとか?」「単にWebSocketを使いやすくするヤツかなと思ったら、Action Cableと接続するライブラリなのか〜😳」「なるほど!」「Action Cableに生でつながれるよりはいいのかな😋」「またフロントエンドの人に嫌がられそう😆」「😆」

<!-- 同記事より -->
<!DOCTYPE html>
<html>
  <head>
    <title>StimulusReflexCableReadyDemo</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <div class="ui centered grid">
      <%= yield %>
    </div>
  </body>
</html>

Ruby Weeklyで上の記事と同じ趣旨の以下の動画が紹介されていました。10分で作れると言ってます。

その他Rails


つっつきボイス:「3本目はAaron Pattersonのキーノート↓がよかったという感想文😆」「まだ見てませんがめっちゃ楽しいって書いてますね」「RailsConf 2020か、後で見てみよっと😋」

動画冒頭ににオーディエンスの笑い声が入ってますが、本物かな?と思ったらイントロは事前に録画してたようです。

「そういえばAaronさんはつい最近GitHubから離れたんでしたっけ」「今度はShopifyだそうです」


前編は以上です。

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

週刊Railsウォッチ(20200428後編)Rubyのバックトレース順序が戻る、KubernetesでRailsをスケール、セキュリティソフト入れますか?ほか

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

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

Rails公式ニュース

Ruby Weekly

Hacklines

Hacklines


CONTACT

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