From b4395f281b7ad0e11866500acadc5f68676bb576 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Wed, 11 Jul 2012 14:57:32 +0200 Subject: [PATCH 1/9] Add basic caching --- lib/active_model/serializer.rb | 56 ++++++++++++++++++-- test/caching_test.rb | 97 ++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 test/caching_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 66dbde81..332a7f3e 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -69,7 +69,15 @@ module ActiveModel class_attribute :use_default_render_json self.use_default_render_json = false + class_attribute :cache + class_attribute :perform_caching + class << self + # set peform caching like root + def cache(value = true) + self.perform_caching = value + end + # Define attributes to be used in the serialization. def attributes(*attrs) @@ -266,6 +274,16 @@ module ActiveModel hash[meta_key] = @options[:meta] if @options.has_key?(:meta) end + def to_json(*args) + if perform_caching? + cache.fetch expand_cache_key([self.class.to_s.underscore, object.cache_key, 'to-json']) do + super + end + else + super + end + end + # Returns a json representation of the serializable # object including the root. def as_json(options={}) @@ -284,10 +302,15 @@ module ActiveModel # Returns a hash representation of the serializable # object without the root. def serializable_hash - return nil if @object.nil? - @node = attributes - include_associations! if _embed - @node +<<<<<<< HEAD +======= + if perform_caching? + cache.fetch expand_cache_key([self.class.to_s.underscore, object.cache_key, 'serializable-hash']) do + _serializable_hash + end + else + _serializable_hash + end end def include_associations! @@ -400,6 +423,31 @@ module ActiveModel alias :read_attribute_for_serialization :send + # def _serializable_hash + # instrument(:serialize, :serializer => self.class.name) do + # node = attributes + # instrument :associations do + # include_associations!(node) if _embed + # end + # node + # end + # end + + def _serializable_hash + return nil if @object.nil? + @node = attributes + include_associations! if _embed + @node + end + + def perform_caching? + perform_caching && cache && object.respond_to?(:cache_key) + end + + def expand_cache_key(*args) + ActiveSupport::Cache.expand_cache_key(args) + end + # Use ActiveSupport::Notifications to send events to external systems. # The event name is: name.class_name.serializer def instrument(name, payload = {}, &block) diff --git a/test/caching_test.rb b/test/caching_test.rb new file mode 100644 index 00000000..614c407d --- /dev/null +++ b/test/caching_test.rb @@ -0,0 +1,97 @@ +require "test_helper" + +class CachingTest < ActiveModel::TestCase + class NullStore + def fetch(key) + return store[key] if store[key] + + store[key] = yield + end + + def clear + store.clear + end + + def store + @store ||= {} + end + + def read(key) + store[key] + end + end + + class Programmer + def name + 'Adam' + end + + def skills + %w(ruby) + end + + def cache_key + name + end + + def read_attribute_for_serialization(name) + send name + end + end + + def test_serializers_have_a_cache_store + ActiveModel::Serializer.cache = NullStore.new + + assert ActiveModel::Serializer.cache + end + + def test_serializers_can_enable_caching + serializer = Class.new(ActiveModel::Serializer) do + cache true + end + + assert serializer.perform_caching + end + + def test_serializers_cache_serializable_hash + serializer = Class.new(ActiveModel::Serializer) do + cache true + attributes :name, :skills + + def self.to_s + 'serializer' + end + end + + serializer.cache = NullStore.new + instance = serializer.new Programmer.new + + instance.to_json + + assert_equal({ + :name => 'Adam', + :skills => ['ruby'], + }, serializer.cache.read('serializer/Adam/serializable-hash')) + end + + def test_serializers_cache_to_json + serializer = Class.new(ActiveModel::Serializer) do + cache true + attributes :name, :skills + + def self.to_s + 'serializer' + end + end + + serializer.cache = NullStore.new + instance = serializer.new Programmer.new + + instance.to_json + + assert_equal({ + :name => 'Adam', + :skills => ['ruby'], + }.to_json, serializer.cache.read('serializer/Adam/to-json')) + end +end From e923174a2653ae57758a3a0cb1d3ffe641fc19b6 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Wed, 11 Jul 2012 15:51:25 +0200 Subject: [PATCH 2/9] #cache_key delegates to #object by default --- lib/active_model/serializer.rb | 16 +++++++++++++--- test/caching_test.rb | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 332a7f3e..8415a8d3 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -276,7 +276,7 @@ module ActiveModel def to_json(*args) if perform_caching? - cache.fetch expand_cache_key([self.class.to_s.underscore, object.cache_key, 'to-json']) do + cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) do super end else @@ -305,7 +305,7 @@ module ActiveModel <<<<<<< HEAD ======= if perform_caching? - cache.fetch expand_cache_key([self.class.to_s.underscore, object.cache_key, 'serializable-hash']) do + cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-hash']) do _serializable_hash end else @@ -441,7 +441,17 @@ module ActiveModel end def perform_caching? - perform_caching && cache && object.respond_to?(:cache_key) + perform_caching && cache && cache_key + end + + # Override this method if you want to create a key based on associations + # Here's an example: your serializer has associations and the scope + # + # def cache_key + # [ object, scope, scope.comments ] + # end + def cache_key + object.try :cache_key end def expand_cache_key(*args) diff --git a/test/caching_test.rb b/test/caching_test.rb index 614c407d..5c31373f 100644 --- a/test/caching_test.rb +++ b/test/caching_test.rb @@ -94,4 +94,27 @@ class CachingTest < ActiveModel::TestCase :skills => ['ruby'], }.to_json, serializer.cache.read('serializer/Adam/to-json')) end + + def test_can_use_defined_cache_key + serializer = Class.new(ActiveModel::Serializer) do + cache true + attributes :name, :skills + + def self.to_s + 'serializer' + end + + def cache_key + 'custom-key' + end + end + + serializer.cache = NullStore.new + instance = serializer.new Programmer.new + + instance.to_json + + assert serializer.cache.read('serializer/custom-key/to-json') + assert serializer.cache.read('serializer/custom-key/serializable-hash') + end end From 535a33a33b9bb2e0115eb4b6c4638ba1034077eb Mon Sep 17 00:00:00 2001 From: twinturbo Date: Wed, 11 Jul 2012 16:51:15 +0200 Subject: [PATCH 3/9] Have to opt in with #cache_key --- lib/active_model/serializer.rb | 12 +----------- test/caching_test.rb | 12 ++++++++---- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 8415a8d3..3b71b758 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -441,17 +441,7 @@ module ActiveModel end def perform_caching? - perform_caching && cache && cache_key - end - - # Override this method if you want to create a key based on associations - # Here's an example: your serializer has associations and the scope - # - # def cache_key - # [ object, scope, scope.comments ] - # end - def cache_key - object.try :cache_key + perform_caching && cache && try(:cache_key) end def expand_cache_key(*args) diff --git a/test/caching_test.rb b/test/caching_test.rb index 5c31373f..f3a02144 100644 --- a/test/caching_test.rb +++ b/test/caching_test.rb @@ -30,10 +30,6 @@ class CachingTest < ActiveModel::TestCase %w(ruby) end - def cache_key - name - end - def read_attribute_for_serialization(name) send name end @@ -61,6 +57,10 @@ class CachingTest < ActiveModel::TestCase def self.to_s 'serializer' end + + def cache_key + object.name + end end serializer.cache = NullStore.new @@ -82,6 +82,10 @@ class CachingTest < ActiveModel::TestCase def self.to_s 'serializer' end + + def cache_key + object.name + end end serializer.cache = NullStore.new From e3888f0a40c1764466851fe6a0a5475b1c01dbea Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sat, 14 Jul 2012 14:19:08 +0200 Subject: [PATCH 4/9] Clean up interface inconistency --- lib/active_model/serializer.rb | 4 ++-- test/caching_test.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 3b71b758..72228b19 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -74,7 +74,7 @@ module ActiveModel class << self # set peform caching like root - def cache(value = true) + def cached(value = true) self.perform_caching = value end @@ -441,7 +441,7 @@ module ActiveModel end def perform_caching? - perform_caching && cache && try(:cache_key) + perform_caching && cache && respond_to?(:cache_key) end def expand_cache_key(*args) diff --git a/test/caching_test.rb b/test/caching_test.rb index f3a02144..53327985 100644 --- a/test/caching_test.rb +++ b/test/caching_test.rb @@ -38,12 +38,12 @@ class CachingTest < ActiveModel::TestCase def test_serializers_have_a_cache_store ActiveModel::Serializer.cache = NullStore.new - assert ActiveModel::Serializer.cache + assert_kind_of NullStore, ActiveModel::Serializer.cache end def test_serializers_can_enable_caching serializer = Class.new(ActiveModel::Serializer) do - cache true + cached true end assert serializer.perform_caching @@ -51,7 +51,7 @@ class CachingTest < ActiveModel::TestCase def test_serializers_cache_serializable_hash serializer = Class.new(ActiveModel::Serializer) do - cache true + cached true attributes :name, :skills def self.to_s @@ -76,7 +76,7 @@ class CachingTest < ActiveModel::TestCase def test_serializers_cache_to_json serializer = Class.new(ActiveModel::Serializer) do - cache true + cached true attributes :name, :skills def self.to_s @@ -101,7 +101,7 @@ class CachingTest < ActiveModel::TestCase def test_can_use_defined_cache_key serializer = Class.new(ActiveModel::Serializer) do - cache true + cached true attributes :name, :skills def self.to_s From 5027f044410568baec300fa0acd7600fb4f64f3d Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sat, 14 Jul 2012 14:37:28 +0200 Subject: [PATCH 5/9] Update railtie --- lib/active_model_serializers.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 3abb9c1e..992a4713 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -23,6 +23,11 @@ if defined?(Rails) include app.routes.url_helpers end end + + initializer "caching.active_model_serializer" do |app| + ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching + ActiveModel::Serializer.cache = Rails.cache + end end end end From b854d492983fd8c1b708bfab570558c934eba774 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sat, 9 Feb 2013 21:03:11 +0100 Subject: [PATCH 6/9] Rebase against master --- lib/active_model/serializer.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 72228b19..7c1c1409 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -302,8 +302,6 @@ module ActiveModel # Returns a hash representation of the serializable # object without the root. def serializable_hash -<<<<<<< HEAD -======= if perform_caching? cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-hash']) do _serializable_hash @@ -455,7 +453,7 @@ module ActiveModel ActiveSupport::Notifications.instrument(event_name, payload, &block) end end - + # DefaultSerializer # # Provides a constant interface for all items, particularly From bd90af0e49bb242f27dd277057f460c96b4c58dc Mon Sep 17 00:00:00 2001 From: twinturbo Date: Thu, 14 Mar 2013 22:41:34 +0100 Subject: [PATCH 7/9] Remove commented code --- lib/active_model/serializer.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7c1c1409..b49b8cb5 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -421,16 +421,6 @@ module ActiveModel alias :read_attribute_for_serialization :send - # def _serializable_hash - # instrument(:serialize, :serializer => self.class.name) do - # node = attributes - # instrument :associations do - # include_associations!(node) if _embed - # end - # node - # end - # end - def _serializable_hash return nil if @object.nil? @node = attributes From c3966fe741136903d3a94b89d9d6df0bfb2871a9 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Thu, 14 Mar 2013 22:54:05 +0100 Subject: [PATCH 8/9] ArraySerializer is also cached --- lib/active_model/array_serializer.rb | 76 +++++++++++++++++++++------- lib/active_model_serializers.rb | 3 ++ test/caching_test.rb | 22 ++++++++ 3 files changed, 82 insertions(+), 19 deletions(-) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index e92ad04a..a3b760cf 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -19,26 +19,18 @@ module ActiveModel class_attribute :root - def initialize(object, options={}) - @object, @options = object, options + class_attribute :cache + class_attribute :perform_caching + + class << self + # set peform caching like root + def cached(value = true) + self.perform_caching = value + end end - def serializable_array - @object.map do |item| - if @options.has_key? :each_serializer - serializer = @options[:each_serializer] - elsif item.respond_to?(:active_model_serializer) - serializer = item.active_model_serializer - end - - serializable = serializer ? serializer.new(item, @options) : DefaultSerializer.new(item) - - if serializable.respond_to?(:serializable_hash) - serializable.serializable_hash - else - serializable.as_json - end - end + def initialize(object, options={}) + @object, @options = object, options end def meta_key @@ -61,6 +53,52 @@ module ActiveModel serializable_array end end - end + def to_json(*args) + if perform_caching? + cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) do + super + end + else + super + end + end + + def serializable_array + if perform_caching? + cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-array']) do + _serializable_array + end + else + _serializable_array + end + end + + private + def _serializable_array + @object.map do |item| + if @options.has_key? :each_serializer + serializer = @options[:each_serializer] + elsif item.respond_to?(:active_model_serializer) + serializer = item.active_model_serializer + end + + serializable = serializer ? serializer.new(item, @options) : DefaultSerializer.new(item) + + if serializable.respond_to?(:serializable_hash) + serializable.serializable_hash + else + serializable.as_json + end + end + end + + def expand_cache_key(*args) + ActiveSupport::Cache.expand_cache_key(args) + end + + def perform_caching? + perform_caching && cache && respond_to?(:cache_key) + end + end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 992a4713..b904d7d7 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -26,7 +26,10 @@ if defined?(Rails) initializer "caching.active_model_serializer" do |app| ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching + ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching + ActiveModel::Serializer.cache = Rails.cache + ActiveModel::ArraySerializer.cache = Rails.cache end end end diff --git a/test/caching_test.rb b/test/caching_test.rb index 53327985..e7ec5816 100644 --- a/test/caching_test.rb +++ b/test/caching_test.rb @@ -121,4 +121,26 @@ class CachingTest < ActiveModel::TestCase assert serializer.cache.read('serializer/custom-key/to-json') assert serializer.cache.read('serializer/custom-key/serializable-hash') end + + def test_array_serializer_uses_cache + serializer = Class.new(ActiveModel::ArraySerializer) do + cached true + + def self.to_s + 'array_serializer' + end + + def cache_key + 'cache-key' + end + end + + serializer.cache = NullStore.new + instance = serializer.new [Programmer.new] + + instance.to_json + + assert serializer.cache.read('array_serializer/cache-key/serializable-array') + assert serializer.cache.read('array_serializer/cache-key/to-json') + end end From 4a5e1e1ae5d9b996c2d3a02579c98d5d85d23a44 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Fri, 15 Mar 2013 08:41:39 +0100 Subject: [PATCH 9/9] Attemp to fix bad test --- test/caching_test.rb | 60 ++++---------------------------------------- 1 file changed, 5 insertions(+), 55 deletions(-) diff --git a/test/caching_test.rb b/test/caching_test.rb index e7ec5816..869f0f93 100644 --- a/test/caching_test.rb +++ b/test/caching_test.rb @@ -49,7 +49,7 @@ class CachingTest < ActiveModel::TestCase assert serializer.perform_caching end - def test_serializers_cache_serializable_hash + def test_serializers_use_cache serializer = Class.new(ActiveModel::Serializer) do cached true attributes :name, :skills @@ -68,58 +68,8 @@ class CachingTest < ActiveModel::TestCase instance.to_json - assert_equal({ - :name => 'Adam', - :skills => ['ruby'], - }, serializer.cache.read('serializer/Adam/serializable-hash')) - end - - def test_serializers_cache_to_json - serializer = Class.new(ActiveModel::Serializer) do - cached true - attributes :name, :skills - - def self.to_s - 'serializer' - end - - def cache_key - object.name - end - end - - serializer.cache = NullStore.new - instance = serializer.new Programmer.new - - instance.to_json - - assert_equal({ - :name => 'Adam', - :skills => ['ruby'], - }.to_json, serializer.cache.read('serializer/Adam/to-json')) - end - - def test_can_use_defined_cache_key - serializer = Class.new(ActiveModel::Serializer) do - cached true - attributes :name, :skills - - def self.to_s - 'serializer' - end - - def cache_key - 'custom-key' - end - end - - serializer.cache = NullStore.new - instance = serializer.new Programmer.new - - instance.to_json - - assert serializer.cache.read('serializer/custom-key/to-json') - assert serializer.cache.read('serializer/custom-key/serializable-hash') + assert_equal(instance.serializable_hash, serializer.cache.read('serializer/Adam/serializable-hash')) + assert_equal(instance.to_json, serializer.cache.read('serializer/Adam/to-json')) end def test_array_serializer_uses_cache @@ -140,7 +90,7 @@ class CachingTest < ActiveModel::TestCase instance.to_json - assert serializer.cache.read('array_serializer/cache-key/serializable-array') - assert serializer.cache.read('array_serializer/cache-key/to-json') + assert_equal instance.serializable_array, serializer.cache.read('array_serializer/cache-key/serializable-array') + assert_equal instance.to_json, serializer.cache.read('array_serializer/cache-key/to-json') end end