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

週刊Railsウォッチ(20200713前編)rspec-openapiでスキーマ自動生成、Rails Architect Conf動画、where()ハッシュキーに比較演算子条件を書ける機能ほか

こんにちは、hachi8833です。これが今度のAWS Summit Tokyoの目玉イベントなんですね。

  • 各記事冒頭には⚓でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄

つっつきボイス:「もう今年も半分終わりか〜」「半分超えちゃいましたね」「AWS Summit Tokyo、今年はリモートでやるってどこかで見たナ🎉」

「その『電笑戦 ~ AI は人を笑わせられるのか』とかいう電脳大喜利みたいな企画に募集がいっぱい来たそうです」「『ボケて』のAI版みたいな😆」「また大変そうな企画を😆」「ちゃんと生成モデルを作ってやるんですね」

「素人目にはこれで笑い取れるものを生成できるんだろうかって思いますけど」「たしかこの種の試みって他でもやっていて、一発で大ネタをキメるのは難しいけど、いくつか候補を出してその中から選ぶみたいなのだと割といい感じにできるらしいし、学習結果をフィードバックしたりして強化したりもできるので、何度も回しているとだんだん上手くなってくるという感じらしいですね」「人間様が集まって勝手にフィルターとして機能してくれると最高でしょうね」「笑いの場合、どういうコンテキストなのかにもよるでしょうね」「時代もありそうですし」

「深層学習は、人間でもなぜそれを思い付いたのか説明できないようなことを扱うのに適してますけど、その代わりAIもなぜそれが正しいのかという理由の説明はできないという」「そこですよね」「結果の説明責任がないのが深層学習ですし」「『理由はわかりませんが皆さんこうしてらっしゃいます』的な」「深層学習はその辺が誤解されやすいところ」「傍から見ると野生の勘とか女の勘みたいな挙動ですよね😆」

「それに普通に考えたら難しそうなことにチャレンジするところに価値があるでしょうし」「やってみたら意外にいい結果が出るかもしれませんよね」

今年のAWS Summit Tokyoは

「今年のAWS Summit Tokyoは9月にリモート開催か〜」「まあ人多すぎてしんどいイベントなので、むしろリモートでいいと思います」「同意です!」「講演聞くだけなら自宅の方が快適ですよね」「人が多いと面白そうなセッションの部屋があっという間に埋まっちゃうのもつらくて、最近行ってませんでしたし」「入れないのつらいですよね…」「どの開催地でも結局部屋足りなくなるときしかありませんでしたね」

「まあ行ったら行ったでAWS認定持っている人だけが入れる休憩ゾーンみたいなのを使えますけど😋」「そんな空港のVIPルームみたいなのがあるんですか、いいな〜」「あれでAWS認定取った甲斐はあったかなと」

「リモート開催が今後平常運転になるかもですね」「その分、会場で仕事探したり商談したい人には残念でしょうけど」「それもそうか」「興行として成功するにはそのあたりが今後の課題かも」「AWS Summitはテック系イベントですけど、ブースのあたりを見ていると来客の半分ぐらいがビジネス系寄りな印象がちょっとありますね」「へぇ〜」

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

今回は以下のコミットリストのChangelogを中心に見繕いました。ドキュメントの修正が増えてるので、そろそろ6.1が近い?

ActionDispatch::SSLリダイレクトのデフォルトHTTPステータスを308に

# #L
-   def initialize(app, redirect: {}, hsts: {}, secure_cookies: true)
+   def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, ssl_default_redirect_status: nil)
      @app = app

      @redirect = redirect
      @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
      @secure_cookies = secure_cookies

      @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
+     @ssl_default_redirect_status = ssl_default_redirect_status
    end
...

      def redirection_status(request)
        if request.get? || request.head?
          301 # Issue a permanent redirect via a GET request.
+       elsif @ssl_default_redirect_status
+         @ssl_default_redirect_status
        else
          307 # Issue a fresh request redirect to preserve the HTTP method.
        end
      end

