週刊Railsウォッチ(20180323)Rails 5.2.0 RC2リリース、「サーバーレスなRubyが欲しい」、capybara風JSテストフレームワークCypressほか

こんにちは、hachi8833です。明日のRails Developers Meetup 2018準備で青ざめてます。

今週から記事数をできるだけ一定に保つようにいたしました。腹八分目のウォッチ、いってみましょう。

Rails: 今週の改修

Rails 5.2.0 RC2リリース

5.2.0はほぼ仕上がったそうです。

大きな変更はTechRachoでもおおよそ取り上げたので、5.2 RC2リリースノートの中からこれまで取り上げていなかったコミットを中心に見ていきます。

capify!の非推奨化

Capistrano 3でコマンドがcap installに変わったことへの対応です。!なしは元々なかったんですね。

# railties/lib/rails/generators/actions.rb#L229
      def capify!
+        ActiveSupport::Deprecation.warn("`capify!` is deprecated and will be removed in the next version of Rails.")
         log :capify, ""
         in_root { run("#{extify(:capify)} .", verbose: false) }
       end

つっつきボイス: 「そうそう、昔のCapistranoではcapファイルの作成とか設定とかデプロイに使うコマンドがcapifyでしたね」「あれ、CapistranoってRails標準だったかな?」「あ、そういえば: と思ったらGemfileではデフォルトでコメントアウトされて↓るからオプション扱いですね」

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

「ところでCapistranoって最近それほど見かけない気がしてましたが」「いやいや、もう当たり前に使われまくっててことさら話題になってないだけ: capファイルは普通にプロジェクトにあるし」「昔はCapistranoの設定方法がよくわからんという声もちょくちょくあったりしましたが、今もう聞かないっすね」

参考: Generators::Actions#capify!

config.ruの古い挙動のサポートを非推奨化

Rails 4以降で生成されるconfig.ruではデフォルトでRails.applicationのインスタンスが使われるようになってからだいぶ経ったことで非推奨化されました。

# railties/lib/rails/commands/server/server_command.rb#L21
-    # TODO: this is no longer required but we keep it for the moment to support older config.ru files.
     def app
       @app ||= begin
         app = super
+        if app.is_a?(Class)
+          ActiveSupport::Deprecation.warn(<<-MSG.squish)
+            Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0.
+            Please change `run #{app}` to `run Rails.application` in config.ru.
+          MSG
+        end
         app.respond_to?(:to_app) ? app.to_app : app
       end
     end

つっつきボイス: 「これもむかーしに、Rails起動時にアプリ名じゃなくてRails.applicationを指定できるように変わったので、その絡みでしょうね: Rails 4からだったかな」

後で探してみると、2013年の#9669↓がそれのようです。

# railties/lib/rails/generators/rails/app/templates/config.ru
 # This file is used by Rack-based servers to start the application.

 require ::File.expand_path('../config/environment',  FILE)
-run <%= app_const %>
+run Rails.application

rails runnerの引数で-をサポート

# railties/lib/rails/commands/runner/runner_command.rb#L15
       def self.banner(*)
-        "#{super} [<'Some.ruby(code)'> | <filename.rb>]"
+        "#{super} [<'Some.ruby(code)'> | <filename.rb> | -]"
       end

       def perform(code_or_file = nil, *command_argv)
# @@ -29,7 +29,9 @@ def perform(code_or_file = nil, *command_argv)

         ARGV.replace(command_argv)

-        if File.exist?(code_or_file)
+        if code_or_file == "-"
+          eval($stdin.read, binding, "stdin")
+        elsif File.exist?(code_or_file)
           $0 = code_or_file
           Kernel.load code_or_file
         else

つっつきボイス: 「ははぁ、-を指定することで標準入力が使えると: -自体は標準入力と標準出力のどっちにでも使われうるけどここでは標準入力ですね」

Relationに対するArelメソッド呼び出しが非推奨化

kamipoさんのPRです。

# activerecord/test/cases/relation/delegation_test.rb#L24
+  module DeprecatedArelDelegationTests
+    AREL_METHODS = [
+      :with, :orders, :froms, :project, :projections, :taken, :constraints, :exists, :locked, :where_sql,
+      :ast, :source, :join_sources, :to_dot, :bind_values, :create_insert, :create_true, :create_false
+    ]
+
+    def test_deprecate_arel_delegation
+      AREL_METHODS.each do |method|
+        assert_deprecated { target.public_send(method) }
+      end
+    end
+  end

