週刊Railsウォッチ(20190805-1/2前編)Rails 6のActive Recordは速くなった、Windows WSL2+VSCodeでのRails開発、Martin Fowler記事ほか

こんにちは、hachi8833です。7payが9月に静かに息を引き取ることが先週決まったそうです。

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

今回は「公開つっつき会」第13回を元にしています。ご参加いただいた皆さま、ありがとうございます!

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

6.0.0リリースが迫りつつありますね。今回は主に以下から見繕いました。

6-0-stableブランチはもう立っていますね。


つっつきボイス:「Rails 6、8月になったしもうそろそろかな😋」「マイルストーンはいったんゼロになったんですが、今日見たら6つになってました(その後5つになりました)」「なるほど、rc2でissue増えましたか」「@kamipoさんが『rc2のうちに使ってみてね』と呼びかけてたのが功を奏した感じ🙏」「以下は基本的にmasterへのマージですが、ものによってはバックポートされるかなと」「致命的なissueならバックポートされるかもですね☺️」

レスポンスにVary: Acceptヘッダーを追加

/users/1のようなリクエストでは、返すべきものを決定するのにAcceptヘッダーを用いる。そのレスポンスヘッダーにVaryを追加しないと、ブラウザが誤って別の種類のコンテンツをキャッシュする可能性があり、コンテンツの代わりにJavaScriptコードが露出するかもしれない。このプルリクは、そのようなクエストにVary: Acceptを追加することで修正する。詳しくは#36213を参照。
Stan Lo
changelogより大意

# actionpack/lib/abstract_controller/rendering.rb#L17
  module Rendering
    extend ActiveSupport::Concern
    include ActionView::ViewPaths
    # Normalizes arguments, options and then delegates render_to_body and
    # sticks the result in <tt>self.response_body</tt>.
    def render(*args, &block)
      options = _normalize_render(*args, &block)
      rendered_body = render_to_body(options)
      if options[:html]
        _set_html_content_type
      else
        _set_rendered_content_type rendered_format
      end
+     _set_vary_header
      self.response_body = rendered_body
    end
# actionpack/lib/action_controller/metal/rendering.rb#L80
+     def _set_vary_header
+       self.headers["Vary"] = "Accept" if request.should_apply_vary_header?
+     end
# actionpack/lib/action_dispatch/http/mime_negotiation.rb#L150
+     def should_apply_vary_header?
+       !params_readable? && use_accept_header && valid_accept_header
+     end
...
      private
...
+       def params_readable? # :doc:
+         parameters[:format]
+       rescue *RESCUABLE_MIME_FORMAT_ERRORS
+         false
+       end

Rails 4.2.1+Ruby 2.1.7p400でAcceptヘッダーを追加すると、レスポンスにVary: Acceptヘッダーが含まれるはずが、すべてのレスポンスがuser agent(ブラウザ)側から等しいとみなされてキャッシュの問題が発生する。以下も参照。
94369 - Backing doesn’t handle Vary header correctly - chromium - Monorail
issueより大意

Goby言語の@st0012ことStan Loさんによるコミットです。


同リポジトリより


つっつきボイス:「Varyヘッダーってそういえばあったかも🤔」「こんなのがあったとは↓」

参考: RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
日本語: RFC 7231 — HTTP/1.1: Semantics and Content (日本語訳)

「解説記事探す方が早そう↓」

参考: Vary - HTTP | MDN

Vary HTTP レスポンスヘッダは、オリジンサーバから新しく要求するのではなく、キャッシュされたレスポンスを使用できるかどうかを決定するために将来のリクエストヘッダをどのように一致させるかを決定します。これは、コンテンツネゴシエーションアルゴリズムでリソースの表現を選択するときにどのヘッダを使用したかを示すためにサーバによって使用されます。
developer.mozilla.orgより

「もしかすると、AWS Cloudfrontとかでキャッシュキーに指定すると参照されるのかも?」「この記事がまさにそれっぽい↓」