つっつきボイス:「これはこの間話題になったHTTP 308か(ウォッチ20200629)」「まだステータス名覚えられない😅」「308 parmanent redirect、っと…」

新機能: whereでハッシュキーへの比較演算子条件記入をサポート


つっつきボイス:「@kamipoさんがしれっと入れてました」「おぉ、これは😍」

# 同PRより
posts = Post.order(:id)

posts.where("id >": 9).pluck(:id)  # => [10, 11]
posts.where("id >=": 9).pluck(:id) # => [9, 10, 11]
posts.where("id <": 3).pluck(:id)  # => [1, 2]
posts.where("id <=": 3).pluck(:id) # => [1, 2, 3]

型キャストとテーブル名/カラム名解決という点においては、("create_at >= ?", time)よりもwhere("create_at >=": time)という書き方の方がよい。
同PRより大意

# 同PRより
class Post < ActiveRecord::Base
  attribute :created_at, :datetime, precision: 3
end

time = Time.now.utc # => 2020-06-24 10:11:12.123456 UTC

posts = Post.order(:id)
posts.create!(created_at: time) # => #<Post id: 1, created_at: "2020-06-24 10:11:12.123000">

# SELECT `posts`.* FROM `posts` WHERE (created_at >= '2020-06-24 10:11:12.123456')
posts.where("created_at >= ?", time) # => []

# SELECT `posts`.* FROM `posts` WHERE `posts`.`created_at` >= '2020-06-24 10:11:12.123000'
posts.where("created_at >=": time) # => [#<Post id: 1, created_at: "2020-06-24 10:11:12.123000">]

「これは"create_at >= ?"のようにハッシュのキーに条件を入れるというやり方を選んだのか!」「へぇ〜、そんな方法が!」「ハッシュの、しかもキーに!?」「RuboCopに怒られないかしら?😆」

「DSLをいたずらに拡張するよりはこの方がうまいですね〜: これなら互換性も保たれるし、条件を文字列で直書きするのと違ってSQLインジェクションにも対策できるし」「このプルリク、ハートマークが47個も付いてますね❤️」「引数で渡した文字列がそのままSQLに渡されるよりはいいよねとみんなも思ってるんじゃないかしら」

「あと型キャストについてですが、?プレースホルダで渡す従来の方式だと、渡したTimeWithZoneオブジェクトに入ってる値がそのままSQLのプレースホルダに渡されようとするので、Active Recordが本来ミリ秒で値を丸める処理が通らない: なので、Active Recordがクエリを投げる前に通常のActive Record内でRuby→SQLの間の型変換を通せるようにArelで解釈ができる仕組みにしたかったのかなと思いました」「おぉ」

# activerecord/lib/active_record/relation/predicate_builder.rb#L112
          elsif table.aggregated_with?(key)
            mapping = table.reflect_on_aggregation(key).mapping
            values = value.nil? ? [nil] : Array.wrap(value)
            if mapping.length == 1 || values.empty?
              column_name, aggr_attr = mapping.first
              values = values.map do |object|
                object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
              end
              build(table.arel_attribute(column_name), values)
            else
              queries = values.map do |object|
                mapping.map do |field_attr, aggregate_attr|
                  build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
                end
              end

              grouping_queries(queries)
            end
+         elsif key.end_with?(">", ">=", "<", "<=") && /\A(?<key>.+?)\s*(?<operator>>|>=|<|<=)\z/ =~ key
+           build(table.arel_attribute(key), value, OPERATORS[-operator])
          else
            build(table.arel_attribute(key), value)
          end

「条件の中身もend_with?(">", ">=", "<", "<=")でちゃんと解析してますね↑: 何でも書けるわけじゃなくてここでチェックされる演算子しか使えないということで」「なるほど、やんちゃできないようになってると」「ぱっと見何でも書けそうに見えるけど対策済み😋」

