Lazy-caching

Ruby gem for lazy caching (in background)

View the Project on GitHub iliabylich/lazy-caching

LazyCaching

Gem Version Build Status Code Climate Coverage Status Dependency Status

Ruby gem for lazy caching (in background).

Don't afraid to be lazy.

Currently supports:

Installation

Add this line to your application's Gemfile:

gem 'lazy_caching'

And then execute:

$ bundle

Or install it yourself as:

$ gem install lazy_caching

Usage

First, run lazy_caching config generator

$ rails generate lazy_caching:install

Open the file config/initializers/lazy_caching.rb. It should contain

LazyCaching.setup do |config|
  # option `base` means NO background processing
  config.processor = :delayed_job
  # or :resque for Resque
  # or :delayed_job for DJ
  # or :sidekiq for Sidekiq

  # You can also specify processors for different environments
  # config.processor = {
  #   :development => :resque,
  #   :test => :base,
  #   :production => :delayed_job
  # }

  # You can specify logger
  config.logger = Logger.new(STDOUT) # default value
  # Or set
  # config.logger = Rails.logger
  # Or disable logging.
  # config.logger = nil
end

Run DJ, Resque or Sidekiq workers.


You can use DSL like this

has_cached_attribute do
  # Some DSL
end

DSL:

  1. name
    Just name of your attribute. Required field. Any string or symbol, without spaces.
  2. type
    Type of attribute. Required field. Available values :class or :instance.
  3. processor
    Background processor. Optional field. Available values:
    • :base
    • :delayed_job
    • :resque
    • :sidekiq
    This value can override value from global configuration (in initializer).
  4. expire
    Takes a block. Optional field. Has a sub-DSL.
    1. after
      Using this option you can clear cache for current attribute after specified callbacks.
      Example: after [:save]
    2. before
      Using this option you can clear cache for current attribute before specified callbacks.
      Example: before [:save]
  5. result
    Takes a block. Required field.
    * Context of evaluating of passed block depends on type value.
  6. default
    Takes any value or block. If cache for current cached attribute is blank, you will receive this value.

Examples

So, in your model simply write something like this

- Example for class attribute

class Shop < ActiveRecord::Base

  include LazyCaching::ActiveRecordExtensions
  # don't forget to include this module

  has_cached_attribute do
    name :names
    type :class
    result { pluck(:name) }
  end
end

Shop.product_names.lazy_recache!
# and wait for background processing ....
Shop.product_names.cached
# => ["product1", "product2", "product3"]

- Example for instance attribute

class Profile < ActiveRecord::Base

  include LazyCaching::ActiveRecordExtensions

  has_many :followers

  has_cached_attribute do
    name :followers_counter
    type :instance
    result { followers.count }
  end
end

p = Profile.first
p.followers_counter.lazy_recache!
# wait again ....
p.followers_counter.cached
# => 3 # or something else :)

Explanation

In fact, when you define cached attribute using has_cachd_attribute method, you just define another method with the same name as cached attribute name. And when you call this method, you receieves an instance of LazyCaching::CachedAttribute class. This class has 4 base methods:

So, you can even do something like this:

Product.product_names.tap do |p|
  p.recache!
  puts p.cached
end

Customization

You can specify default processor for each attribute like this:

has_cached_attribute do
  name :names
  type :class
  processor :base # No background processing for this attribute.
  result { pluck(:name) }
end

You can specify default value for cached attribute (if it's blank)

has_cached_attribute do
  name :names
  type :class
  processor :base
  result { pluck(:name) }
  default ["product1", "product2"]
  # or as a block
  # default do
  #   ["product1", "product2"]
  # end
end

Rails.cache.clear
Product.names.cached
# => ["product1", "product2"]

By default, each call of lazy_recache! checks, do we have a job with the same arguments in our queue. If it's true, then recache action will be blocked. But if you are sure that you need to recache your data again (twice), simpy call

Product.names.lazy_recache!(:force => true)

If you wan't to recache same attribute with different processor, pass paramater :processor to lazy_recache!

Product.names.lazy_recache!(:processor => :base)

This means that we have 3 levels of processor definition:

  1. global - in configuration file
  2. for attribute - with attribute definition (processor option in has_cached_attribute block)
  3. for call - parameter :processor for lazy_recache! method

You can combine different options as you wish!


Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request