つっつきボイス: 「今まではAR::Relationに対して直接arelを呼ぶと委譲されてたのが、今後は呼べなくなる方向になるってことか」「非推奨になるArelメソッドのexists↑は?なしの方なんですね: それなら直接呼べなくなってもいいかな」「fromsとかbind_valuesあたりは、ひょっとすると?使うことがあるかもしれないですが」「自分はArel直接呼ぶような事態になったら生SQL書くから別にいいや」

参考: exists?

「つまるところ、Arelは本当はprivateなAPIなんだぞってことを示してるんですかね」「Arelをどうしても使うならArelだけで組み立てて欲しい: AR::Relationで中途半端に組み立てたクエリをArelでいじるのとかやめて欲しいわー」「それで発生するバグつらそうですね…」「AR::Relationでスコープ変えたりするとまたわけわからなくなるし」

「kamipoさんのコミットメッセージ↓にこう書かれているから、relation.arel.#{method}はあっても基本使うなよってことですね」「privateなんだから代わりに呼ぶんじゃないよと」「もう禁止w」「それだけ事故が多かったからですかね」「というよりArel混ぜられると死ぬほど読みにくい!に尽きる」

I removed “Use relation.arel.#{method} instead.” in the message because it’s private API and fixed the next version of Rails to 6.0.
同PRより

ネストしたトランザクションの親のステートをベースにレコードのステートを適用

# active_record/connection_adapters/abstract/transaction.rb#L133
     class SavepointTransaction < Transaction
-      def initialize(connection, savepoint_name, options, *args)
+      def initialize(connection, savepoint_name, parent_transaction, options, *args)
         super(connection, options, *args)
+
+        parent_transaction.state.add_child(@state)
+
         if options[:isolation]
           raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
         end

つっつきボイス: 「ここまで頑張ってネステッドトランザクション書いたことないなー: そもそもどう処理されるか不安だし」「ただトランザクションって意図せずにネストすることがありうるから、最終的に挙動は知っておいたほうがいいけど」
「そもそも生SQLでもネステッドトランザクション書いたことないしw」「ストアドプロシージャとか、使い所はなくもないですけど、普通の業務で使うかなー?と私も思いますね」「できるのはわかるけど下手するとデッドロックしかねないし」

#0237da2のコミットメッセージを雑に訳してみました:


ネストしたトランザクションがあってレコードが両方でsaveされたとします。外側のトランザクションがcloseするとロールバックします。従来は、外側のトランザクションが「not persisted」とマーキングされていても内側のトランザクションが「persisted」になることがありました。

Post.transaction do
  post_one.save # ロールバックされる

  Post.transaction(requires_new: true) do
    post_two.save # 誤ってpersistedになる
  end

  raise ActiveRecord::Rollback
end

修正のため、このPRではトランザクションの扱いを変更して、子のトランザクションが親にマーキングの状態を確認するようにします。子トランザクションがあると、スタックが空でないため常にSavpointTransactionになります。その状態からparent_transactionを子のSavepointTransaction(親への子の追加はここで行われる)に渡すと、親は内側のトランザクションを「rolledback」とマーキングするのでレコードを「not persisted」とマーキングします。

update_attributes_from_transaction_stateでは、すべてのトランザクションがrolledbackとマーキングされ、内側のレコードがnot persistedと正しくマーキングされるよう、completed?チェックを用いる。

Post.transaction do
  post_one.save # ロールバックされる

  Post.transaction(requires_new: true) do
    post_two.save # 新しい振る舞いではnot persistedと正しくマーキングされる
    on rollback
  end

  raise ActiveRecord::Rollback
end

(同commitのテストを見ながら)「miniTestに#refute_predicateってありますね」「refute: 論破する、だから否定のアサーションか」

# activerecord/test/cases/transactions_test.rb#L372
...
+    refute_predicate topic_one, :persisted?
+    refute_predicate topic_two, :persisted?
+    refute_predicate topic_three, :persisted?
...

参考: refute_predicate

Rails

ActiveRecordをfixtureに変換する(RubyFlowより)

とても短い記事です。


つっつきボイス: 「おーこれあると便利なやつ: これは.to_yaml一発だけど他にもやり方は色々ある」
「マスターデータ的なやつならこんな感じでざっとfixtureにするのはラクでいいですよ」

「fixtureを使う場合問題になるのは外部キーで、ロードするときに面倒くさい」「あー順番に依存しているからか: それは面倒くさいっすね」「yamlってだけで読みづらいのに」
「その点FactoryBotで書けば順番とかよしなにやってくれますからね: fixtureだとそれがない」
「裏技的にMySQLの外部キー制約を一時的にオフにするオプションとかあるけど、そういうことをやり始めると知らないうちにfixtureがひっそりと壊れてたり」「なんやかんやで、fixtureで何でもやろうとするのは無理ありますね」

