Giter Site home page Giter Site logo

garner's Introduction

Garner

Gem Version Build Status Dependency Status Code Climate Coverage Status

Garner 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!

If 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 at GoRuCo 2012.

Upgrading

The 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.

Usage

Application Logic Caching

Add Garner to your Gemfile with gem "garner" and run bundle install. Next, include the appropriate mixin in your app:

  • For plain-old Ruby apps, include Garner::Cache::Context.
  • 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.)

Now, 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.

get "/system/counts/all" do
  # Compute once and cache for subsequent reads
  garner do
    {
      "orders_count" => Order.count,
      "users_count"  => User.count
    }
  end
end

The 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.

get "/me/address" do
  # Invalidate when current_user is modified
  garner.bind(current_user) do
    current_user.address
  end
end

ORM Integrations

Mongoid

To 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:

require "garner/mixins/mongoid"

module Mongoid
  module Document
    include Garner::Mixins::Mongoid::Document
  end
end

This enables binding to Mongoid classes as well as instances. For example:

get "/system/counts/orders" do
  # Invalidate when any order is created, updated or deleted
  garner.bind(Order) do
    {
      "orders_count" => Order.count,
    }
  end
end

What 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:

get "/order/:id" do
  # Invalidate when Order.find(params[:id]) is modified
  garner.bind(Order.identify(params[:id])) do
    Order.find(params[:id])
  end
end

In 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.:

Garner.configure do |config|
  config.mongoid_identity_fields = [:_id, :_slugs]
end

These 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.

The 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.

order = Order.garnered_find(3)

Explicit 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:

Order.invalidate_garner_caches
Order.find(3).invalidate_garner_caches

ActiveRecord

Garner 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:

require "garner/mixins/active_record"

module ActiveRecord
  class Base
    include Garner::Mixins::ActiveRecord::Base
  end
end

Cache Options

You can pass additional options directly to the cache implementation:

get "/latest_order" do
  # Expire the latest order every 15 minutes
  garner.options(expires_in: 15.minutes) do
    Order.latest
  end
end

Under The Hood: Bindings

As 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:

get "/system/counts/all" do
  # Invalidate when any order or user is modified
  garner.bind(Order).bind(User) do
    {
      "orders_count" => Order.count,
      "users_count"  => User.count
    }
  end
end

Binding 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.

Additional binding and invalidation strategies can be written. To use them, set Garner.config.binding_key_strategy and Garner.config.binding_invalidation_strategy.

Under The Hood: Cache Context Keys

Explicit 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:

  • Garner::Strategies::Context::Key::Caller inserts the calling file and line number, allowing multiple calls from the same function to generate different results.
  • 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.
  • 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.
  • 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.

Additional 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:

get "/order/:id" do
  garner.bind(Order.identify(params[:id])).key({ role: current_user.role }) do
    Order.find(params[:id])
  end
end

As 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.

Configuration

By 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.

Garner.configure do |config|
  config.cache = ActiveSupport::Cache::FileStore.new
end

The full list of Garner.config attributes is:

  • :global_cache_options: A hash of options to be passed on every call to Garner.config.cache, like { :expires_in => 10.minutes }. Defaults to {}
  • :context_key_strategies: An array of context key strategies, to be applied in order. Defaults to [Garner::Strategies::Context::Key::Caller]
  • :rack_context_key_strategies: Rack-specific context key strategies. Defaults to:
[
  Garner::Strategies::Context::Key::Caller,
  Garner::Strategies::Context::Key::RequestGet,
  Garner::Strategies::Context::Key::RequestPost,
  Garner::Strategies::Context::Key::RequestPath
]
  • :binding_key_strategy: Binding key strategy. Defaults to Garner::Strategies::Binding::Key::SafeCacheKey.
  • :binding_invalidation_strategy: Binding invalidation strategy. Defaults to Garner::Strategies::Binding::Invalidation::Touch.
  • :mongoid_identity_fields: Identity fields considered legal for the identity method. Defaults to [:_id].
  • :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.
  • :invalidate_mongoid_root: If set to true, invalidates the _root document along with any embedded Mongoid document binding. Defaults to true.
  • :whiny_nils: If set to true, raises an exception when a nil binding is specified (i.e., garner.bind(nil)). Defaults to true.

Contributing

Fork the project. Make your feature addition or bug fix with tests. Send a pull request.

Copyright and License

MIT License, see LICENSE for details.

(c) 2012-2013 Artsy, Frank Macreery, Daniel Doubrovkine and contributors.

garner's People

Contributors

andyw8 avatar chussenot avatar dblock avatar icirellik avatar joeyaghion avatar mzikherman avatar oripekelman avatar potomak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

garner's Issues

support for Sequel

I can see that there is support for ActiveRecord, but what about Sequel, has anyone maintaining this gem given any though to it?

Using non-ORM bindings

I'm using Garner in a project (really, a couple projects) where we don't ever need to bind to ActiveRecord or Mongoid objects. Instead, we want to cache certain data (the roles to which a user belongs) and bind it to the OAuth token they present.

Right now, I'm doing this by monkey-patching the AccessToken class in an initializer:

Garner.configure do |config|
  # Other configuration options...
  config.binding_key_strategy = Garner::Strategies::Binding::Key::CacheKey
end

module Fridge
  class AccessToken
    include Garner::Cache::Binding

    def cache_key
      id
    end
  end
end

Then I can do stuff like:

garner.bind(token) { roles.map(&:href) }

What do we think is the appropriate way to "Garner-ify" non-ORM objects? Is re-opening the class the right way to do this? Right now, it's necessary to, at a minimum include Garner::Cache::Binding in any class whose instances will be used as bindings. Is there a better way?

garner fails if mongoid not loaded yet

$ bundle exec rspec

/Users/joey/.rvm/gems/ruby-1.9.2-p290@global/gems/rake-0.9.2/lib/rake/ext/module.rb:36:in `const_missing': uninitialized constant Garner::Mixins::Mongoid (NameError)
    from /Users/joey/dev/artsy/gravity/config/initializers/mongoid_document.rb:4:in `<module:Document>'
    from /Users/joey/dev/artsy/gravity/config/initializers/mongoid_document.rb:2:in `<module:Mongoid>'
    from /Users/joey/dev/artsy/gravity/config/initializers/mongoid_document.rb:1:in `<top (required)>'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/engine.rb:588:in `block (2 levels) in <class:Engine>'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/engine.rb:587:in `each'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/engine.rb:587:in `block in <class:Engine>'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/initializable.rb:30:in `instance_exec'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/initializable.rb:30:in `run'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/initializable.rb:55:in `block in run_initializers'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/initializable.rb:54:in `each'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/initializable.rb:54:in `run_initializers'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/application.rb:136:in `initialize!'
    from /Users/joey/dev/artsy/gravity/config/application.rb:46:in `initialize!'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/railtie/configurable.rb:30:in `method_missing'
    from /Users/joey/dev/artsy/gravity/config/environment.rb:48:in `<top (required)>'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/application.rb:103:in `require_environment!'
    from /Users/joey/.rvm/gems/ruby-1.9.2-p290@artsy/gems/railties-3.2.6/lib/rails/commands.rb:40:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>

Add cache logging support

Create a feature around cache logging. We want to see what Garner is reading or writing to/from cache with a single switch. Currently this monkey-patch:

module StoreEx
  def read(key, options = nil)
    puts "read: #{key}"
    super
  end

  def read_multi(*names)
    puts "read_multi: #{names}"
    super
  end

  def write(key, value, options = nil)
    puts "write: #{key} => #{value.class}"
    super
  end

  def fetch(name, options = nil)
    puts "fetch: #{name}"
    super
  end
end

Garner.config.cache.extend StoreEx

Where to include Garner::Mixins::Rack ?

Hi,

I'd like to try this gem out, but I can't get it to recognize the mixin in my Rails app. It would be great if the documentation were more specific as to where the include needs to go. Here is my setup:

app/api/api.rb:

module API
  class Root < Grape::API
    prefix 'api'
    format :json
    version 'v1', using: :header, vendor: 'ohanapi'

    mount Ohana::API
    Grape::Endpoint.send :include, LinkHeader
    add_swagger_documentation markdown: true, hide_documentation_path: true,
      hide_format: true, api_version: 'v1'
  end
end

app/api/ohana.rb:

module Ohana
  class API < Grape::API

  resource "/" do
...

I tried including it in both files, and also in the Grape::Endpoint.send line, but I keep getting uninitialized constant Garner::Mixins. What am I doing wrong?

Caching the Index method

Hi!
In my Orders controller, there's an index method that returns all orders for a specific account.
Is there a way to cache calls like current_account.orders (get all orders that the current_account has), invalidating only when the number of orders for current_account changes?
Thanks!

Support ActiveRecord 4

ActiveRecord 4 cache_key is not in nsec format.

Cache keys are now "activists/1-20140717140709836493000". It used to be in the :number format, but now it's in the :nsec format. SafeCacheKey shortcuts apply because that is not in a valid format per SafeCacheKey::VALID_FORMAT.

Mongoid document proxy binding doesn't work with aliased updated_at field

The current implementation of Garner::Mixins::Mongoid::Document::_latest_by_updated_at currently checks for the existence of the updated_at field. This doesn't cover documents that alias the updated_at field (typically by including include Mongoid::Timestamps::Short). This should consider aliased fields. The latest document query can also be optimized to return a single document. Something along these lines:

def self._latest_by_updated_at
  # Only find the latest if we can order by :updated_at
  return nil unless fields['updated_at'] || aliased_fields['updated_at'] 
  only(:_id, :_type, :updated_at).order_by(updated_at: :desc).limit(1).first
end

May serve to document the proxied binding behavior on model classes in the readme, so devs know to create indexes on the updated_at field if they so choose.

Cheers!

Mongoid garnered_find causes two similar queries

Consider Garner::Mixins::Mongoid::Document.garnered_find.

def self.garnered_find(handle)
  return nil unless (binding = identify(handle))
  identity = Garner::Cache::Identity.new
  identity.bind(binding).key({ :source => :garnered_find }) do
    find(handle)
  end
end

The identity.bind(binding) call will cause a database=garner_test collection=mongers selector={"$or"=>[{"_id"=>"m1"}, {"_slugs"=>"m1"}]} flags=[:slave_ok] limit=1 skip=0 batch_size=nil fields={"_id"=>1, "_type"=>1, "updated_at"=>1} query to generate the cache key for this instance.

Then, if we have a cache miss, the second query is placed: database=garner_test collection=mongers selector={"_slugs"=>{"$in"=>["m1"]}} flags=[:slave_ok] limit=1 skip=0 batch_size=nil fields=nil