# activerecord/lib/active_record/relation/predicate_builder.rb#L138
+         OPERATORS = { ">" => :gt, ">=" => :gteq, "<" => :lt, "<=" => :lteq }.freeze

「あぁこれ見て納得した↑: もともとArelの中ではここにある演算子を使って比較演算の式をビルドするんですよ」「あ〜なるほど」

「ナイス落とし所: 引数で渡した文字列をそのままSQLに渡さずに済むという点でとても現実的な解だと思います👍」「この書き方を初めて目にしたらちょっとドキドキするかも」「そうかも😆」「あってもおかしくないけど、もしかすると見慣れない書き方に拒否反応が出るかもしれなくてやらなかったのかな?」「ハッシュのキーというとスペースを含まないスネークケースの文字という意識が先に立ちそうですし」

Journeyの文字列を式展開に変えてアロケーションを削減


つっつきボイス:「こちらは小ネタですが」「+による文字列結合がよろしくないので式展開に変えたというヤツですね」

# actionpack/lib/action_dispatch/journey/path/pattern.rb#L110
          def visit_STAR(node)
-           re = @matchers[node.left.to_sym] || ".+"
-           "(#{re})"
+           re = @matchers[node.left.to_sym]
+           re ? "(#{re})" : "(.+)"
          end

Rubyでの文字列連結に「#+」ではなく式展開「#{}」を使うべき理由

ガイド: Action Cableの記述を追加


つっつきボイス:「こちらも小ネタですが、Action Cableの機能説明が1個足されてました」「いいですね〜」

# guides/source/action_cable_overview.md#L757
### クライアントサイドログ出力
クライアントサイドログ出力はデフォルトで無効になっています。これは`ActionCable.logger.enabled`をtrueにすることで有効にできます。
  ```ruby
  import * as ActionCable from '@rails/actioncable'
  ActionCable.logger.enabled = true
  ```

Rails

大規模マイグレーションを高速化するためにコールバックを全部スキップした(Hacklinesより)

# 同記事より
# GOOD
class BackfillEmployeesWithFriendlyId < ActiveRecord::Migration[5.0]

  # 空のクラスのおかげで大規模マイグレーションを遅くするコールバックを簡単に全スキップできる
  class FriendlyIdEmployee < ActiveRecord::Base
    self.table_name = 'employees'
    extend FriendlyId
    friendly_id :slug_candidate, use: [:slugged, :finders]

    def slug_candidate
      if first_name || last_name
        "#{first_name} #{last_name}"[0, 20]
      else
        "employee"
      end + " #{SecureRandom.hex[0, 8]}"
    end
  end

  def up
    print "Updating friendly_id slug for employees"
    FriendlyIdEmployee.where(slug: nil).each do |row|
      row.save; print('.')
    end
    puts ''
  end
end

つっつきボイス:「タイトルでわかっちゃうぐらい短い記事ですが」「マイグレーション高速化のためにコールバックをスキップするという選択肢はありでしょうね」「コールバックチェインは何やっても遅くなるし」「スキップしないのが理想だけどそうせざるを得ないときはありますし」「ここではfriendly_idを入れたことで大規模マイグレーションすることになったと」「friendly_idはきっかけということですね」

何ということでしょう…Employee.all.each(&:save)がproduction環境で地獄のように遅い。
同記事より

Rails Architects Conference 2020 Onlineの動画がアップ

Rails Architects Conference 2020が7/1より順次オンライン開催


つっつきボイス:「以前記事にしたArkencyさんのRails Architects Conference、結局網膜裂孔で見そびれました…😢」「そうそう、動画上がってたので後で見ようと思ってた」「うっしゃ、後で見ようっと」

「ざっとスライド見た感じでは、話すこと前提で何もかもは書かない方針かな」「英語圏とかキーノートスピーチによくあそう」「プレゼンをちゃんと聞いて欲しいなら、全部盛りしないでこうやって話す用のスライドにした方がいいでしょうね」「たしかに」