テスティングアンチパターン: セットアップしたデータの漏れ(Hacklinesより)

# 同記事より: 悪例
describe User do
  let(:user) { create(:user) }
  let(:account) { create(:account, user: user) }
  let(:role) { create(:role, user: user) }
  let(:post) { create(:post, user: user) }

  describe 'account-related stuff' do
    # tests go here
  end
  ... 
end

つっつきボイス:letを全部外側に置くとそのスコープ全部に効いちゃうからやめろよっていう、しごく普通のハナシ」「let嫌いーw: 遅延評価されるところとか」「お、let嫌いの同志を発見!」「let好き勢も社内にいますからねー」「letはキレイに使えるとかっこいいんだけど」「テストの内容によってはランダマイザーの影響とかで数回に1回とか数十回にコケたりすることもあるし」「途中でreloadする羽目になって面倒くさくなるし、reload使うと何だか負けな気がしたり」「基本letで定義したものは参照だけにしてテストの中で変えない方がいい

モデル内の翻訳する文章をJSON化する(Hacklinesより)


同記事より


つっつきボイス: 「これもローカライズ絡みということで」「まあ普通に行われていることですね: mangareborn.jpではyamlでやってたし」「データがでかくなると大変ですが」

loofah: nokogiriベースのHTML/XML操作/サニタイズgem

# 同リポジトリより
span2div = Loofah::Scrubber.new do |node|
  node.name = "div" if node.name == "span"
end

doc = Loofah.fragment("<h1>Title</h1><div>Content</div>")
doc.text    # => "TitleContent"           # probably not what you want
doc.to_text # => "\nTitle\n\nContent\n"   # better

つっつきボイス: 「ほー、HTMLを食わせて要素を置き換えたりto_textしたりサニタイズできる: XPathも使えるみたいだし、うんなかなかよさそうです」「ちなみにこのgemは脆弱性情報↓で知りました」「この種のパーサーはともすると脆弱性が潜むことがあるといえばありますからね」

JetBrains IDEはVMを設定しよう


つっつきボイス: 「JetBrainsにかぎらず、巨大IDEはEclipseでも何でもVMを適切に設定しておけばそう重くなったりしないですよ」

morimorihogeさんのRubyMineでは少なくとも以下ぐらいにVMを設定しているそうです。私のはデフォルトのままだった…

-Xms1024m
-Xmx8192m

ViewModelを分けることについて


つっつきボイス: 「TechRacho記事を引用いただいていたので見つけました」「ViewModelとPresenterを分けるっていう話のようですね」「ViewModelって?」「いわゆるMVVMパターン: JavaScriptの方でよく使われてる」「確かにPresenterが分かれている方がテストはしやすいですね」「正しいモデルを作りにくいけどビューの見た目だけチェックしたいときとか、ViewModelだけ差し替えられるようになってるとテストしやすい」「そういえばこの間の社内勉強会で近年のAndroidの動向を扱ったときもこの辺りの話が出ましたね」

参考: Wikipedia-ja MVVM

GitLabで重大なセキュリティリリース(Hacklinesより)


つっつきボイス: 「おっと、社内のGitLabもアップデートするか」

Ruby trunkより

ヒアドキュメントで改行をエスケープしたときの問題

puts <<~TXT.inspect
  1 \
  2
TXT

# 期待:
"1 2\n"

# 実際:
"1   2\n"

つっつきボイス: 「Rubyのヒアドキュメント記法って6つぐらいなかったっけ?覚えるの大変」「この<<~をSQLクエリで使うと先頭のスペースがなくなるからログが読みやすくなるって確かTechRachoの記事で見ましたよ」「(う、どこだったかな…記号検索難しい…)」「Rubyではこういうヒアドキュメントが言語仕様として用意されているあたり、ワカッテラッシャル」

参考: Rubyリファレンスマニュアル: ヒアドキュメント

誤った代入でsegfaultする->#14261で修正済みだった

# 同issueより
def foo
  puts 'hi'
end

foo, true  # 落ちる

つっつきボイス: 「むー、trueの前にカンマがあるからブロックと勘違いされたわけではなさそうだけど」「パーサーの気持ちになるのは難しす…」

CLI向けに文字が全角か半角かを取れるメソッドが欲しい

他の言語やgemにもある↓から言語にも取り入れて欲しいということのようです。

