<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by TAGAWA Takao on Medium]]></title>
        <description><![CDATA[Stories by TAGAWA Takao on Medium]]></description>
        <link>https://medium.com/@dounokouno?source=rss-c496081679c4------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*mCChbbBhpzf2dbBkJhpLkg.png</url>
            <title>Stories by TAGAWA Takao on Medium</title>
            <link>https://medium.com/@dounokouno?source=rss-c496081679c4------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 09:15:34 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@dounokouno/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Ruby on Rails のフラグメントキャッシュのキャッシュキーはどのように決まるか]]></title>
            <link>https://medium.com/traveloco-tech/ruby-on-rails-fragment-caching-cache-key-9697c4c6a831?source=rss-c496081679c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/9697c4c6a831</guid>
            <category><![CDATA[フラグメントキャッシュ]]></category>
            <category><![CDATA[ruby-on-rails]]></category>
            <category><![CDATA[fragment-caching]]></category>
            <dc:creator><![CDATA[TAGAWA Takao]]></dc:creator>
            <pubDate>Thu, 29 Aug 2019 05:10:21 GMT</pubDate>
            <atom:updated>2020-02-04T12:40:46.034Z</atom:updated>
            <content:encoded><![CDATA[<p>※2020年1月28日に「トラベロコ」は「<a href="https://locotabi.jp/">ロコタビ</a>」にサービス名称を変更しました。詳しい経緯などは<a href="https://note.com/yshiiya/n/nece4b36333a1">代表の note 記事</a>をご覧ください。</p><p>こんにちは。<a href="https://locotabi.jp/">ロコタビ</a>（旧トラベロコ）の開発チーム所属の多川です。<a href="https://medium.com/traveloco-tech/%E3%81%8A%E3%81%A3%E3%81%95%E3%82%93%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E6%95%99%E3%81%88%E3%82%8B-%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%AA%AD%E3%82%80%E3%81%9F%E3%82%81%E3%81%AE%EF%BC%94%E3%81%A4%E3%81%AE%E6%96%B9%E6%B3%95-2c6a2dd28bda">先々週から突然</a>、<strong>株式会社トラベロコのエンジニアブログ</strong>が始まりました。</p><p><a href="https://traveloco.co.jp/">株式会社トラベロコ</a>は、<a href="https://locotabi.jp/">ロコタビ</a>（旧トラベロコ）という<strong>海外在住日本人と日本在住日本人をつなぐスキルシェアリングサービス</strong>を運営している会社で、サービス自体であったり、<strong>創業時からフルリモートワーク</strong>だったり、<a href="https://www.wantedly.com/companies/traveloco/post_articles/182660">世界中にスタッフがいたり</a>（例えば、日本から遠いところではポーランドやコロンビア、アルゼンチンなど）、といった働き方などがピックアップされがちなのですが、サービスの開発に関わるエンジニアたちの日々の苦労（？）をブログ記事にしていくと面白いのでは、ということで、エンジニアブログが始まりました。</p><p><a href="https://www.wantedly.com/companies/traveloco/post_articles/182660">【勤務地が自由な理由】最大時差14時間、世界点在リモートワーク集団の実態-Part2- | トラベロコのコト</a></p><p>もちろん、エンジニアも全員、フルリモートワークです。</p><p><a href="https://medium.com/traveloco/how-i-work-in-traveloco-b56db3943fb3">2017年9月28日に私が書いたブログ記事</a>の時点では日本人のエンジニアは私を含めて3人だけでしたが、本エントリーの執筆時点では、インターンも含めると9人になりました。</p><p>年齢も、これまでのキャリアも、住んでいる国や地域もバラバラなトラベロコのエンジニアたちの、日々の開発のあれこれについて、共感してもらえたら嬉しく思います。</p><p>さて、今回の私の記事は <strong>Ruby on Rails のフラグメントキャッシュ</strong>についてです。以前、サービス内にフラグメントキャッシュを導入した際にめちゃくちゃいろいろ調べたのに、数ヶ月後に改めてフラグメントキャッシュを使おうと思ったら調べたことをすっかり忘れてしまっていたので、未来の私に向けて、調べたことをまとめておきたいと思います 😁</p><h3>Ruby on Rails のフラグメントキャッシュとは</h3><p>Ruby on Rails には標準でキャッシュ機構が組み込まれており、いくつかのレイヤーでキャッシュを利用できるのですが、 view 部分のキャッシュのことを「<strong>フラグメントキャッシュ</strong>」（ Fragment Caching ）と呼びます。</p><p>フラグメントキャッシュの利用方法は、 view ファイルの中でキャッシュしたい内容を cacheや cache_ifや cache_unlessなどのヘルパーメソッドのブロックで囲むことで、囲んだブロック内が初回アクセス時にキャッシュとして保存され、2回目以降はキャッシュが利用されます。</p><p>基本的な使い方はオフィシャルの日本語ドキュメントが詳しいです。</p><p><a href="https://railsguides.jp/caching_with_rails.html">Rails のキャッシュ機構 - Rails ガイド</a></p><p>このブログ記事では、 cacheメソッドの第一引数（ cache_ifメソッドや cache_unlessメソッドだと第二引数）にどのような値を渡したときに、どのようなキャッシュキーになるか、を詳しく見ていきたいと思います。</p><h3>ベースとなる Rails アプリケーション</h3><p>Area に複数の User が所属している、という検証用の Rails アプリケーションを新しく作成しました。 Rails アプリケーション作成時に、検証がしづらいそうだったので、 turbolinks は無効化しました。</p><p>検証に利用した Ruby および Rails のバージョンは下記の通りです。</p><ul><li>Ruby: 2.6.3</li><li>Ruby on Rails: 5.2.3</li></ul><h3>cache メソッドの第一引数を省略した場合</h3><p>まずは cacheメソッドの第一引数を省略した場合を見ていきたいと思います。</p><p>Area 一覧の view （ index.html.erb ）は下記のように用意しました。</p><pre>&lt;h1&gt;Area 一覧&lt;/h1&gt;<br>&lt;% cache do %&gt;<br>&lt;ul&gt;<br>  &lt;% @areas.each do |area| %&gt;<br>  &lt;li&gt;&lt;%= link_to area.name, area %&gt;&lt;/li&gt;<br>  &lt;% end %&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;</pre><p>この状態で rails server を立ち上げて、ブラウザでアクセスすると、下記のようにログが出力されます。</p><pre>Started GET &quot;/areas&quot; for ::1 at 2019–08–19 11:11:40 +0900<br>Processing by AreasController#index as HTML<br>  Rendering areas/index.html.erb within layouts/application<br>Read fragment views/areas/index:aa2673bd2355d2048fb1537c03182bd1/localhost:3000/areas (0.1ms)<br>  Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:4<br>Write fragment views/areas/index:aa2673bd2355d2048fb1537c03182bd1/localhost:3000/areas (0.2ms)<br>  Rendered areas/index.html.erb within layouts/application (10.3ms)<br>Completed 200 OK in 35ms (Views: 26.1ms | ActiveRecord: 0.8ms)</pre><p>Read fragmentの部分でキャッシュを探し、見つからなかったため、 Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;を実行しています。そのあと、 Write fragmentでキャッシュへの書き込みを行なっています。</p><p>次に2回目のアクセスのログを見ていきます。</p><pre>Started GET &quot;/areas&quot; for ::1 at 2019–08–19 11:11:42 +0900<br>Processing by AreasController#index as HTML<br>  Rendering areas/index.html.erb within layouts/application<br>Read fragment views/areas/index:aa2673bd2355d2048fb1537c03182bd1/localhost:3000/areas (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (2.1ms)<br>Completed 200 OK in 19ms (Views: 18.4ms | ActiveRecord: 0.0ms)</pre><p>Read fragmentでキャッシュが見つかったため、 SQL の発行は行わず、キャッシュを利用してレンダリングを行なっています。そのあと、キャッシュへの書き込みも行なっていません。</p><p>次に、 view ファイルを編集して保存したあとにブラウザで再度、アクセスした場合のログを見ていきます。</p><pre>Started GET &quot;/areas&quot; for ::1 at 2019–08–19 11:12:11 +0900<br>Processing by AreasController#index as HTML<br>  Rendering areas/index.html.erb within layouts/application<br>Read fragment views/areas/index:766cd21b9c5766e8256975f6a78509fb/localhost:3000/areas (0.1ms)<br>  Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:4<br>Write fragment views/areas/index:766cd21b9c5766e8256975f6a78509fb/localhost:3000/areas (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (3.9ms)<br>Completed 200 OK in 22ms (Views: 19.6ms | ActiveRecord: 0.2ms)</pre><p>view ファイルが更新されたため、 views/areas/index:のあとの32文字の英数字部分が変わり、古いキャッシュは利用されませんでした。</p><p>ここまでは Area 一覧ページの動作を見てきましたが、次に Area 詳細ページの動作を見ていきます。</p><p>Area 詳細の view （ show.html.erb ）を下記のように用意しました。</p><pre>&lt;h1&gt;Area 詳細&lt;/h1&gt;<br>&lt;% cache do %&gt;<br>&lt;ul&gt;<br>  &lt;li&gt;エリア名: &lt;%= @area.name %&gt;&lt;/li&gt;<br>  &lt;li&gt;作成日時: &lt;%= @area.created_at %&gt;&lt;/li&gt;<br>  &lt;li&gt;更新日時: &lt;%= @area.updated_at %&gt;&lt;/li&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;</pre><p>初回アクセスのログは下記の通りでした。</p><pre>Started GET &quot;/areas/1&quot; for ::1 at 2019–08–19 11:13:13 +0900<br>Processing by AreasController#show as HTML<br>  Parameters: {&quot;id&quot;=&gt;&quot;1&quot;}<br>  Area Load (0.3ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot; WHERE &quot;areas&quot;.&quot;id&quot; = ? LIMIT ? [[&quot;id&quot;, 1], [&quot;LIMIT&quot;, 1]]<br>  ↳ app/controllers/areas_controller.rb:7<br>  Rendering areas/show.html.erb within layouts/application<br>Read fragment views/areas/show:848ca6833bea261b8f5af75b912ee11a/localhost:3000/areas/1 (0.1ms)<br>Write fragment views/areas/show:848ca6833bea261b8f5af75b912ee11a/localhost:3000/areas/1 (0.1ms)<br>  Rendered areas/show.html.erb within layouts/application (2.9ms)<br>Completed 200 OK in 35ms (Views: 22.4ms | ActiveRecord: 0.3ms)</pre><p>見ていただきたい箇所は Read fragmentの行で、 Area 一覧では localhost:3000/areasとなっていた箇所が localhost:3000/areas/1となっています。</p><p>ここまででわかることとしては、 cacheメソッドの第一引数を省略した場合は、キャッシュキーは {view ファイルのパス}:{32文字の英数字}/{プロトコルを除いた URL}となっていました。 {32文字の英数字}は view ファイルの更新日時から一意の文字列を生成しているようです。</p><p>一見すると第一引数を省略しても問題ないように見えますが、第一引数を省略した場合、</p><ul><li>DB に新しいレコードを挿入してもキャッシュは更新されない</li><li>DB の既存のレコードを更新しても（ updated_at が更新されても）キャッシュは更新されない</li></ul><p>といった問題が発生することが考えられます。</p><p>通常、 areas テーブルの内容が変わったら変更された内容を表示して欲しいので、この方法はあまり良くなさそうです。（ URL をキーとして、 DB の更新は考えず一定期間キャッシュしたい、といった用途には使えそうです。）</p><h3>cache メソッドの第一引数に文字列を指定した場合</h3><p>次は cacheメソッドの第一引数に文字列を指定した場合を見ていきます。</p><p>view （ index.html.erb ）の cacheメソッドの第一引数に areasという文字列を指定してみます。</p><pre>&lt;h1&gt;エリア一覧&lt;/h1&gt;<br>&lt;% cache &#39;areas&#39; do %&gt;<br>&lt;ul&gt;<br>  &lt;% @areas.each do |area| %&gt;<br>  &lt;li&gt;&lt;%= link_to area.name, area %&gt;&lt;/li&gt;<br>  &lt;% end %&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;</pre><p>初回アクセス時のログは下記の通りです。</p><pre># （一部省略）<br>Read fragment views/areas/index:07c72219a48d9da13a54ec7693c6f7f7/areas (0.1ms)<br>  Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:4<br>Write fragment views/areas/index:07c72219a48d9da13a54ec7693c6f7f7/areas (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (15.5ms)</pre><p>2回目のアクセス時のログは下記の通りです。</p><pre># （一部省略）<br>Read fragment views/areas/index:07c72219a48d9da13a54ec7693c6f7f7/areas (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (1.7ms)</pre><p>見ていただきたいのは Read fragmentの行で、 cacheメソッドの第一引数を省略した場合のときと {view ファイルのパス}:{32文字の英数字}/までは同じで、そのあとが引数に指定した文字列になりました。</p><p>cacheメソッドの第一引数に文字列を指定した場合、 cacheメソッドの引数を省略した場合と同様、</p><ul><li>DB に新しいレコードを挿入してもキャッシュは更新されない</li><li>DB の既存のレコードを更新しても（ updated_at が更新されても）キャッシュは更新されない</li></ul><p>といった問題が発生することが考えられます。</p><h3>cache メソッドの引数にモデルのインスタンスを指定した場合</h3><p>次は cacheメソッドの第一引数にモデルのインスタンス、例えば Area.allで取得した Area 一覧を指定した場合を見ていきます。</p><p>AreasController は下記のように記述しました。</p><pre>def index<br>  @areas = Area.all<br>end</pre><p>view （ index.html.erb ）は下記の通りで、 cacheメソッドの第一引数を @areasとしています。</p><pre>&lt;h1&gt;Area 一覧&lt;/h1&gt;<br>&lt;% cache @areas do %&gt;<br>&lt;ul&gt;<br>  &lt;% @areas.each do |area| %&gt;<br>  &lt;li&gt;&lt;%= link_to area.name, area %&gt;&lt;/li&gt;<br>  &lt;% end %&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;</pre><p>この場合の初回アクセスのログは下記の通りです。</p><pre>Started GET &quot;/areas&quot; for ::1 at 2019–08–19 13:55:05 +0900<br>Processing by AreasController#index as HTML<br>  Rendering areas/index.html.erb within layouts/application<br>   (0.5ms) SELECT COUNT(*) AS &quot;size&quot;, MAX(&quot;areas&quot;.&quot;updated_at&quot;) AS timestamp FROM &quot;areas&quot;<br> ↳ app/views/areas/index.html.erb:2<br> Area Load (0.1ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br> ↳ app/views/areas/index.html.erb:2<br>Read fragment views/areas/index:38a39dc611fce5d0106a20bbaa82693f/areas/query-278ec56659165b7d60ec0dbd85df284f-3–20190819022935879662 (26.0ms)<br>Write fragment views/areas/index:38a39dc611fce5d0106a20bbaa82693f/areas/query-278ec56659165b7d60ec0dbd85df284f-3–20190819022935879662 (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (32.6ms)</pre><p>上記のログでは Area は3つでした。</p><p>次に Area を一つ追加して、再度、アクセスしたログは下記の通りです。</p><pre>Started GET &quot;/areas&quot; for ::1 at 2019–08–19 13:55:31 +0900<br>Processing by AreasController#index as HTML<br>  Rendering areas/index.html.erb within layouts/application<br>   (0.2ms) SELECT COUNT(*) AS &quot;size&quot;, MAX(&quot;areas&quot;.&quot;updated_at&quot;) AS timestamp FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:2<br>  Area Load (0.1ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:2<br>Read fragment views/areas/index:38a39dc611fce5d0106a20bbaa82693f/areas/query-278ec56659165b7d60ec0dbd85df284f-4–20190819045527821007 (1.5ms)<br>Write fragment views/areas/index:38a39dc611fce5d0106a20bbaa82693f/areas/query-278ec56659165b7d60ec0dbd85df284f-4–20190819045527821007 (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (3.0ms)</pre><p>次に Area の一つを更新して（ Area.first.touch ）、再度、アクセスしたログは下記の通りです。</p><pre>Started GET &quot;/areas&quot; for ::1 at 2019–08–19 16:57:53 +0900<br>Processing by AreasController#index as HTML<br>  Rendering areas/index.html.erb within layouts/application<br>   (0.2ms) SELECT COUNT(*) AS &quot;size&quot;, MAX(&quot;areas&quot;.&quot;updated_at&quot;) AS timestamp FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:3<br>  Area Load (0.1ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:3<br>Read fragment views/areas/index:66b2f6d256139676c6fc592565a02677/areas/query-278ec56659165b7d60ec0dbd85df284f-4–20190819050857241886 (1.2ms)<br>Write fragment views/areas/index:66b2f6d256139676c6fc592565a02677/areas/query-278ec56659165b7d60ec0dbd85df284f-4–20190819050857241886 (0.2ms)<br>  Rendered areas/index.html.erb within layouts/application (4.1ms)</pre><p>見ていただきたいのはやはり Read fragmentの行で、それぞれのログを見比べると、 views/areas/index:{32文字の英数字}/areas/query-{32文字の英数字}- までは同じで、ログの SQL の通りですが、 areas テーブルの countと MAX(&quot;areas&quot;.&quot;updated_at&quot;)をタイムスタンプとしてキャッシュキーに使用しています。ちなみに、 query-のあとの32文字の英数字は SELECT &quot;areas&quot;.* FROM &quot;areas&quot;の SQL 分から一意の文字列を生成しているようです。</p><p>これまでに見てきたように、「<strong> </strong><strong>cacheメソッドの第一引数を省略した場合</strong>」や「<strong> </strong><strong>cacheメソッドの第一引数に文字列を指定した場合</strong>」とは違い、 countや updated_atをキャッシュキーの一部に使用することで、「<strong>古いキャッシュを読みにいってしまいページが更新されない</strong>」といったことを防いでいます。</p><p>同様に Area 詳細ページの場合を見ていきます。</p><p>view （ show.htmlerb ）は下記の通りで、 cacheメソッドの第一引数を @areaとしています。</p><pre>&lt;h1&gt;Area 詳細&lt;/h1&gt;<br>&lt;% cache @area do %&gt;<br>&lt;ul&gt;<br>  &lt;li&gt;エリア名: &lt;%= @area.name %&gt;&lt;/li&gt;<br>  &lt;li&gt;created_at: &lt;%= @area.created_at %&gt;&lt;/li&gt;<br>  &lt;li&gt;updated_at: &lt;%= @area.updated_at %&gt;&lt;/li&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;</pre><p>初回アクセス時のログは下記の通りです。</p><pre>Started GET &quot;/areas/1&quot; for ::1 at 2019–08–19 16:51:19 +0900<br>Processing by AreasController#show as HTML<br>  Parameters: {&quot;id&quot;=&gt;&quot;1&quot;}<br>  Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot; WHERE &quot;areas&quot;.&quot;id&quot; = ? LIMIT ? [[&quot;id&quot;, 1], [&quot;LIMIT&quot;, 1]]<br>  ↳ app/controllers/areas_controller.rb:7<br>  Rendering areas/show.html.erb within layouts/application<br>Read fragment views/areas/show:4bcb5ad040193c4a62aea5d187cd18c2/areas/1–20190819050857241886 (0.5ms)<br>Write fragment views/areas/show:4bcb5ad040193c4a62aea5d187cd18c2/areas/1–20190819050857241886 (0.1ms)<br>  Rendered areas/show.html.erb within layouts/application (2.9ms)</pre><p>/areas/2への初回アクセスのログは下記の通りです。</p><pre>Started GET &quot;/areas/2&quot; for ::1 at 2019–08–19 16:51:48 +0900<br>Processing by AreasController#show as HTML<br>  Parameters: {&quot;id&quot;=&gt;&quot;2&quot;}<br>  Area Load (0.1ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot; WHERE &quot;areas&quot;.&quot;id&quot; = ? LIMIT ? [[&quot;id&quot;, 2], [&quot;LIMIT&quot;, 1]]<br>  ↳ app/controllers/areas_controller.rb:7<br>  Rendering areas/show.html.erb within layouts/application<br>Read fragment views/areas/show:4bcb5ad040193c4a62aea5d187cd18c2/areas/2–20190819022935876855 (0.2ms)<br>Write fragment views/areas/show:4bcb5ad040193c4a62aea5d187cd18c2/areas/2–20190819022935876855 (0.1ms)<br>  Rendered areas/show.html.erb within layouts/application (1.9ms)</pre><p>今度は少し変わって、 query-{32文字の英数字}や countは利用されず、 idと updated_atのタイムスタンプが利用されています。</p><p>Area 詳細ページも Area 一覧ページと同様に idと updated_atをキャッシュキーの一部に使用することで、「<strong>古いキャッシュを読みにいってしまいページが更新されない</strong>」といったことを防いでいます。</p><h3>cache メソッドの第一引数に中身は文字列の配列を渡した場合</h3><p>次は cacheメソッドの第一引数に中身は文字列の配列を指定した場合を見ていきます。</p><p>view （ index.html.erb ）の cacheメソッドの第一引数に %w(foo bar)という配列を指定してみます。</p><pre>&lt;h1&gt;Area 一覧&lt;/h1&gt;<br>&lt;% cache %w(foo bar) do %&gt;<br>&lt;ul&gt;<br>  &lt;% @areas.each do |area| %&gt;<br>  &lt;li&gt;&lt;%= link_to area.name, area %&gt;&lt;/li&gt;<br>  &lt;% end %&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;</pre><p>初回アクセス時のログは下記の通りです。</p><pre># （一部省略）<br>Read fragment views/areas/index:6ad6a9c54f98aacd1ed6e47c151ec8d2/foo/bar (0.1ms)<br>  Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot;<br>  ↳ app/views/areas/index.html.erb:5<br>Write fragment views/areas/index:6ad6a9c54f98aacd1ed6e47c151ec8d2/foo/bar (0.1ms)<br>  Rendered areas/index.html.erb within layouts/application (3.2ms)</pre><p>結果は「<strong> </strong><strong>cacheメソッドの引数に文字列を指定した場合</strong>」とほぼ同じで、末尾の文字列が、配列の場合は文字列を / で結合したものになりました。</p><h3>cache メソッドの第一引数に中身はモデルのインスタンスの配列を指定した場合</h3><p>次は cacheメソッドの第一引数に中身はモデルのインスタンスの配列を指定した場合を見ていきます。</p><p>例えば、 Area 詳細ページで、所属している User の一覧も一緒にフラグメントキャッシュにしたい場合に、 cacheメソッドの引数に [@area, @area.users]のようにそれぞれがモデルのインスタンスな配列を指定します。（ show.html.erb ）</p><pre>&lt;h1&gt;Area 詳細&lt;/h1&gt;<br>&lt;% cache [@area, @area.users] do %&gt;<br>&lt;ul&gt;<br>  &lt;li&gt;エリア名: &lt;%= @area.name %&gt;&lt;/li&gt;<br>  &lt;li&gt;作成日時: &lt;%= @area.created_at %&gt;&lt;/li&gt;<br>  &lt;li&gt;更新日時: &lt;%= @area.updated_at %&gt;&lt;/li&gt;<br>&lt;/ul&gt;<br>&lt;h2&gt;所属ユーザー&lt;/h2&gt;<br>&lt;% @area.users.each do |user| %&gt;<br>&lt;ul&gt;<br>  &lt;li&gt;名前: &lt;%= user.last_name %&gt; &lt;%= user.first_name %&gt;（ 作成日時: &lt;%= user.created_at %&gt;, 更新日時: &lt;%= user.updated_at %&gt;）&lt;/li&gt;<br>&lt;/ul&gt;<br>&lt;% end %&gt;<br>&lt;% end %&gt;</pre><p>初回アクセス時のログは下記の通りです。</p><pre>Started GET &quot;/areas/1&quot; for ::1 at 2019–08–19 14:30:31 +0900<br>Processing by AreasController#show as HTML<br>  Parameters: {&quot;id&quot;=&gt;&quot;1&quot;}<br>  Area Load (0.2ms) SELECT &quot;areas&quot;.* FROM &quot;areas&quot; WHERE &quot;areas&quot;.&quot;id&quot; = ? LIMIT ? [[&quot;id&quot;, 1], [&quot;LIMIT&quot;, 1]]<br>  ↳ app/controllers/areas_controller.rb:7<br>  Rendering areas/show.html.erb within layouts/application<br>   (0.2ms) SELECT COUNT(*) AS &quot;size&quot;, MAX(&quot;users&quot;.&quot;updated_at&quot;) AS timestamp FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;area_id&quot; = ? [[&quot;area_id&quot;, 1]]<br>  ↳ app/views/areas/show.html.erb:2<br>  User Load (0.2ms) SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;area_id&quot; = ? [[&quot;area_id&quot;, 1]]<br>  ↳ app/views/areas/show.html.erb:2<br>Read fragment views/areas/show:9e82795170690db7608139468ae31b16/areas/1–20190819050857241886/users/query-7d20f7de2b9a71e2dd719ccf3d1bbd7e-2–20190819023722782392 (27.3ms)<br>Write fragment views/areas/show:9e82795170690db7608139468ae31b16/areas/1–20190819050857241886/users/query-7d20f7de2b9a71e2dd719ccf3d1bbd7e-2–20190819023722782392 (0.4ms)<br>  Rendered areas/show.html.erb within layouts/application (101.2ms)</pre><p>ログの通りですが、「<strong> </strong><strong>cacheメソッドの第一引数にモデルのインスタンスを渡した場合</strong>」で出てきた Area 詳細の「<strong> </strong><strong>idとタイムスタンプ</strong>」と Area 一覧の「<strong> </strong><strong>query-{32文字の英数字}と </strong><strong>countとタイムスタンプ</strong>」を /で結合した文字列がキャッシュキーになっています。</p><p>つまり、 @areaが更新された場合と @area.usersに更新や行の追加があった場合に、<strong>古いキャッシュを読みにいってしまうことを防いでいます</strong>。</p><h3>トラベロコでのフラグメントキャッシュのキャッシュキーの決め方</h3><p>ここまで読んでいただいた方はもうお分かりかと思いますが、 cacheメソッドの第一引数は、下記のようなルールで運用すると良さそうで、実際にトラベロコでもそのようなルールで運用しています。</p><ul><li>モデルのインスタンスの内容をキャッシュしたい場合はモデルのインスタンスを指定する<br> — 例1: &lt;% cache @areas %&gt;<br> — 例2: &lt;% cache @area %&gt;</li><li>複数のモデルのインスタンスの内容をキャッシュしたい場合は、中身がモデルのインスタンスの配列を指定する<br> — 例: &lt;% cache [@area, @area.users] %&gt;<br> — または、<a href="https://railsguides.jp/caching_with_rails.html#%E3%83%AD%E3%82%B7%E3%82%A2%E3%83%B3%E3%83%89%E3%83%BC%E3%83%AB%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5">ロシアンドールキャッシュ</a>を利用する</li><li>DB の更新状況に依存したくない場合は文字列を指定してもOK<br> — 例えば、更新頻度が高いけど常に最新を表示しなくても良い場合など<br> — ただし、その場合は適切な有効期限を設定する<br> — 例: &lt;% cache &#39;area_in_asia&#39;, expries_in: 1.hour %&gt;</li></ul><p>以上、 Ruby on Rails のフラグメントキャッシュのキャッシュキーがどのように決まるか、を詳しく調べてみた話でした。どなたかのご参考になれば幸いです。</p><p>トラベロコでは、キャッシュ利用した（キャッシュじゃなくてもいいけど）パフォーマンス改善に興味のある Web エンジニアを募集中です。</p><p><a href="https://www.wantedly.com/projects/136728">世界の日本人が繋がるシェアリングエコノミーを創るWebエンジニア by Traveloco</a></p><h3>参考</h3><ul><li><a href="https://railsguides.jp/caching_with_rails.html">Rails のキャッシュ機構 — Rails ガイド</a></li><li><a href="https://qiita.com/suketa/items/eeae7e2196520323f694">Railsのフラグメントキャッシュについて調べてみた — Qiita</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9697c4c6a831" width="1" height="1" alt=""><hr><p><a href="https://medium.com/traveloco-tech/ruby-on-rails-fragment-caching-cache-key-9697c4c6a831">Ruby on Rails のフラグメントキャッシュのキャッシュキーはどのように決まるか</a> was originally published in <a href="https://medium.com/traveloco-tech">locotabi-tech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[トラベロコでの私の働き方]]></title>
            <link>https://medium.com/traveloco/how-i-work-in-traveloco-b56db3943fb3?source=rss-c496081679c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/b56db3943fb3</guid>
            <dc:creator><![CDATA[TAGAWA Takao]]></dc:creator>
            <pubDate>Thu, 28 Sep 2017 04:59:21 GMT</pubDate>
            <atom:updated>2020-02-04T12:36:26.343Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jo1mScFoOv27R28qjDqq3w.jpeg" /><figcaption>photo credit: MorseInteractive <a href="http://www.flickr.com/photos/150712825@N03/34825518513">http://www.flickr.com/photos/150712825@N03/34825518513</a> <a href="https://creativecommons.org/licenses/by/2.0/">https://creativecommons.org/licenses/by/2.0/</a></figcaption></figure><p>※2020年1月28日に「トラベロコ」は「<a href="https://locotabi.jp/">ロコタビ</a>」にサービス名称を変更しました。詳しい経緯などは<a href="https://note.com/yshiiya/n/nece4b36333a1">代表の note 記事</a>をご覧ください。</p><p>はじめまして。いろいろなウェブサービスでは ID を dounokouno で取得している、<a href="https://locotabi.jp/">ロコタビ</a>（旧トラベロコ）の開発チームに所属している多川（タガワ）です。</p><p>今回は、日本在住の日本人（とわざわざ書かないといけないわけがあります。わけは後述します）である私の、トラベロコでの働き方をご紹介したいと思います。</p><h3>トラベロコにジョインしたきっかけ</h3><p>トラベロコにジョインしたきっかけは、元々、一番はじめの<a href="https://locotabi.jp/">ロコタビ</a>（旧トラベロコ）のサービスをリリースのとき（α版と呼んでいました）にデザインと開発のお手伝いをしたのが、当時、フリーランスとして活動していた私と妻（私が主に開発を、妻が主にデザインを担当しました）だったりします。</p><p>その後、一度、別の会社に就職をしたのですが、辞めたあと、代表の <a href="https://medium.com/u/ae739b0be66c">Yutaka Shiiya</a> に誘っていただき、トラベロコに出戻ったような形になります。</p><h3>開発チームに所属？</h3><p>「開発チームに所属」と書きましたが、トラベロコでは明確に部署やチームが分かれているわけではありません。</p><p>私が担当する仕事が主に「開発を担当する人たちと同じ」であることから、「開発チームに所属」と自主的に書きました。まだ小さい会社ですので、各自が所属チームに関わらずいろんなことをやっています。</p><p>とは言えだいたい、「開発」「カスタマーサポート」「プロモーション」といったチームには分かれているように思います。</p><h3>日本在住の日本人？</h3><p>前述の通り、私は日本在住の日本人です。</p><p>と書かなければいけないのは、 CTO （日本人）を含む開発チームはベトナムにオフィスがあり、ベトナムが開発の本拠地になっているからです。</p><p>開発チーム所属の日本在住の日本人は私を含め2名です。あとはベトナム在住の日本人と、ベトナム在住のベトナム人、といった構成になっています。言葉だけだとわかりにくいと思いますので、円グラフにまとめてみました。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FMkuKUqqr83pYimfrN3ORw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*H3oRM3xMuVWPchrMfgtZfg.png" /></figure><h3>開発チームは一度、ベトナムで働いてみている</h3><p>開発チームの日本人は今のところ全員、一度、ベトナムの CTO の元で働いてみています。</p><p>ベトナムで働いてみることは強制ではありませんが、同じオフィスで一緒に働いてみるという経験は、その後のリモートワークでは重要なことかなと思っています。</p><h3>開発チームの共通語は英語</h3><p>日本人は日本語を、ベトナム人はベトナム語を主に書いたり話したりしますので、共通語は必然的に英語になります。Issue Tracker などでは基本的に英語で書くことになっています。（私と他の日本人だけわかればいいことの場合は日本語で書く場合もあります。）</p><p>私個人は英語がそれほど得意というわけではありませんので、自作の<a href="https://webtools.dounokouno.com/translate/index.html">英和翻訳比較ツール</a>を使ったりして頑張って英文を書いています。</p><p><a href="https://webtools.dounokouno.com/translate/index.html">英和和英翻訳比較ツール</a></p><h3>スタッフは全員リモートワーク</h3><p><a href="https://locotabi.jp/">ロコタビ</a>（旧トラベロコ）に関わるスタッフは日本の各地、世界の各地にいるので、全員がリモートワークで働いています。</p><p>私が把握している限りでも、前述の通りベトナムを始め、タイ、アルゼンチンにもスタッフがいます。当然（？）ですが、直接お会いしたことがない人もいます。</p><p>上記以外にも、記事執筆や各種調査を世界各地のロコ（海外在住日本人）に依頼したりしていますので、そういった方も含めると世界各地、1400以上の都市にスタッフが散らばっていることになります。すごい！</p><h3>私は自宅がオフィス</h3><p>私はフリーランスで活動しているときに自宅をオフィスにしていましたので、そのまま、自宅がオフィスになっています。デスクは IKEA 、チェアはアーロンチェアでなかなか快適です。</p><p>仕事場の空間を自分の好きなようにできるのも、リモートワークの良いところかなと思います。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TR-kNbWbXhAVCEFgmLopUw.jpeg" /><figcaption>自宅兼オフィスの様子</figcaption></figure><h3>まとめ</h3><p>今回は<a href="https://locotabi.jp/">ロコタビ</a>（旧トラベロコ）というちょっと変わったサービスを提供している、ちょっと変わった組織の、ちょっと変わった働き方の一端をご紹介させていただきました。</p><p>トラベロコでは随時、スタッフを募集していますので、トラベロコでの働き方に興味が湧いた方は、ぜひご応募ください！</p><p><a href="https://www.wantedly.com/companies/traveloco/projects">Travelocoの採用/求人一覧 - Wantedly</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b56db3943fb3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/traveloco/how-i-work-in-traveloco-b56db3943fb3">トラベロコでの私の働き方</a> was originally published in <a href="https://medium.com/traveloco">Traveloco</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>