6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails6 のちょい足しな新機能を試す43(ActiveRecord Cache編)

Posted at

はじめに

Rails 6 に追加されそうな新機能を試す第43段。 今回は、 ActiveRecord Cache 編です。
Rails 6 では、 has_manybelongs_to などの関連がある model の reload 時の ActiveRecord のキャッシュに関連する動作が修正されています。 reload しているのに、データベースから読み直されず、キャッシュの古い値が取得されてしまう動作がなくなり、一貫性が感じられる動作になっています。

Ruby 2.6.3, Rails 6.0.0.rc1, Rails 5.2.3 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$ rails --version
Rails 6.0.0.rc1

プロジェクトを作る

$ bin/rails new rails6_0_0rc1
$ cd rails6_0_0rc1

モデルを作る

has_manybelongs_to の関係を作るため、 Author, Book, Edition の3つのモデルを作成します。

本 ( Book ) には1人の著者 ( Author ) がひもづき ( belongs_to )、複数の版 ( Edition ) が存在します。

$ bin/rails g model Author name
$ bin/rails g model Book title author:references
$ bin/rails g model Edition name book:references

has_many を追加する

AuthorBook にそれぞれ has_many を追加します。

app/models/author.rb
class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end
app/models/book.rb
class Book < ApplicationRecord
  belongs_to :author
  has_many :editions, dependent: :destroy
end

db:migrate を実行する

一旦、ここで、 db:migrate をしておきます。

$ bin/rails db:create db:migrate

Controller と View を作る

Book controller と index View を作ります。

$ bin/rails g controller books index

初期データを登録するメソッドを定義する

BooksController に初期データを登録するプライベートメソッド initialize_data を作成します。
毎回同じデータでテストできるように、一旦、データを削除してから、 Author , Book , Edition のデータを作成するようにします。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  ...
  private

  def initialize_data
    Author.first&.destroy
    author = Author.create(name: 'Dave Tomas')
    book = Book.create(title: 'Pragmatic Programer', author: author)
    Edition.create([{ name: '1st', book: book }, { name: '2nd', book: book }])
  end
end

model に値を修正するメソッドを追加する

各モデルにそれぞれの値を更新するメソッドを追加します。
普通にDBの値を更新すると、今回の事象が確認できないので、Threadを作って、Thread の中で更新します。
Thread#join を使って更新が終わるまで待つようにします。

Book モデルの title を更新するメソッドを追加します。

app/models/book.rb
class Book < ApplicationRecord
  ...
  def self.fix_title
    Thread.start do
      Book.where(title: 'Pragmatic Programer').update(title: 'Pragmatic Programmer')
    end.join
  end
end

同じように、Author モデルの name を更新するメソッドを追加します。

app/models/author.rb
class Author < ApplicationRecord
  ...
  def self.fix_name
    Thread.start do
      Author.where(name: 'Dave Tomas').update(name: 'Dave Thomas')
    end.join
  end
end

Edition モデルにも name を更新するメソッドを追加します。

app/models/edition.rb
class Edition < ApplicationRecord
  ...
  def self.fix_name
    Thread.start do
      Edition.where(name: '2nd').update(name: '20th Anniversary Edition')
    end.join
  end
end

book.reload のキャッシュの動作を試すメソッドを作る

book.reload のキャッシュの動作を試すプライベートメソッドを作ります。
book.reload の前に booktitle を更新します。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  ...
  private
  ...
  # try book.reload
  def try_book_reload
    book = Book.first
    @old_title = book.title
    Book.fix_title
    @new_title = book.reload.title
  end
end

book.reload_author のキャッシュの動作を試すメソッドを作る

book.reload_author のキャッシュの動作を試すプライベートメソッドを作ります。
book.reload_author の前に authorname を更新します。
更新の前後で、 Author.find を呼び出します。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  ...
  private
  ...

  # try book.reload_author
  def try_book_reload_author
    book = Book.first
    author_id = Author.first.id
    @old_author_name = Author.find(author_id).name
    Author.fix_name
    @reloaded_author_name = book.reload_author.name
    @new_author_name = Author.find(author_id).name
  end
end

book.editions.reload を試す

book.editions.reload のキャッシュの動作を試すプライベートメソッドを作ります。
book.editions.reload の前に editionname を更新します。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  ...
  private
  ...

  # try book.editions.reload
  def try_book_editions_reload
    book = Book.first
    @old_editions = book.editions.reload.map(&:name)
    Edition.fix_name
    @new_editions = book.editions.reload.map(&:name)
  end
end

index メソッドを修正する

index メソッドを修正します。
これまで作ったプライベートメソッドを順に呼び出します。
index は GET メソッドなので、データを登録したり更新したりするのは RESTful ではないので、良くないです。
今回は動作確認のためなので、見逃してください。

app/controllers/books_controller.rb
class BooksController < ApplicationController
  def index
    initialize_data

    try_book_reload
    try_book_reload_author
    try_book_editions_reload
  end
  ...
end

View を作成する

Controller で求めた値を表示するように View を修正します。

app/views/books/index.html.erb
<h1>Books#index</h1>

<table>
  <thead>
    <tr>
      <th>variable</th>
      <th>value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>@old_title</td>
      <td><%= @old_title %></td>
    </tr>
    <tr>
      <td>@new_title</td>
      <td><%= @new_title %></td>
    </tr>
    <tr>
      <td>@old_author_name</td>
      <td><%= @old_author_name %></td>
    </tr>
    <tr>
      <td>@reloaded_author_name</td>
      <td><%= @reloaded_author_name %></td>
    </tr>
    <tr>
      <td>@new_author_name</td>
      <td><%= @new_author_name %></td>
    </tr>
    <tr>
      <td>@old_editions</td>
      <td><%= @old_editions %></td>
    </tr>
    <tr>
      <td>@new_editions</td>
      <td><%= @new_editions %></td>
    </tr>
  </tbody>
</table>

Rails 5 では

画面を表示すると以下のようになります。
データを更新していますが、 @new_author_name の値と @new_editions の値は変わっていません。
キャッシュが働いているためです。
rails5.png

Rails 6 では

画面を表示すると以下のようになります。
@new_author_name の値と @new_editions の値は更新後の値に変わっています。

rails6.png

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try043_query_cache

参考情報

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?