{"id":13483138,"url":"https://github.com/artsy/garner","last_synced_at":"2025-05-16T16:02:40.218Z","repository":{"id":3648267,"uuid":"4715906","full_name":"artsy/garner","owner":"artsy","description":"A set of Rack middleware and cache helpers that implement various caching strategies.","archived":false,"fork":false,"pushed_at":"2020-05-27T17:43:19.000Z","size":480,"stargazers_count":343,"open_issues_count":19,"forks_count":24,"subscribers_count":71,"default_branch":"master","last_synced_at":"2025-04-12T14:56:01.024Z","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/artsy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-06-19T16:04:26.000Z","updated_at":"2024-10-19T18:28:44.000Z","dependencies_parsed_at":"2022-09-04T00:41:13.093Z","dependency_job_id":null,"html_url":"https://github.com/artsy/garner","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artsy%2Fgarner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artsy%2Fgarner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artsy%2Fgarner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artsy%2Fgarner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/artsy","download_url":"https://codeload.github.com/artsy/garner/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254556818,"owners_count":22091006,"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-07-31T17:01:08.540Z","updated_at":"2025-05-16T16:02:40.176Z","avatar_url":"https://github.com/artsy.png","language":"Ruby","readme":"Garner\n======\n\n[![Gem Version](https://badge.fury.io/rb/garner.svg)](https://badge.fury.io/rb/garner)\n[![Build Status](https://travis-ci.org/artsy/garner.svg?branch=master)](https://travis-ci.org/artsy/garner)\n[![Dependency Status](https://gemnasium.com/artsy/garner.svg)](https://gemnasium.com/artsy/garner)\n[![Code Climate](https://codeclimate.com/github/artsy/garner.svg)](https://codeclimate.com/github/artsy/garner)\n[![Coverage Status](https://coveralls.io/repos/artsy/garner/badge.svg?branch=master\u0026service=github)](https://coveralls.io/github/artsy/garner?branch=master)\n\nGarner is a cache layer for Ruby and Rack applications, supporting model and instance binding and hierarchical invalidation. To \"garner\" means to gather data from various sources and to make it readily available in one place, kind of like a cache!\n\nIf you're not familiar with HTTP caching, ETags and If-Modified-Since, watch us introduce Garner in [From Zero to API Cache in 10 Minutes](https://www.youtube.com/watch?v=e9HLflRXMcA) at GoRuCo 2012.\n\nUpgrading\n---------\n\nThe current stable release line of Garner is 0.5.x, and contains many breaking changes from the previous 0.3.x series. For a summary of important changes, see [UPGRADING](UPGRADING.md).\n\nUsage\n-----\n\n### Application Logic Caching\n\nAdd Garner to your Gemfile with `gem \"garner\"` and run `bundle install`. Next, include the appropriate mixin in your app:\n\n* For plain-old Ruby apps, `include Garner::Cache::Context`.\n* For Rack apps, first `require \"garner/mixins/rack\"`, then `include Garner::Mixins::Rack`. (This provides saner defaults for injecting request parameters into the cache context key. More on cache context keys later.)\n\nNow, to use Garner's cache, invoke `garner` with a logic block from within your application. The result of the block will be computed once, and then stored in the cache.\n\n``` ruby\nget \"/system/counts/all\" do\n  # Compute once and cache for subsequent reads\n  garner do\n    {\n      \"orders_count\" =\u003e Order.count,\n      \"users_count\"  =\u003e User.count\n    }\n  end\nend\n```\n\nThe cached value can be bound to a particular model instance. For example, if a user has an address that may or may not change when the user is saved, you will want the cached address to be invalidated every time the user record is modified.\n\n``` ruby\nget \"/me/address\" do\n  # Invalidate when current_user is modified\n  garner.bind(current_user) do\n    current_user.address\n  end\nend\n```\n\n\nORM Integrations\n----------------\n\n### Mongoid\n\nTo use Mongoid 3, 4 or 5 documents and classes for Garner bindings, use `Garner::Mixins::Mongoid::Document`. You can set it up in an initializer:\n\n``` ruby\nrequire \"garner/mixins/mongoid\"\n\nmodule Mongoid\n  module Document\n    include Garner::Mixins::Mongoid::Document\n  end\nend\n```\n\nThis enables binding to Mongoid classes as well as instances. For example:\n\n```ruby\nget \"/system/counts/orders\" do\n  # Invalidate when any order is created, updated or deleted\n  garner.bind(Order) do\n    {\n      \"orders_count\" =\u003e Order.count,\n    }\n  end\nend\n```\n\nWhat if you want to bind a cache result to a persisted object that hasn't been retrieved yet? Consider the example of caching a particular order without a database query:\n\n```ruby\nget \"/order/:id\" do\n  # Invalidate when Order.find(params[:id]) is modified\n  garner.bind(Order.identify(params[:id])) do\n    Order.find(params[:id])\n  end\nend\n```\n\nIn the above example, the `Order.identify` call will not result in a database query. Instead, it just communicates to Garner's cache sweeper that whenever the order with identity `params[:id]` is updated, this cache result should be invalidated. The `identify` method is provided by the Mongoid mixin. To use it, you should configure `Garner.config.mongoid_identity_fields`, e.g.:\n\n```ruby\nGarner.configure do |config|\n  config.mongoid_identity_fields = [:_id, :_slugs]\nend\n```\n\nThese may be scalar or array fields. Only uniquely-constrained fields should be used here; otherwise you risk caching the same result for two different blocks.\n\nThe Mongoid mixin also provides helper methods for cached `find` operations. The following code will fetch an order once (via `find`) from the database, and then fetch it from the cache on subsequent requests. The cache will be invalidated whenever the underlying `Order` changes in the database.\n\n```ruby\norder = Order.garnered_find(3)\n```\n\nExplicit invalidation should be unnecessary, since callbacks are declared to invalidate the cache whenever a Mongoid object is created, updated or destroyed, but for special cases, `invalidate_garner_caches` may be called on a Mongoid object or class:\n\n```ruby\nOrder.invalidate_garner_caches\nOrder.find(3).invalidate_garner_caches\n```\n\n### ActiveRecord\n\nGarner provides rudimentary support for `ActiveRecord`. To use ActiveRecord models for Garner bindings, use `Garner::Mixins::ActiveRecord::Base`. You can set it up in an initializer:\n\n``` ruby\nrequire \"garner/mixins/active_record\"\n\nmodule ActiveRecord\n  class Base\n    include Garner::Mixins::ActiveRecord::Base\n  end\nend\n```\n\nCache Options\n-------------\n\nYou can pass additional options directly to the cache implementation:\n\n``` ruby\nget \"/latest_order\" do\n  # Expire the latest order every 15 minutes\n  garner.options(expires_in: 15.minutes) do\n    Order.latest\n  end\nend\n```\n\nUnder The Hood: Bindings\n------------------------\n\nAs we've seen, a cache result can be bound to a model instance (e.g., `current_user`) or a virtual instance reference (`Order.identify(params[:id])`). In some cases, we may want to compose bindings:\n\n```ruby\nget \"/system/counts/all\" do\n  # Invalidate when any order or user is modified\n  garner.bind(Order).bind(User) do\n    {\n      \"orders_count\" =\u003e Order.count,\n      \"users_count\"  =\u003e User.count\n    }\n  end\nend\n```\n\nBinding keys are computed via pluggable strategies, as are the rules for invalidating caches when a binding changes. By default, Garner uses `Garner::Strategies::Binding::Key::SafeCacheKey` to compute binding keys: this uses `cache_key` if defined on an object; otherwise it always bypasses cache. Similarly, Garner uses `Garner::Strategies::Binding::Invalidation::Touch` as its default invalidation strategy. This will call `:touch` on a document if it is defined; otherwise it will take no action.\n\nAdditional binding and invalidation strategies can be written. To use them, set `Garner.config.binding_key_strategy` and `Garner.config.binding_invalidation_strategy`.\n\n\nUnder The Hood: Cache Context Keys\n----------------------------------\n\nExplicit cache context keys are usually unnecessary in Garner. Given a cache binding, Garner will compute an appropriately unique cache key. Moreover, in the context of `Garner::Mixins::Rack`, Garner will compose the following key factors by default:\n\n* `Garner::Strategies::Context::Key::Caller` inserts the calling file and line number, allowing multiple calls from the same function to generate different results.\n* `Garner::Strategies::Context::Key::RequestGet` inserts the value of HTTP request's GET parameters into the cache key when `:request` is present in the context.\n* `Garner::Strategies::Context::Key::RequestPost` inserts the value of HTTP request's POST parameters into the cache key when `:request` is present in the context.\n* `Garner::Strategies::Context::Key::RequestPath` inserts the value of the HTTP request's path into the cache key when `:request` is present in the context.\n\nAdditional key factors may be specified explicitly using the `key` method. To see a specific example of this in action, let's consider the case of role-based caching. For example, an order may have a different representation for an admin versus an ordinary user:\n\n```ruby\nget \"/order/:id\" do\n  garner.bind(Order.identify(params[:id])).key({ role: current_user.role }) do\n    Order.find(params[:id])\n  end\nend\n```\n\nAs with bindings, context key factors may be composed by calling `key()` multiple times on a `garner` invocation. The keys will be applied in the order in which they are called.\n\n\nConfiguration\n-------------\n\nBy default `Garner` will use an instance of `ActiveSupport::Cache::MemoryStore` in a non-Rails and `Rails.cache` in a Rails environment. You can configure it to use any other cache store.\n\n``` ruby\nGarner.configure do |config|\n  config.cache = ActiveSupport::Cache::FileStore.new\nend\n```\n\nThe full list of  `Garner.config` attributes is:\n\n* `:global_cache_options`: A hash of options to be passed on every call to `Garner.config.cache`, like `{ :expires_in =\u003e 10.minutes }`. Defaults to `{}`\n* `:context_key_strategies`: An array of context key strategies, to be applied in order. Defaults to `[Garner::Strategies::Context::Key::Caller]`\n* `:rack_context_key_strategies`: Rack-specific context key strategies. Defaults to:\n```ruby\n[\n  Garner::Strategies::Context::Key::Caller,\n  Garner::Strategies::Context::Key::RequestGet,\n  Garner::Strategies::Context::Key::RequestPost,\n  Garner::Strategies::Context::Key::RequestPath\n]\n```\n* `:binding_key_strategy`: Binding key strategy. Defaults to `Garner::Strategies::Binding::Key::SafeCacheKey`.\n* `:binding_invalidation_strategy`: Binding invalidation strategy. Defaults to `Garner::Strategies::Binding::Invalidation::Touch`.\n* `:mongoid_identity_fields`: Identity fields considered legal for the `identity` method. Defaults to `[:_id]`.\n* `:caller_root`: Root path of application, to be stripped out of value strings generated by the `Caller` context key strategy. Defaults to `Rails.root` if in a Rails environment; otherwise to the nearest ancestor directory containing a Gemfile.\n* `:invalidate_mongoid_root`: If set to true, invalidates the `_root` document along with any embedded Mongoid document binding. Defaults to `true`.\n* `:whiny_nils`: If set to true, raises an exception when a `nil` binding is specified (i.e., `garner.bind(nil)`). Defaults to `true`.\n\nContributing\n------------\n\nFork the project. Make your feature addition or bug fix with tests. Send a pull request.\n\nCopyright and License\n---------------------\n\nMIT License, see [LICENSE](LICENSE.md) for details.\n\n(c) 2012-2013 [Artsy](http://artsy.github.com), [Frank Macreery](https://github.com/fancyremarker), [Daniel Doubrovkine](https://github.com/dblock) and [contributors](CHANGELOG.md).\n\n","funding_links":[],"categories":["Ruby","Middlewares","Caching"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartsy%2Fgarner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartsy%2Fgarner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartsy%2Fgarner/lists"}