Permalink
Browse files

Introducing Model.cache { ... } for the occasional query caching need…

…s. ( fantastic to reduce the 200 SELECT * from accounts WHERE id=1 queries in your views )

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6138 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent 23b2abe commit f458b376c545d8eae5189d1ecc16245cafe7e796 Tobias Lütke committed Feb 6, 2007
View
3 activerecord/CHANGELOG
@@ -1,5 +1,8 @@
*SVN*
+* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however [Tobias Luetke]
+ Task.cache { Task.find(1); Task.find(1) } #=> 1 query
+
* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
* Oracle: fix lob and text default handling. #7344 [gfriedrich, Michael Schoen]
View
51 activerecord/lib/active_record/query_cache.rb
@@ -6,7 +6,7 @@ def initialize(connection)
end
def clear_query_cache
- @query_cache = {}
+ @query_cache.clear
end
def select_all(sql, name = nil)
@@ -16,14 +16,27 @@ def select_all(sql, name = nil)
def select_one(sql, name = nil)
@query_cache[sql] ||= @connection.select_one(sql, name)
end
+
+ def select_values(sql, name = nil)
+ (@query_cache[sql] ||= @connection.select_values(sql, name)).dup
+ end
+
+ def select_value(sql, name = nil)
+ @query_cache[sql] ||= @connection.select_value(sql, name)
+ end
+
+ def execute(sql, name = nil)
+ clear_query_cache
+ @connection.execute(sql, name)
+ end
def columns(table_name, name = nil)
@query_cache["SHOW FIELDS FROM #{table_name}"] ||= @connection.columns(table_name, name)
end
- def insert(sql, name = nil, pk = nil, id_value = nil)
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
clear_query_cache
- @connection.insert(sql, name, pk, id_value)
+ @connection.insert(sql, name, pk, id_value, sequence_name)
end
def update(sql, name = nil)
@@ -41,24 +54,26 @@ def method_missing(method, *arguments, &proc)
@connection.send(method, *arguments, &proc)
end
end
-
+
class Base
# Set the connection for the class with caching on
class << self
- alias_method :connection_without_query_cache=, :connection=
-
- def connection=(spec)
- if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache]
- spec = QueryCache.new(self.send(spec.adapter_method, spec.config))
- end
- self.connection_without_query_cache = spec
+ alias_method :connection_without_query_cache, :connection
+
+ def query_caches
+ (Thread.current[:query_cache] ||= {})
+ end
+
+ def cache
+ query_caches[self] = QueryCache.new(connection)
+ yield
+ ensure
+ query_caches[self] = nil
+ end
+
+ def connection
+ query_caches[self] || connection_without_query_cache
end
end
- end
-
- class AbstractAdapter #:nodoc:
- # Stub method to be able to treat the connection the same whether the query cache has been turned on or not
- def clear_query_cache
- end
- end
+ end
end
View
36 activerecord/test/abstract_unit.rb
@@ -36,16 +36,10 @@ def assert_date_from_db(expected, actual, message = nil)
end
def assert_queries(num = 1)
- ActiveRecord::Base.connection.class.class_eval do
- self.query_count = 0
- alias_method :execute, :execute_with_query_counting
- end
+ $query_count = 0
yield
ensure
- ActiveRecord::Base.connection.class.class_eval do
- alias_method :execute, :execute_without_query_counting
- end
- assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
@@ -60,16 +54,26 @@ def current_adapter?(*types)
end
end
-ActiveRecord::Base.connection.class.class_eval do
- cattr_accessor :query_count
+def uses_mocha(test_name)
+ require 'mocha'
+ require 'stubba'
+ yield
+rescue LoadError
+ $stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
+end
- # Array of regexes of queries that are not counted against query_count
- @@ignore_list = [/^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/]
+ActiveRecord::Base.connection.class.class_eval do
+
+ if not (const_get('IGNORED_SQL') rescue nil)
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/]
+
+ def execute_with_counting(sql, name = nil, &block)
+ $query_count ||= 0
+ $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
+ execute_without_counting(sql, name, &block)
+ end
- alias_method :execute_without_query_counting, :execute
- def execute_with_query_counting(sql, name = nil, &block)
- self.query_count += 1 unless @@ignore_list.any? { |r| sql =~ r }
- execute_without_query_counting(sql, name, &block)
+ alias_method_chain :execute, :counting
end
end
View
80 activerecord/test/query_cache_test.rb
@@ -0,0 +1,80 @@
+require 'abstract_unit'
+require 'fixtures/topic'
+require 'fixtures/reply'
+require 'fixtures/task'
+
+
+class QueryCacheTest < Test::Unit::TestCase
+ fixtures :tasks
+
+
+ def test_find_queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ end
+
+ def test_find_queries_with_cache
+ Task.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ end
+
+ def test_find_queries_with_cache
+ Task.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ end
+
+ def test_cache_is_scoped_on_actual_class_only
+ Task.cache do
+ assert_queries(2) { Topic.find(1); Topic.find(1) }
+ end
+ end
+end
+
+uses_mocha('QueryCacheExpiryTest') do
+
+class QueryCacheExpiryTest < Test::Unit::TestCase
+ fixtures :tasks
+
+ def test_find
+ ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(0)
+
+ Task.cache do
+ Task.find(1)
+ end
+ end
+
+ def test_save
+ ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)
+
+ Task.cache do
+ Task.find(1).save
+ end
+ end
+
+ def test_destroy
+ ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).at_least_once
+
+ Task.cache do
+ Task.find(1).destroy
+ end
+ end
+
+ def test_create
+ ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)
+
+ Task.cache do
+ Task.create!
+ end
+ end
+
+ def test_new_save
+ ActiveRecord::QueryCache.any_instance.expects(:clear_query_cache).times(1)
+
+ Task.cache do
+ Task.new.save
+ end
+ end
+end
+
+end

0 comments on commit f458b37

Please sign in to comment.