Navigation Menu

Skip to content

Commit

Permalink
Added statement cache
Browse files Browse the repository at this point in the history
  • Loading branch information
olliwer committed Apr 10, 2013
1 parent 45321a6 commit af1a4bd
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
17 changes: 17 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,5 +1,22 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##


* Added Statement Cache to allow the caching of a single statement. The cache works by
duping the relation returned from yielding a statement which allows skipping the AST
building phase.

Example:

cache = ActiveRecord::StatementCache.new do
Book.where(:name => "my book").limit(100)
end

books = cache.execute

The solution attempts to get closer to the speed of `find_by_sql` but still maintaining
the expressiveness of the AR queries.

*Olli Rissanen*

* Fixing issue #8345. Now throwing an error when one attempts to touch a * Fixing issue #8345. Now throwing an error when one attempts to touch a
new object that has not yet been persisted. For instance: new object that has not yet been persisted. For instance:


Expand Down
1 change: 1 addition & 0 deletions activerecord/lib/active_record.rb
Expand Up @@ -56,6 +56,7 @@ module ActiveRecord
autoload :SchemaMigration autoload :SchemaMigration
autoload :Scoping autoload :Scoping
autoload :Serialization autoload :Serialization
autoload :StatementCache
autoload :Store autoload :Store
autoload :Timestamp autoload :Timestamp
autoload :Transactions autoload :Transactions
Expand Down
26 changes: 26 additions & 0 deletions activerecord/lib/active_record/statement_cache.rb
@@ -0,0 +1,26 @@
module ActiveRecord

# Statement cache is used to cache a single statement in order to avoid creating the AST again.
# Initializing the cache is done by passing the statement in the initialization block:
#
# cache = ActiveRecord::StatementCache.new do
# Book.where(:name => "my book").limit(100)
# end
#
# The cached statement is executed by using the +execute+ method:
#
# cache.execute
#
# The relation returned by yield is cached, and for each +execute+ call the cached relation gets duped.
# Database is queried when +to_a+ is called on the relation.
class StatementCache
def initialize
@relation = yield
raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
end

def execute
@relation.dup.to_a
end
end
end
64 changes: 64 additions & 0 deletions activerecord/test/cases/statement_cache_test.rb
@@ -0,0 +1,64 @@
require 'cases/helper'
require 'models/book'
require 'models/liquid'
require 'models/molecule'
require 'models/electron'

module ActiveRecord
class StatementCacheTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
end

def test_statement_cache_with_simple_statement
cache = ActiveRecord::StatementCache.new do
Book.where(name: "my book").where("author_id > 3")
end

Book.create(name: "my book", author_id: 4)

books = cache.execute
assert_equal "my book", books[0].name
end

def test_statement_cache_with_nil_statement_raises_error
assert_raise(ArgumentError) do
cache = ActiveRecord::StatementCache.new do
nil
end
end
end

def test_statement_cache_with_complex_statement
cache = ActiveRecord::StatementCache.new do
Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton')
end

salty = Liquid.create(name: 'salty')
molecule = salty.molecules.create(name: 'dioxane')
electron = molecule.electrons.create(name: 'lepton')

liquids = cache.execute
assert_equal "salty", liquids[0].name
end

def test_statement_cache_values_differ
cache = ActiveRecord::StatementCache.new do
Book.where(name: "my book")
end

for i in 0..2 do
Book.create(name: "my book")
end

first_books = cache.execute

for i in 0..2 do
Book.create(name: "my book")
end

additional_books = cache.execute
assert first_books != additional_books
end
end
end

0 comments on commit af1a4bd

Please sign in to comment.