Giter VIP home page Giter VIP logo

has_cache's Introduction

HasCache

Convenience wrapper for the Rails Cache Store

Using has_cache in your classes provides a cached method that allows automatic caching of the result of a method that is normally available on the class, or an instance of the class.

It mitigates the hassle of creating and tracking keys as you would with the standard Cache Store interface, by inferring keys from the location cached is invoked.

Usage

Enable caching on your class/model

Include has_cache in your Rails project's Gemfile:

gem 'has_cache'

Call has_cache in your class, for example in a model:

class User < ActiveRecord::Base
  has_many :posts, inverse_of: :user

  has_cache
end

class Post < ActiveRecord::Base
  belongs_to :user, inverse_of: :posts
end

Populate and retrieve cached entities

Having enabled caching on your class, you can call:

user = User.first
user_posts = user.cached.posts

And the result of user.posts will be cached using the Rails Cache Store, so that the next time you call user.cached.posts, the result will be returned from the cache, rather than the model.

You may call any method that the class would normally respond to, for example, caching all records via ActiveRecord::Base.all:

all_users = User.cached.all

If you wish to cache the result of chained methods, you may use block syntax, as follows:

first_user = User.first
first_users_first_post = user.cached{ posts.first }

Delete cached entities

To delete cached entities, simply replace the cached method with delete_cached:

user = User.first
# Cache some entities
user_posts = user.cached.posts
# Delete the cache
user.delete_cached.posts

The delete_cached method takes all the same arguments as the cached method, and to ensure that the correct cache key is deleted, you must pass the exact same arguments, and chain the same methods, as the original call to cached.

Options

Cache options

The has_cache method can take any options that are supported by your Rails Cache Store. These options will be used as defaults for calls to the cached method on both the class and it's instances.

For example, with our User model:

class User < ActiveRecord::Base
  ...

  has_cache expires_in: 1.hour
end

All calls to User.cached would store items in the cache with expiry of one hour.

You can also specify Cache Store options when calling cached to override any default options, ie:

User.cached(expires_in: 1.day).all

The above would cause the cache to store that particular result with an expiry of one day, rather than the default one hour we specified above.

Custom keys

Much of the convenience of has_cache comes from it's ability to automatically generate keys for the Cache Store, however sometimes you may need to generate keys by some other means.

Generated key names take the form of:

Called on object Example call Generated key
Class User.cached.all 'User/class/all'
Instance user = User.find(1); user.cached.posts 'User/instance/1/posts'

There are a number of ways to customize keys in has_cache.

As an argument to #cached

Firstly, you may specify a key in the call to cached:

User.cached(key: 'widget').all

Would result in a key of: User/class/widget/all

user = User.find(1)
user_posts = user.cached(key: 'widget').posts

Would result in a key of: User/instance/widget/posts

If for some reason you need to drop the method name or arguments from the key, you may add canonical_key to the arguments, like so:

User.cached(key: 'widget', canonical_key: true)

Would result in a key of: User/class/widget

If the passed key is a Proc or lambda, it will be executed in the scope of the caller:

user = User.create(email: '[email protected]')
user_posts = user.cached(key: lambda { email }).posts

Would result in a key of: User/instance/[email protected]/posts

Be careful of scope here though, as obviously using the same lambda on the class would fail:

User.cached(key: lambda { email }).find(1)
=> Exception: NoMethodError: undefined method `email' for User:Class`

As a method

Next, you may generate the key by implementing a has_cache_key class or instance method. This is quite powerful, so let's look at a somewhat involved example.

Assuming our User class allows versioning, and when viewing a versioned instance (retrieved via #get_version), it responds true to #versioned? and returns the version number via #version_number, our #has_cache_key method might look like the following:

class User < ActiveRecord::Base
  has_many :posts, inverse_of: :user

  has_cache

  def has_cache_key
    key = [id]
    key += [{ version: version_number }] if versioned?
  end
end

Now, if we're looking the original user:

user = User.find(1)
user_posts = user.cached.posts

Our generated key would be: User/instance/1/posts

However, if we're looking at a versioned instance:

user = User.find(1)
versioned_user = user.get_version(7)
versioned_user_posts = versioned_user.cached.posts

Our generated key would be: User/instance/1/version=7/posts

It is important to not that as we've only defined has_cache_key as an instance method, calls to cached on the class remain unaffected, however defining a has_cache_key class method is also supported.

Caveats

Chained methods

As mentioned briefly above, only the first method following cached is stored in the cache, so in a prior example, if you call:

first_user = User.cached.all.first

The cache will be populated with the result from User.all, which will then have #first called on it. This may not be what you expect, in which case the block notation should be used, ie:

first_user = User.cached{ all.first }

In which case, the result from User.all.first will be cached as expected.

Block notation keys

Currently, when using block notation as above, we generate keys by converting the block to source using the sourcify gem. This is not an optimal solution as it's slow and gets easily confused by nested blocks. Sometimes, it simply fails to generate keys entirely, requiring the user to specify a custom key, which is far from optimal. I'm investigating alternatives, but don't have a better solution at this stage.

TODO

  • Needs more specs, particularly custom key handling is untested currently.
  • Documentation - the code is appallingly light on comments
  • Block parsing using sourcify seems like a really hacky solution, would welcome pull requests for a better solution.

License

This project rocks and uses the MIT-LICENSE.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.