参考: コンテンツキャッシュとVaryヘッダとnginx - Qiita
参考: Amazon CloudFront(グローバルなコンテンツ配信ネットワーク)| AWS


aws.amazon.comより

「スマホ向けとか?」「スマホもそうでしょうし、Accept-Languageヘッダーで言語別にキャッシュしたりとかもあるでしょうね☺️: URLは同じだけど言語が違う場合にもキャッシュを効かせたいときとか」「Varyで指定したヘッダーの組み合わせをキャッシュのキーに使うというもののようだ」

参考: Accept-Language - HTTP | MDN

「そしてプルリク#36213はというと、『/users/1のようなリクエストでは、返すべきものを決定するのにAcceptヘッダーを用いる』、あ〜たしかに!」「おぉ?」「Railsの通常のリクエストフォーマットだと、Acceptヘッダーで指定した内容に基づいて、JSONを返したりHTMLを返したりします: そしてVaryを指定しないと違うものをキャッシュしちゃうかもしれないので、このプルリクでVary: Acceptを追加したと」「これはごもっともな修正!」

「issueを見ると、Rails 4で見つかったとありますね」「全員ではないにしても、ハマってた人はおそらく以前からいたでしょうね😅」「あ〜」「主にどこで問題になるのかですけど、たぶんブラウザというよりはコンテンツプロキシの方じゃないかな〜?🤔」

後にst0012さんに尋ねたところ「主にブラウザが対象のつもりだった」とのことでした。

参考: カスタムオリジンの場合のリクエストとレスポンスの動作 - Amazon CloudFront

オリジンが応答で Vary:* を返し、対応するキャッシュ動作の [Minimum TTL] の値が [0] の場合、CloudFront はオブジェクトをキャッシュしますが、そのオブジェクトの後続のすべてのリクエストをオリジンに転送して、オブジェクトの最新バージョンが含まれていることを確認します。CloudFront には、If-None-Match や If-Modified-Since などの条件付きヘッダーは含まれません。その結果、オリジンはすべてのリクエストに応じて CloudFront にオブジェクトを返します。
オリジンが応答で Vary:* を返し、レスポンスで返される、対応するキャッシュ動作の [Minimum TTL] の値が別の値になっている場合、CloudFront は「CloudFront が削除または更新する HTTP レスポンスヘッダー」に記述されている方法で Vary ヘッダーを処理します。
docs.aws.amazon.comより

「CloudFrontはVaryヘッダー対応しているのかな?と思ったら『対応してない』という2015年のブクマコメント↓が目に止まったけど、実際どうなんだろう?🤔」

※コメントを直接埋め込めなかったので、「〜のコメント」をクリックしてください。

コンテンツキャッシュとVaryヘッダとnginx – Qiita

ちなみにCloudFrontはVaryヘッダについて感知しない。Accept-EncodingとForward Headers設定単位でキャッシュする。

2015/12/23 22:05

その後社内での記事見直しで、『CloudFrontはあくまでCDNに対して「どのヘッダをキャッシュキーにするか」を設定する必要があるので「Originサーバーが任意にキャッシュキーを設定できる」Varyヘッダの動きはしてくれないであろう』と推測されましたが、このあたりに詳しい方の情報お待ちしています🙇。

「もしかすると、ウサギさんアイコンでおなじみのVarnishあたりならVaryに対応してるかな?」「以前ウォッチで取り上げましたね🐰」「Varnishは爆速コンテンツキャッシュサーバーですけど、最近はあまり自前では建てなくなったかな〜」「ともあれVaryヘッダーはその種のコンテンツキャッシュサーバーとかnginxとかに関連するヤツですね」

参考: Vary — Varnish version 3.0.7 documentation


varnish-cache.orgより

「コミットした@st0012さんも『自分的にクリーンヒットだったと思う』と言ってました」「このissueは知らずに踏んだら正体不明になりがち😆」「原因突き止めるのめちゃ大変そう…😅 」

MatchDataを作らないMime::Type\#match?を追加