In this particular scenario it may be more efficient to fetch the entire record in the first query and reuse it as the result, but I am not sure how to express this in code, therefore avoiding the double find.

Release Next

@fancyremarker: Should this be a 0.4.6 or a 0.5.0? There're new features, sounds like a 0.5.0. Feel free to cut a release, I am happy to as well if you don't want to.

Add support for caching both body and headers in Garner::Mixins::Rack

We have monkey patched the Rack mixin to allow caching both headers and body, like this:

module Garner
  module Cache
    class Identity
      TRACKED_HEADERS = %w{ X-Total-Count }

      alias_method :_fetch, :fetch
      def fetch(&block)
        if @key_hash[:tracked_grape_headers]
          result, headers = _fetch do
            result = yield
            headers = @ruby_context.header.slice(*TRACKED_HEADERS) if @ruby_context.respond_to?(:header)
            [result, headers]
          end

          (headers || {}).each do |key, value|
            @ruby_context.header(key, value) if @ruby_context.respond_to?(:header)
          end

          result
        else
          _fetch(&block)
        end
      end

    end
  end
end
require "garner/mixins/rack"

module Garner
  module Mixins
    module Rack

      alias_method :_garner, :garner
      def garner(&block)
        response, headers = _garner.key({ tracked_grape_headers: true }, &block)
      end

    end
  end
end

It should be possible to roll this functionality into Garner. The Identity part should probably become more generic to store data and metadata, while the Rack mixin would just reuse that functionality.

`garnered_find` breaks on case-insensitive `find` with `BindingIndex` strategy

We have overridden Mongoid::Document#find as follows:

class Monger
  class << self
    alias_method :mongoid_find, :find
    def find(*args)
      if args.count == 1 && args[0].is_a?(String)
        mongoid_find(args[0].downcase)
      else
        mongoid(find(*args))
      end
    end
  end
end

Now, when we call garnered_find on this class, we end up returning invalid values all over the damn place:

Garner.configure do |config|
  config.binding_key_strategy = Garner::Strategies::Binding::Key::BindingIndex
  config.binding_invalidation_strategy = Garner::Strategies::Binding::Invalidation::BindingIndex
end

Monger.create!({ :name => "M1" })
# => #<Monger _id: 51d71b6a4baef8e63d000001, created_at: 2013-07-05 19:15:54 UTC, updated_at: 2013-07-05 19:15:54 UTC, name: "M1", _slugs: ["m1"], subdocument: nil>
Monger.garnered_find("M1")
# => #<Monger _id: 51d71b6a4baef8e63d000001, created_at: 2013-07-05 19:15:54 UTC, updated_at: 2013-07-05 19:15:54 UTC, name: "M1", _slugs: ["m1"], subdocument: nil>
Monger.garnered_find("M44")
# => #<Monger _id: 51d71b6a4baef8e63d000001, created_at: 2013-07-05 19:15:54 UTC, updated_at: 2013-07-05 19:15:54 UTC, name: "M1", _slugs: ["m1"], subdocument: nil>
Monger.garnered_find(nil)
# => #<Monger _id: 51d71b6a4baef8e63d000001, created_at: 2013-07-05 19:15:54 UTC, updated_at: 2013-07-05 19:15:54 UTC, name: "M1", _slugs: ["m1"], subdocument: nil>

Thanks to @dblock for reporting this.

File cache store is limited by filename length constraint.

The following error came up in development with long URL cache keys:

File name too long - /Users/gib/Code/artsy/gravity/gib/tmp/cache/proxied_binding%3DGarner%3A%3AMixins%3A%3AMongoid%3A%3AIdentity%2Fklass%3DArtwork%2Chandle%3Dkammy-roulner-when-confronted-with-a-piece-of-art-dot-dot-dot%2Fstrategy%3DGarner%3A%3AStrategies%3A%3ABinding%3A%3AKey%3A%3ABindingIndex20130704-19291-s7dqiv.lock

Thanks for the speedy response and suggestions @fancyremarker!

Stale record retrieved via garnered_find

Trying to track a cache invalidation issue. A garnered_find of a record returns a stale result and no saving/invalidating of the original is clearing anything.

gravity:production> Profile.garnered_find('andrea-meislin-gallery')
fetch: {:strategy=>Garner::Strategies::Binding::Key::BindingIndex, :proxied_binding=>"Garner::Mixins::Mongoid::Identity/klass=Profile,handle=andrea-meislin-gallery"}
fetch: {:strategy=>Garner::Strategies::Binding::Key::BindingIndex, :proxied_binding=>"Profile/id=51120ae9d0c2eb4545004a16"}
fetch: {:binding_keys=>["831181d39281d48a71f76e43"], :context_keys=>{:garnered_find_args=>["andrea-meislin-gallery"]}}
=> #<Profile _id: 54188b287261692d7acf0200, created_at: 2014-09-16 19:10:32 UTC, updated_at: 2014-10-13 16:13:25 UTC, handle: "andrea-meislin-gallery", _slugs: ["andrea-meislin-gallery"], published: true, published_at: 2014-10-13 15:57:55 UTC, published_changed_at: 2014-10-13 15:57:55 UTC, locked_at: nil, locked_until: nil, short_description: nil, bio: "Andrea Meislin Gallery focuses on internationally recognized artists whose work contributes to the diasporic-based dialogue domestically and abroad.", website: "", location: "", default_icon_version: :square, subtype: nil, menu_color_class: nil, private: false, follows_count: 0, owner_type: "PartnerGallery", owner_id: BSON::ObjectId('54188b287261692d7acd0200')>

