{"id":20829787,"url":"https://github.com/pdf/has_cache","last_synced_at":"2025-05-07T22:09:58.595Z","repository":{"id":11480151,"uuid":"13950516","full_name":"pdf/has_cache","owner":"pdf","description":"Convenience wrapper for the Rails Cache Store","archived":false,"fork":false,"pushed_at":"2015-02-13T05:27:49.000Z","size":232,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-07T22:09:53.362Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pdf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-29T08:42:52.000Z","updated_at":"2015-02-13T05:27:45.000Z","dependencies_parsed_at":"2022-09-05T20:50:33.037Z","dependency_job_id":null,"html_url":"https://github.com/pdf/has_cache","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fhas_cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fhas_cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fhas_cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pdf%2Fhas_cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pdf","download_url":"https://codeload.github.com/pdf/has_cache/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252961841,"owners_count":21832197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-17T23:22:08.258Z","updated_at":"2025-05-07T22:09:58.570Z","avatar_url":"https://github.com/pdf.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HasCache\nConvenience wrapper for the [Rails Cache Store](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores)\n\nUsing `has_cache` in your classes provides a `cached` method that allows\nautomatic caching of the result of a method that is normally available on\nthe class, or an instance of the class.\n\nIt mitigates the hassle of creating and tracking keys as you would with\nthe standard Cache Store interface, by inferring keys from the location\n`cached` is invoked.\n\n## Usage\n### Enable caching on your class/model\nInclude `has_cache` in your Rails project's `Gemfile`:\n\n```ruby\ngem 'has_cache'\n```\n\nCall `has_cache` in your class, for example in a model:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_many :posts, inverse_of: :user\n\n  has_cache\nend\n\nclass Post \u003c ActiveRecord::Base\n  belongs_to :user, inverse_of: :posts\nend\n```\n\n### Populate and retrieve cached entities\nHaving enabled caching on your class, you can call:\n\n```ruby\nuser = User.first\nuser_posts = user.cached.posts\n```\n\nAnd the result of `user.posts` will be cached using the [Rails Cache Store](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores),\nso that the next time you call `user.cached.posts`, the result will be returned\nfrom the cache, rather than the model.\n\nYou may call any method that the class would normally respond to, for example,\ncaching all records via `ActiveRecord::Base.all`:\n\n```ruby\nall_users = User.cached.all\n```\n\nIf you wish to cache the result of chained methods, you may use block syntax,\nas follows:\n\n```ruby\nfirst_user = User.first\nfirst_users_first_post = user.cached{ posts.first }\n```\n\n### Delete cached entities\nTo delete cached entities, simply replace the `cached` method with `delete_cached`:\n\n```ruby\nuser = User.first\n# Cache some entities\nuser_posts = user.cached.posts\n# Delete the cache\nuser.delete_cached.posts\n```\n\nThe `delete_cached` method takes all the same arguments as the `cached` method, and\nto ensure that the correct cache key is deleted, you must pass the exact same\narguments, and chain the same methods, as the original call to `cached`.\n\n## Options\n### Cache options\nThe `has_cache` method can take any options that are supported by your [Rails Cache Store](http://guides.rubyonrails.org/caching_with_rails.html#cache-stores).\nThese options will be used as defaults for calls to the `cached` method on both\nthe class and it's instances.\n\nFor example, with our `User` model:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  ...\n\n  has_cache expires_in: 1.hour\nend\n```\n\nAll calls to `User.cached` would store items in the cache with expiry of one\nhour.\n\nYou can also specify Cache Store options when calling `cached` to override\nany default options, ie:\n\n```ruby\nUser.cached(expires_in: 1.day).all\n```\n\nThe above would cause the cache to store that particular result with an\nexpiry of one day, rather than the default one hour we specified above.\n\n### Custom keys\nMuch of the convenience of `has_cache` comes from it's ability to automatically\ngenerate keys for the Cache Store, however sometimes you may need to generate\nkeys by some other means.\n\nGenerated key names take the form of:\n\n| Called on object | Example call                             | Generated key             |\n|------------------|------------------------------------------|---------------------------|\n| Class            | `User.cached.all`                        | `'User/class/all'`        |\n| Instance         | `user = User.find(1); user.cached.posts` | `'User/instance/1/posts'` |\n\nThere are a number of ways to customize keys in `has_cache`.\n\n#### As an argument to #cached\nFirstly, you may specify a key in the call to `cached`:\n\n```ruby\nUser.cached(key: 'widget').all\n```\n\nWould result in a key of: `User/class/widget/all`\n\n```ruby\nuser = User.find(1)\nuser_posts = user.cached(key: 'widget').posts\n```\n\nWould result in a key of: `User/instance/widget/posts`\n\nIf for some reason you need to drop the method name or arguments from the key,\nyou may add `canonical_key` to the arguments, like so:\n\n```ruby\nUser.cached(key: 'widget', canonical_key: true)\n```\n\nWould result in a key of: `User/class/widget`\n\nIf the passed key is a `Proc` or `lambda`, it will be executed in the scope of\nthe caller:\n\n```ruby\nuser = User.create(email: 'user@example.com')\nuser_posts = user.cached(key: lambda { email }).posts\n```\n\nWould result in a key of: `User/instance/user@example.com/posts`\n\nBe careful of scope here though, as obviously using the same lambda on the class\nwould fail:\n\n```ruby\nUser.cached(key: lambda { email }).find(1)\n=\u003e Exception: NoMethodError: undefined method `email' for User:Class`\n```\n\n#### As a method\nNext, you may generate the key by implementing a `has_cache_key` class or\ninstance method.  This is quite powerful, so let's look at a somewhat\ninvolved example.\n\nAssuming our `User` class allows versioning, and when viewing a versioned\ninstance (retrieved via `#get_version`), it responds `true` to `#versioned?`\nand returns the version number via `#version_number`, our `#has_cache_key`\nmethod might look like the following:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_many :posts, inverse_of: :user\n\n  has_cache\n\n  def has_cache_key\n    key = [id]\n    key += [{ version: version_number }] if versioned?\n  end\nend\n```\n\nNow, if we're looking the original user:\n\n```ruby\nuser = User.find(1)\nuser_posts = user.cached.posts\n```\n\nOur generated key would be: `User/instance/1/posts`\n\nHowever, if we're looking at a versioned instance:\n\n```ruby\nuser = User.find(1)\nversioned_user = user.get_version(7)\nversioned_user_posts = versioned_user.cached.posts\n```\n\nOur generated key would be: `User/instance/1/version=7/posts`\n\nIt is important to not that as we've only defined `has_cache_key` as an\ninstance method, calls to `cached` on the class remain unaffected, however\ndefining a `has_cache_key` class method is also supported.\n\n## Caveats\n### Chained methods\nAs mentioned briefly above, only the first method following `cached` is stored\nin the cache, so in a prior example, if you call:\n\n```ruby\nfirst_user = User.cached.all.first\n```\n\nThe cache will be populated with the result from `User.all`, which will then\nhave `#first` called on it.  This may not be what you expect, in which case\nthe block notation should be used, ie:\n\n```ruby\nfirst_user = User.cached{ all.first }\n```\n\nIn which case, the result from `User.all.first` will be cached as expected.\n\n### Block notation keys\nCurrently, when using block notation as above, we generate keys by converting\nthe block to source using the `sourcify` gem.  This is not an optimal solution\nas it's slow and gets easily confused by nested blocks.  Sometimes, it simply\nfails to generate keys entirely, requiring the user to specify a custom key,\nwhich is far from optimal.  I'm investigating alternatives, but don't have a\nbetter solution at this stage.\n\n# TODO\n- Needs more specs, particularly custom key handling is untested currently.\n- Documentation - the code is appallingly light on comments\n- Block parsing using `sourcify` seems like a really hacky solution, would\n  welcome pull requests for a better solution.\n\n# License\nThis project rocks and uses the `MIT-LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdf%2Fhas_cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpdf%2Fhas_cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpdf%2Fhas_cache/lists"}