# actionpack/lib/action_dispatch/http/mime_type.rb#L286
+   def match?(mime_type)
+     return false unless mime_type
+     regexp = Regexp.new(Regexp.quote(mime_type.to_s))
+     @synonyms.any? { |synonym| synonym.to_s.match?(regexp) } || @string.match?(regexp)
+   end

つっつきボイス:「amatsudaさんのコミットなんですが、次の2つは最初のに関連しているのかなと思って」「=~をやめてmatch?メソッドに変えたのね」

# actionpack/lib/action_dispatch/http/mime_type.rb#L204
      def parse_data_with_trailing_star(type)
-       Mime::SET.select { |m| m =~ type }
+       Mime::SET.select { |m| m.match?(type) }
      end

「これは高速化かしら?」「コミットには特に説明ありませんね😅」「たしか=~はグローバル変数を書き換えるけどmatch?は書き換えない分速いとかがあったような?」「そういえば前にウォッチでも取り上げたような」

こちらでした↓。2.4で入ったんですね。

参考: Ruby 2.4 implements Regexp#match? without polluting global variables | BigBinary Blog

=~は自分が見つけた記事では2.6でdeprecatedになってる?」「おお?」「もうそこまで進んでた?」「自分、割と使ってますけど」「便利ですよね😋」「=~の順序、どっちが先かわからなくなりがちですけど😆」「順序変わると意味変わりますし☺️」「それにしても記号はググりにくいの〜😅」

参考: サンプルコードでわかる!Ruby 2.6の主な新機能と変更点 - Qiita

「おや、でも記事にnil=~は明示的に実装されたとありますし、どうやら2.6でdeprecatedになったのはObject#=~だけらしい」「あ、そういうことか!」「Object#=~はなまじnilを返すんじゃなくて、NoMethod errorを出して欲しいと」「なので=~自体はなくならない😋」「@mameさんのチケット↓を見ても、Object#=~の話はあってもRegexは直接関係なさそうだし」

参考: Feature #15231: Remove `Object#=~` - Ruby master - Ruby Issue Tracking System

frozen文字列がtransliterateされたときのエラーを防止

ActiveSupport::Inflector.transliterateはエンコードが変更されると文字列を改変する。#36702から本コミットまでの間は、frozen文字列を渡すとFrozenErrorが発生することがあった。transliterateする前にはfrozen文字列をdupするよう変更した。
同PRより大意

# activesupport/lib/active_support/inflector/transliterate.rb#L63
    def transliterate(string, replacement = "?", locale: nil)
+     string = string.dup if string.frozen?
      raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)


つっつきボイス:「修正は文字列がfreezeされてた場合に対処したと」

「ところで今のRubyはもうfrozen文字列ってデフォルトになったんだっけ?」「えっとまだだったかな?3.0で入るんだったかな?😅」「こないだRuboCopに怒られたような気が😆」

参考: [Ruby] Ruby 3.0 の特大の非互換について - まめめも — 2015年の記事です

その後BPS社内Slackで、「最終的にRuby 3ではfrozen-string-literalsをデフォルトにしないことになった」↓と指摘をもらいました。

参考: Ruby3.0とRails6.0では主にどのような変更や新機能が加わりますか?登場の際、開発、保守、運用、コミュニティ、その他言語とのポジション(特にPython2と3)はどんな変化が考えられますか? - Quora
参考: Feature #11473: Immutable String literal in Ruby 3 - Ruby master - Ruby Issue Tracking System

I consider this for years. I REALLY like the idea but I am sure introducing this could cause HUGE compatibility issue, even bigger than Ruby 1.9. So I officially abandon making frozen-string-literals default (for Ruby3).
This does not mean we are going to remove the frozen-string-literal feature that can be specified by magic comments.
Matz.
#11473より

transliterateよもやま

「しかしtransliterateってそもそも何やねん🤣」「えっと、Rubyをルビーと書くとか、改善をKaizenと書くみたいに外国語の音を写し取ることを一般にtransliterateって言いますね」「へぇ〜」「そんなメソッドがあったとは😆」

