diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 48f57edce..789cfd56b 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -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' @@ -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 `'" - # AND - # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `'" - # 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 @@ -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 ActionController::Base.cache_store - # 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 @@ -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 = [] @@ -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: diff --git a/lib/active_model/serializer/caching.rb b/lib/active_model/serializer/caching.rb new file mode 100644 index 000000000..e8db6f27c --- /dev/null +++ b/lib/active_model/serializer/caching.rb @@ -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 `'" + # AND + # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `'" + # 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 ActionController::Base.cache_store + # 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