Skip to content

Commit

Permalink
Move CachedSerializer/FragmentCache to AMS namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
bf4 committed Jan 28, 2016
1 parent 25e85bf commit 38de293
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 184 deletions.
3 changes: 1 addition & 2 deletions lib/active_model/serializer/adapter/base.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
require 'active_model/serializer/cached_serializer'
module ActiveModel
class Serializer
module Adapter
Expand Down Expand Up @@ -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
Expand Down
44 changes: 0 additions & 44 deletions lib/active_model/serializer/cached_serializer.rb

This file was deleted.

109 changes: 0 additions & 109 deletions lib/active_model/serializer/fragment_cache.rb

This file was deleted.

2 changes: 2 additions & 0 deletions lib/active_model_serializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
module ActiveModelSerializers
extend ActiveSupport::Autoload
autoload :Model
autoload :CachedSerializer
autoload :FragmentCache
autoload :Callbacks
autoload :Deserialization
autoload :Logging
Expand Down
42 changes: 42 additions & 0 deletions lib/active_model_serializers/cached_serializer.rb
Original file line number Diff line number Diff line change
@@ -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
107 changes: 107 additions & 0 deletions lib/active_model_serializers/fragment_cache.rb
Original file line number Diff line number Diff line change
@@ -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 <tt>User::Admin</tt>
# 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
56 changes: 27 additions & 29 deletions test/serializers/fragment_cache_test.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 38de293

Please sign in to comment.