Rails Cache Cache Created as ActiveRecord :: Relation

I am trying to create a fragment cache (using the Dalli / Memcached repository), however, the key is generated using "#" as part of the key, so Rails does not seem to recognize that the cache value exists and ends up in the database.

My cache key in the view is as follows:

cache([@jobs, "index"]) do 

The controller has:

 @jobs = @current_tenant.active_jobs 

With the actual active record request:

 def active_jobs self.jobs.where("published = ? and expiration_date >= ?", true, Date.today).order("(featured and created_at > now() - interval '" + self.pinned_time_limit.to_s + " days') desc nulls last, created_at desc") end 

Looking at the rails server, I see that the cache is being read, but SQL Query still works:

 Cache read: views/#<ActiveRecord::Relation:0x007fbabef9cd58>/1-index Read fragment views/#<ActiveRecord::Relation:0x007fbabef9cd58>/1-index (1.0ms) (0.6ms) SELECT COUNT(*) FROM "jobs" WHERE "jobs"."tenant_id" = 1 AND (published = 't' and expiration_date >= '2013-03-03') Job Load (1.2ms) SELECT "jobs".* FROM "jobs" WHERE "jobs"."tenant_id" = 1 AND (published = 't' and expiration_date >= '2013-03-03') ORDER BY (featured and created_at > now() - interval '7 days') desc nulls last, created_at desc 

Any ideas on what I might be doing wrong? I'm sure this should be done with key generation and ActiveRecord :: Relation, but I'm not sure how to do this.

+4
source share
9 answers

I had similar problems, I could not successfully pass the relation to the cache function, and your @jobs variable is the relation.

I have encoded a cache key solution that addresses this issue along with some others that I had. This is mainly due to the generation of the cache key by iterating through the relation.

The full entry on my site is here.

http://mark.stratmann.me/content_items/rails-caching-strategy-using-key-based-approach

In conclusion, I added the get_cache_keys function for ActiveRecord :: Base

 module CacheKeys extend ActiveSupport::Concern # Instance Methods def get_cache_key(prefix=nil) cache_key = [] cache_key << prefix if prefix cache_key << self self.class.get_cache_key_children.each do |child| if child.macro == :has_many self.send(child.name).all.each do |child_record| cache_key << child_record.get_cache_key end end if child.macro == :belongs_to cache_key << self.send(child.name).get_cache_key end end return cache_key.flatten end # Class Methods module ClassMethods def cache_key_children(*args) @v_cache_key_children = [] # validate the children args.each do |child| #is it an association association = reflect_on_association(child) if association == nil raise "#{child} is not an association!" end @v_cache_key_children << association end end def get_cache_key_children return @v_cache_key_children ||= [] end end end # include the extension ActiveRecord::Base.send(:include, CacheKeys) 

Now I can create cache fragments by doing

 cache(@model.get_cache_key(['textlabel'])) do 
+2
source

Background:

The problem is that the string representation of the relationship is different every time you run your code:

  |This changes| views/#<ActiveRecord::Relation:0x007fbabef9cd58>/... 

Thus, each time you get a different cache key.

In addition, it is impossible to completely get rid of database queries. (Your own answer is the best you can do)

Decision:

To create a valid key, instead

 cache([@jobs, "index"]) 

do the following:

 cache([@jobs.to_a, "index"]) 

This queries the database and creates an array of models from which cache_key is extracted.

PS: I can swear using the relationships that worked in previous versions of Rails ...

+8
source

We did exactly what you mention in production for about a year. I extracted it in a gem a few months ago:

https://github.com/cmer/scope_cache_key

Basically, it allows you to use an area as part of your cache key. There are significant performance advantages for this, since now you can cache a page containing several records in one cache element, rather than looping each element in the area and receiving caches separately. I believe that combining this with the standard Russian Caching Caching principles is optimal.

+3
source

Although I marked @ mark-stratmann's answer as correct, I did resolve this by simplifying the implementation. I added touch: true for my description of model relationships:

 belongs_to :tenant, touch: true 

and then set the cache key based on the tenant (with the required request parameter):

 <% cache([@current_tenant, params[:query], "#{@current_tenant.id}-index"]) do %> 

Thus, if a new task is added, it also applies to the Tenant cache. Not sure if this is the best route, but it works and seems pretty simple.

+1
source

Im using this code:

 class ActiveRecord::Base def self.cache_key pluck("concat_ws('/', '#{table_name}', group_concat(#{table_name}.id), date_format(max(#{table_name}.updated_at), '%Y%m%d%H%i%s'))").first end def self.updated_at maximum(:updated_at) end end 
+1
source

I did something like Hopsoft, but it uses the method in the Rails Guide as a template. I used the MD5 digest to distinguish between relationships (therefore User.active.cache_key can be distinguished from User.deactivated.cache_key ) and use count and max updated_at to automatically expire the cache when the relationship is updated.

 require "digest/md5" module RelationCacheKey def cache_key model_identifier = name.underscore.pluralize relation_identifier = Digest::MD5.hexdigest(to_sql.downcase) max_updated_at = maximum(:updated_at).try(:utc).try(:to_s, :number) "#{model_identifier}/#{relation_identifier}-#{count}-#{max_updated_at}" end end ActiveRecord::Relation.send :include, RelationCacheKey 
+1
source

maybe this can help you https://github.com/casiodk/class_cacher , it generates cache_key from the model itself, but maybe you can use some of the principles in the code base

0
source

As a starting point, you can try something like this:

 def self.cache_key ["#{model_name.cache_key}-all", "#{count}-#{updated_at.utc.to_s(cache_timestamp_format) rescue 'empty'}" ] * '/' end def self.updated_at maximum :updated_at end 

I have a normalized database where several models refer to the same other model, think about clients, locations, etc., all have addresses using street_id.

With this solution, you can create cache keys based on scope, e.g.

 cache [@client, @client.locations] do # ... end cache [@client, @client.locations.active, 'active'] do # ... end 

and I could just change self.updated top to also include related objects (because has_many does not support โ€œtouchโ€, so if I update the street, it will not be visible in the cache otherwise):

 belongs_to :street def cache_key [street.cache_key, super] * '/' end # ... def self.updated_at [maximum(:updated_at), joins(:street).maximum('streets.updated_at') ].max end 

Until you โ€œrestoreโ€ records and use touch in the property, you should be fine with the assumption that the cache key, consisting of count and max updated_at, is sufficient.

0
source

I am using a simple patch for ActiveRecord :: Relation to generate cache keys for relationships.

 require "digest/md5" module RelationCacheKey def cache_key Digest::MD5.hexdigest to_sql.downcase end end ActiveRecord::Relation.send :include, RelationCacheKey 
-1
source

All Articles