Tech Racho エンジニアの「?」を「!」に。
  • 開発

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

こんにちは、hachi8833です。ついさっきruby-jp Slackのワークスペースアイコン↓が見ている目の前で突然変わってびっくりしました😳。おめでとうございます!🎉

その後ちょっぴりリサイズしたようです↓。

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

※今回のウォッチは分割していません
※今回のつっつきはSlackベースで行いました

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

第14回目公開つっつき会は、9月5日(木)19:30〜にBPS会議スペースにて開催されます。皆さまのお気軽なご参加をお待ちしております🙇。

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

公式の更新情報のほとんどを先週の「先週の改修」で先取りしていました😋。今回も6-0-stableの更新を中心に見てみました。

他に、6.0.1マイルストーンのclosedからも見繕いました。

(6-0-stable、master)fork後にConnectionPool::Reaperが親のコネクションを刈り取る問題を修正

先週#36999#36998に関連していそうです。また「attr_reader :poolsに依存しないようにした」ともあります。

# activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L332
          private
            def spawn_thread(frequency)
              Thread.new(frequency) do |t|
                running = true
                while running
                  sleep t
                  @mutex.synchronize do
-                   @pools[frequency].select!(&:weakref_alive?)
+                   @pools[frequency].select! do |pool|
+                     pool.weakref_alive? && !pool.discarded?
+                   end
+
                    @pools[frequency].each do |p|
                      p.reap
                      p.flush
                    rescue WeakRef::RefError
                    end
                    if @pools[frequency].empty?
                      @pools.delete(frequency)
                      @threads.delete(frequency)
                      running = false
                    end
                  end
                end
              end
            end
...
      def discard! # :nodoc:
        synchronize do
-         return if @connections.nil? # already discarded
+         return if self.discarded?
          @connections.each do |conn|
            conn.discard!
          end
          @connections = @available = @thread_cached_conns = nil
        end
      end

+     def discarded? # :nodoc:
+       @connections.nil?
+     end
...

つっつきボイス:「この辺はGC系とかも絡んでいるようでデバッグが大変そうなところだなあ」

content_type=の追加部分が落ちないよう修正

# actionpack/lib/action_dispatch/http/response.rb#L420
 private