transliterate v. (他国語の文字などに)書き直す; 音訳[字訳]する.

参考: ActiveSupport::Inflector — なお、APIdockでは文字化けしてます😇。

transliterate('Ærøskøbing')
# => "AEroskobing"

「ロシア語を英語表記にするみたいなときに使いそうですね」「APIには『非ASCII文字をASCII文字で近似したりする』とありますね」「なるほど、アクセント記号付きのアルファベットをアクセント記号なしにするとか」

参考: ダイアクリティカルマーク - Wikipedia

transliterate、その気になればジャパニーズ実装もできそう😋」「yamlファイルでできるみたいですね」「関西弁オプションとか🤣」「🤣」「日本語のローマ字、大きく分けただけでもヘボン式とか文科省がやってるヤツとかいろいろバラついてて難しそう😅」「transliterate、日本人にはピンとこないけどヨーロッパの人はとても欲しいでしょうね」「使うシチュエーションがあまり思いつきませんしっ😆」

参考: ヘボン式ローマ字 - Wikipedia
参考: 日本式および訓令式 - Wikipedia

Azureにもファイル名とdispositionをアップロード

# activestorage/lib/active_storage/service/azure_storage_service.rb#L20
-   def upload(key, io, checksum: nil, content_type: nil, **)
+   def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
      instrument :upload, key: key, checksum: checksum do
        handle_errors do
-         blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type)
+         content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition && filename
+
+         blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type, content_disposition: content_disposition)
        end
      end
    end


つっつきボイス:「dispositionはContent-Dispositionヘッダで合ってます?」「合ってると思います: IEではこれを付けないと…😇というヤツ」「それかっ!」「PR自体はAzureのStorageにアップロードするコードにmeta情報を付けられるようにしたということみたい」

参考: BK通信 - ブラウザのバッドノウハウ コンテンツ編

「IEはContent-Dispositionでファイル名を渡すんですが、FirefoxやChromeは普通にfilenameで渡せばUTF-8文字列でもちゃんと食べてくれる😋: Content-Dispositionが本来何をするものなのかよくわかってませんが😆」「IE対応という印象しかないというか、IEのファイルダウンロードはこれを設定しないとだいたいおかしくなる😢」

参考: Content-Disposition - HTTP | MDN

通常の HTTP レスポンスにおける Content-Disposition レスポンスヘッダーは、コンテンツがブラウザでインラインで表示されることを求められているか、つまり、Webページとして表示するか、Webページの一部として表示するか、ダウンロードしてローカルに保存する添付ファイルとするかを示します。
developer.mozilla.orgより

Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

「今更MDNを見てみると↑、コンテンツをインラインで表示するか添付ファイルとしてダウンロードするかを指定するものだったのね😆」「PDFでinlineを指定するとブラウザの中でPDFが開いたり、attachmentを指定するとファイルとして保存したり、ということをやるためと」「そしてfilename=というオプションが使えると: IEはこっちを優先しちゃってたりするのかもな〜」

「これも関連してそうですね↓」「こっちはActive Storageのストレージごとのオプションを実装したと」「こんな実装なのか↓: 引数最後にある**が、以後の引数を全部捨てにかかってる感😆」「互換性維持とかのために、知らないオプションがやってきてもよしなに握りつぶす感😆」「もしかすると**でやれば仮引数代入が発生しない分ちょっぴり速くなったりするかな〜、なんて😆」「どちらかというと、指定にない引数を付け呼び出してもArgumentErrorが出ないで処理してくれる方が重要だったりするかも🤔」

# activestorage/lib/active_storage/service/s3_service.rb#L23
-   def upload(key, io, checksum: nil, content_type: nil, **)
+   def upload(key, io, checksum: nil, filename: nil, content_type: nil, disposition: nil, **)
      instrument :upload, key: key, checksum: checksum do
+       content_disposition = content_disposition_with(filename: filename, type: disposition) if disposition && filename
+
        if io.size < multipart_upload_threshold
