Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rails6.1で新しく入る機能について

 Rails6.1で新しく入る機能について

技術顧問が語る最新Ruby on Rails/Vue.js iCARE Dev Meetup #12 で話すスライドです

https://icare.connpass.com/event/183716/

Shinichi Maeshima

August 21, 2020
Tweet

More Decks by Shinichi Maeshima

Other Decks in Technology

Transcript

  1. Rails 6.1
    で新しく⼊る機能について
    @willnet
    1

    View Slide

  2. フリーランス
    Rails
    技術顧問をしつつ、
    空いた時間で
    savanna.io
    などを開発し
    ています
    2

    View Slide

  3. 最近の
    Rails
    リリース⽇
    6.0.0 (2019/08/06)
    5.2.0 (2018/04/09)
    5.1.0 (2017/04/27)
    5.0.0 (2016/06/30)
    だいたい1
    年くらいでマイナー、メジャーバージョンが上がる
    RailsConf(
    毎年だいたい4
    末くらいに開催)
    がターゲットになっていそ

    3

    View Slide

  4. ではそろそろ
    6.1.0
    でるの?というとま
    だっぽい雰囲気を感じる
    https://github.com/rails/rails/pulls?
    q=is%3Aopen+is%3Apr+milestone%3A6.1.0
    4

    View Slide

  5. 5

    View Slide

  6. ⼀応
    6.1.beta1
    ⽤のブログエントリの準
    備⽤
    PR
    はある
    (
    けどいまアクティブでは
    ない
    )
    Add post about 6.1.beta1 release by eileencodes · Pull Request #222 ·
    rails/weblog
    6

    View Slide

  7. basecamp
    の⾯々が
    hey.com
    の開発で
    忙しかったという噂。今年中に出ると
    いいな


    7

    View Slide

  8. 6.1
    のリリースはまだだけど、この
    1

    ですでに
    master
    にマージされた機能は
    たくさんあるので、今⽇はそのうちの
    ⼀部を紹介します
    (6.0
    の機能を知り
    たい⼈はパーフェクト
    Ruby on Rails

    いう素敵な本があるのでそちらを御覧
    ください
    )
    8

    View Slide

  9. ビューで「どのテンプレートを呼び出
    しているか」が
    HTML
    コメントで表⽰
    できるようになった
    .annotate_template_file_names annotates HTML output with
    template names by joelhawksley · Pull Request #38848 · rails/rails
    Add the configuration option for annotating templates with file
    names to the generated app by prathamesh-sonpatki · Pull Request
    #39204 · rails/rails
    9

    View Slide

  10. 10

    View Slide

  11. config.action_view.annotate_rendered_view_with_filenames = true
    とすると有効になる(
    デフォルトだと
    config/environments/developmrnt.rb
    でコメントアウトされた状態
    になっている)
    べんり!
    ただし現時点ではERB
    のみ対応
    PR
    チャンスかも
    11

    View Slide

  12. config/routes.rb
    の内容を別ファイルに
    分割できるようになった
    config/routes.rb
    の内容を別ファイルに外だしできるようになった
    昔(Rails 4.0
    がリリースされる前)
    ⼊った内容なんだけどDHH
    のお気
    に召さなかったようでrevert→6
    年たってDHH
    の気が変わった模様
    これまでもゴニョゴニョすればできたけど、公式のやり⽅が提供さ
    れたので安⼼して使えるようになったのがよいですね
    12

    View Slide


  13. のように書く
    # config/routes.rb
    Rails.application.routes.draw do
    draw(:admin)
    end
    # config/routes/admin.rb
    get :foo, to: 'foo#bar'
    routes.rb
    が⼤量にあって、かつ分けやすい箇所がある(ex:
    管理画⾯、
    API)
    のであれば試してみるとよいのでは
    13

    View Slide

  14. ActiveRecord

    signed_id
    メソッドと
    find_signed
    メソッドが追加
    id
    を暗号化、かつ改ざん検知ができるトークンを付与したものを⽣
    成するsigned_id
    メソッドと、それを使ってfind
    できるfind_signed

    ソッドが追加されました
    トークンに有効期限を含めることもできます
    14

    View Slide

  15. signed_id = User.first.signed_id(
    expires_in: 15.minutes, purpose: :password_reset
    )
    User.find_signed(signed_id) # => nil (purpose
    がない)
    User.find_signed(signed_id, purpose: :password_reset) # => User.first
    # 16
    分後...
    User.find_signed(signed_id, purpose: :password_reset) # => nil (expire
    した)
    User.find_signed!("bad data") # =>
    エラー
    コード例のように、パスワードリセット機能などを作るときに便利で

    15

    View Slide

  16. strict_loading
    の導⼊
    strict_loading
    メソッド経由で取得したオブジェクトは、以後SQL
    の発
    ⾏を伴う関連を呼び出せない(
    呼び出すとエラー)
    。これによって
    includes
    などのeager load
    を強制させることができる。
    user = User.strict_loading.first
    user.posts.to_a
    #=> ActiveRecord::StrictLoadingViolationError
    user = User.strict_loading.includes(:posts).first
    user.posts.to_a #=> OK
    16

    View Slide

  17. 関連先にもstrict_loading
    は伝播する。
    user = User.strict_loading.includes(:posts).first
    user.posts.first.comments
    #=> ActiveRecord::StrictLoadingViolationError
    17

    View Slide

  18. 関連のオプションとして設定することもできる。↓
    のようにすると、
    posts
    関連はincludes
    やpreload
    など経由でしか呼び出せない。
    class User < ApplicationRecord
    has_many :posts, strict_loading: true
    end
    user = User.first
    user.posts.first #=> ActiveRecord::StrictLoadingViolationError
    18

    View Slide

  19. モデル単位で設定することもできる。
    class User < ApplicationRecord
    self.strict_loading_by_default = true
    has_many :posts
    end
    user = User.first
    user.posts.first
    # => ActiveRecord::StrictLoadingViolationError Exception

    のどちらかで、AR
    全体にstrict_loading_by_default
    を設定することも
    できる
    ActiveRecord::Base.strict_loading_by_default = true
    Rails.application.config.active_record.strict_loading_by_default = true 19

    View Slide

  20. これはあえてstrict_loading
    にしたくないんですよ、という場合は次の
    ようにできる
    user = User.strict_loading(false).first
    user.posts.to_a #=> ok
    strict_loading
    を使うとpreload
    が強制されて、N+1
    を防ぐことができ
    るぞ!というのは便利そうだけど、代わりに↓
    のように無駄にオブジ
    ェクトを作ってしまうケースが出てきそうですね。なにも考えずにコ
    ードを書けるようになるわけではない。
    user = User.includes(:posts).first
    user.posts.last
    #=>
    ⼀つだけPost
    があればいいのに全件分のPost
    オブジェクトを⽣成してしまう!
    20

    View Slide

  21. 複数
    DB

    sharding
    対応が⼊った
    Rails6.0
    でプライマリ/
    レプリカの複数DB
    に対応した
    Rails6.1
    ではそれを拡張して、sharding
    にも対応
    6.0
    では次のように、プライマリ/
    レプリカを指定していた
    class ApplicationRecord < ApplicationRecord
    connects_to database: { writing: :primary, reading: :secondary }
    end
    21

    View Slide

  22. connects_to
    メソッドに、次のようにsharding
    の定義を書けるよう
    になった。
    ⾒てわかるようにsharding
    とprimary/replica
    を併⽤できる
    6.0
    の記法からから1
    階層増えた感じ
    class ApplicationRecord < ActiveRecord::Base
    self.abstract_class = true
    connects_to shards: {
    default: { writing: :primary, reading: :primary_replica },
    shard_one: { writing: :primary_shard_one, reading: :primary_shard_one_replica }
    }
    end
    22

    View Slide

  23. 6.0
    のときと同じようにconnected_to
    でDB
    を切り替える
    ActiveRecord::Base.connected_to(shard: :default) do
    @id = Record.create! #
    デフォルトのshard
    のDB
    でレコードを作成する
    end
    ActiveRecord::Base.connected_to(shard: :shard_one) do
    Record.find(@id) #
    もう⼀つのshard
    からfind
    しているのでレコードは⾒つからない
    end
    role
    とshard
    を⼀度に両⽅切り替えることもできる
    ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one) do
    Record.first
    end
    23

    View Slide

  24. Deprecation warning
    の代わりに例外
    を発⽣させることができるようになっ

    Rails
    がバージョンアップしたときにdeprecated
    になったメソッド
    を実⾏すると、deprecation warning
    なメッセージが表⽰される
    メッセージを表⽰する代わりに、例外を発⽣させることができるよ
    うになった
    24

    View Slide

  25. 次のように、対象となるdeprecation warnings
    を指定できる
    warning
    の⽂章の⼀部を⽂字列もしくはシンボルで指定する
    warning
    の⽂章にマッチする正規表現を指定する
    ActiveSupport::Deprecation.disallowed_warnings = [
    "bad_method",
    :worse_method,
    /(horrible|unsafe)_method/,
    ]
    25

    View Slide

  26. 全部対象にしたい場合は次のようにする
    ActiveSupport::Deprecation.disallowed_warnings = :all
    対象になったらどうするか、を次のように指定できる。production

    外は例外を発⽣させて、production
    はログに残すだけの例。
    if Rails.env.production?
    ActiveSupport::Deprecation.disallowed_behavior = [:log]
    else
    ActiveSupport::Deprecation.disallowed_behavior = [:raise]
    end
    26

    View Slide

  27. deprecated
    で例外を発⽣させる設定をデフォルトにしたんだけど、こ
    こはまだdeprecated
    なメソッドを使いたい、というようなときには明
    ⽰的にそれを許可することもできる。
    ActiveSupport::Deprecation.allow do
    User.do_thing_that_calls_bad_and_worse_method
    end
    引数で許可するものを絞り込むこともできる
    ActiveSupport::Deprecation.allow [:bad_method, "worse_method"] do
    User.do_thing_that_calls_bad_and_worse_method
    end
    27

    View Slide

  28. 条件を満たすときだけ許可することもできる
    ActiveSupport::Deprecation.allow [:bad_method], if: Rails.env.production? do
    User.do_thing_that_calls_bad_method
    end
    deprecation warning
    に頑張って対応したPR
    をマージしたあとに、す
    ぐまた別のPR
    でdeprecation warning
    が発⽣する、ということを防ぐ
    ことができて便利!
    28

    View Slide

  29. Active Storage
    でアップロードしたファイルへ直接ア
    クセスできる
    URL
    を使えるようになった
    Active Storage
    でアップロードしたファイルへのアクセスは次のよう
    な挙動だった
    1.
    まずクライアントがRails
    サーバへリクエストを送る
    2.
    時間制限(
    デフォルト5
    分)
    つきのファイルへのURL
    を⽣成してリダイ
    レクトする
    3.
    ファイルをダウンロードする
    29

    View Slide

  30. この⼿法は制限付きのリンクがいい感じに⽣成されてべんりなのだけ
    ど、広く公開して問題ないようなファイルだと無駄が多い。

    のようにするとファイルの中⾝を直接返すURL
    を⽣成する(
    設定でデ
    フォルトの挙動を変更することもできる)

    <%= image_tag rails_storage_proxy_path(@user.avatar) %>
    30

    View Slide

  31. Active Storage
    のサムネイル管理⽅法
    の変更と改善
    これまで、Active Storage
    でサムネイルを表⽰するときには次のよう
    な挙動になっていた(
    ストレージがS3
    だと仮定)
    1.
    サムネイルがS3
    に存在するかどうかをチェック
    2.
    存在しなければ元画像をダウンロードしてサムネイルを作りS3
    にア
    ップロードする
    3.
    サムネイルの画像⽤のURL
    を作成する
    31

    View Slide

  32. この⽅式は次の問題があった
    毎回S3
    にサムネイルが存在するか確認しにいくので、その分レス
    ポンスを返すまでに時間がかかる
    S3
    の仕様的に、存在確認後にすぐアップロードすると、しばらく
    の間アップロードしたのにファイルがない、ということになる
    たぶん存在確認の結果をしばらくキャッシュしてるんじゃない
    かな…
    これを解決するために、サムネイルが存在しているかどうかを保持
    しておくテーブルが新設された
    Rails.application.config.active_storage.track_variants = true

    すると新しいやりかたが有効になる
    32

    View Slide

  33. ActiveModel::Errors
    の改善
    ActiveModel::Errors
    とは、 user = User.new; user.errors
    のようにす
    ると返ってくるオブジェクト
    33

    View Slide

  34. ActiveModel::Errors
    は実質的に2
    つのHash
    オブジェクトから構成さ
    れている
    messages
    ex: {:email=>["
    を⼊⼒してください"]}
    details
    ex: {:email=>[{:error=>:blank}]}
    Hash
    をゴニョゴニョするのがめんどくさいケースが有る
    ex:
    特定のメッセージに紐づくdetail
    を取得したいときに、
    messages
    のindex
    を取得して details[:email][index]
    としなけれ
    ばいけない
    34

    View Slide

  35. 今回の修正により、ActiveModel::Errors
    は実質的に
    ActiveModel::Error
    オブジェクト(
    新設)
    の集合となった
    これによりエラー内容に対しての細かい操作が、よりオブジェクト
    指向っぽい書き⽅ができるようになった(Hash
    もオブジェクトでは
    あるけどね)
    関連先のエラーもいい感じに表現できている
    (ActiveModel::Errors
    のネストができる)
    Errors
    にwhere
    メソッドが⽣えた
    model.errors.where(:name, :foo, bar: 3).first
    message
    に紐づくdetail
    が簡単に取得できるようになった
    35

    View Slide

  36. ⾮互換な変更なので対応が必要なものもある
    book.errors[:title] << 'is not interesting enough.'
    のよう
    な、直接Hash
    をいじるような操作はdepricated
    になった
    book.errors.add(:title, 'is not interesting enough.')
    のよ
    うに書く
    book.errors.each do |attribute, error_message| ...
    は depreated
    book.errors.each do |error|
    のように変更する
    関連モデルを⼤量に保存するようなアプリケーションだと、エラー
    内容の解析やらなんやらが楽になってよいのでは
    36

    View Slide

  37. クエリメソッド
    missing
    の追加
    関連先が存在しないレコードを取得したいとき、次のようなコードを
    かくと思います。
    Post.left_joins(:author).where(authors: { id: nil })
    これを次のように短縮して書けるようになりました
    Post.where.missing(:author)
    37

    View Slide

  38. 次のように引数に複数の関連先を書くこともできる(author

    comments
    両⽅とも存在しないレコードが返る)
    Post.where.missing(:author, :comments)
    38

    View Slide

  39. check
    制約に対応した
    add_check_constraint :products, "price > 0", name: "price_check"
    remove_check_constraint :products, name: "price_check"
    create_table :distributors do |t|
    t.string :zipcode
    t.check_constraint "zipchk", "char_length(zipcode) = 5"
    end
    MySQL
    の場合は8.0.16
    以降サポート
    そもそもMySQL
    は8.0.16
    までcheck
    制約に対応していなかった
    これまでも直接SQL
    を発⾏すればcheck
    制約を追加できたけど、そ
    の場合はschema.rb
    をやめてstructure.sql
    にする必要があった
    39

    View Slide

  40. enum
    にデフォルト値を設定できるよう
    になった
    class Book < ActiveRecord::Base
    enum status: [:proposed, :written, :published], _default: :published
    end
    Book.new.status # => "published"
    キーが _default
    なのは「既存のカラムにdefault
    があったときに壊れ
    るから」とのこと
    40

    View Slide

  41. ここまで機能追加の話でした。時間が
    あったら機能変更の話をするぞ
    (
    多分こ
    こまでこれない
    )
    41

    View Slide

  42. CSRF
    トークンのエンコード形式の変更
    CSRF
    トークンのエンコード⽅式がbase64
    からurlsafe
    版のbase64

    変更されました
    base64
    はa-z,A-Z,0-9,+,/
    でエンコードする⽅式
    +
    と /
    はURL
    エンコードしないといけない→
    それぞれ -
    と _
    に変
    更した
    Ruby
    にはurlsafe
    版のbase64
    エンコード⽤メソッドがある
    Base64.urlsafe_encode64
    CSRF
    トークンをcookie
    としてそのまま送りたいようなケースで、
    クライアント側でエンコード・デコードが必要になるのがだるいの
    でこうなったとのこと
    42

    View Slide

  43. 僕らの⽣活的にはあまり影響はないのだけど、「6.1
    アップグレード時
    にCSRF
    トークンのエンコード⽅式が変わる」というのは影響がある
    ローリングアップデートなどで6.0
    と6.1
    のサーバが混在しているタ
    イミングがあると、エンコード形式とデコード形式が変わって
    CSRF
    トークンのチェックがうまくいかずにエラーになる
    6.1
    にアップグレードしたあとに障害があり、6.0
    にロールバックし
    たらエンコード形式とデコード形式が変わって(ry
    6.0
    形式でエンコードされたものを6.1
    でデコードすることはできる
    ようになっている
    43

    View Slide

  44. Rails.application.config.action_controller.urlsafe_csrf_tokens =
    true
    で挙動を切り替えることができるので、いったんfalse
    の状態で6.1

    アップグレードし、折を⾒て⼀気にtrue
    にすればローリングアップデ
    ートでも⼤丈夫
    44

    View Slide

  45. cookie
    関連の設定を、ローリングアッ
    プデートでも適⽤できるようになった
    cookie
    のシリアライズの設定をmarshal
    からhybrid
    に変更するとき、
    hybrid
    はjson
    でシリアライズするので、ローリングアップデートする
    とjson
    でシリアライズしたものをmarshal
    設定でデシリアライズして
    エラーになる可能性がある
    45

    View Slide

  46. Rails5.1->5.2
    でcookies
    の暗号化形式が変わった
    Rails.application.config.action_dispatch.use_authenticated_cook
    ie_encryption
    で設定可能

    を変更してローリングアップデートしようとすると、新しい暗
    号化形式で暗号化したものをで古い暗号化形式で復号化しようと
    してエラーになる
    6.1
    では古い設定のサーバに新しい設定のリクエストがきてもよし
    なにしてくれるようになり、深く考えずにローリングアップデート
    できるようになった
    46

    View Slide

  47. ActiveRecord

    merge
    メソッドの挙動
    変更
    Rails6.1
    までのmerge
    は同じカラムがmerge
    されるときに、Hash

    ように後勝ち(merge
    の引数側が採⽤される)
    になるケースと、なら
    ないケース(
    両⽅のクエリが統合される)
    がある
    後勝ちになったりならなかったりするのは意図的なものではないの
    で後勝ちになるように修正する
    6.1
    では後勝ちにならないケースでdeprecate
    メッセージが表⽰さ
    れ、6.2
    でデフォルト後勝ちになる
    6.1
    で6.2
    の挙動(
    後勝ちにしたければrewhere
    オプションを指定する)
    47

    View Slide

  48. # Rails 6.1
    で後勝ちになるパターン(IN
    句)
    Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob]
    # Rails 6.1
    で両⽅の指定が採⽤されるパターン
    # where id between "david
    のid" and "mary
    のid" and id = bob
    のid
    Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => []
    # 6.1
    で6.2
    相当の挙動を使うにはrewhere
    を使う
    Author.where(id: david.id..mary.id).merge(Author.where(id: bob), rewhere: true) # => [bob]
    # Rails 6.2
    ではどのようなケースでも後勝ち
    Author.where(id: [david.id, mary.id]).merge(Author.where(id: bob)) # => [bob]
    Author.where(id: david.id..mary.id).merge(Author.where(id: bob)) # => [bob]
    48

    View Slide

  49. 後勝ちにしない、を明⽰的にするためのメソッドも追加ずみ
    david_and_mary = Author.where(id: [david, mary])
    mary_and_bob = Author.where(id: [mary, bob]) # => [bob]
    david_and_mary.merge(mary_and_bob) # => [mary, bob]
    david_and_mary.and(mary_and_bob) # => [mary]
    david_and_mary.or(mary_and_bob) # => [david, mary, bob]
    49

    View Slide

  50. 今⽇紹介した以外にもほかにもたくさ
    んの機能追加と修正があります!
    Rails
    6.1
    早く使いたいですね
    50

    View Slide