Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

最近の 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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

6.1 のリリースはまだだけど、この 1 年 ですでに master にマージされた機能は たくさんあるので、今⽇はそのうちの ⼀部を紹介します (6.0 の機能を知り たい⼈はパーフェクト Ruby on Rails と いう素敵な本があるのでそちらを御覧 ください ) 8

Slide 9

Slide 9 text

ビューで「どのテンプレートを呼び出 しているか」が 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

Slide 10

Slide 10 text

10

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

↓ のように書く # 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

Slide 14

Slide 14 text

ActiveRecord に signed_id メソッドと find_signed メソッドが追加 id を暗号化、かつ改ざん検知ができるトークンを付与したものを⽣ 成するsigned_id メソッドと、それを使ってfind できるfind_signed メ ソッドが追加されました トークンに有効期限を含めることもできます 14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

モデル単位で設定することもできる。 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

Slide 20

Slide 20 text

これはあえて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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

全部対象にしたい場合は次のようにする ActiveSupport::Deprecation.disallowed_warnings = :all 対象になったらどうするか、を次のように指定できる。production 以 外は例外を発⽣させて、production はログに残すだけの例。 if Rails.env.production? ActiveSupport::Deprecation.disallowed_behavior = [:log] else ActiveSupport::Deprecation.disallowed_behavior = [:raise] end 26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

条件を満たすときだけ許可することもできる 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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

この⼿法は制限付きのリンクがいい感じに⽣成されてべんりなのだけ ど、広く公開して問題ないようなファイルだと無駄が多い。 ↓ のようにするとファイルの中⾝を直接返すURL を⽣成する( 設定でデ フォルトの挙動を変更することもできる) 。 <%= image_tag rails_storage_proxy_path(@user.avatar) %> 30

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

⾮互換な変更なので対応が必要なものもある 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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Rails.application.config.action_controller.urlsafe_csrf_tokens = true で挙動を切り替えることができるので、いったんfalse の状態で6.1 に アップグレードし、折を⾒て⼀気にtrue にすればローリングアップデ ートでも⼤丈夫 44

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Rails5.1->5.2 でcookies の暗号化形式が変わった Rails.application.config.action_dispatch.use_authenticated_cook ie_encryption で設定可能 ↑ を変更してローリングアップデートしようとすると、新しい暗 号化形式で暗号化したものをで古い暗号化形式で復号化しようと してエラーになる 6.1 では古い設定のサーバに新しい設定のリクエストがきてもよし なにしてくれるようになり、深く考えずにローリングアップデート できるようになった 46

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

# 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

Slide 49

Slide 49 text

後勝ちにしない、を明⽰的にするためのメソッドも追加ずみ 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

Slide 50

Slide 50 text

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