-         upload_with_single_part key, io, checksum: checksum, content_type: content_type
+         upload_with_single_part key, io, checksum: checksum, content_type: content_type, content_disposition: content_disposition
        else
-         upload_with_multipart key, io, content_type: content_type
+         upload_with_multipart key, io, content_type: content_type, content_disposition: content_disposition
        end
      end
    end

「そういう書き方ってしてなかったので参考になる〜😋」「他人が書いたのを見ないと知らないままになりそう」

Issue: Sidekiqがclassicオートローダーでコケる


つっつきボイス:「6.0.0のマイルストーンから1つだけissueを拾ってみました」「Zeitwerkからクラシックオートローダーに切り替えると、よりによってAction Textでデッドロックする!」「よく見つけたな〜これ😳」

「ぱっと見、Action Textだからビューに関連してるかと思ったけど、Sidekiqジョブを投入する側のオートローダーとジョブを処理するワーカー側のオートローダーが異なるとおかしくなるらしい」「スレを見ると、productionでは起きずdevelopmentで起きるとか、Sidekiqプロセスのコンカレンシーを1にすると回避できるとありますね」

Sidekiqログ: https://gist.githubusercontent.com/langsharpe//Sidekiq.log

「これがSidekiqのログ↑」「冒頭に絵が入ってくるんですね😆」