The proxy record is pointing to a stale record, which has the wrong ID 51120ae9d0c2eb4545004a16 (this profile belongs to a user), while the real record has an ID of 54188b287261692d7acf0200 (this profile belongs to a gallery). At some point the profile belonged to the user, and then it had to be renamed (handle changed), then the partner's profile was renamed to the same handle. The repro code however didn't exhibit this problem, this spec passed:

profile = Fabricate(:profile)
partner = Fabricate(:partner, given_name: 'gallery')
expect(partner.profile.handle).to_not eq 'gallery'
profile.update_attributes!(handle: 'not-gallery')
partner.profile.update_attributes!(handle: 'gallery')
expect(Profile.garnered_find('gallery').id).to eq partner.profile.id

How did we get here?

A save of the profile that is supposed to invalidate the above:

gravity:production> profile = Profile.find('andrea-meislin-gallery')
=> #<Profile _id: 54188b287261692d7acf0200, created_at: 2014-09-16 19:10:32 UTC, updated_at: 2014-10-14 19:02:38 UTC, handle: "andrea-meislin-gallery", _slugs: ["andrea-meislin-gallery"], published: true, published_at: 2014-10-13 15:57:55 UTC, published_changed_at: 2014-10-13 15:57:55 UTC, locked_at: nil, locked_until: nil, short_description: nil, bio: "Andrea Meislin Gallery focuses on internationally recognized artists whose work contributes to the diasporic-based dialogue domestically and abroad.", website: "", location: "", default_icon_version: :square, subtype: nil, menu_color_class: nil, private: false, follows_count: 0, owner_type: "PartnerGallery", owner_id: BSON::ObjectId('54188b287261692d7acd0200')>
gravity:production> profile.save!
write: {:strategy=>Garner::Strategies::Binding::Key::BindingIndex, :proxied_binding=>"Profile/id=54188b287261692d7acf0200"} => String
write: {:strategy=>Garner::Strategies::Binding::Key::BindingIndex, :proxied_binding=>"Profile/id=54188b287261692d7acf0200"} => String
write: {:strategy=>Garner::Strategies::Binding::Key::BindingIndex, :proxied_binding=>"Profile"} => String
write: {:strategy=>Garner::Strategies::Binding::Key::BindingIndex, :proxied_binding=>"Profile"} => String

Had to manually clear it:

Garner.config.cache.delete(:binding_keys=>["831181d39281d48a71f76e43"], :context_keys=>{:garnered_find_args=>["andrea-meislin-gallery"]})

This could be a total red herring. In

Garner.config.cache.delete(compound_key) unless result
:

result = Garner.config.cache.fetch(compound_key, options_hash) do
    yield
end
Garner.config.cache.delete(compound_key) unless result

Note the fetch here is by compound key with options, but the delete is without the options. Was this intentional?

Replace multiple identity support with a canonical id and a lookup

There're many problems with supporting multiple ID fields, especially when a single objects gets cached by any of those ID fields (slug, id). We basically don't support ID changes in that case.

Implement a strategy that does a slug -> id lookup, and only caches objects by ID. Things like slug become external lookup fields that hit the cache as well. It's 2 cache reads, but it's a lot less writes.

How to cache only anonymous(non-login) request?

I'm using grape for api and devise for authentication.
I want to cache api response only if user-request hasn't login info.( Non-login status ). Then, how to do it?

  1. logged-in request serves normal response without caching, don't expires cache data(don't affect with caching).
  2. not-logged-in request serves response with caching

Garner::Strategies::Keys::Caller fails when running inside IRB

As an example, we have a method in our codebase

def find_cached_by_slug(slug)
  Garner::Cache::ObjectIdentity.cache(bind: [self, slug]) do
    self.find_by_slug(slug)
  end
end

When calling Model.find_cached_by_slug("slug") from inside an IRB session, we get the following exception:

>> Model.find_cached_by_slug("slug”)
Errno::ENOENT: No such file or directory - /Users/macreery/code/artsy/project/(irb)

Inconsistency between Mongoid find and Garner::Mixins::Mongoid::Document.garnered_find

Considering Garner::Mixins::Mongoid::Document.garnered_find I believe the API's for the two 'find' methods should be identical, here is a repro of an example that will cause different results between the two.

Here is the garnered_find method for reference:

def self.garnered_find(handle)
  return nil unless (binding = identify(handle))
  identity = Garner::Cache::Identity.new
  identity.bind(binding).key({ :source => :garnered_find }) do
    find(handle)
  end
end

Assuming an emptied cache:

Artist.garnered_find(['andy-warhol'])  
# returns array of one artist (correct)

Artist.garnered_find('andy-warhol')  
# returns array of one artist, rather than the object itself (incorrect)

Or (assuming an empty cache)

Artist.garnered_find('andy-warhol')  
# returns artist object (correct)

Artist.garnered_find(['andy-warhol'])  
# returns artist object, rather than an array (incorrect)

Contrast with find:

Artist.find(['andy-warhol'])   
# returns array of one artist (correct)

Artist.find('andy-warhol')   
# returns artist object (correct)

Different application root locations result in different cache keys

The Caller context key strategy employs the calling code's full file path in generating a cache key. Our environment is run from heterogeneous servers at different file paths, so we discovered that one environment would continue to fetch stale cache values after an invalidation occurred in a different environment.

The operative lines are here:

ruby_context.send(:caller).each do |line|

Example of the different results:

caller[0].split(':')[0]  # => "/app/app/api/api.rb" on Heroku
caller[0].split(':')[0]  # => "/srv/www/myapp/releases/20130612030626/app/api/api.rb" on OpsWorks

garnered_find slower than find?

I encountered this surprise in our own application, and was able to reproduce it from a checkout of garner with the minimal models used by specs:

$ GARNER_MEMCACHE_SERVER=localhost bundle exec irb -I lib/
irb(main):001:0> require 'rspec'
=> true
irb(main):002:0> require './spec/support/cache'
=> true
irb(main):003:0> require './spec/support/mongoid'
=> true
irb(main):004:0> Monger.delete_all
=> 100
irb(main):005:0> (1..100).each { |i| Monger.create!(name: "monger ##{i}") }
=> 1..100
irb(main):006:0> ids = Monger.pluck(:_id); ids.size
=> 100
irb(main):007:0> n = 100
=> 100
irb(main):008:0> Benchmark.bm(15) do |x|
irb(main):009:1*   x.report('find') { n.times { ids.each{|id| Monger.find(id) } } }
irb(main):010:1>   x.report('garnered_find') { n.times { ids.each{|id| Monger.garnered_find(id) } } }
irb(main):011:1> end
                      user     system      total        real
find              4.210000   0.440000   4.650000 (  5.451388)
garnered_find     8.770000   0.910000   9.680000 ( 11.849314)

It's possible that this set-up isn't configuring the cache optimally, but I'd expect this to be sufficient to see cache hits and the library's benefits. What am I doing wrong?

"TypeError: singleton can't be dumped" error on Heroku

Everything is working fine in development, but on Heroku, I sometimes get TypeError: singleton can't be dumped. Any idea why?

An example is when visiting a specific location: /api/locations/521d32be1974fcdb2b000103

Here's the Grape+Garner code for that endpoint:

resource "locations" do
  desc "Get the details for a specific location"
  get ':id' do
    garner.options(expires_in: 5.minutes) do
      location = Location.find(params[:id])
      location = present location, with: Entities::Location
      location.as_json
    end
  end
end

Here's the stack trace from New Relic:

…undle/ruby/2.0.0/gems/garner-0.4.4/lib/garner/cache.rb:  12:in `fetch'
…y/2.0.0/gems/garner-0.4.4/lib/garner/cache/identity.rb:  22:in `fetch'
…y/2.0.0/gems/garner-0.4.4/lib/garner/cache/identity.rb:  54:in `options'
                                  /app/app/api/ohana.rb:  41:in `block (2 levels) in <class:API>'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb:  31:in `call'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb:  31:in `block in generate_api_method'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb: 400:in `call'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb: 400:in `run'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb: 164:in `block in call!'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  24:in `call'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  24:in `call!'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  18:in `call'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  24:in `call!'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  18:in `call'
…dor/bundle/ruby/2.0.0/gems/rack-1.4.5/lib/rack/etag.rb:  23:in `call'
…/ruby/2.0.0/gems/rack-1.4.5/lib/rack/conditionalget.rb:  25:in `call'
…y/2.0.0/gems/grape-0.5.0/lib/grape/middleware/error.rb:  26:in `block in call!'
…y/2.0.0/gems/grape-0.5.0/lib/grape/middleware/error.rb:  25:in `catch'
…y/2.0.0/gems/grape-0.5.0/lib/grape/middleware/error.rb:  25:in `call!'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  18:in `call'
…/newrelic-grape-1.3.1/lib/newrelic-grape/instrument.rb:  19:in `block in call!'
…/newrelic-grape-1.3.1/lib/newrelic-grape/instrument.rb:  18:in `call!'
…by/2.0.0/gems/grape-0.5.0/lib/grape/middleware/base.rb:  18:in `call'
…dor/bundle/ruby/2.0.0/gems/rack-1.4.5/lib/rack/head.rb:   9:in `call'
…/bundle/ruby/2.0.0/gems/rack-1.4.5/lib/rack/builder.rb: 134:in `call'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb: 165:in `call!'
…ndle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/endpoint.rb: 155:in `call'
….0.0/gems/rack-mount-0.8.3/lib/rack/mount/route_set.rb: 152:in `block in call'
…ems/rack-mount-0.8.3/lib/rack/mount/code_generation.rb:  96:in `block in recognize'
…ems/rack-mount-0.8.3/lib/rack/mount/code_generation.rb:  82:in `optimized_each'
…ems/rack-mount-0.8.3/lib/rack/mount/code_generation.rb:  95:in `recognize'
….0.0/gems/rack-mount-0.8.3/lib/rack/mount/route_set.rb: 141:in `call'
…or/bundle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/api.rb: 480:in `call'
…or/bundle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/api.rb:  49:in `call!'
…or/bundle/ruby/2.0.0/gems/grape-0.5.0/lib/grape/api.rb:  45:in `call'
…le/ruby/2.0.0/gems/journey-1.0.4/lib/journey/router.rb:  68:in `block in call'
…le/ruby/2.0.0/gems/journey-1.0.4/lib/journey/router.rb:  56:in `each'
…le/ruby/2.0.0/gems/journey-1.0.4/lib/journey/router.rb:  56:in `call'
…undle/ruby/2.0.0/gems/rack-pjax-0.7.0/lib/rack/pjax.rb:  12:in `call'
…goid-3.1.4/lib/rack/mongoid/middleware/identity_map.rb:  34:in `block in call'
…y/2.0.0/gems/mongoid-3.1.4/lib/mongoid/unit_of_work.rb:  39:in `unit_of_work'
…goid-3.1.4/lib/rack/mongoid/middleware/identity_map.rb:  34:in `call'
…undle/ruby/2.0.0/gems/rack-cors-0.2.8/lib/rack/cors.rb:  54:in `call'
…dle/ruby/2.0.0/gems/warden-1.2.3/lib/warden/manager.rb:  35:in `block in call'
…dle/ruby/2.0.0/gems/warden-1.2.3/lib/warden/manager.rb:  34:in `catch'
…dle/ruby/2.0.0/gems/warden-1.2.3/lib/warden/manager.rb:  34:in `call'
…dor/bundle/ruby/2.0.0/gems/rack-1.4.5/lib/rack/etag.rb:  23:in `call'
…/ruby/2.0.0/gems/rack-1.4.5/lib/rack/conditionalget.rb:  25:in `call'
…0.0/gems/remotipart-1.2.1/lib/remotipart/middleware.rb:  27:in `call'
…/2.0.0/gems/rack-1.4.5/lib/rack/session/abstract/id.rb: 210:in `context'
…/2.0.0/gems/rack-1.4.5/lib/rack/session/abstract/id.rb: 205:in `call'
…/ruby/2.0.0/gems/rack-1.4.5/lib/rack/methodoverride.rb:  21:in `call'
…/bundle/ruby/2.0.0/gems/rack-1.4.5/lib/rack/runtime.rb:  17:in `call'
                               /app/lib/api_defender.rb:  47:in `call'
…dor/bundle/ruby/2.0.0/gems/rack-1.4.5/lib/rack/lock.rb:  15:in `call'
…by/2.0.0/gems/rack-cache-1.2/lib/rack/cache/context.rb: 136:in `forward'
…by/2.0.0/gems/rack-cache-1.2/lib/rack/cache/context.rb: 245:in `fetch'
…by/2.0.0/gems/rack-cache-1.2/lib/rack/cache/context.rb: 185:in `lookup'
…by/2.0.0/gems/rack-cache-1.2/lib/rack/cache/context.rb:  66:in `call!'
…by/2.0.0/gems/rack-cache-1.2/lib/rack/cache/context.rb:  51:in `call'
…ruby/2.0.0/gems/rack-timeout-0.0.4/lib/rack/timeout.rb:  16:in `block in call'
       /app/vendor/ruby-2.0.0/lib/ruby/2.0.0/timeout.rb:  66:in `timeout'
…ruby/2.0.0/gems/rack-timeout-0.0.4/lib/rack/timeout.rb:  16:in `call'
…by/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb: 552:in `process_client'
…by/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb: 632:in `worker_loop'
…by/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb: 500:in `spawn_missing_workers'
…by/2.0.0/gems/unicorn-4.6.3/lib/unicorn/http_server.rb: 142:in `start'

Garner::Strategies::Keys::Caller broken on Heroku

Create a more robust way to identity the caller location. On Heroku you get this:

LINE: /app/.bundle/gems/ruby/1.9.1/gems/garner-0.1.2/lib/garner/cache/object_identity.rb:94:in `block in key_context'
SET: /app/.bundle/gems/ruby/1.9.1/gems/garner-0.1.2/lib/garner/cache/object_identity.rb:94

This is because the bundle is under app. All the rest of the hackery is not great.

Add automatic support for Grape entities

I'm trying to cache the output generated from a custom entity:

get '/products/:id' do
  cache do
    product = Product.find params[:id]
    present product, with: Entities::Product
  end
end

But this fails with:

no _dump_data is defined for class StringIO

I guess the stream somehow needs to be converted to a String first?

Run tests on JRuby

Right now bundle install complains that posix-spawn is required, which needs a C extension.

SafeCacheKey + Touch doesn't pass spec/integration/mongoid_spec.rb

SafeCacheKey + Touch doesn't pass spec/integration/mongoid_spec.rb.

  1) Mongoid integration using Garner::Strategies::Binding::Key::SafeCacheKey with Garner::Strategies::Binding::Invalidation::Touch end-to-end caching and invalidation binding at the instance level binding via find does not invalidate results for other like-classed objects
     Failure/Error: cached_object_namer.call.should eq 'M1'

       expected: "M1"
            got: "M2"

       (compared using ==)
     # ./spec/integration/mongoid_spec.rb:176:in `block (8 levels) in <top (required)>'
     # ./spec/support/active_record.rb:32:in `block (3 levels) in <top (required)>'
     # ./spec/support/active_record.rb:31:in `block (2 levels) in <top (required)>'

  2) Mongoid integration using Garner::Strategies::Binding::Key::SafeCacheKey with Garner::Strategies::Binding::Invalidation::Touch end-to-end caching and invalidation binding at the instance level binding via find with an embedded document with :invalidate_mongoid_root = true invalidates the root document
     Failure/Error: root_cached_object_namer.call.should eq 'Sockeye'

       expected: "Sockeye"
            got: "Trout"

       (compared using ==)
     # ./spec/integration/mongoid_spec.rb:269:in `block (10 levels) in <top (required)>'
     # ./spec/support/active_record.rb:32:in `block (3 levels) in <top (required)>'
     # ./spec/support/active_record.rb:31:in `block (2 levels) in <top (required)>'

  3) Mongoid integration using Garner::Strategies::Binding::Key::SafeCacheKey with Garner::Strategies::Binding::Invalidation::Touch end-to-end caching and invalidation binding at the instance level binding via identify does not invalidate results for other like-classed objects
     Failure/Error: cached_object_namer.call.should eq 'M1'

       expected: "M1"
            got: "M2"

       (compared using ==)
     # ./spec/integration/mongoid_spec.rb:176:in `block (8 levels) in <top (required)>'
     # ./spec/support/active_record.rb:32:in `block (3 levels) in <top (required)>'
     # ./spec/support/active_record.rb:31:in `block (2 levels) in <top (required)>'

  4) Mongoid integration using Garner::Strategies::Binding::Key::SafeCacheKey with Garner::Strategies::Binding::Invalidation::Touch end-to-end caching and invalidation binding at the instance level binding via identify with an embedded document with :invalidate_mongoid_root = true invalidates the root document
     Failure/Error: root_cached_object_namer.call.should eq 'Sockeye'

       expected: "Sockeye"
            got: "Trout"

       (compared using ==)
     # ./spec/integration/mongoid_spec.rb:269:in `block (10 levels) in <top (required)>'
     # ./spec/support/active_record.rb:32:in `block (3 levels) in <top (required)>'
     # ./spec/support/active_record.rb:31:in `block (2 levels) in <top (required)>'