-   ContentTypeHeader = Struct.new :mime_type, :extra, :charset
-   NullContentTypeHeader = ContentTypeHeader.new nil, nil, nil
+   ContentTypeHeader = Struct.new :mime_type, :charset
+   NullContentTypeHeader = ContentTypeHeader.new nil, nil
+
+   CONTENT_TYPE_PARSER = /
+     \A
+     (?<mime_type>[^;\s]+\s*(?:;\s*(?:(?!charset)[^;\s])+)*)?
+     (?:;\s*charset=(?<quote>"?)(?<charset>[^;\s]+)\k<quote>)?
+   /x # :nodoc:

    def parse_content_type(content_type)
      if content_type && match = CONTENT_TYPE_PARSER.match(content_type)
-       ContentTypeHeader.new(match[:type], match[:extra], match[:charset])
+       ContentTypeHeader.new(match[:mime_type], match[:charset])
      else
-       ContentTypeHeader.new(content_type, nil)
+       NullContentTypeHeader
      end
    end

つっつきボイス:「この間からcontent_type周りが繰り返し修正されているようですが、ちょっとややこしいことになってそう?」「これは@kamipoさんが貼ってくれてるこのPR(#35549)↓が発端みたいすね: この辺は使ってる人たちがテストしないとなかなか踏まなそうなところ」「通常の使い方をしている分には問題はないんでしょうか?」「いや、そもそも既存のコードで使ってた人たちに問題が出たというものだと思います: ただ、今回のIssueを踏むような使い方をしている人たちがそれほど多くなくて発見が遅くなったみたいな話だと思う」

Content-Typeヘッダーに、charset以外のオプションパラメータが含まれる可能性がある。
このヘッダー行の有無を示す例として、text/csv typeに以下のようなheaderパラメータがあるとする。

Content-Type: text/csv; charset=utf-16; header=present

この種のオプションパラメータを#charsetから除外するため、このPRで#parse_content_typeの実装を変更した。
#35549より大意

@kamipoさんのレスには「#33549でdeprecation期間を置かずに変更したのは急ぎすぎだった」とあります。

(6.0.1)attachables.nil?条件を除去

# activestorage/lib/active_storage/attached/model.rb#L91
      def has_many_attached(name, dependent: :purge_later)
        generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
          def #{name}
            @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self)
          end
          def #{name}=(attachables)
            if ActiveStorage.replace_on_assign_to_many
              attachment_changes["#{name}"] =
-               if attachables.nil? || Array(attachables).none?
+               if Array(attachables).none?
                  ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
                else
                  ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
                end
            else
-             if !attachables.nil? || Array(attachables).any?
+             if Array(attachables).any?
                attachment_changes["#{name}"] =
                  ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, #{name}.blobs + attachables)
              end
            end
          end
        CODE

つっつきボイス:「リファクタリングでしょうか?」「これはリファクタリング&読みやすさに倒したみたいな感じみたいですね: PR読めばそんなに難しいこと書いてない感じ」

(6.0、6.0.1)connected_towhile_preventing_writesを呼ぶようになった

  • while_preventing_writesconnected_toから直接呼び出すようにした

アプリケーションの作者がデータベース切り替えミドルウェアを使ってconnected_toを明示的に呼び出したい場合がある。アプリは書き込みをオフにできるが、connected_to(role: :writing)を呼び出すまではオンに戻らない。
アプリはこの変更によって、(明示的に書き込みをオフに場合を除いて)書き込みを許可したいロールが書き込みを行っているかどうかを仮定することで修正できる。
CHANGELOGより

# activerecord/lib/active_record/connection_handling.rb#L116
-   # When using the database key a new connection will be established every time.
-   def connected_to(database: nil, role: nil, &blk)
+   # When using the database key a new connection will be established every time. It is not
+   # recommended to use this outside of one-off scripts.
+   def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
      if database && role
        raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
      elsif database
        if database.is_a?(Hash)
          role, database = database.first
          role = role.to_sym
        end
        config_hash = resolve_config_for_connection(database)
        handler = lookup_connection_handler(role)
        handler.establish_connection(config_hash)

        with_handler(role, &blk)
      elsif role
-       with_handler(role.to_sym, &blk)
+       if role == writing_role
+         with_handler(role.to_sym) do
+           connection_handler.while_preventing_writes(prevent_writes, &blk)
+         end
+       else
+         with_handler(role.to_sym, &blk)
+       end
      else
        raise ArgumentError, "must provide a `database` or a `role`."
      end
    end

つっつきボイス:「connected_torole: :writer + prevent_writesをしようと思ったときにrace conditionしちゃう的な話なのか↓」「確信はないけどこの辺のやつで外側のAR::Base.connected_toと内側のAR::Base.connection_handlerで別のDBコネクション取っちゃうみたいなケースがあったのかなあ?」

# activerecord/lib/active_record/middleware/database_selector/resolver.rb#L46
        private
          def read_from_primary(&blk)
-           ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
-             ActiveRecord::Base.connection_handler.while_preventing_writes(true) do
-                instrumenter.instrument("database_selector.active_record.read_from_primary") do
-                 yield
-               end
+           ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role, prevent_writes: true) do
+              instrumenter.instrument("database_selector.active_record.read_from_primary") do
+               yield
              end
            end
          end

番外: メンテナンスポリシーの記載を明確にした

# guides/source/maintenance_policy.md#L67
For severe security issues all releases in the current major series, and also the
- last major release series will receive patches and new versions. The
+ last release in the previous major series will receive patches and new versions. The
classification of the security issue is judged by the core team.

つっつきボイス:「これはまあ書いてあるとおりということで☺️」

参考: Ruby on Rails のメンテナンスポリシー - Rails ガイド

上の修正に基づくと、Railsガイドの該当箇所は以下のようになります。後ほどRailsガイドを更新します(#883)。

重大なセキュリティ問題については、最新のメジャーなシリーズにおけるすべてのリリースと、前回のメジャーなシリーズの最後のリリースに対してセキュリティパッチと新バージョンを提供します。セキュリティ問題の重大性の分類はコアチームによって行われます。

つまり今回の場合、Rails 6がリリースされたことで、Rails 5.1.z以前についてはセキュリティパッチや新バージョンは提供されなくなったということになります。

Rails

Jetsの実験的Rails互換「アフターバーナーモード」機能

まだexperimentalですが、RackをLambda実行コンテキストで起動することで、Railsアプリを無改造でデプロイできるようにする試みだそうです。

参考: AWS Lambda 実行コンテキスト - AWS Lambda

注意:

  • RailsプロジェクトのGemfileにjetsを追加しないこと
  • Railsアプリを必ずしもそのままサーバーレス化できるとは限らない
    • たとえばファイルや画像をファイルシステムにアップロードするアプリはそのままでは無理
  • Lambdaレイヤのサイズは250MBに制限されている
    • Railsの基本gemだけで146MBぐらいにはなる
  • 複雑なRailsアプリについてはJetsのメガモードも検討
  • アフターバーナーモードよりはJetsで直接アプリにする方が一般におすすめ

参考: AWS Lambda の制限 - AWS Lambda


つっつきボイス:「Jetsのafterburner modeはかなり初期からexperimentalとして公式ドキュメントにはありましたね: ただ、どれくらいまともに使われてるのかが謎すぎるので全然触ってない」「あ、そんなに前からあったんですね😅」「そもそもRailsをそのまま変換して動くケースの方が極レアだと思うので、そこまで魅力は感じなかったなあ(誰か人柱している人がいればレポートは見てみたいけど)」

「まあ特に魅力に感じなかったので「夢の試みだなあ」くらいにしか見てなかった😆」「アフターバーナー、見るからに重そう...」「Railsに必要なRack Middlewareを毎回ロードするということを考えるとちょっと厳しすぎるだろう感はありますねえ」

Shrine gem 3.0で予定されている新機能(RubyFlowより)


  • ミュータブルなstructからの切り離し(Repositoryパターンに対応するため)
  • derivativesプラグイン
  • backgroundingプラグイン
  • 一時ストレージをスキップする機能
  • ファイル取り出しの高速化
  • 永続化APIを複数のプラグインで標準化

つっつきボイス:「Shrine、Active Storageに押されて更新やめちゃうとかも心配してたけど、開発続行してくれる感じなんですね: 既存システムのアップグレードパスとしてもありがたいし、Active Storageでできない機能とかもちょいちょいありそうだしありがたい話だ🙏」「記事には『現行のShrineはActive Recordとかにはよく合う設計だが、Hanamiとかに合わないのでstructを切り離した』とありました」「あーなるほど。確かにRails専用ってわけじゃないですもんね、それは分かる😋」「今後のメンテも期待できそうですね❤️」


shrinerb.comより

Rails 6のWebpackerを理解する(Awesome Rubyより)


  • デフォルトでapp/javascriptに置かれる
  • app/javascript/packsにはapplication packが置かれる
  • bin/webpack-dev-serverでライブリロードできる
  • production環境ではassets:precompileタスクにwebpacker:compileが追加される
  • javascript_pack_tagヘルパーメソッドはアセットパイプラインのjavascript_link_tagと同等

つっつきボイス:「記事の行間が開きすぎてて読みにくい💦」「😆」「Understandingとあるけど割と簡単なことしか書いてない気もするので、いわゆる「完全に理解した」的な記事かな😇」「Webpackerの「Rails 6での変更点」を理解してみた的な?」「あ、たしかにRails6になって変わった部分もピックアップされてますね: 5.2からのmigration path的にはありがたい話なのかも」「Railsガイドにはありそうですが、変更点までは網羅されてないかもですね(後で見ます)」

意外にもRailsガイドにはWebpackerのまとまった記述がありませんでした😳。アップグレードガイドでWebpackerについてごく簡単に触れられている程度です↓。

参考: 2.1 Webpackerの利用について -- Rails アップグレードガイド - Rails ガイド

Ruby

Ruby 2.6.4がリリース: セキュリティ修正(Ruby公式ニュースより)


つっつきボイス:「このツイート見るとRDocの再生成も必要...😅: RDoc生成を抑制していれば大丈夫?」「Doc生成してしかもそれをPublic公開してない限りは問題ないやつですね: 使ってる人が少ない機能とかだとなかなか発見が遅れるやつ」

主にgemの作者にとって影響がありそうですね。

参考: library rdoc (Ruby 2.6.0)

なお、現在は--no-ri--no-rdocではなくno-documentで抑制すると今頃知りました↓。

参考: ruby - How to make --no-ri --no-rdoc the default for gem install? - Stack Overflow
参考: gemrcの--no-riと--no-rdoc、deprecatedなoptionなのでみなおしたほうがいいかもですよ - Qiita

Bundler 2.1.0pre.1リリース


つっつきボイス:「かなり内部をアップデートしたそうです」「マイナーバージョンアップでpreを踏むの、慎重でありがたいすね: Bundlerの挙動がおかしくなると世の中のRailsエンジニアのあちこちから悲鳴あがるし、じっくり検証&フィードバック期間があるのはありがたい🙏」

後でリリースノートを見ると機能とバグフィックスがたくさん載っていますが、機能のみざっと見てみました。

  • configコマンドをサブコマンドで再実装
  • bundle plugin listを追加
  • bundle lock --gemfileフラグを追加
  • ローカルgitリポジトリソースを指定する--local_gitオプションを追加(プラグインインストール用)
  • quietフラグをインラインbundlerに追加
  • prefer_patch設定を導入(bundle updateの振る舞いをbundle update --patchのようにする)
  • Bundler.original_systemBundler.original_execを導入
  • bundle info GEMを追加(旧bundle show GEMに相当)
  • Gemfileのgemグループのリストを表示するbundle listを導入(従来はbundle showのエイリアスだった)
  • bundle outdated --filter-strictを導入(bundle outdated --strictのエイリアス)
  • bundle add:gitオプションと:branchオプションを追加
  • ruby_26:platform(s) DSLに有効な値として追加
  • bundle packageで提供されているすべての機能をbundle cacheに含めた
  • 新しいgemテンプレートを改善
  • bundle gem--[no-]gitオプションを追加(ソース管理しないgemを生成、単一リポジトリなどで有用)

スライド「All About Ruby in 2019」


event.shoeisha.jp/devsumiより


つっつきボイス:「Ruby 2.3からの差分の歴史とかもさらっとまとまっててありがたいな: これは良い👍」「スライドで情報が完結していてありがたいです🙏」

「RubyのDockerイメージ、Docker Community版しか知らなかったけどRubyコアチーム版もあるのか↑: 確かにDocker Community版のコンテナは実は色々盛りだくさんに入っているので、pureなRubyを動かすだけならもっと軽いイメージの選択肢はあるなと思っていた(その代わり、gem install railsがすぐにできるように色々入ってる利点もある): まだExperimentalって書いてあるからいまいち詳しいことはわからないけど」

「コアチーム版のメンテナーはmrknさんなのね↓」


「DockerHub版のDockerfile↑はなんかちょいちょいHackぽいこともやってるように見えるのでrubylang/ruby の方が整理されている感じはある、と思ったら環境変数が違うとかちゃんとスライドに書いてあった↓」

スライドには、他にRubyのPlaygroundサイトなども紹介されています。要チェックだと思います😋。


ruby.github.io/TryRubyより


なお、このスライドは以下のツイートで知りました。

スライドによると、発表後の2つの変更点は以下でした。

  • numbered parameterの記法が@1から_1

  • パイプライン演算子|>をいったんご破算(記法の変更も含め将来に再検討するかも)

その他Ruby

つっつきボイス:「RubyのソースでANYARGSとかいうのが使われすぎてたのを修正したそうです」「ANYARGSだけじゃなくてvoid*とかも駆逐されたりしていて相当でかい修正だな。しゅごい😳」「RubyのCのソース、表記スタイルが長年にわたってばらつきまくっているという話が以前ありましたけど、今からだと修正量が莫大になっちゃいそうだからなかなか行われないのかな...」「ソースのスタイルに大きく手を入れると開発者脳内にある関数名とかマクロ名とかのポインタがロストしてしまうので、一部のメンバーが中心になってコード書いてるようなプロジェクトでは変化させないほうが全体効率が良い、とかはあるかもですね☺️」

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

Google App Engineスタンダード環境でRuby 2.5をサポート(ベータ)


つっつきボイス:「GAEは正直あんまり詳しくないけど、AWSのLambda Layerみたいな自由度あるのかなあ🤔」「記事のリンクによるとApp Engineには「スタンダード環境」と「フレキシブル環境(Ruby対応済み)」というのがあるそうです↓」

参考: App Engine 環境の選択  |  App Engine ドキュメント  |  Google Cloud

「GAEはstarndard / flexibleで全然環境自体が違うんですね: Lambdaは実行環境自体はAWS提供の環境でもユーザーの作ったLambda Layerでも基本的に同じはず(Firecrackerだっけかな)」「GAEのflexible は完全にDockerみたいなので、LambdaとかGAE standardと比べるには不利すぎるかな🤔」

参考: Firecracker – サーバーレスコンピューティングのための軽量な仮想化機能 | Amazon Web Services ブログ

追記(2019/09/04)

Quora: (3) とうとうGoogle App EngineでRubyが標準サポートになりましたが、心境のほどはいかがでしょうか?に対するYukihiro Matsumotoさんの回答 - Quora

JavaScript

知っておきたいJavaScriptの新機能7つ


つっつきボイス:「はてブで見つけました: privateの記法がキモがられてるっぽいです」「optional chaining(?.)はなんかで見ましたね: Rubyで言う&.(ぼっち演算子)的な」「クライアントはブラウザ依存あるから、これらの機能が使えるようになるまでまだ時間かかりそうだけど、サーバーサイドJSで使う分には問題ないから知っておいてもよさそう😋」

// 同記事より
// privateフィールドは'#'で始まらなければならない
// classブロックの外部からはアクセスできない

class Counter {
  #x = 0;

  #increment() {
    this.#x++;
  }

  onClick() {
    this.#increment();
  }

}

const c = new Counter();
c.onClick(); // 動く
c.#increment(); // エラーになる
// 同記事より: optional chaining
const stop = please?.make?.it?.stop;

Rubyのぼっち演算子はRailsの`Object#try`より高速(翻訳)

その他

やはり名前が大事


つっつきボイス:「以前のウォッチでも、コーディングで一番時間がかかるのが「名前付け」という記事を散りあげた覚えがあります」「命名はコーディングというよりは『設計』なんですよね: 名は体を表すの通りなので、適切な命名がされればあとは純粋なロジックを書くのに集中できるという」

番外

iPS細胞で視力を取り戻す

https://www.youtube.com/watch?v=zNKYudKmXsQ

参考: 人工多能性幹細胞 - Wikipedia


つっつきボイス:「20年もしたら普通になってきそうですね」「この辺はもう治験に入ってるんだなあ: 未来感ある🚀」「度胸さえあれば、白内障になったときにこれ↓試してみたい気も😆」

参考: 3Dプリンターで完璧に機能する目「バイオニックアイ」を作り出す研究 - GIGAZINE


今回は以上です。

おたより発掘

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

週刊Railsウォッチ(20190826)6-0-stableの更新を見てみる、『Morning Cup of Coding』ニュースレター、Rails TutorialがRails 6対応に動き出すほか

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

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

Ruby 公式ニュース

Rails公式ニュース

Awesome Ruby

RubyFlow

160928_1638_XvIP4h


CONTACT

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