以下は同サイトに上がっている動画です(日本語タイトルは仮)。最後の2つは現時点でスライドなしの動画のみです。

キーノート: 今こそ変革の時 — 次のRailsアプリを正しく始めるには

Railsでのマルチテナント

つらくないRailsアップグレード方法とは

Railsのビューを高速化するシンプルな方法

一見よさげなRubyコードにも「低凝集度」「強結合」が潜む(動画のみ)

顧客が知っているのは「何が欲しい」ではなく「今欲しい」(動画のみ)

pastrubies.com: 過去のRuby/Rails情報を配信(RubyFlowより)


同サイトより


つっつきボイス:「詳しい説明が見当たらないんですけど、サイト名のとおり過去のRuby/Railsに関する記事を配信するサイトのようです」「RubyConf 2005 Agenda Releaseというタイトルとかちょっと衝撃的」「当時はペチパーやってたな〜」「どういう基準で選んでるんだろう?」「開いてみたらarchive.orgだったとは」

Rails Bytes: Railsアプリテンプレートを置けるリポジトリ(Hacklinesより)


同サイトより


つっつきボイス:「Everyday Railsの記事で、このRails Bytesがいいよと紹介されていました」「なるほど、Railsアプリのテンプレートを公開できるサイトね↓」「自分で作ったものもアップロードできるようです」


同サイトより

「適当にrails newで作っておいてrails app:template LOCATION="https://railsbytes.com/script/x7msKX"みたいに実行すると一発起動できるそうです」「他の人の作ったテンプレートを見られるのはよさそうですね👍」


後でやってみました。

$ bundle exec rails app:template LOCATION='https://railsbytes.com/script/x7msKX'
/Users/hachi8833/deve/rails/hello_world/vendor/bundle/ruby/2.7.0/gems/thor-1.0.1/lib/thor/actions.rb:222: warning: calling URI.open via Kernel#open is deprecated, call URI.open directly or use URI#open
hello world from https://railsbytes.com 👋

rspec-openapi: request specからOpenAPIスキーマを生成

# 同リポジトリより
openapi: 3.0.3
info:
  title: rspec-openapi
paths:
  "/tables":
    get:
      summary: 'tables #index'
      parameters:
      - name: page
        in: query
        schema:
          type: integer
      - name: per
        in: query
        schema:
          type: integer
      responses:
        '200':
          description: returns a list of tables
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
                    # ...


つっつきボイス:「この間の銀座Railsの#23でk0kubunさんがちらっとお話していたgemです」「あああ〜これ欲しかったヤツ!😂」「これはたしかに欲しいですね〜」「request specから自動生成か〜、もう作っちゃったけど使っちゃおうかな〜?」

「こういうの探してたんですよ〜」「あとは開発手順にうまくはまるかどうかでしょうね: Railsエンジニアが完全にAPI仕様の決定権を持っているならこれでやるのがよさそうだけど、フロントエンド側のエンジニアがAPI仕様を決めたいという場合だとしんどくなるかも」「あ、たしかに」「フロントエンドエンジニアもAPI仕様策定に参加するなら、これまでどおりSwagger(OpenAPI)でやる方がどちらも中身を読めるでしょうし」

参考: API Documentation & Design Tools for Teams | Swagger

「ともあれスキーマ生成は人間がやらずに済めばそれに越したことはないので、これでうまくいくなら使いたいですね」「このgem、まだ作って1か月経ってないのか」「今だとGraphQLの方が新しく使われそうな気もしますけど」「結構よさそう👍」


前編は以上です。

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

週刊Railsウォッチ(20200707後編)Rubyで無名structリテラル提案、書籍『AWS認定ソリューションアーキテクト』、21世紀のC言語ほか

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

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

Rails公式ニュース

RubyFlow

160928_1638_XvIP4h

Hacklines

Hacklines


CONTACT

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