This article is written by beginners who have been using Rails for less than a year. Please note that my environment is old (Rails 4), so the latest version may not have the same problem.
In the sample source below, the tests table is searched in the order of col1, col2, col3, and if data that matches the search conditions is found, the search results are returned without performing subsequent processing. I will. The search results are kept in the cache (30.minutes), and if it is executed again within the specified period, it is expected that the data held in the cache will be returned without accessing the database. I will.
def get_testdata( col1 , col2 , col3 )
Rails.cache.fetch("test-#{col1}-#{col2}-#{col3}", expires_in: 30.minutes ) do
test = Test.where(col1: col1).first
return test if test
test = Test.where(col2: col2).first
return test if test
test = Test.where(col3: col3).first
return test if test
nil
end
end
Now, let's actually run this process from the Rails console.
[2] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
Oh, I got the data. Since SQL is executed 3 times, it seems that there was data that matched only the condition of col3. The next time you run it under the same conditions, it will be retrieved from the cache and the SQL should not be executed.
[3] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (7.1ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (105.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (54.5ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
... It looks like the same SQL as the first time is being executed. It may be due to your feelings, so let's try it again.
[4] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.4ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[5] pry(main)>
... it didn't seem to be my fault. It looks like you're accessing the database every time because the cache isn't working well. Create a simplified source for cache checking.
def get_testdata2( col1 )
Rails.cache.fetch("test2-#{col1}", expires_in: 30.minutes ) do
Test.where(col1: col1).first
end
end
That's why I created a sample source for checking the cache. If this does not work, there may be a problem with the execution environment.
[2] pry(main)> get_testdata2("test3")
Cache read: test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
Cache write: test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
It seems that the data was acquired properly. The characters "Cache write", which were not output at the time of the previous execution, are output in the execution result.
[3] pry(main)> get_testdata2("test3")
Cache read: test2-test3 ({:expires_in=>1800 seconds})
Cache fetch_hit: test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
Oh, I was able to get the data without executing SQL. The execution environment doesn't seem to be a problem because the cache is working properly. Next, we'll add some processing little by little to see where the problem is occurring.
def get_testdata3( col1 )
Rails.cache.fetch("test3-#{col1}", expires_in: 30.minutes ) do
return Test.where(col1: col1).first
end
end
I added only return to the sample source (for cache check). Let's do it.
[2] pry(main)> get_testdata3("test3")
Cache read: test3-test3 ({:expires_in=>1800 seconds})
Cache generate: test3-test3 ({:expires_in=>1800 seconds})
Test Load (0.9ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
It seems that the data was acquired properly. The characters "Cache write" output in the sample source for cache confirmation are no longer output in the execution result.
[3] pry(main)> get_testdata3("test3")
Cache read: test3-test3 ({:expires_in=>1800 seconds})
Cache generate: test3-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
It was confirmed that the same SQL as the first time is executed the second time as well as the first sample source. It seems that the cause is that you have exited the cached block using the return clause. Now let's modify the sample source so that it doesn't use return.
def get_testdata( col1 , col2 , col3 )
Rails.cache.fetch("test-#{col1}-#{col2}-#{col3}", expires_in: 30.minutes ) do
test = Test.where(col1: col1).first
test = Test.where(col2: col2).first if test.blank?
test = Test.where(col3: col3).first if test.blank?
end
end
If return is a problem, this should work! Let's do it.
[2] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache generate: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Test Load (0.7ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col1` = 'test1' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.5ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col2` = 'test2' ORDER BY `tests`.`id` ASC LIMIT 1
Test Load (0.6ms) SELECT `tests`.* FROM `tests` WHERE `tests`.`col3` = 'test3' ORDER BY `tests`.`id` ASC LIMIT 1
Cache write: test-test1-test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[3] pry(main)>
The SQL is executed and the data is acquired in the same way as when it was first executed. Since the characters "Cache write" are output in the execution result, you can expect it.
[3] pry(main)> get_testdata("test1","test2","test3")
Cache read: test-test1-test2-test3 ({:expires_in=>1800 seconds})
Cache fetch_hit: test-test1-test2-test3 ({:expires_in=>1800 seconds})
=> #<Test id: 1, col1: "test3", col2: "test3", col3: "test3">
[4] pry(main)>
Hooray! !! !! I was able to get the data without executing the SQL. As a result, the cache works properly even in the original processing, and data can be retrieved without accessing the database.
It has been confirmed that no data is saved in the cache when the process is terminated using the return clause in the block running in Rails.cache.fetch.
When I first created it, I didn't expect the cache to stop working just by mentioning a return. If you think about it carefully, each method will be exited when the return is executed, so you may wonder what is cached ... Well, I'm glad that the problem was solved safely.
I hope this will be helpful for those who are experiencing similar problems.