diff --git a/lib/active_model/serializer/adapter/base.rb b/lib/active_model/serializer/adapter/base.rb index 46f0fea35..5563e9054 100644 --- a/lib/active_model/serializer/adapter/base.rb +++ b/lib/active_model/serializer/adapter/base.rb @@ -1,4 +1,3 @@ -require 'active_model/serializer/cached_serializer' module ActiveModel class Serializer module Adapter @@ -30,7 +29,7 @@ def fragment_cache(cached_hash, non_cached_hash) end def cache_check(serializer) - ActiveModel::Serializer::CachedSerializer.new(serializer).cache_check(self) do + ActiveModelSerializers::CachedSerializer.new(serializer).cache_check(self) do yield end end diff --git a/lib/active_model/serializer/cached_serializer.rb b/lib/active_model/serializer/cached_serializer.rb deleted file mode 100644 index a5eac9541..000000000 --- a/lib/active_model/serializer/cached_serializer.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'active_model/serializer/fragment_cache' -module ActiveModel - class Serializer - class CachedSerializer - def initialize(serializer) - @cached_serializer = serializer - @klass = @cached_serializer.class - end - - def cache_check(adapter_instance) - if cached? - @klass._cache.fetch(cache_key, @klass._cache_options) do - yield - end - elsif fragment_cached? - FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch - else - yield - end - end - - def cached? - @klass._cache && !@klass._cache_only && !@klass._cache_except - end - - def fragment_cached? - @klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except - end - - def cache_key - parts = [] - parts << object_cache_key - parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest] - parts.join('/') - end - - def object_cache_key - object_time_safe = @cached_serializer.object.updated_at - object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) - (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key - end - end - end -end diff --git a/lib/active_model/serializer/fragment_cache.rb b/lib/active_model/serializer/fragment_cache.rb deleted file mode 100644 index 49185f625..000000000 --- a/lib/active_model/serializer/fragment_cache.rb +++ /dev/null @@ -1,109 +0,0 @@ -module ActiveModel - class Serializer - class FragmentCache - attr_reader :serializer - - def initialize(adapter, serializer, options) - @instance_options = options - @adapter = adapter - @serializer = serializer - end - - # TODO: Use Serializable::Resource - # TODO: call +constantize+ less - # 1. Create a CachedSerializer and NonCachedSerializer from the serializer class - # 2. Serialize the above two with the given adapter - # 3. Pass their serializations to the adapter +::fragment_cache+ - def fetch - klass = serializer.class - # It will split the serializer into two, one that will be cached and one that will not - serializers = fragment_serializer(serializer.object.class.name, klass) - - # Instantiate both serializers - cached_serializer = serializers[:cached].constantize.new(serializer.object) - non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object) - - cached_adapter = adapter.class.new(cached_serializer, instance_options) - non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options) - - # Get serializable hash from both - cached_hash = cached_adapter.serializable_hash - non_cached_hash = non_cached_adapter.serializable_hash - - # Merge both results - adapter.fragment_cache(cached_hash, non_cached_hash) - end - - protected - - attr_reader :instance_options, :adapter - - private - - # Given a serializer class and a hash of its cached and non-cached serializers - # 1. Determine cached attributes from serializer class options - # 2. Add cached attributes to cached Serializer - # 3. Add non-cached attributes to non-cached Serializer - def cached_attributes(klass, serializers) - attributes = serializer.class._attributes - cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) } - non_cached_attributes = attributes - cached_attributes - - cached_attributes.each do |attribute| - options = serializer.class._attributes_keys[attribute] - options ||= {} - # Add cached attributes to cached Serializer - serializers[:cached].constantize.attribute(attribute, options) - end - - non_cached_attributes.each do |attribute| - options = serializer.class._attributes_keys[attribute] - options ||= {} - # Add non-cached attributes to non-cached Serializer - serializers[:non_cached].constantize.attribute(attribute, options) - end - end - - # Given a resource name and its serializer's class - # 1. Dyanmically creates a CachedSerializer and NonCachedSerializer - # for a given class 'name' - # 2. Call - # CachedSerializer.cache(serializer._cache_options) - # CachedSerializer.fragmented(serializer) - # NonCachedSerializer.cache(serializer._cache_options) - # 3. Build a hash keyed to the +cached+ and +non_cached+ serializers - # 4. Call +cached_attributes+ on the serializer class and the above hash - # 5. Return the hash - # - # @example - # When +name+ is User::Admin - # creates the Serializer classes (if they don't exist). - # User_AdminCachedSerializer - # User_AdminNonCachedSerializer - # - def fragment_serializer(name, klass) - cached = "#{to_valid_const_name(name)}CachedSerializer" - non_cached = "#{to_valid_const_name(name)}NonCachedSerializer" - - Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached) - Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached) - - klass._cache_options ||= {} - klass._cache_options[:key] = klass._cache_key if klass._cache_key - - cached.constantize.cache(klass._cache_options) - - cached.constantize.fragmented(serializer) - non_cached.constantize.fragmented(serializer) - - serializers = { cached: cached, non_cached: non_cached } - cached_attributes(klass, serializers) - serializers - end - - def to_valid_const_name(name) - name.gsub('::', '_') - end - end - end -end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index d92823b5a..d62d63516 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -4,6 +4,8 @@ module ActiveModelSerializers extend ActiveSupport::Autoload autoload :Model + autoload :CachedSerializer + autoload :FragmentCache autoload :Callbacks autoload :Deserialization autoload :Logging diff --git a/lib/active_model_serializers/cached_serializer.rb b/lib/active_model_serializers/cached_serializer.rb new file mode 100644 index 000000000..6b21fd8dc --- /dev/null +++ b/lib/active_model_serializers/cached_serializer.rb @@ -0,0 +1,42 @@ +require 'active_model_serializers/fragment_cache' +module ActiveModelSerializers + class CachedSerializer + def initialize(serializer) + @cached_serializer = serializer + @klass = @cached_serializer.class + end + + def cache_check(adapter_instance) + if cached? + @klass._cache.fetch(cache_key, @klass._cache_options) do + yield + end + elsif fragment_cached? + FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch + else + yield + end + end + + def cached? + @klass._cache && !@klass._cache_only && !@klass._cache_except + end + + def fragment_cached? + @klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except + end + + def cache_key + parts = [] + parts << object_cache_key + parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest] + parts.join('/') + end + + def object_cache_key + object_time_safe = @cached_serializer.object.updated_at + object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) + (@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key + end + end +end diff --git a/lib/active_model_serializers/fragment_cache.rb b/lib/active_model_serializers/fragment_cache.rb new file mode 100644 index 000000000..627400465 --- /dev/null +++ b/lib/active_model_serializers/fragment_cache.rb @@ -0,0 +1,107 @@ +module ActiveModelSerializers + class FragmentCache + attr_reader :serializer + + def initialize(adapter, serializer, options) + @instance_options = options + @adapter = adapter + @serializer = serializer + end + + # TODO: Use Serializable::Resource + # TODO: call +constantize+ less + # 1. Create a CachedSerializer and NonCachedSerializer from the serializer class + # 2. Serialize the above two with the given adapter + # 3. Pass their serializations to the adapter +::fragment_cache+ + def fetch + klass = serializer.class + # It will split the serializer into two, one that will be cached and one that will not + serializers = fragment_serializer(serializer.object.class.name, klass) + + # Instantiate both serializers + cached_serializer = serializers[:cached].constantize.new(serializer.object) + non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object) + + cached_adapter = adapter.class.new(cached_serializer, instance_options) + non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options) + + # Get serializable hash from both + cached_hash = cached_adapter.serializable_hash + non_cached_hash = non_cached_adapter.serializable_hash + + # Merge both results + adapter.fragment_cache(cached_hash, non_cached_hash) + end + + protected + + attr_reader :instance_options, :adapter + + private + + # Given a serializer class and a hash of its cached and non-cached serializers + # 1. Determine cached attributes from serializer class options + # 2. Add cached attributes to cached Serializer + # 3. Add non-cached attributes to non-cached Serializer + def cached_attributes(klass, serializers) + attributes = serializer.class._attributes + cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) } + non_cached_attributes = attributes - cached_attributes + + cached_attributes.each do |attribute| + options = serializer.class._attributes_keys[attribute] + options ||= {} + # Add cached attributes to cached Serializer + serializers[:cached].constantize.attribute(attribute, options) + end + + non_cached_attributes.each do |attribute| + options = serializer.class._attributes_keys[attribute] + options ||= {} + # Add non-cached attributes to non-cached Serializer + serializers[:non_cached].constantize.attribute(attribute, options) + end + end + + # Given a resource name and its serializer's class + # 1. Dyanmically creates a CachedSerializer and NonCachedSerializer + # for a given class 'name' + # 2. Call + # CachedSerializer.cache(serializer._cache_options) + # CachedSerializer.fragmented(serializer) + # NonCachedSerializer.cache(serializer._cache_options) + # 3. Build a hash keyed to the +cached+ and +non_cached+ serializers + # 4. Call +cached_attributes+ on the serializer class and the above hash + # 5. Return the hash + # + # @example + # When +name+ is User::Admin + # creates the Serializer classes (if they don't exist). + # User_AdminCachedSerializer + # User_AdminNonCachedSerializer + # + def fragment_serializer(name, klass) + cached = "#{to_valid_const_name(name)}CachedSerializer" + non_cached = "#{to_valid_const_name(name)}NonCachedSerializer" + + Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached) + Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached) + + klass._cache_options ||= {} + klass._cache_options[:key] = klass._cache_key if klass._cache_key + + cached.constantize.cache(klass._cache_options) + + cached.constantize.fragmented(serializer) + non_cached.constantize.fragmented(serializer) + + serializers = { cached: cached, non_cached: non_cached } + cached_attributes(klass, serializers) + serializers + end + + def to_valid_const_name(name) + name.gsub('::', '_') + end + end +end diff --git a/test/serializers/fragment_cache_test.rb b/test/serializers/fragment_cache_test.rb index f66def8c5..c0801176f 100644 --- a/test/serializers/fragment_cache_test.rb +++ b/test/serializers/fragment_cache_test.rb @@ -1,35 +1,33 @@ require 'test_helper' -module ActiveModel - class Serializer - class FragmentCacheTest < ActiveSupport::TestCase - def setup - super - @spam = Spam::UnrelatedLink.new(id: 'spam-id-1') - @author = Author.new(name: 'Joao M. D. Moura') - @role = Role.new(name: 'Great Author', description: nil) - @role.author = [@author] - @role_serializer = RoleSerializer.new(@role) - @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) - @role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {}) - @spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {}) - end +module ActiveModelSerializers + class FragmentCacheTest < ActiveSupport::TestCase + def setup + super + @spam = Spam::UnrelatedLink.new(id: 'spam-id-1') + @author = Author.new(name: 'Joao M. D. Moura') + @role = Role.new(name: 'Great Author', description: nil) + @role.author = [@author] + @role_serializer = RoleSerializer.new(@role) + @spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam) + @role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {}) + @spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {}) + end - def test_fragment_fetch_with_virtual_attributes - expected_result = { - id: @role.id, - description: @role.description, - slug: "#{@role.name}-#{@role.id}", - name: @role.name - } - assert_equal(@role_hash.fetch, expected_result) - end + def test_fragment_fetch_with_virtual_attributes + expected_result = { + id: @role.id, + description: @role.description, + slug: "#{@role.name}-#{@role.id}", + name: @role.name + } + assert_equal(@role_hash.fetch, expected_result) + end - def test_fragment_fetch_with_namespaced_object - expected_result = { - id: @spam.id - } - assert_equal(@spam_hash.fetch, expected_result) - end + def test_fragment_fetch_with_namespaced_object + expected_result = { + id: @spam.id + } + assert_equal(@spam_hash.fetch, expected_result) end end end