$ bundle exec sidekiq


         m,
         `$b
    .ss,  $$:         .,d$
    `$$P,d$P'    .,md$P"'
     ,$$$$$bmmd$$$P^'
   .d$$$$$$$$$$P'
   $$^' `"^$$$'       ____  _     _      _    _
   $:     ,$$:       / ___|(_) __| | ___| | _(_) __ _
   `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
          $$:         ___) | | (_| |  __/   <| | (_| |
          $$         |____/|_|\__,_|\___|_|\_\_|\__, |
        .d$$                                       |_|

2019-07-26T06:55:53.814Z 17273 TID-oxdgbihcp INFO: Running in ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18]
2019-07-26T06:55:53.814Z 17273 TID-oxdgbihcp INFO: See LICENSE and the LGPL-3.0 for licensing details.
2019-07-26T06:55:53.815Z 17273 TID-oxdgbihcp INFO: Upgrade to Sidekiq Pro for more features and support: http://sidekiq.org
2019-07-26T06:55:53.815Z 17273 TID-oxdgbihcp INFO: Booting Sidekiq 5.2.7 with redis options {:id=>"Sidekiq-server-PID-17273", :url=>nil}
2019-07-26T06:55:53.872Z 17273 TID-oxdgbihcp INFO: Starting processing, hit Ctrl-C to stop
...

「再現手順をチラ見した感じでは割と特殊なことやってるっぽいけど😆」「Zeitwerkの状態でジョブを実行してコンプリートしてから、クラッシックオートローダーに切り替えると、起きると」「逆はやってないけどやったらどうなるんだろう😆」「よくぞ見つけた👍」「6にアップグレードするときはSidekiqあたりに注意が必要かも」

amatsudaさんがアロケーションなどを削減

ほとんどが細かなリファクタリングです。

# activesupport/lib/active_support/inflector/methods.rb#L378
-       parts.reverse.inject(last) do |acc, part|
+       parts.reverse!.inject(last) do |acc, part|
          part.empty? ? acc : "#{part}(::#{acc})?"
        end

つっつきボイス:「@amatsudaさんがアロケーションを削減したりした細かなコミットがたくさんあって、それぞれやってることが少しずつ違ってて参考になりそうだったので」「ひたすら黙々と」「こういう感じで書くことでメモリコピーが減るので、やって大丈夫であれば有効でしょうね」「たくさん並べましたが、今つっつきで全部開けると時間かかりそうなので適当なところまででいいです😅」

「こういうふうに*で置き換えたりも↓」「引数を単独の*にするのって意味的にはどういうものでしたっけ?」「メソッドシグネチャとしては受けられるけど中では使わない、ということでしょうね」「さっきの**もそうですし」「渡してもinvalid argumentとかにはならないけど、使われない」

# actionpack/lib/abstract_controller/callbacks.rb#L40
-   def process_action(*args)
+   def process_action(*)
      run_callbacks(:process_action) do
        super
      end
    end

Rails


つっつきボイス:「上のツイートは本日(公開つっつき会の日)のついさっき見つけました」「自分もさっき社に戻る途中で見た😋」「@kamipoさん半端ないわやっぱ!」「Rails 6だいぶ速くなったみたいですし」「オブジェクトアロケーション系の操作って遅くなりやすいから、そういうところなんかも速くなるのはうれしいですね😂」「このベンチ結果を眺めながらrc2で遊ぶとはかどりそう❤️」


rubybench.orgより

ポリモーフィック関連付けでのeager loading

2017年の記事です。


つっつきボイス:「コードが9割を占めている感じの記事でした」

流れ:

  • ポリモーフィック関連付けのeager loadingでN+1を避けるのは面倒
  • (普通のサンプル)
  • 動かすとN+1が発生
  • preloadで即解決
  • (ポリモーフィック関連付けのサンプル)
  • ちょい面倒
  • ActiveRecord::Association::Preloaderを書いて解決
  • ActiveRecord::Association::Preloaderpreloadと組み合わせて解決

「この辺、最近のRailsだともうちょっとよくなってたような覚えがうっすらと」「N+1が起きなくなるんでしょうか?」「いや、起きるんだけど効率がもう少しよくなってた気がする: あれはどこでやってたかな〜?🤔」

「普通にやってN+1が起きたらpreloadでIN展開できる」

SELECT "films".* FROM "films"
SELECT "directors".* FROM "directors" WHERE "directors"."id" IN (...)

「ポリモーフィック関連付けではPreloaderを手作りしてる」「え、こういう書き方してるのか〜😳」「でもちゃんとINに展開されてますね」

class Like::Preloader
  def self.preload(likes)
    preloader = ActiveRecord::Associations::Preloader.new
    preloader.preload(likes.select { |like| like.resource_type.eql?(Book.name) }, { resource: :author })
    preloader.preload(likes.select { |like| like.resource_type.eql?(Film.name) }, { resource: :director })
  end
end
SELECT "likes".* FROM "likes"
SELECT "books".* FROM "books" WHERE "books"."id" IN (...)
SELECT "authors".* FROM "authors" WHERE "authors"."id" IN (...)
SELECT "films".* FROM "films" WHERE "films"."id" IN (...)
SELECT "directors".* FROM "directors" WHERE "directors"."id" IN (...)

「たぶんですけど、ポリモーフィック関連付けだけの問題じゃないんでしょうね: Bookだとauthor.nameを取ってFilmだとdirector.nameを取るとかしないといけなさそうですし、ポリモーフィック関連付けをポリモーフィックなまま1つのpreloadでやれるようにするということのようだ🤔」「ポリモーフィックでのN+1、自分も以前踏んだことあったような😅」

profiles = Profile.preload(:likes)
Profile::Preloader.preload(profiles)
profiles.each do |like|
  puts profile.name
  profile.likes.each do |like|
    puts like.resource.name
    puts case like.resource
    when Book like.resource.author.name
    when Film like.resource.director.name
    end
  end
end

「まあここまでやるか?という気もちょっとしますけど😆」「諦めてクエリを複数に分けるとかする手も」

高品質ソフトウェアのためにコストをかける価値はあるか

Martin Fowlerさんの記事です。


つっつきボイス:「今回はMartin Fowlerさんが自分で書いてますね☺️」「お、この図どっかで見たことあるし↓: 図だけで思い出すと、コードをきちんと書いてリファクタリングもしておくと、全体のコード量はあまり進まなくても将来新機能をラクに追加できるようになるので、時間をかけてでもちゃんと書く方が長い目で見て結果的に時間を節約できる、みたいな説明だったかな」「たしかに〜」「深みがある」


同記事より

「cruit…?」「英辞郎によると、cruitは新兵とか新人という意味で、recruitを略したものらしいけど、ここでは作るのに時間のかかる新機能という意味で使われてるっぽい」

参考: cruitの意味・使い方|英辞郎 on the WEB:アルク

「この図↓とかまさにそれで、この変曲点を超えるかどうかという話」「品質の低いコードを急いで書いても、時間をかけて品質の高いコードを書く方が将来機能が増えたときの品質も上がるし開発速度も上がるよと、その変わり目となる変曲点ですね😋」


同記事より

「もうひとつ、手掛けているサービスがこの分岐点を超えるほど生きながらえるのかという問題とのバランスですよね🤣」「🤣」「🤣」「まあでも実際そうで、製品がリリースされるまでに開発費用が底をついたらおしまいですし💸、どんなに目も当てられないようなコードであってもまずはリリースできなければ価値ゼロですし」「このさじ加減がなかなか難しい😅」「冒頭の、9月に終わるサービスみたいな😆」「特にベンチャーがVCから資金を受けて開発しているような場合はタイムリミットを越えたら投資を引き上げられるので、そういう条件下だと別の力学が働き始めるといいますか😆」「そういう場合でなければこの図のように考えたいですね☺️」

Railsマイグレーションのちょっとしたコツ(Hacklinesより)

短い記事です。


つっつきボイス:「schema_migrationsテーブルを操作するためのつらい方法とラクな方法」「delete from schema_migrationsとか普通やらなくない?🤣」「db:rollbackでやりましょう🤣」

# 同記事より
delete from schema_migrations where version in (20190726133807, 20190726191446);

「ラクな方はdb:rollbackでやると記事にあるけど先に言われちゃいましたね😆」

rake db:rollback

「うっかりブランチ切り替えたときなんかに、殺したくないテーブルがあるからそれを避けて手動で消すとかありそうですけど😆」「ありがちなのは、マイグレーションコードの中にエラーを仕込んで途中で止まっちゃったときとか、add columnsされたものを手動でちまちま消さないと次のマイグレーションができなくてイラつくとか😆」

「そうそう、db:migrate:downでバージョン指定できる↓…ってこれRailsガイドに書いてあるんじゃ?😆」「ありますね〜😆」「tipsでもtrickでもなく、仕様でした😆」「hard wayの方は…」「hard wayはつらいヨ、とだけ言っておきます😭」

rake db:migrate:down VERSION=20190726133807

参考: 4.4 特定のマイグレーションのみを実行する Active Record マイグレーション - Rails ガイド

WSL2とVScodeでのRails開発はイイゾ(Hacklinesより)


つっつきボイス:「お、WSL2」「中身まだ読んでませんが、喜びが伝わってくる感じです」「今の自分は、WindowsのDockerコンテナでRailsを動かして、RubyMineで書いたコードをそこにコピーして普通にやれてます☺️」「そういえば今日Docker for Windowsの新しいstable版も出てDockerのバージョンも上がったし、Docker DesktopのDockerバージョンも19.03になったので、やった幸せになれるという感じ❤️」「先週話題にした19.03ですね(ウォッチ20190731)」「docker-composeのバージョンが上がったのが特にありがたい🙏」

参考: Docker Desktop for Mac and Windows | Docker

「もうWindowsでもDocker動かせばRails開発できますヨ😋」「最近のVSCodeにはRubyMineみたいなオートデプロイメント機能があったと思いますし」

「ちなみにWSL2を使ってる人はこの中にいらっしゃいます?」「お、まだ少ない」「使ってみたいとは思ってるんですけど😅」「Windows 10のHomを使ってる人ならWSL2欲しいですよね😋: ProならHyper-V使えますけど」

参考: Windows 10 Home と Pro はどう違うの?比較表などで解説するよっ! | Tanweb.net


前編は以上です。

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

週刊Railsウォッチ(20190730-2/2後編)Docker 19.03の新機能に注目、ngrokはスゴい、redis-namespaceほか

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

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

Rails公式ニュース

Hacklines

Hacklines

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