Python: unicodedata.east_asian_width (standard library)
https://docs.python.org/3.6/library/unicodedata.html#unicodedata.east_asian_width

Perl: "East_Asian_Width: *" of Unicode properties (regular expression in language)
https://perldoc.perl.org/perluniprops.html

Go: golang.org/x/text/width
https://godoc.org/golang.org/x/text/width

PHP: mb_strwidth (standard library)
http://php.net/manual/en/function.mb-strwidth.php

JavaScript: eastasianwidth (npm library)
https://www.npmjs.com/package/eastasianwidth

RubyGems: unicode-display_width gem
https://rubygems.org/gems/unicode-display_width

つっつきボイス: 「Unicodeの文字幅って全角と半角しかないんだっけ?」「んー、合字とかを別にすればそうだったかもしれないけど、どうだったかな…」

後で取り急ぎ以下を見つけました。

参考: [Ruby] Unicode 文字列の幅をそれなりにがんばって取得する - あおたくノート

プロポーショナルフォントの場合どうしようもないんだけど Unicode の文字は EastAsianWidth という仕様があって文字毎の幅がいわゆる全角か半角のどちらかになるかが決められている。
決められているといっても一部の文字は「Ambiguous(決定できない)」というふうに決められているのでそういう文字は自分で全角に倒すか半角に倒すか選ぶ必要はある。
同記事より

issueでも言及されているjanlelis/unicode-display_widthで「一応」取れるようです。

Ruby

「サーバーレスなRubyが欲しい」署名運動(Ruby Weeklyより)


つっつきボイス: 「サーバーレスRubyね…、あったらうれしいけどRailsが動くわけじゃないだろうし」「ここで署名している人の半分ぐらい、もしかしてRailsを動かしたいんじゃ?」「AWSのLamdbaでRubyがまともにサーバーレスで動くようになってごく軽いマイクロサービスとか建てられるようになったら確かにうれしい」「LambdaではRubyはまだちゃんと使えないんでしたっけ?」「このサイトのファビコンにLambdaのアイコンがあるぐらいだからまだっぽいですね」「確かまだだったと思う」「JRubyは動くみたいですけどJavaが動くから当然動くし」
「本格的なサーバーレスRubyはまだ難しいんじゃないですかね: gemはどこまで使える?とか、ActiveRecordのこの機能だけ使いたいとか」「ネイティブのgemぐらいなら使えるかもしれないけど」

参考: QuickStart Guide to Using the AWS SDK for Ruby - AWS SDK for Ruby

bundler-stats: gemの依存関係を調べるgem(Ruby Weeklyより)

# 同リポジトリより
> bundle-stats

+------------------------------|-----------------|-----------------+
| Name                         | Total Deps      | 1st Level Deps  |
+------------------------------|-----------------|-----------------+
... omitted stuff here ...
| fog                          | 15              | 6               |
| fancybox2-rails              | 15              | 1               |
| quiet_assets                 | 15              | 1               |
| coffee-rails                 | 18              | 2               |
| angular-rails-templates      | 19              | 3               |
| devise                       | 19              | 6               |
| rspec-rails                  | 20              | 7               |
| sass-rails                   | 21              | 4               |
| foundation-icons-sass-rails  | 22              | 2               |
| rails                        | 29              | 9               |
| angular_rails_csrf           | 30              | 1               |
| ngannotate-rails             | 31              | 2               |
| activeadmin                  | 48              | 12              |
+------------------------------|-----------------|-----------------+

Declared Gems:     35
Total Gems:        113

Unpinned Versions: 30
Github Refs:       1

つっつきボイス: 「こういう依存関係の情報取れるのはちょっとありがたいかも」

Ducalis: RuboCopベースの静的コードアナライザ(RubyFlowより)

# 同リポジトリより
ducalis --ci --repo="author/repo" --id=3575 --dry
ducalis --ci --repo="author/repo" --id=3575
ducalis --ci --adapter=circle # mode for running on CircleCI

参考: Wikipedia-ja 静的コード解析

もう何回言われたか覚えてない「Railsは死んだか: 2018年版」


つっつきボイス: 「2018年版ってのがウケる」「恒例の行事」

Ruby 2.6 preview 1

Noah Gibbsさんの記事とツイッターのやりとりです。

その他の記事/リポジトリ

SQL

pg_badplan: クエリプランと実際のクエリの乖離をチェック(Postgres Weeklyより)

-- 同記事より
\copy zip_codes from ~/src/create-statistics-talk/no_postal_codes_utf.csv with csv header;
COPY 4574