Does Garner support binding ActiveRecord models?

Just getting started with Garner and I am having trouble binding to ActiveRecord models. I debugged down into the code and it seems that if you bind an ActiveRecord model class such as garner.bind(Patient) ... end, by default the SafeCacheKey strategy will be used, but since the model class wont respond to :cache_key or :updated_at, we return from the apply method of the strategy with no binding_keys which effectively skips the cache.

Am I doing something wrong? I tried figure out what to do with ActiveRecord from the Mongoid samples.

Any help would be greatly appreciated, also sorry if this is not the correct place for questions, I looked for a Google Group and didn't find one.

ActiveRecord mixin - incorrect initializer code, and dumping problems

@fancyremarker When I add the code to the initializer,

/Users/yan/dev/reverb/config/initializers/garner.rb:4:in `<module:ActiveRecord>': Base is not a module (TypeError)

Which is because Base is actually a Class. The correct initializer code is:

require "garner/mixins/active_record"

module ActiveRecord
  class Base
    include Garner::Mixins::ActiveRecord::Base
  end
end

Note the require line, and the use of class Base. I can make a pull request for that, but I'm afraid there are other issues.

But then I run into further problems when actually trying to cache that resources:
no _dump_data is defined for class Proc

/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/rspec-mocks-2.12.2/lib/rspec/mocks/extensions/marshal.rb:5:in `dump'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/rspec-mocks-2.12.2/lib/rspec/mocks/extensions/marshal.rb:5:in `dump_with_mocks'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/activesupport-3.2.12/lib/active_support/cache.rb:561:in `initialize'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/activesupport-3.2.12/lib/active_support/cache.rb:363:in `new'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/activesupport-3.2.12/lib/active_support/cache.rb:363:in `block in write'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/activesupport-3.2.12/lib/active_support/cache.rb:520:in `instrument'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/activesupport-3.2.12/lib/active_support/cache.rb:362:in `write'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/gems/activesupport-3.2.12/lib/active_support/cache.rb:299:in `fetch'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/bundler/gems/garner-b880202c454c/lib/garner/cache.rb:12:in `fetch'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/bundler/gems/garner-b880202c454c/lib/garner/cache/identity.rb:16:in `fetch'
/Users/yan/.rvm/gems/ruby-1.9.3-p392@reverb/bundler/gems/garner-b880202c454c/lib/garner/cache/identity.rb:24:in `bind'

Does Garner work with Roar?

Hi,
Me again 😄
I'm using Roar as my representer. I have 3 models: Organization, Location, and Services.
Here's my LocationRepresenter, which also includes some properties from the Organization and Service models:

require 'roar/representer/json'
require 'roar/representer/feature/hypermedia'

module LocationRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :id
  # more location properties

  property :organization, extend: OrganizationRepresenter, class: Organization

  collection :services, :extend => ServiceRepresenter, :class => Service
end

Here's my Grape API location resource without Garner, which works fine:

...
get do
  locations = Location.includes([:organization, :services]).page(params[:page]).per(params[:per_page])
  locations.extend LocationsRepresenter
end

If I add Garner like this:

get do
  garner.options(expires_in: 15.minutes) do
    locations = Location.includes([:organization, :services]).page(params[:page]).per(params[:per_page])
    locations.extend LocationsRepresenter
  end
end

On the first request, I get the full representation. On the garnered request, I get the non-represented version of locations which is missing the organization and services stuff. I also tried garner.bind(Location) and garner.bind(Location).bind(Organization).bind(Service) and I'm still not getting the full representation back from garner.

If I try the same thing on an individual location:

get ':id' do
  garner.options(expires_in: 15.minutes) do
    location = Location.includes([:organization, :services]).find(params[:id])
    location.extend LocationRepresenter
  end
end

I get can't dump anonymous class #<Module:0x007f875130b3f0>.

I get the same dump error with the bind method:

get ':id' do
  garner.bind(Location) do
    location = Location.includes([:organization, :services]).find(params[:id])
    location.extend LocationRepresenter
  end
end

Any help would be greatly appreciated! Thanks!

Mongoid mixin should offer a `garnered_find` class method with automatically appropriate key and binding

module Mongoid
  module Document
    module ClassMethods
      def garnered_find(id, options = {})
        Garner::Cache::ObjectIdentity.cache({ bind: [self, id] }, { caller: nil }) do
          self.find(id)
        end
      end
    end
  end
end

The above implementation comes close, but doesn't properly key off parent relations. Specifically, something like Model.garnered_find("id") works, but something like supermodel.models.garnered_find("id") won't work (it will return the same thing regardless of the supermodel's identity).

Caller context key strategy totally borks if Garner::Cache::Context#garner is monkey-patched

Let's say I do the following

# config/initializers/garner.rb

module Garner
  module Mixins
    module Rack

      alias_method :_garner, :garner
      def garner(&block)
        response, headers = _garner.key({ foo: "bar" }, &block)
      end
    end
  end
end

Now the calling line number will always be config/initializers/garner.rb:9, instead of where the method was originally called from.

Can we be smarter about this?

Undefined method [] for Pathname(/path/to/my/app) (problem with Caller key strategy)

I seem to be getting an issue with my caller key strategy. I actually don't want to use caller, so I tried to override it like this:

Garner.configure do |config|
  config.rack_context_key_strategies = [
    Garner::Strategies::Context::Key::RequestGet,
    Garner::Strategies::Context::Key::RequestPost,
    Garner::Strategies::Context::Key::RequestPath
  ]
end

But that failed with no method error on rack_context_key_strategies

Has anyone successfully used a non-caller key strategy? I just want to use this with Grape so I am interested in caching based on the request params and my custom binding.

Garner/Grape Entity issue

Hey guys!
I'm having an issue using Grape with cache (Garner) and send the results with grape-entity.

The issue is: the second time I request an object Garner start working, but the call for Grape Entity have a problem, because it sets the options as nil, making grape-entity to throw an error, options is not define.

Versions
Grape 0.6.1
Grape-entity 0.4.0
Garner 0.3.2

Default expires_in override global_cache_options

In README

  • :global_cache_options: A hash of options to be passed on every call to Garner.config.cache, like { :expires_in => 10.minutes }. Defaults to {}

But In

@options_hash.merge!(expires_in: Garner.config.expires_in)

@options_hash = Garner.config.global_cache_options || {}
@options_hash.merge!(expires_in: Garner.config.expires_in)

The default expires_in is nil, it will override expires_in in global_cache_options.
So possible changes:

  • Update README to use another key name and add expires_in to full list of Garner.config attributes.

Or

@options_hash[:expires_in] ||= Garner.config.expires_in

Can't get it to work with Grape

Any attempt to cache anything (even without bindings and options) end up like this:

15:10:05 web.1    | 2014-06-13 15:10:05 -0700: Rack app error: #<NoMethodError: undefined method `realpath' for nil:NilClass>
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/strategies/context/key/caller.rb:21:in `default_root'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/config.rb:99:in `default_caller_root'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/config.rb:106:in `caller_root'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/strategies/context/key/caller.rb:49:in `block in apply'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/strategies/context/key/caller.rb:43:in `each'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/strategies/context/key/caller.rb:43:in `apply'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/mixins/rack.rb:24:in `block in garner'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/mixins/rack.rb:23:in `each'
15:10:05 web.1    | /home/vagrant/.rvm/gems/ruby-2.1.0/gems/garner-0.4.5/lib/garner/mixins/rack.rb:23:in `garner'
15:10:05 web.1    | /vagrant/flipagram-rb-music/app/api/admin/category.rb:44:in `block (2 levels) in <class:Categories>'

