Skip to content

Commit

Permalink
Extract caching into its own module.
Browse files Browse the repository at this point in the history
  • Loading branch information
beauby committed Dec 28, 2015
1 parent bb67735 commit fd06a8a
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 89 deletions.
98 changes: 9 additions & 89 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'active_model/serializer/include_tree'
require 'active_model/serializer/associations'
require 'active_model/serializer/attributes'
require 'active_model/serializer/caching'
require 'active_model/serializer/configuration'
require 'active_model/serializer/fieldset'
require 'active_model/serializer/lint'
Expand All @@ -15,63 +16,19 @@ class Serializer
include Configuration
include Associations
include Attributes
include Caching
require 'active_model/serializer/adapter'

# Matches
# "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
# AND
# "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
# AS
# c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
CALLER_FILE = /
\A # start of string
.+ # file path (one or more characters)
(?= # stop previous match when
:\d+ # a colon is followed by one or more digits
:in # followed by a colon followed by in
)
/x

# Hashes contents of file for +_cache_digest+
def self.digest_caller_file(caller_line)
serializer_file_path = caller_line[CALLER_FILE]
serializer_file_contents = IO.read(serializer_file_path)
Digest::MD5.hexdigest(serializer_file_contents)
rescue TypeError, Errno::ENOENT
warn <<-EOF.strip_heredoc
Cannot digest non-existent file: '#{caller_line}'.
Please set `::_cache_digest` of the serializer
if you'd like to cache it.
EOF
''.freeze
end

with_options instance_writer: false, instance_reader: false do |serializer|
serializer.class_attribute :_type, instance_reader: true
serializer.class_attribute :_links # @api private : links definitions, @see Serializer#link
serializer.class_attribute :_links # @api private : links definitions, @see Serializer#link
self._links ||= {}
serializer.class_attribute :_cache # @api private : the cache object
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key
serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists cached_attributes. Cannot combine with except
serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists cached_attributes. Cannot combine with only
serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
# _cache_options include:
# expires_in
# compress
# force
# race_condition_ttl
# Passed to ::_cache as
# serializer._cache.fetch(cache_key, @klass._cache_options)
serializer.class_attribute :_cache_digest # @api private : Generated
end

# Serializers inherit _attribute_mappings, _reflections, and _links.
# Generates a unique digest for each serializer at load.
def self.inherited(base)
caller_line = caller.first
base._links = _links.dup
base._cache_digest = digest_caller_file(caller_line)
super
end

Expand All @@ -86,43 +43,6 @@ def self.link(name, value = nil, &block)
_links[name] = block || value
end

# @api private
# Used by FragmentCache on the CachedSerializer
# to call attribute methods on the fragmented cached serializer.
def self.fragmented(serializer)
self._fragmented = serializer
end

# Enables a serializer to be automatically cached
#
# Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
# when Rails.configuration.action_controller.perform_caching
#
# @params options [Hash] with valid keys:
# key : @see ::_cache_key
# only : @see ::_cache_only
# except : @see ::_cache_except
# skip_digest : does not include digest in cache_key
# all else : @see ::_cache_options
#
# @example
# class PostSerializer < ActiveModel::Serializer
# cache key: 'post', expires_in: 3.hours
# attributes :title, :body
#
# has_many :comments
# end
#
# @todo require less code comments. See
# https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
def self.cache(options = {})
self._cache = ActiveModelSerializers.config.cache_store if ActiveModelSerializers.config.perform_caching
self._cache_key = options.delete(:key)
self._cache_only = options.delete(:only)
self._cache_except = options.delete(:except)
self._cache_options = (options.empty?) ? nil : options
end

# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
# @return [ActiveModel::Serializer]
# Preferentially returns
Expand All @@ -145,12 +65,6 @@ def self.adapter
ActiveModel::Serializer::Adapter.lookup(config.adapter)
end

# Used to cache serializer name => serializer class
# when looked up by Serializer.get_serializer_for.
def self.serializers_cache
@serializers_cache ||= ThreadSafe::Cache.new
end

# @api private
def self.serializer_lookup_chain_for(klass)
chain = []
Expand All @@ -165,6 +79,12 @@ def self.serializer_lookup_chain_for(klass)
chain
end

# Used to cache serializer name => serializer class
# when looked up by Serializer.get_serializer_for.
def self.serializers_cache
@serializers_cache ||= ThreadSafe::Cache.new
end

# @api private
# Find a serializer from a class and caches the lookup.
# Preferentially retuns:
Expand Down
100 changes: 100 additions & 0 deletions lib/active_model/serializer/caching.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
module ActiveModel
class Serializer
module Caching
extend ActiveSupport::Concern

included do
with_options instance_writer: false, instance_reader: false do |serializer|
serializer.class_attribute :_cache # @api private : the cache object
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key
serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists cached_attributes. Cannot combine with except
serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists cached_attributes. Cannot combine with only
serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
# _cache_options include:
# expires_in
# compress
# force
# race_condition_ttl
# Passed to ::_cache as
# serializer._cache.fetch(cache_key, @klass._cache_options)
serializer.class_attribute :_cache_digest # @api private : Generated
end
end

# Matches
# "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
# AND
# "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
# AS
# c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
CALLER_FILE = /
\A # start of string
.+ # file path (one or more characters)
(?= # stop previous match when
:\d+ # a colon is followed by one or more digits
:in # followed by a colon followed by in
)
/x

module ClassMethods
def inherited(base)
super
caller_line = caller[1]
base._cache_digest = digest_caller_file(caller_line)
end

# Hashes contents of file for +_cache_digest+
def digest_caller_file(caller_line)
serializer_file_path = caller_line[CALLER_FILE]
serializer_file_contents = IO.read(serializer_file_path)
Digest::MD5.hexdigest(serializer_file_contents)
rescue TypeError, Errno::ENOENT
warn <<-EOF.strip_heredoc
Cannot digest non-existent file: '#{caller_line}'.
Please set `::_cache_digest` of the serializer
if you'd like to cache it.
EOF
''.freeze
end

# @api private
# Used by FragmentCache on the CachedSerializer
# to call attribute methods on the fragmented cached serializer.
def fragmented(serializer)
self._fragmented = serializer
end

# Enables a serializer to be automatically cached
#
# Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
# when Rails.configuration.action_controller.perform_caching
#
# @params options [Hash] with valid keys:
# key : @see ::_cache_key
# only : @see ::_cache_only
# except : @see ::_cache_except
# skip_digest : does not include digest in cache_key
# all else : @see ::_cache_options
#
# @example
# class PostSerializer < ActiveModel::Serializer
# cache key: 'post', expires_in: 3.hours
# attributes :title, :body
#
# has_many :comments
# end
#
# @todo require less code comments. See
# https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
def cache(options = {})
self._cache = ActiveModelSerializers.config.cache_store if ActiveModelSerializers.config.perform_caching
self._cache_key = options.delete(:key)
self._cache_only = options.delete(:only)
self._cache_except = options.delete(:except)
self._cache_options = (options.empty?) ? nil : options
end
end
end
end
end

0 comments on commit fd06a8a

Please sign in to comment.