Skip to content

Commit 38de293

Browse files
committed
Move CachedSerializer/FragmentCache to AMS namespace
1 parent 25e85bf commit 38de293

File tree

7 files changed

+179
-184
lines changed

7 files changed

+179
-184
lines changed

lib/active_model/serializer/adapter/base.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
require 'active_model/serializer/cached_serializer'
21
module ActiveModel
32
class Serializer
43
module Adapter
@@ -30,7 +29,7 @@ def fragment_cache(cached_hash, non_cached_hash)
3029
end
3130

3231
def cache_check(serializer)
33-
ActiveModel::Serializer::CachedSerializer.new(serializer).cache_check(self) do
32+
ActiveModelSerializers::CachedSerializer.new(serializer).cache_check(self) do
3433
yield
3534
end
3635
end

lib/active_model/serializer/cached_serializer.rb

Lines changed: 0 additions & 44 deletions
This file was deleted.

lib/active_model/serializer/fragment_cache.rb

Lines changed: 0 additions & 109 deletions
This file was deleted.

lib/active_model_serializers.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
module ActiveModelSerializers
55
extend ActiveSupport::Autoload
66
autoload :Model
7+
autoload :CachedSerializer
8+
autoload :FragmentCache
79
autoload :Callbacks
810
autoload :Deserialization
911
autoload :Logging
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
require 'active_model_serializers/fragment_cache'
2+
module ActiveModelSerializers
3+
class CachedSerializer
4+
def initialize(serializer)
5+
@cached_serializer = serializer
6+
@klass = @cached_serializer.class
7+
end
8+
9+
def cache_check(adapter_instance)
10+
if cached?
11+
@klass._cache.fetch(cache_key, @klass._cache_options) do
12+
yield
13+
end
14+
elsif fragment_cached?
15+
FragmentCache.new(adapter_instance, @cached_serializer, adapter_instance.instance_options).fetch
16+
else
17+
yield
18+
end
19+
end
20+
21+
def cached?
22+
@klass._cache && !@klass._cache_only && !@klass._cache_except
23+
end
24+
25+
def fragment_cached?
26+
@klass._cache_only && !@klass._cache_except || !@klass._cache_only && @klass._cache_except
27+
end
28+
29+
def cache_key
30+
parts = []
31+
parts << object_cache_key
32+
parts << @klass._cache_digest unless @klass._cache_options && @klass._cache_options[:skip_digest]
33+
parts.join('/')
34+
end
35+
36+
def object_cache_key
37+
object_time_safe = @cached_serializer.object.updated_at
38+
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
39+
(@klass._cache_key) ? "#{@klass._cache_key}/#{@cached_serializer.object.id}-#{object_time_safe}" : @cached_serializer.object.cache_key
40+
end
41+
end
42+
end
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
module ActiveModelSerializers
2+
class FragmentCache
3+
attr_reader :serializer
4+
5+
def initialize(adapter, serializer, options)
6+
@instance_options = options
7+
@adapter = adapter
8+
@serializer = serializer
9+
end
10+
11+
# TODO: Use Serializable::Resource
12+
# TODO: call +constantize+ less
13+
# 1. Create a CachedSerializer and NonCachedSerializer from the serializer class
14+
# 2. Serialize the above two with the given adapter
15+
# 3. Pass their serializations to the adapter +::fragment_cache+
16+
def fetch
17+
klass = serializer.class
18+
# It will split the serializer into two, one that will be cached and one that will not
19+
serializers = fragment_serializer(serializer.object.class.name, klass)
20+
21+
# Instantiate both serializers
22+
cached_serializer = serializers[:cached].constantize.new(serializer.object)
23+
non_cached_serializer = serializers[:non_cached].constantize.new(serializer.object)
24+
25+
cached_adapter = adapter.class.new(cached_serializer, instance_options)
26+
non_cached_adapter = adapter.class.new(non_cached_serializer, instance_options)
27+
28+
# Get serializable hash from both
29+
cached_hash = cached_adapter.serializable_hash
30+
non_cached_hash = non_cached_adapter.serializable_hash
31+
32+
# Merge both results
33+
adapter.fragment_cache(cached_hash, non_cached_hash)
34+
end
35+
36+
protected
37+
38+
attr_reader :instance_options, :adapter
39+
40+
private
41+
42+
# Given a serializer class and a hash of its cached and non-cached serializers
43+
# 1. Determine cached attributes from serializer class options
44+
# 2. Add cached attributes to cached Serializer
45+
# 3. Add non-cached attributes to non-cached Serializer
46+
def cached_attributes(klass, serializers)
47+
attributes = serializer.class._attributes
48+
cached_attributes = (klass._cache_only) ? klass._cache_only : attributes.reject { |attr| klass._cache_except.include?(attr) }
49+
non_cached_attributes = attributes - cached_attributes
50+
51+
cached_attributes.each do |attribute|
52+
options = serializer.class._attributes_keys[attribute]
53+
options ||= {}
54+
# Add cached attributes to cached Serializer
55+
serializers[:cached].constantize.attribute(attribute, options)
56+
end
57+
58+
non_cached_attributes.each do |attribute|
59+
options = serializer.class._attributes_keys[attribute]
60+
options ||= {}
61+
# Add non-cached attributes to non-cached Serializer
62+
serializers[:non_cached].constantize.attribute(attribute, options)
63+
end
64+
end
65+
66+
# Given a resource name and its serializer's class
67+
# 1. Dyanmically creates a CachedSerializer and NonCachedSerializer
68+
# for a given class 'name'
69+
# 2. Call
70+
# CachedSerializer.cache(serializer._cache_options)
71+
# CachedSerializer.fragmented(serializer)
72+
# NonCachedSerializer.cache(serializer._cache_options)
73+
# 3. Build a hash keyed to the +cached+ and +non_cached+ serializers
74+
# 4. Call +cached_attributes+ on the serializer class and the above hash
75+
# 5. Return the hash
76+
#
77+
# @example
78+
# When +name+ is <tt>User::Admin</tt>
79+
# creates the Serializer classes (if they don't exist).
80+
# User_AdminCachedSerializer
81+
# User_AdminNonCachedSerializer
82+
#
83+
def fragment_serializer(name, klass)
84+
cached = "#{to_valid_const_name(name)}CachedSerializer"
85+
non_cached = "#{to_valid_const_name(name)}NonCachedSerializer"
86+
87+
Object.const_set cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(cached)
88+
Object.const_set non_cached, Class.new(ActiveModel::Serializer) unless Object.const_defined?(non_cached)
89+
90+
klass._cache_options ||= {}
91+
klass._cache_options[:key] = klass._cache_key if klass._cache_key
92+
93+
cached.constantize.cache(klass._cache_options)
94+
95+
cached.constantize.fragmented(serializer)
96+
non_cached.constantize.fragmented(serializer)
97+
98+
serializers = { cached: cached, non_cached: non_cached }
99+
cached_attributes(klass, serializers)
100+
serializers
101+
end
102+
103+
def to_valid_const_name(name)
104+
name.gsub('::', '_')
105+
end
106+
end
107+
end
Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
require 'test_helper'
2-
module ActiveModel
3-
class Serializer
4-
class FragmentCacheTest < ActiveSupport::TestCase
5-
def setup
6-
super
7-
@spam = Spam::UnrelatedLink.new(id: 'spam-id-1')
8-
@author = Author.new(name: 'Joao M. D. Moura')
9-
@role = Role.new(name: 'Great Author', description: nil)
10-
@role.author = [@author]
11-
@role_serializer = RoleSerializer.new(@role)
12-
@spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
13-
@role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {})
14-
@spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {})
15-
end
2+
module ActiveModelSerializers
3+
class FragmentCacheTest < ActiveSupport::TestCase
4+
def setup
5+
super
6+
@spam = Spam::UnrelatedLink.new(id: 'spam-id-1')
7+
@author = Author.new(name: 'Joao M. D. Moura')
8+
@role = Role.new(name: 'Great Author', description: nil)
9+
@role.author = [@author]
10+
@role_serializer = RoleSerializer.new(@role)
11+
@spam_serializer = Spam::UnrelatedLinkSerializer.new(@spam)
12+
@role_hash = FragmentCache.new(RoleSerializer.adapter.new(@role_serializer), @role_serializer, {})
13+
@spam_hash = FragmentCache.new(Spam::UnrelatedLinkSerializer.adapter.new(@spam_serializer), @spam_serializer, {})
14+
end
1615

17-
def test_fragment_fetch_with_virtual_attributes
18-
expected_result = {
19-
id: @role.id,
20-
description: @role.description,
21-
slug: "#{@role.name}-#{@role.id}",
22-
name: @role.name
23-
}
24-
assert_equal(@role_hash.fetch, expected_result)
25-
end
16+
def test_fragment_fetch_with_virtual_attributes
17+
expected_result = {
18+
id: @role.id,
19+
description: @role.description,
20+
slug: "#{@role.name}-#{@role.id}",
21+
name: @role.name
22+
}
23+
assert_equal(@role_hash.fetch, expected_result)
24+
end
2625

27-
def test_fragment_fetch_with_namespaced_object
28-
expected_result = {
29-
id: @spam.id
30-
}
31-
assert_equal(@spam_hash.fetch, expected_result)
32-
end
26+
def test_fragment_fetch_with_namespaced_object
27+
expected_result = {
28+
id: @spam.id
29+
}
30+
assert_equal(@spam_hash.fetch, expected_result)
3331
end
3432
end
3533
end

0 commit comments

Comments
 (0)