EXPLAIN (ANALYZE, TIMING off)
SELECT * FROM zip_codes WHERE city = 'Oslo' AND county = 'Oslo';
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Seq Scan on zip_codes  (cost=0.00..108.61 rows=90 width=36) (actual rows=642 loops=1)
   Filter: ((city = 'Oslo'::text) AND (county = 'Oslo'::text))
   Rows Removed by Filter: 3932
 Planning time: 0.357 ms
 Execution time: 0.679 ms
(5 rows)

データベース制約は最後の砦(Postgres Weeklyより)

CitusDataによる平易な解説記事です。

PostgreSQLで行の重複をサーチアンドデストロイする(Postgres Weeklyより)

-- 同記事より
SELECT id, firstname, lastname, startdate, position FROM
  (SELECT id, firstname, lastname, startdate, position,
     ROW_NUMBER() OVER 
(PARTITION BY (firstname, lastname) ORDER BY startdate DESC) rn
   FROM people
  ) tmp WHERE rn = 1;

JavaScript

JSプログラマーはどこで一番よく間違えるか(Frontend Weeklyより)


つっつきボイス: 「single biggest mistakeとある割に記事長いなー: 今読んでられない」「翻訳してみようかしら」

Cypress: フロントエンド向けテスティングフレームワーク


同リポジトリより

Railsでもbin/rake db:purge && cypress openで使えるそうです。


つっつきボイス: 「これはこれは、とってもCapybaraみたい↓」

describe("main navigation", () => {
  it("can navigate", () => {
    cy.visit("/");
    cy.get("[data-cy='nav-blog']").click();
    cy.location("pathname").should("eq", "/blog");

    cy.viewport("iphone-6");
    cy.get("[data-cy='nav-blog']").click();
    cy.location("pathname").should("eq", "/blog");
    cy.get("[data-cy='nav-about']").click();
    cy.location("pathname").should("eq", "/about");
  });
});

サードパーティJavaScript読み込みの影響(Frontend Weeklyより)


同記事より

Googleの解説記事です。

CSS/HTML/フロントエンド

CloudFlare Workers APIでJavaScriptを高速実行(JavaScript Weeklyより)

JavaScript向けの高速なCloudFlare Workers APIがでリリースされたという情報です。ドキュメントによるとHTTPトラフィックを扱う「Cloudflare Service Workers」とは別物だそうです。

センサーAPIがW3CでCRに(Frontend Focusより)


つっつきボイス: 「加速度計、ジャイロスコープ、磁気センサ、方位センサに間接光センサ…」

Wrapparizer: サイトで使われているソフトウェアを簡単に表示


つっつきボイス: 「既存システムがある案件の見積フェーズで有用そう」「Railsは50%で推測してますね」「JavaScriptの方をチェックしているのかも」

その他

Java 10がリリース


同記事より

参考: Java 10が本日付で正式リリース。ローカル変数の型推論、ガベージコレクタが入れ替え可能、不揮発性メモリ対応など。Java 9は早くもサポート期間終了 - Publickey

参考: Java 10新機能まとめ - Qiita

日本で働く外人エンジニア

読み物記事です。日本語混じりなので翻訳が逆に難しそう。


つっつきボイス: 「これはいろいろ面白いー」「忖度忖度」「『現在和訳中です』か…」「↓これもいいですね」

参考: How To Write Letters In Japanese

その手があった

番外

奥村先生が受賞

おめでとうございます。

塩粒以下のコンピュータ

アリさんに埋め込んだりできるでしょうか。


つっつきボイス: 「もう花粉症引き起こせますね」「こういうのが互いに相互連携して動くみたいなやつも出始めてるらしい」「体内で動くのはちょっと怖いかも」

参考: IBMが世界最小のコンピューターを発表、塩の粒より小さいサイズ - GIGAZINE

ブレインスキャナーがウェアラブルに

ポイントは「地磁気の影響をどうやって遮断するか」だったそうです。


今週は以上です。明日のRails Developers Meetup 2018でお会いしましょう。スライドがんばる…

バックナンバー(2018年度)

週刊Railsウォッチ(20180316)Rails 5.2のドキュメント更新中、Value Objectの使い方、RubyがTIOBEトップテン復活、Rails「雪だるま」エンコーディングほか

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

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

Rails公式ニュース

Ruby Weekly

Awesome Ruby

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines

Postgres Weekly

postgres_weekly_banner

Frontend Weekly

frontendweekly_banner_captured

Frontend Focus

frontendfocus_banner_captured

JavaScript Weekly

javascriptweekly_logo_captured

JavaScript Live

jslive_logo_captured

JSer.info

jser.info_logo_captured

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