|
1 | 1 | module ActiveModel
|
2 | 2 | class Serializer
|
| 3 | + UndefinedCacheKey = Class.new(StandardError) |
3 | 4 | module Caching
|
4 | 5 | extend ActiveSupport::Concern
|
5 | 6 |
|
@@ -62,6 +63,14 @@ def _skip_digest?
|
62 | 63 | _cache_options && _cache_options[:skip_digest]
|
63 | 64 | end
|
64 | 65 |
|
| 66 | + def cached_attributes |
| 67 | + _cache_only ? _cache_only : _attributes - _cache_except |
| 68 | + end |
| 69 | + |
| 70 | + def non_cached_attributes |
| 71 | + _attributes - cached_attributes |
| 72 | + end |
| 73 | + |
65 | 74 | # @api private
|
66 | 75 | # Used by FragmentCache on the CachedSerializer
|
67 | 76 | # to call attribute methods on the fragmented cached serializer.
|
@@ -145,6 +154,173 @@ def fragment_cache_enabled?
|
145 | 154 | perform_caching? && cache_store &&
|
146 | 155 | (_cache_only && !_cache_except || !_cache_only && _cache_except)
|
147 | 156 | end
|
| 157 | + |
| 158 | + # Read cache from cache_store |
| 159 | + # @return [Hash] |
| 160 | + def cache_read_multi(collection_serializer, adapter_instance, include_tree) |
| 161 | + return {} if ActiveModelSerializers.config.cache_store.blank? |
| 162 | + |
| 163 | + keys = object_cache_keys(collection_serializer, adapter_instance, include_tree) |
| 164 | + |
| 165 | + return {} if keys.blank? |
| 166 | + |
| 167 | + ActiveModelSerializers.config.cache_store.read_multi(*keys) |
| 168 | + end |
| 169 | + |
| 170 | + # Find all cache_key for the collection_serializer |
| 171 | + # @param serializers [ActiveModel::Serializer::CollectionSerializer] |
| 172 | + # @param adapter_instance [ActiveModelSerializers::Adapter::Base] |
| 173 | + # @param include_tree [ActiveModel::Serializer::IncludeTree] |
| 174 | + # @return [Array] all cache_key of collection_serializer |
| 175 | + def object_cache_keys(collection_serializer, adapter_instance, include_tree) |
| 176 | + cache_keys = [] |
| 177 | + |
| 178 | + collection_serializer.each do |serializer| |
| 179 | + cache_keys << object_cache_key(serializer, adapter_instance) |
| 180 | + |
| 181 | + serializer.associations(include_tree).each do |association| |
| 182 | + if association.serializer.respond_to?(:each) |
| 183 | + association.serializer.each do |sub_serializer| |
| 184 | + cache_keys << object_cache_key(sub_serializer, adapter_instance) |
| 185 | + end |
| 186 | + else |
| 187 | + cache_keys << object_cache_key(association.serializer, adapter_instance) |
| 188 | + end |
| 189 | + end |
| 190 | + end |
| 191 | + |
| 192 | + cache_keys.compact.uniq |
| 193 | + end |
| 194 | + |
| 195 | + # @return [String, nil] the cache_key of the serializer or nil |
| 196 | + def object_cache_key(serializer, adapter_instance) |
| 197 | + return unless serializer.present? && serializer.object.present? |
| 198 | + |
| 199 | + serializer.class.cache_enabled? ? serializer.cache_key(adapter_instance) : nil |
| 200 | + end |
| 201 | + end |
| 202 | + |
| 203 | + # Get attributes from @cached_attributes |
| 204 | + # @return [Hash] cached attributes |
| 205 | + # def cached_attributes(fields, adapter_instance) |
| 206 | + def cached_fields(fields, adapter_instance) |
| 207 | + cache_check(adapter_instance) do |
| 208 | + attributes(fields) |
| 209 | + end |
| 210 | + end |
| 211 | + |
| 212 | + def cache_check(adapter_instance) |
| 213 | + if self.class.cache_enabled? |
| 214 | + self.class.cache_store.fetch(cache_key(adapter_instance), self.class._cache_options) do |
| 215 | + yield |
| 216 | + end |
| 217 | + elsif self.class.fragment_cache_enabled? |
| 218 | + fetch_fragment_cache(adapter_instance) |
| 219 | + else |
| 220 | + yield |
| 221 | + end |
| 222 | + end |
| 223 | + |
| 224 | + # 1. Create a CachedSerializer and NonCachedSerializer from the serializer class |
| 225 | + # 2. Serialize the above two with the given adapter |
| 226 | + # 3. Pass their serializations to the adapter +::fragment_cache+ |
| 227 | + # |
| 228 | + # It will split the serializer into two, one that will be cached and one that will not |
| 229 | + # |
| 230 | + # Given a resource name |
| 231 | + # 1. Dynamically creates a CachedSerializer and NonCachedSerializer |
| 232 | + # for a given class 'name' |
| 233 | + # 2. Call |
| 234 | + # CachedSerializer.cache(serializer._cache_options) |
| 235 | + # CachedSerializer.fragmented(serializer) |
| 236 | + # NonCachedSerializer.cache(serializer._cache_options) |
| 237 | + # 3. Build a hash keyed to the +cached+ and +non_cached+ serializers |
| 238 | + # 4. Call +cached_attributes+ on the serializer class and the above hash |
| 239 | + # 5. Return the hash |
| 240 | + # |
| 241 | + # @example |
| 242 | + # When +name+ is <tt>User::Admin</tt> |
| 243 | + # creates the Serializer classes (if they don't exist). |
| 244 | + # CachedUser_AdminSerializer |
| 245 | + # NonCachedUser_AdminSerializer |
| 246 | + # |
| 247 | + # Given a hash of its cached and non-cached serializers |
| 248 | + # 1. Determine cached attributes from serializer class options |
| 249 | + # 2. Add cached attributes to cached Serializer |
| 250 | + # 3. Add non-cached attributes to non-cached Serializer |
| 251 | + def fetch_fragment_cache(adapter_instance) |
| 252 | + serializer_class_name = self.class.name.gsub('::'.freeze, '_'.freeze) |
| 253 | + self.class._cache_options ||= {} |
| 254 | + self.class._cache_options[:key] = self.class._cache_key if self.class._cache_key |
| 255 | + |
| 256 | + cached_serializer = _get_or_create_fragment_cached_serializer(serializer_class_name) |
| 257 | + cached_hash = ActiveModelSerializers::SerializableResource.new( |
| 258 | + object, |
| 259 | + serializer: cached_serializer, |
| 260 | + adapter: adapter_instance.class |
| 261 | + ).serializable_hash |
| 262 | + |
| 263 | + non_cached_serializer = _get_or_create_fragment_non_cached_serializer(serializer_class_name) |
| 264 | + non_cached_hash = ActiveModelSerializers::SerializableResource.new( |
| 265 | + object, |
| 266 | + serializer: non_cached_serializer, |
| 267 | + adapter: adapter_instance.class |
| 268 | + ).serializable_hash |
| 269 | + |
| 270 | + # Merge both results |
| 271 | + adapter_instance.fragment_cache(cached_hash, non_cached_hash) |
| 272 | + end |
| 273 | + |
| 274 | + def _get_or_create_fragment_cached_serializer(serializer_class_name) |
| 275 | + cached_serializer = _get_or_create_fragment_serializer "Cached#{serializer_class_name}" |
| 276 | + cached_serializer.cache(self.class._cache_options) |
| 277 | + cached_serializer.type(self.class._type) |
| 278 | + cached_serializer.fragmented(self) |
| 279 | + self.class.cached_attributes.each do |attribute| |
| 280 | + options = self.class._attributes_keys[attribute] || {} |
| 281 | + cached_serializer.attribute(attribute, options) |
| 282 | + end |
| 283 | + cached_serializer |
| 284 | + end |
| 285 | + |
| 286 | + def _get_or_create_fragment_non_cached_serializer(serializer_class_name) |
| 287 | + non_cached_serializer = _get_or_create_fragment_serializer "NonCached#{serializer_class_name}" |
| 288 | + non_cached_serializer.type(self.class._type) |
| 289 | + non_cached_serializer.fragmented(self) |
| 290 | + self.class.non_cached_attributes.each do |attribute| |
| 291 | + options = self.class._attributes_keys[attribute] || {} |
| 292 | + non_cached_serializer.attribute(attribute, options) |
| 293 | + end |
| 294 | + non_cached_serializer |
| 295 | + end |
| 296 | + |
| 297 | + def _get_or_create_fragment_serializer(name) |
| 298 | + return Object.const_get(name) if Object.const_defined?(name) |
| 299 | + Object.const_set(name, Class.new(ActiveModel::Serializer)) |
| 300 | + end |
| 301 | + |
| 302 | + def cache_key(adapter_instance) |
| 303 | + return @cache_key if defined?(@cache_key) |
| 304 | + |
| 305 | + parts = [] |
| 306 | + parts << object_cache_key |
| 307 | + parts << adapter_instance.cached_name |
| 308 | + parts << self.class._cache_digest unless self.class._skip_digest? |
| 309 | + @cache_key = parts.join('/') |
| 310 | + end |
| 311 | + |
| 312 | + # Use object's cache_key if available, else derive a key from the object |
| 313 | + # Pass the `key` option to the `cache` declaration or override this method to customize the cache key |
| 314 | + def object_cache_key |
| 315 | + if object.respond_to?(:cache_key) |
| 316 | + object.cache_key |
| 317 | + elsif (serializer_cache_key = (self.class._cache_key || self.class._cache_options[:key])) |
| 318 | + object_time_safe = object.updated_at |
| 319 | + object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime) |
| 320 | + "#{serializer_cache_key}/#{object.id}-#{object_time_safe}" |
| 321 | + else |
| 322 | + fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{self.class}.cache'" |
| 323 | + end |
148 | 324 | end
|
149 | 325 | end
|
150 | 326 | end
|
|
0 commit comments