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/

6ac7c50770603b53964d44db373e8e48?s=128

Shinichi Maeshima

August 21, 2020
Tweet

Transcript

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

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

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

  5. 5

  6. ⼀応 6.1.beta1 ⽤のブログエントリの準 備⽤ PR はある ( けどいまアクティブでは ない )

    Add post about 6.1.beta1 release by eileencodes · Pull Request #222 · rails/weblog 6
  7. basecamp の⾯々が hey.com の開発で 忙しかったという噂。今年中に出ると いいな … 。 7

  8. 6.1 のリリースはまだだけど、この 1 年 ですでに master にマージされた機能は たくさんあるので、今⽇はそのうちの ⼀部を紹介します (6.0

    の機能を知り たい⼈はパーフェクト Ruby on Rails と いう素敵な本があるのでそちらを御覧 ください ) 8
  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
  10. 10

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

    のみ対応 PR チャンスかも 11
  12. config/routes.rb の内容を別ファイルに 分割できるようになった config/routes.rb の内容を別ファイルに外だしできるようになった 昔(Rails 4.0 がリリースされる前) ⼊った内容なんだけどDHH のお気

    に召さなかったようでrevert→6 年たってDHH の気が変わった模様 これまでもゴニョゴニョすればできたけど、公式のやり⽅が提供さ れたので安⼼して使えるようになったのがよいですね 12
  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
  14. ActiveRecord に signed_id メソッドと find_signed メソッドが追加 id を暗号化、かつ改ざん検知ができるトークンを付与したものを⽣ 成するsigned_id メソッドと、それを使ってfind

    できるfind_signed メ ソッドが追加されました トークンに有効期限を含めることもできます 14
  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
  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
  17. 関連先にもstrict_loading は伝播する。 user = User.strict_loading.includes(:posts).first user.posts.first.comments #=> ActiveRecord::StrictLoadingViolationError 17

  18. 関連のオプションとして設定することもできる。↓ のようにすると、 posts 関連はincludes やpreload など経由でしか呼び出せない。 class User < ApplicationRecord

    has_many :posts, strict_loading: true end user = User.first user.posts.first #=> ActiveRecord::StrictLoadingViolationError 18
  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
  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
  21. 複数 DB の sharding 対応が⼊った Rails6.0 でプライマリ/ レプリカの複数DB に対応した Rails6.1

    ではそれを拡張して、sharding にも対応 6.0 では次のように、プライマリ/ レプリカを指定していた class ApplicationRecord < ApplicationRecord connects_to database: { writing: :primary, reading: :secondary } end 21
  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
  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
  24. Deprecation warning の代わりに例外 を発⽣させることができるようになっ た Rails がバージョンアップしたときにdeprecated になったメソッド を実⾏すると、deprecation warning

    なメッセージが表⽰される メッセージを表⽰する代わりに、例外を発⽣させることができるよ うになった 24
  25. 次のように、対象となるdeprecation warnings を指定できる warning の⽂章の⼀部を⽂字列もしくはシンボルで指定する warning の⽂章にマッチする正規表現を指定する ActiveSupport::Deprecation.disallowed_warnings = [

    "bad_method", :worse_method, /(horrible|unsafe)_method/, ] 25
  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
  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
  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
  29. Active Storage でアップロードしたファイルへ直接ア クセスできる URL を使えるようになった Active Storage でアップロードしたファイルへのアクセスは次のよう な挙動だった

    1. まずクライアントがRails サーバへリクエストを送る 2. 時間制限( デフォルト5 分) つきのファイルへのURL を⽣成してリダイ レクトする 3. ファイルをダウンロードする 29
  30. この⼿法は制限付きのリンクがいい感じに⽣成されてべんりなのだけ ど、広く公開して問題ないようなファイルだと無駄が多い。 ↓ のようにするとファイルの中⾝を直接返すURL を⽣成する( 設定でデ フォルトの挙動を変更することもできる) 。 <%= image_tag

    rails_storage_proxy_path(@user.avatar) %> 30
  31. Active Storage のサムネイル管理⽅法 の変更と改善 これまで、Active Storage でサムネイルを表⽰するときには次のよう な挙動になっていた( ストレージがS3 だと仮定)

    1. サムネイルがS3 に存在するかどうかをチェック 2. 存在しなければ元画像をダウンロードしてサムネイルを作りS3 にア ップロードする 3. サムネイルの画像⽤のURL を作成する 31
  32. この⽅式は次の問題があった 毎回S3 にサムネイルが存在するか確認しにいくので、その分レス ポンスを返すまでに時間がかかる S3 の仕様的に、存在確認後にすぐアップロードすると、しばらく の間アップロードしたのにファイルがない、ということになる たぶん存在確認の結果をしばらくキャッシュしてるんじゃない かな… これを解決するために、サムネイルが存在しているかどうかを保持

    しておくテーブルが新設された Rails.application.config.active_storage.track_variants = true と すると新しいやりかたが有効になる 32
  33. ActiveModel::Errors の改善 ActiveModel::Errors とは、 user = User.new; user.errors のようにす ると返ってくるオブジェクト

    33
  34. ActiveModel::Errors は実質的に2 つのHash オブジェクトから構成さ れている messages ex: {:email=>[" を⼊⼒してください"]} details

    ex: {:email=>[{:error=>:blank}]} Hash をゴニョゴニョするのがめんどくさいケースが有る ex: 特定のメッセージに紐づくdetail を取得したいときに、 messages のindex を取得して details[:email][index] としなけれ ばいけない 34
  35. 今回の修正により、ActiveModel::Errors は実質的に ActiveModel::Error オブジェクト( 新設) の集合となった これによりエラー内容に対しての細かい操作が、よりオブジェクト 指向っぽい書き⽅ができるようになった(Hash もオブジェクトでは あるけどね)

    関連先のエラーもいい感じに表現できている (ActiveModel::Errors のネストができる) Errors にwhere メソッドが⽣えた model.errors.where(:name, :foo, bar: 3).first message に紐づくdetail が簡単に取得できるようになった 35
  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
  37. クエリメソッド missing の追加 関連先が存在しないレコードを取得したいとき、次のようなコードを かくと思います。 Post.left_joins(:author).where(authors: { id: nil })

    これを次のように短縮して書けるようになりました Post.where.missing(:author) 37
  38. 次のように引数に複数の関連先を書くこともできる(author と comments 両⽅とも存在しないレコードが返る) Post.where.missing(:author, :comments) 38

  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
  40. enum にデフォルト値を設定できるよう になった class Book < ActiveRecord::Base enum status: [:proposed,

    :written, :published], _default: :published end Book.new.status # => "published" キーが _default なのは「既存のカラムにdefault があったときに壊れ るから」とのこと 40
  41. ここまで機能追加の話でした。時間が あったら機能変更の話をするぞ ( 多分こ こまでこれない ) 41

  42. CSRF トークンのエンコード形式の変更 CSRF トークンのエンコード⽅式がbase64 からurlsafe 版のbase64 に 変更されました base64 はa-z,A-Z,0-9,+,/

    でエンコードする⽅式 + と / はURL エンコードしないといけない→ それぞれ - と _ に変 更した Ruby にはurlsafe 版のbase64 エンコード⽤メソッドがある Base64.urlsafe_encode64 CSRF トークンをcookie としてそのまま送りたいようなケースで、 クライアント側でエンコード・デコードが必要になるのがだるいの でこうなったとのこと 42
  43. 僕らの⽣活的にはあまり影響はないのだけど、「6.1 アップグレード時 にCSRF トークンのエンコード⽅式が変わる」というのは影響がある ローリングアップデートなどで6.0 と6.1 のサーバが混在しているタ イミングがあると、エンコード形式とデコード形式が変わって CSRF トークンのチェックがうまくいかずにエラーになる

    6.1 にアップグレードしたあとに障害があり、6.0 にロールバックし たらエンコード形式とデコード形式が変わって(ry 6.0 形式でエンコードされたものを6.1 でデコードすることはできる ようになっている 43
  44. Rails.application.config.action_controller.urlsafe_csrf_tokens = true で挙動を切り替えることができるので、いったんfalse の状態で6.1 に アップグレードし、折を⾒て⼀気にtrue にすればローリングアップデ ートでも⼤丈夫 44

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

    とjson でシリアライズしたものをmarshal 設定でデシリアライズして エラーになる可能性がある 45
  46. Rails5.1->5.2 でcookies の暗号化形式が変わった Rails.application.config.action_dispatch.use_authenticated_cook ie_encryption で設定可能 ↑ を変更してローリングアップデートしようとすると、新しい暗 号化形式で暗号化したものをで古い暗号化形式で復号化しようと してエラーになる

    6.1 では古い設定のサーバに新しい設定のリクエストがきてもよし なにしてくれるようになり、深く考えずにローリングアップデート できるようになった 46
  47. ActiveRecord の merge メソッドの挙動 変更 Rails6.1 までのmerge は同じカラムがmerge されるときに、Hash の

    ように後勝ち(merge の引数側が採⽤される) になるケースと、なら ないケース( 両⽅のクエリが統合される) がある 後勝ちになったりならなかったりするのは意図的なものではないの で後勝ちになるように修正する 6.1 では後勝ちにならないケースでdeprecate メッセージが表⽰さ れ、6.2 でデフォルト後勝ちになる 6.1 で6.2 の挙動( 後勝ちにしたければrewhere オプションを指定する) 47
  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
  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
  50. 今⽇紹介した以外にもほかにもたくさ んの機能追加と修正があります! Rails 6.1 早く使いたいですね 50