はじめに
Rails 6 に追加されそうな新機能を試す第43段。 今回は、 ActiveRecord Cache
編です。
Rails 6 では、 has_many
や belongs_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_many
や belongs_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
を追加する
Author
と Book
にそれぞれ has_many
を追加します。
class Author < ApplicationRecord
has_many :books, dependent: :destroy
end
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
のデータを作成するようにします。
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 を更新するメソッドを追加します。
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 を更新するメソッドを追加します。
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 を更新するメソッドを追加します。
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
の前に book
の title
を更新します。
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
の前に author
の name
を更新します。
更新の前後で、 Author.find
を呼び出します。
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
の前に edition
の name
を更新します。
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 ではないので、良くないです。
今回は動作確認のためなので、見逃してください。
class BooksController < ApplicationController
def index
initialize_data
try_book_reload
try_book_reload_author
try_book_editions_reload
end
...
end
View を作成する
Controller で求めた値を表示するように View を修正します。
<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
の値は変わっていません。
キャッシュが働いているためです。
Rails 6 では
画面を表示すると以下のようになります。
@new_author_name
の値と @new_editions
の値は更新後の値に変わっています。
試したソース
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try043_query_cache