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/serializer.rb b/lib/active_model/serializer.rb index 66dbde81..b49b8cb5 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 cached(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, 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,13 @@ 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 + if perform_caching? + cache.fetch expand_cache_key([self.class.to_s.underscore, cache_key, 'serializable-hash']) do + _serializable_hash + end + else + _serializable_hash + end end def include_associations! @@ -400,6 +421,21 @@ module ActiveModel alias :read_attribute_for_serialization :send + def _serializable_hash + return nil if @object.nil? + @node = attributes + include_associations! if _embed + @node + end + + def perform_caching? + perform_caching && cache && 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) @@ -407,7 +443,7 @@ module ActiveModel ActiveSupport::Notifications.instrument(event_name, payload, &block) end end - + # DefaultSerializer # # Provides a constant interface for all items, particularly diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 3abb9c1e..b904d7d7 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -23,6 +23,14 @@ 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::ArraySerializer.perform_caching = app.config.action_controller.perform_caching + + ActiveModel::Serializer.cache = Rails.cache + ActiveModel::ArraySerializer.cache = Rails.cache + end end end end diff --git a/test/caching_test.rb b/test/caching_test.rb new file mode 100644 index 00000000..869f0f93 --- /dev/null +++ b/test/caching_test.rb @@ -0,0 +1,96 @@ +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 read_attribute_for_serialization(name) + send name + end + end + + def test_serializers_have_a_cache_store + ActiveModel::Serializer.cache = NullStore.new + + assert_kind_of NullStore, ActiveModel::Serializer.cache + end + + def test_serializers_can_enable_caching + serializer = Class.new(ActiveModel::Serializer) do + cached true + end + + assert serializer.perform_caching + end + + def test_serializers_use_cache + 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(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 + 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_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