Grape is standlone app without rails.

Find a better way to rewrite how the Mongoid::Identity mixin yields conditions

We set Garner.config.mongoid_identity_fields = [:_id, :_slugs]. In our case, these are two separate ways to find an instance of a Model that are mutually exclusive and follow distinct formats.

Garner's Garner::Mixins::Mongoid::Identity will combine these into an $or. Here're two possible queries:

{"$or"=>[{"_id"=>"jason-bryant-hope-slash-hate"}, {"_slugs"=>"jason-bryant-hope-slash-hate"}]}

{"$or"=>[{"_id"=>"51ca3bec8b3b8132c60002ea"}, {"_slugs"=>"51ca3bec8b3b8132c60002ea"}]}

This is not very efficient and we can make the decision about whether the query is done by slug or by id upfront, so we monkey patch this as follows.

module Garner
  module Mixins
    module Mongoid
      class Identity
        private

        def self.conditions_for(klass, handle)
          # optimize away the query from an $or to a find where appropriate
          # in the case of Gravity _id and _slugs can be used independently
          # and we can never have a non BSON _id and valid BSON IDs are redundant with _id inside _slugs
          conditions = Moped::BSON::ObjectId.legal?(handle) ? { _id: handle } : { _slugs: handle }

          # _type conditions
          selector = klass.where({})
          conditions.merge!(selector.send(:type_selection)) if selector.send(:type_selectable?)

          conditions
        end
      end
    end
  end
end

This is very much custom. But maybe there's a better way to describe this in Garner without writing a completely different mixin?

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.