Permalink
Browse files

Enable caching by default for all enumerations

Allow caching to be turned off on an application-wide basis
Allow cache store to be configurable
  • Loading branch information...
1 parent 9f65588 commit 34fa8786da416a6ebe25062e3959ecb739216787 @obrie obrie committed Apr 28, 2009
Showing with 100 additions and 49 deletions.
  1. +3 −0 CHANGELOG.rdoc
  2. +38 −25 lib/enumerate_by.rb
  3. +2 −0 test/test_helper.rb
  4. +57 −24 test/unit/enumerate_by_test.rb
View
@@ -1,5 +1,8 @@
== master
+* Enable caching by default for all enumerations
+* Allow caching to be turned off on an application-wide basis
+* Allow cache store to be configurable
* Automatically trigger in-memory caching of the enumeration's table when bootstrapping
* Add #bootstrap for automatically synchronizing the records in an enumeration's table
* Improve serialization performance
View
@@ -7,6 +7,15 @@
# numerical order. This extension provides a general technique for using
# ActiveRecord classes to define enumerations.
module EnumerateBy
+ # Whether to enable enumeration caching (default is true)
+ mattr_accessor :perform_caching
+ self.perform_caching = true
+
+ # The cache store to use for queries within enumerations (default is a
+ # memory store)
+ mattr_accessor :cache_store
+ self.cache_store = ActiveSupport::Cache::MemoryStore.new
+
module MacroMethods
def self.extended(base) #:nodoc:
base.class_eval do
@@ -27,6 +36,10 @@ def self.extended(base) #:nodoc:
# defined for the given attribute since all records must have this value
# in order to be properly enumerated.
#
+ # Configuration options:
+ # * <tt>:cache</tt> - Whether to cache all finder queries for this
+ # enumeration. Default is true.
+ #
# == Defining enumerators
#
# The enumerators of the class uniquely identify each record in the
@@ -43,9 +56,8 @@ def self.extended(base) #:nodoc:
# Color['red'] # => #<Color id: 1, name: "red">
# Color['green'] # => #<Color id: 2, name: "green">
#
- # When used in association with #bootstrap, these records are cached so
- # there is no performance hit and the same object can be compared against
- # itself, i.e. Color['red'] == Color['red']
+ # When caching is enabled, these lookup queries are cached so that there
+ # is no performance hit.
#
# == Associations
#
@@ -74,16 +86,20 @@ def self.extended(base) #:nodoc:
# Car.all(:conditions => {:color => 'red'})
#
# For more information about finders, see EnumerateBy::Extensions::BaseConditions.
- def enumerate_by(attribute = :name)
+ def enumerate_by(attribute = :name, options = {})
+ options.reverse_merge!(:cache => true)
+ options.assert_valid_keys(:cache)
+
extend EnumerateBy::ClassMethods
include EnumerateBy::InstanceMethods
# The attribute representing a record's enumerator
cattr_accessor :enumerator_attribute
self.enumerator_attribute = attribute
- # In-memory cache of database queries for this model
- cattr_accessor :enumerator_cache
+ # Whether to perform caching of enumerators within finder queries
+ cattr_accessor :perform_enumerator_caching
+ self.perform_enumerator_caching = options[:cache]
validates_presence_of attribute
validates_uniqueness_of attribute
@@ -96,12 +112,6 @@ def enumeration?
end
module ClassMethods
- def self.extended(base) #:nodoc:
- class << base
- alias_method_chain :find_by_sql, :enumerations
- end
- end
-
# Does this class define an enumeration? Always true.
def enumeration?
true
@@ -133,17 +143,16 @@ def find_by_enumerator(enumerator)
first(:conditions => {enumerator_attribute => enumerator})
end
- # Adds support for looking up records from an in-memory process cache for
- # the model before querying the database.
- #
- # This allows for models that have been bootstrapped (and, thus, will
- # never change) to be permanently cached avoiding unnecessary queries to
- # the database.
- def find_by_sql_with_enumerations(sql)
- if enumerator_cache
- enumerator_cache.fetch(sql) { find_by_sql_without_enumerations(sql) }
+ # Adds support for looking up records from the enumeration cache for
+ # before querying the database.
+ #
+ # This allows for enumerations to permanently cache find queries, avoiding
+ # unnecessary lookups in the database.
+ def find_by_sql(sql)
+ if EnumerateBy.perform_caching && perform_enumerator_caching
+ EnumerateBy.cache_store.fetch(sql) { super }
else
- find_by_sql_without_enumerations(sql)
+ super
end
end
@@ -195,6 +204,10 @@ def find_by_sql_with_enumerations(sql)
# only be synchronized if the attribute is nil in the database.
# Otherwise, any changes to that column remain there.
def bootstrap(*records)
+ # Temporarily turn off caching
+ perform_caching = perform_enumerator_caching
+ self.perform_enumerator_caching = false
+
# Remove records that are no longer being used
delete_all(['id NOT IN (?)', records.map {|record| record[:id]}])
existing = all.inject({}) {|existing, record| existing[record.id] = record; existing}
@@ -217,10 +230,10 @@ def bootstrap(*records)
record
end
- # Enable the query cache
- self.enumerator_cache = ActiveSupport::Cache::MemoryStore.new
-
records
+ ensure
+ # Revert caching back to its original value
+ self.perform_enumerator_caching = perform_caching
end
end
View
@@ -24,3 +24,5 @@ def execute_with_query_record(sql, name = nil, &block)
alias_method_chain :execute, :query_record
end
+
+EnumerateBy.perform_caching = false
@@ -1,5 +1,15 @@
require File.dirname(__FILE__) + '/../test_helper'
+class EnumerateByTest < ActiveRecord::TestCase
+ def test_should_have_a_cache_store
+ assert_instance_of ActiveSupport::Cache::MemoryStore, EnumerateBy.cache_store
+ end
+
+ def test_should_raise_exception_if_invalid_option_specified
+ assert_raise(ArgumentError) { Color.enumerate_by(:id, :invalid => true) }
+ end
+end
+
class ModelWithoutEnumerationTest < ActiveRecord::TestCase
def test_should_not_be_an_enumeration
assert !Car.enumeration?
@@ -119,6 +129,53 @@ def test_should_stringify_enumerator
end
end
+class EnumerationWithCachingTest < ActiveRecord::TestCase
+ def setup
+ @red = create_color(:name => 'red')
+
+ EnumerateBy.perform_caching = true
+ end
+
+ def test_should_perform_enumerator_caching
+ assert Color.perform_enumerator_caching
+ end
+
+ def test_should_cache_all_finder_queries
+ assert_queries(1) { Color.find(@red.id) }
+ assert_queries(0) { Color.find(@red.id) }
+
+ assert_queries(1) { Color.all }
+ assert_queries(0) { Color.all }
+ end
+
+ def teardown
+ EnumerateBy.perform_caching = false
+ end
+end
+
+class EnumerationWithoutCachingTest < ActiveRecord::TestCase
+ def setup
+ @red = create_color(:name => 'red')
+
+ EnumerateBy.perform_caching = true
+ @original_perform_caching = Color.perform_enumerator_caching
+ Color.perform_enumerator_caching = false
+ end
+
+ def test_should_not_cache_finder_queries
+ assert_queries(1) { Color.find(@red.id) }
+ assert_queries(1) { Color.find(@red.id) }
+
+ assert_queries(1) { Color.all }
+ assert_queries(1) { Color.all }
+ end
+
+ def teardown
+ EnumerateBy.perform_caching = false
+ Color.perform_enumerator_caching = @original_perform_caching
+ end
+end
+
class EnumerationBootstrappedTest < ActiveRecord::TestCase
def setup
@red, @green = Color.bootstrap(
@@ -142,22 +199,6 @@ def test_should_create_records
assert_equal @green, Color.find(2)
assert_equal 'green', @green.name
end
-
- def test_should_enable_enumerator_cache
- assert_not_nil Color.enumerator_cache
- end
-
- def test_should_cache_all_finder_queries
- assert_queries(1) { Color.find(1) }
- assert_queries(0) { Color.find(1) }
-
- assert_queries(1) { Color.all }
- assert_queries(0) { Color.all }
- end
-
- def teardown
- Color.enumerator_cache = nil
- end
end
class EnumerationBootstrappedWithExistingRecordsTest < ActiveRecord::TestCase
@@ -178,10 +219,6 @@ def test_should_synchronize_all_attributes
assert_equal 'red', @red.name
assert_equal 'green', @green.name
end
-
- def teardown
- Color.enumerator_cache = nil
- end
end
class EnumerationBootstrappedWithDefaultsTest < ActiveRecord::TestCase
@@ -210,8 +247,4 @@ def test_should_not_update_default_attributes_if_defined
def test_should_update_default_attributes_if_not_defined
assert_equal '#00ff00', @green.html
end
-
- def teardown
- Color.enumerator_cache = nil
- end
end

0 comments on commit 34fa878

Please sign in to comment.