From af1a4bdc564864e80b234755651303dac68ae82e Mon Sep 17 00:00:00 2001 From: Noemj Date: Wed, 10 Apr 2013 17:40:01 +0300 Subject: Added statement cache --- activerecord/CHANGELOG.md | 17 ++++++ activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/statement_cache.rb | 26 +++++++++ activerecord/test/cases/statement_cache_test.rb | 64 +++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 activerecord/lib/active_record/statement_cache.rb create mode 100644 activerecord/test/cases/statement_cache_test.rb (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index c7085a0eaa..a3f5f96f53 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,22 @@ ## 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 new object that has not yet been persisted. For instance: diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index c33f03f13f..58a362cb46 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -56,6 +56,7 @@ module ActiveRecord autoload :SchemaMigration autoload :Scoping autoload :Serialization + autoload :StatementCache autoload :Store autoload :Timestamp autoload :Transactions diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb new file mode 100644 index 0000000000..1ef856a0e0 --- /dev/null +++ b/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 diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb new file mode 100644 index 0000000000..6ed4e5effd --- /dev/null +++ b/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 -- cgit v1.2.3