GithubHelp home page GithubHelp logo

shopify / identity_cache Goto Github PK

View Code? Open in Web Editor NEW
1.9K 485.0 173.0 1.41 MB

IdentityCache is a blob level caching solution to plug into Active Record. Don't #find, #fetch!

Home Page: http://shopify.github.io/identity_cache

License: MIT License

Ruby 99.95% Shell 0.05%
webscale activerecord cache

identity_cache's Introduction

IdentityCache

Build Status

Opt in read through ActiveRecord caching used in production and extracted from Shopify. IdentityCache lets you specify how you want to cache your model objects, at the model level, and adds a number of convenience methods for accessing those objects through the cache. Memcached is used as the backend cache store, and the database is only hit when a copy of the object cannot be found in Memcached.

IdentityCache keeps track of the objects that have cached indexes and uses an after_commit hook to expire those objects, and any up the tree, when they are changed.

Installation

Add this line to your application's Gemfile:

gem 'identity_cache'
gem 'cityhash'        # optional, for faster hashing (C-Ruby only)

gem 'dalli' # To use :mem_cache_store
# alternatively
gem 'memcached_store' # to use the old libmemcached based client

And then execute:

$ bundle

Add the following to all your environment/*.rb files (production/development/test):

If you use Dalli (recommended)

config.identity_cache_store = :mem_cache_store, "mem1.server.com", "mem2.server.com", {
  expires_in: 6.hours.to_i, # in case of network errors when sending a cache invalidation
  failover: false, # avoids more cache consistency issues
}

Add an initializer with this code:

IdentityCache.cache_backend = ActiveSupport::Cache.lookup_store(*Rails.configuration.identity_cache_store)

If you use Memcached (old client)

config.identity_cache_store = :memcached_store,
  Memcached.new(["mem1.server.com"],
    support_cas: true,
    auto_eject_hosts: false,  # avoids more cache consistency issues
  ), { expires_in: 6.hours.to_i } # in case of network errors when sending a cache invalidation

Add an initializer with this code:

IdentityCache.cache_backend = ActiveSupport::Cache.lookup_store(*Rails.configuration.identity_cache_store)

Usage

Basic Usage

class Image < ActiveRecord::Base
  include IdentityCache::WithoutPrimaryIndex
end

class Product < ActiveRecord::Base
  include IdentityCache

  has_many :images

  cache_has_many :images, embed: true
end

# Fetch the product by its id using the primary index as well as the embedded images association.
@product = Product.fetch(id)

# Access the loaded images for the Product.
@images = @product.fetch_images

Note: You must include the IdentityCache module into the classes where you want to use it.

Secondary Indexes

IdentityCache lets you lookup records by fields other than id. You can have multiple of these indexes with any other combination of fields:

class Product < ActiveRecord::Base
  include IdentityCache
  cache_index :handle, unique: true
  cache_index :vendor, :product_type
end

# Fetch the product from the cache by the index.
# If the object isn't in the cache it is pulled from the db and stored in the cache.
product = Product.fetch_by_handle(handle)

# Fetch multiple products by providing an array of index values.
products = Product.fetch_multi_by_handle(handles)

# Fetch a single product by providing composite attributes.
products = Product.fetch_by_vendor_and_product_type(vendor, product_type)

# Fetch multiple products by providing an array of composite attributes.
products = Product.fetch_multi_by_vendor_and_product_type([
  [vendor_1, product_type_1],
  [vendor_2, product_type_2],
  # ...
])

This gives you a lot of freedom to use your objects the way you want to, and doesn't get in your way. This does keep an independent cache copy in Memcached so you might want to watch the number of different caches that are being added.

Reading from the cache

IdentityCache adds fetch_* methods to the classes that you mark with cache indexes, based on those indexes. The example below will add a fetch_by_domain method to the class.

class Shop < ActiveRecord::Base
  include IdentityCache
  cache_index :domain
end

Association caches follow suit and add fetch_* methods based on the indexes added for those associations.

class Product < ActiveRecord::Base
  include IdentityCache
  has_many  :images
  has_one   :featured_image

  cache_has_many :images
  cache_has_one :featured_image, embed: :id
end

@product.fetch_featured_image
@product.fetch_images

To read multiple records in batch use fetch_multi.

class Product < ActiveRecord::Base
  include IdentityCache
end

Product.fetch_multi([1, 2])

Embedding Associations

IdentityCache can easily embed objects into the parents' cache entry. This means loading the parent object will also load the association and add it to the cache along with the parent. Subsequent cache requests will load the parent along with the association in one fetch. This can again mean some duplication in the cache if you want to be able to cache objects on their own as well, so it should be done with care. This works with both cache_has_many and cache_has_one methods.

class Product < ActiveRecord::Base
  include IdentityCache

  has_many :images
  cache_has_many :images, embed: true
end

@product = Product.fetch(id)
@product.fetch_images

With this code, on cache miss, the product and its associated images will be loaded from the db. All this data will be stored into the single cache key for the product. Later requests will load the entire blob of data; @product.fetch_images will not need to hit the db since the images are loaded with the product from the cache.

Caching Polymorphic Associations

IdentityCache tries to figure out both sides of an association whenever it can so it can set those up when rebuilding the object from the cache. In some cases this is hard to determine so you can tell IdentityCache what the association should be. This is most often the case when embedding polymorphic associations.

class Metafield < ActiveRecord::Base
  include IdentityCache
  belongs_to :owner, polymorphic: true
  cache_belongs_to :owner
end

class Product < ActiveRecord::Base
  include IdentityCache
  has_many :metafields, as: :owner
  cache_has_many :metafields
end

Caching Attributes

For cases where you may not need the entire object to be cached, just an attribute from record, cache_attribute can be used. This will cache the single attribute by the key specified.

class Redirect < ActiveRecord::Base
  cache_attribute :target, by: [:shop_id, :path]
end

Redirect.fetch_target_by_shop_id_and_path(shop_id, path)

This will read the attribute from the cache or query the database for the attribute and store it in the cache.

Methods Added to ActiveRecord::Base

cache_index

Options: [:unique] Allows you to say that an index is unique (only one object stored at the index) or not unique, which allows there to be multiple objects matching the index key. The default value is false.

Example: cache_index :handle

cache_has_many

Options: [:embed] When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. When :ids, only the id of the associated records will be included with the parent when caching. Defaults to :ids.

Example: cache_has_many :metafields, embed: true

cache_has_one

Options: [:embed] When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. No other values are currently implemented. When :id, only the id of the associated record will be included with the parent when caching.

Example: cache_has_one :configuration, embed: :id

cache_belongs_to

Example: cache_belongs_to :shop

cache_attribute

Options: [:by] Specifies what key(s) you want the attribute cached by. Defaults to :id.

Example: cache_attribute :target, by: [:shop_id, :path]

Memoized Cache Proxy

Cache reads and writes can be memoized for a block of code to serve duplicate identity cache requests from memory. This can be done for an http request by adding this around filter in your ApplicationController.

class ApplicationController < ActionController::Base
  around_filter :identity_cache_memoization

  def identity_cache_memoization(&block)
    IdentityCache.cache.with_memoization(&block)
  end
end

Versioning

Cache keys include a version number by default, specified in IdentityCache::CACHE_VERSION. This version number is updated whenever the storage format for cache values is modified. If you modify the cache value format, you must run rake update_serialization_format in order to pass the unit tests, and include the modified test/fixtures/serialized_record file in your pull request.

Caveats

IdentityCache is never going to be 100% consistent, since cache invalidations can be lost. As such, it was intentionally designed to be opt-in, so it is only used where cache inconsistency is tolerated. This means IdentityCache does not mess with the way normal Rails associations work, and including it in a model won't change any clients of that model until you switch them to use fetch instead of find. This means that you need to think carefully about when you use fetch and when you use find.

Expected sources of lost cache invalidations include:

  • Database write performed that doesn't trigger an after_commit callback
  • Process/system getting killed or crashing between the database commit and cache invalidation
  • Network unavailability, including transient failures, preventing the delivery of the cache invalidation
  • Memcached unavailability or failure preventing the processing of the cache invalidation request
  • Memcached flush / restart could remove a cache invalidation that would normally interrupt a cache fill that started when the cache key was absent. E.g.
    1. cache key absent (not just invalidated)
    2. process 1 reads cache key
    3. process 1 starts reading from the database
    4. process 2 writes to the database
    5. process 2 writes a cache invalidation marker to cache key
    6. memcached flush
    7. process 1 uses an ADD operation, which succeeds in filling the cache with the now stale data
  • Rollout of cache namespace changes (e.g. from upgrading IdentityCache, adding columns, cached associations or from application changes to IdentityCache.cache_namespace) can result in cache fills to the new namespace that aren't invalidated by cache invalidations from a process still using the old namespace

Cache expiration is meant to be used to help the system recover, but it only works if the application avoids using the cache data as a transaction to write data. IdentityCache avoids loading cached data from its methods during an open transaction, but can't prevent cache data that was loaded before the transaction was opened from being used in a transaction. IdentityCache won't help with scaling write traffic, it was intended for scaling database queries from read-only requests.

IdentityCache also caches the absence of database values (e.g. to avoid performance problems when it is destroyed), so lost cache invalidations can also result in that value continuing to remain absent. As such, avoid sending the id of an uncommitted database record to another process (e.g. queuing it to a background job), since that could result in an attempt to read the record by its id before it has been created. A cache invalidation will still be attempted when the record is created, but that could be lost.

Notes

  • See CHANGELOG.md for a list of changes to the library over time.
  • The library is MIT licensed and we welcome contributions. See CONTRIBUTING.md for more information.

identity_cache's People

Contributors

airhorns avatar arthurnn avatar byroot avatar camilo avatar casperisfine avatar cbothner avatar cribbles avatar daniellaniyo avatar derekstride avatar driv3r avatar dylanahsmith avatar edouard-chin avatar eugeneius avatar fbogsany avatar fw42 avatar george-ma avatar glebglazov avatar gmcgibbon avatar hirocaster avatar hurshagrawal avatar jduff avatar kirs avatar marcandre avatar rafaelfranca avatar simon-shopify avatar slucaskim avatar stivaros avatar thibaut avatar tjoyal avatar wvanbergen 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  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

identity_cache's Issues

IdentityCache::AlreadyIncludedError error when running worker tasks and config.cache_classes = false

When including IdentityCache in a model and running a worker task, such as rake resque:work (one that requires environment) along with the web server in local development environment, I'm getting a IdentityCache::AlreadyIncludedError error upon process start. I'm using foreman to start processes.

It's due to config.cache_classes = false in development and I'm just wondering whether there is a recommended way to handle this scenario? Rescueing if in development or testing whether it's already included or am I overlooking something very simple?

14:18:44 worker.1       | rake aborted!
14:18:44 worker.1       | IdentityCache::AlreadyIncludedError
14:18:44 worker.1       | /Users/carhartl/Workspace/Ubertweek/tweek.tv/vendor/bundle/ruby/1.9.1/gems/identity_cache-0.0.1/lib/identity_cache.rb:97:in `included'
14:18:44 worker.1       | /Users/carhartl/Workspace/Ubertweek/tweek.tv/app/models/video.rb:3:in `include'
14:18:44 worker.1       | /Users/carhartl/Workspace/Ubertweek/tweek.tv/app/models/video.rb:3:in `<class:Video>'
14:18:44 worker.1       | /Users/carhartl/Workspace/Ubertweek/tweek.tv/app/models/video.rb:1:in `<top (required)>'

Edit:

Furthermore, it's probably pretty pointless to include IdentityCache for asynchronous background jobs, isn't it?

Looks like the Rails.configuration.identity_cache_store != IdentityCache.cache

Rails.configuration.identity_cache_store is set to Memcached::Rails
IdentityCache.cache is set to ActiveSupport::Cache::FileStore

Should not both use Memcached::Rails as defined in Rails.configuration.identity_cache_store?

1.9.3p385 :005 > Rails.configuration.identity_cache_store
 => [:mem_cache_store, #<Memcached::Rails:0xa6d19b0 @logger=nil, @string_return_types=nil, @struct=#<Rlibmemcached::MemcachedSt:0xa6d1848>, @options={:hash=>:fnv1_32, :no_block=>false, :noreply=>false, :distribution=>:consistent_ketama, :ketama_weighted=>true, :buffer_requests=>false, :cache_lookups=>true, :support_cas=>false, :tcp_nodelay=>false, :show_backtraces=>false, :retry_timeout=>30, :timeout=>0.25, :rcv_timeout=>0.25, :poll_timeout=>0.25, :connect_timeout=>4, :prefix_key=>"", :prefix_delimiter=>"", :hash_with_prefix_key=>true, :default_ttl=>604800, :default_weight=>8, :sort_hosts=>false, :auto_eject_hosts=>true, :server_failure_limit=>2, :verify_key=>true, :use_udp=>false, :binary_protocol=>false, :credentials=>nil, :experimental_features=>false, :exception_retry_limit=>5, :exceptions_to_retry=>[Memcached::ServerIsMarkedDead, Memcached::ATimeoutOccurred, Memcached::ConnectionBindFailure, Memcached::ConnectionFailure, Memcached::ConnectionSocketCreateFailure, Memcached::Failure, Memcached::MemoryAllocationFailure, Memcached::ReadFailure, Memcached::ServerEnd, Memcached::ServerError, Memcached::SystemError, Memcached::UnknownReadFailure, Memcached::WriteFailure, Memcached::SomeErrorsWereReported]}, @default_ttl=604800, @not_found=#<Memcached::NotFound: Memcached::NotFound>, @not_stored=#<Memcached::NotStored: Memcached::NotStored>>] 
1.9.3p385 :006 > IdentityCache.cache
 => #<IdentityCache::MemoizedCacheProxy:0xba24bac @memcache=#<ActiveSupport::Cache::FileStore:0xa834514 @options={}, @cache_path="/home/row/code/mannaz/mannaz-business-bootcamp/tmp/cache/", @thread_local_key=:active_support_cache_file_store_local_cache_88187530, @middleware=#<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0xb3f57b8 @name="ActiveSupport::Cache::Strategy::LocalCache", @thread_local_key=:active_support_cache_file_store_local_cache_88187530, @app=#<Rack::Runtime:0xa9e0b4c @app=#<Rack::MethodOverride:0xa9e0c3c @app=#<ActionDispatch::RequestId:0xa9e0d18 @app=#<Rails::Rack::Logger:0xa9e0db8 @app=#<ActionDispatch::ShowExceptions:0xa9e0e08 @app=#<ActionDispatch::DebugExceptions:0xa9e0e80 @app=#<BetterErrors::Middleware:0xa9e0fc0 @app=#<Airbrake::Rails::Middleware:0xa9e1268 @app=#<ActionDispatch::RemoteIp:0xa9e136c @app=#<ActionDispatch::Reloader:0xa9e13bc @app=#<ActionDispatch::Callbacks:0xa9e1574 @app=#<ActiveRecord::ConnectionAdapters::ConnectionManagement:0xa9e17cc @app=#<ActiveRecord::QueryCache:0xa9e1b3c @app=#<ActionDispatch::Cookies:0xa9e1c04 @app=#<Rack::P3p:0xa9e1c2c @app=#<ActionDispatch::Session::CookieStore:0xa9e24d8 @secrets=["21197a4b3195792ad56740ac245309a1bb2d448e9ed964704ad879a5055c"], @coder=#<Rack::Session::Cookie::Base64::Marshal:0xa9e1d80>, @app=#<ActionDispatch::Flash:0xa9e2514 @app=#<ActionDispatch::ParamsParser:0xa9e2848 @app=#<ActionDispatch::Head:0xa9e2910 @app=#<Rack::ConditionalGet:0xa9e2960 @app=#<Rack::ETag:0xa9e2a14 @app=#<ActionDispatch::BestStandardsSupport:0xa9d7920 @app=#<Warden::Manager:0xa9d7ce0 @config={:default_scope=>:admin_user, :scope_defaults=>{}, :default_strategies=>{:admin_user=>[:rememberable, :database_authenticatable]}, :intercept_401=>false, :failure_app=>#<Devise::Delegator:0xae477a8>}, @app=#<Rack::MobileDetect:0xa9d91d0 @app=#<ActionDispatch::Routing::RouteSet:0xa9bd354>, @regex_ua_targeted=/iphone|android|ipod|ipad/i, @regex_accept=/vnd\.wap/i, @regex_ua_catchall=/palm|blackberry|nokia|phone|midp|mobi|symbian|chtml|ericsson|minimo|audiovox|motorola|samsung|telit|upg1|windows ce|ucweb|astel|plucker|x320|x240|j2me|sgh|portable|sprint|docomo|kddi|softbank|android|mmp|pdxgw|netfront|xiino|vodafone|portalmmm|sagem|mot-|sie-|ipod|up\.b|webos|amoi|novarra|cdm|alcatel|pocket|ipad|iphone|mobileexplorer|mobile/i, @redirect_to=nil, @redirect_map=nil>>, @header="IE=Edge">, @cache_control="max-age=0, private, must-revalidate", @no_cache_control="no-cache">>>, @parsers={application/xml=>:xml_simple, application/json=>:json}>>, @default_options={:path=>"/", :domain=>nil, :expire_after=>nil, :secure=>false, :httponly=>true, :defer=>false, :renew=>false, :secret=>"21197a4b3195792ad56740ac245309a1bb2d448e9ed964704ad879a5055c", :coder=>#<Rack::Session::Cookie::Base64::Marshal:0xa9e1d80>}, @key="_mannaz-business-bootcamp_session", @cookie_only=true>>>>>>, @condition=#<Proc:0xaa06cc0@/home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/railties-3.2.13/lib/rails/application.rb:267 (lambda)>, @validated=true>, @check_ip=true, @proxies=/
      ^127\.0\.0\.1$                | # localhost
      ^(10                          | # private IP 10.x.x.x
        172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
        192\.168                      # private IP 192.168.x.x
       )\.
    /x>>, @handler=BetterErrors::ErrorPage>>, @exceptions_app=#<ActionDispatch::PublicExceptions:0xaac48ec @public_path="/home/row/code/mannaz/mannaz-business-bootcamp/public">>, @taggers=[]>>>, @header_name="X-Runtime">>>, @key_value_maps={}> 
1.9.3p385 :007 > 

Cache expiration not working as expected from console

I have this simple setup for testing:

class Post < ActiveRecord::Base
  include IdentityCache

  has_many :comments

  cache_has_many :comments, embed: true
end

I have the post with all comments already in the cache (0 db hits when post is loaded)

I load up the rails console and touch the first comment

2.0.0-p0 :002 > Comment.first.touch
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" ORDER BY "comments"."id" ASC LIMIT 1
  SQL (2.8ms)  UPDATE "comments" SET "updated_at" = '2013-06-04 16:14:09.164979' WHERE "comments"."id" = 1
[IdentityCache] expiring=Comment expiring_id=1 expiring_last_updated_at=2013-06-04 16:14:09 UTC
 => true

I expected the post cache to be expired as well, but it isn't; still 0 db hits when it's loaded in browser.

However, if I follow up my Comment.first.touch with a Post.first in the console and then do Comment.first.touch again, it does expire the post cache:

  Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? ORDER BY "posts"."id" ASC LIMIT 1  [["id", 1]]
[IdentityCache] expiring=Post expiring_id=1 expiring_last_updated_at=2013-06-04 15:40:45 UTC
 => true

I was just wondering if this is the correct behavior, or some quirk of working with the data and touching it through the Rails console. I'm also using Rails 4 RC1 and Ruby 2.0, which I'm not sure is supported yet. Thanks!

Support for has_many :through

Currently, identity_cache does not seem to support has_many :through associations; at least I am unable to get this working. Is this something you would consider accepting a patch for?

custom primary key isn't picked up in query_api.rb

I have a model with a custom primary key set with

self.primary_key = :foo

When I try a fetch and the object needs to be read from the DB, the query_api method "fetch_by_id" does not respect my primary key setting, but will always default to using 'id'.

I think this line (Line #36 in query_api.rb) might be the culprit:

self.where(id: id).first

The expected behaviour would be to get the object from ActiveRecord like this:

self.where(foo: id).first

Or maybe my setup is wrong?

Inverse name for association could not be determined. Please use the :inverse_name option to specify the inverse association name for this cache.

I have simple has_many through relationship defined something like this:

 has_many :group_users
 has_many :groups, :through => :group_users, :order => "name ASC", :conditions => "active = 1"

Defining cache version relationship for this ie:

 cache_has_many :groups, :embed => true

is giving me an error

 Inverse name for association could not be determined. Please use the :inverse_name option to specify the inverse association name for this cache.

inverse_name??
Anything that you can suggest on this. I can't see anything related to has_many through in readme file.

Add support for non-integer primary keys

I use UUID's for my primary keys. Using the normal fetch method seems to work. As do the association fetch methods. However, I'm running into issues when fetching based on attributes.

Example:

# SomeModel has a UUID primary key...
class SomeModel < ActiveRecord::Base
  include IdentityCache

  cache_index :some_attribute
end

SomeModel.fetch_by_some_attribute('...')

Here's the SQL that is generated

SELECT  "some_models".* FROM "some_models"  WHERE "some_models"."deleted_at" IS NULL AND "some_models"."id" = 6  ORDER BY "some_models"."id" ASC LIMIT 1

Notice that "some_models"."id" = 6 is an integer and not a UUID.

The id being passed in the query is always the first integer in the UUID primary key.

For instance, if the primary key is 7eec6c1c-48b2-42d2-a0a7-d0a6e5ede558 the id passed will be 7.

Here's the error thrown.

PG::UndefinedFunction: ERROR:  operator does not exist: uuid = integer
LINE 1: ...me_models"."deleted_at" IS NULL AND "some_models"."id" = 6  ORDER...
                                                             ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT  "some_models".* FROM "some_models"  WHERE "some_models"."deleted_at" IS NULL AND "some_models"."id" = 6  ORDER BY "some_models"."id" ASC LIMIT 1

Rails 4 support

gemspec is hard wired to 3.2.13

  gem.add_dependency('activerecord', '3.2.13')
  gem.add_dependency('activesupport', '3.2.13')

is it matter of updating the gemspec or is there more?

Customize cache_key with params?

Is there a way to add extra parameters to the cache key in order to support pagination for fetch_by when caching multiple records?

table_name evaluated at inclusion time for fetching uncached results

Hey guys, just started looking at the gem and it looks pretty great. One issue I had however was including IdentityCache, but specifying the table_name of my model outside the scope of that inclusion. It's not a typical use case I know, but we built Apartment for db multi-tenancy, and it sets tables names dynamically to achieve certain goals (ie non-tenanted models). The problem is that IdentifyCache seems to ignore this when it fetches an id from the database. Here's a gist that outlines the problem/workaround.

This isn't a bug per-se in your code but I wasn't sure exactly where to start a discussion on this. My guess is that the instance_eval with a string evaluates table_name once at the time that the model includes the module, rather than each time the method is called. I've never done an instance eval with a string so maybe I'm wrong; you'd probably know better.

Anyway, as I say I know this isn't a bug but I wanted to get your thoughts. One idea I had was to maybe add something similar to the Rails 4 change in dynamic finders. That is, define a single generic fetch_by that takes a hash of properties, rather than resorting to defining a method for each cache_index declared. I think that would solve our problem as we could just define_method normally and the sql string could be evaluated on each method call. It'd also be a bit cleaner of an implementation as an added bonus.

Thoughts?

Support Redis

It would be great if it would be possible to used redis instead of memcached.

undefined method `fetch' for nil:NilClass

Hi Guys,

I'm running identity_cache 0.1.0 with Rails 4. I've connected to memcache using Dalli like so, this is the same memcache server I use for inbuilt rails cache so know that it is working:

config.identity_cache_store = :dalli_store, 'localhost:11211'

However I'm getting the above error when I get a cache hit. If there is a cache miss then all appears to work just fine.

Here's the output from the Rails console

Cache read: IDC:3:index:Page:subdomain:4582232394373926202
[IdentityCache] (cache_backend) cache hit for IDC:3:index:Page:subdomain:4582232394373926202
Cache read: IDC:3:blob:Page:16815512066119561261:7
[IdentityCache] (cache_backend) cache hit for IDC:3:blob:Page:16815512066119561261:7
Completed 500 Internal Server Error in 114ms

The tail end of the stack trace looks something like this:

activerecord (4.0.0) lib/active_record/attribute_methods/read.rb:80:in `block in read_attribute'
activerecord (4.0.0) lib/active_record/attribute_methods/read.rb:79:in `fetch'
activerecord (4.0.0) lib/active_record/attribute_methods/read.rb:79:in `read_attribute'
activerecord (4.0.0) lib/active_record/attribute_methods/primary_key.rb:19:in `id'
identity_cache (0.1.0) lib/identity_cache/query_api.rb:31:in `block in fetch_by_id'
identity_cache (0.1.0) lib/identity_cache/query_api.rb:163:in `require_if_necessary'
identity_cache (0.1.0) lib/identity_cache/query_api.rb:27:in `fetch_by_id'
identity_cache (0.1.0) lib/identity_cache/configuration_dsl.rb:189:in `identity_cache_single_value_dynamic_fetcher'
identity_cache (0.1.0) lib/identity_cache/configuration_dsl.rb:53:in `fetch_by_subdomain'

Any suggestions on what might be going on here?

Thanks,

Robert

stack level too deep

After fixing #59 (fix: #60) i have an other issue.

Get the error:

/home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:240: stack level too deep (SystemStackError)

console output:

โžœ  mannaz-business-bootcamp git:(master) โœ— bundle exec rails c
  debug: fetch("IDC:index:B56Survey::Survey:name:15512851623136796155", #<Proc:0xb373164@/home/row/code/b56/identity_cache/lib/identity_cache/configuration_dsl.rb:188>)
  debug: IdentityCache.should_cache? true
  debug: require_if_necessary
/home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:240: stack level too deep (SystemStackError)
โžœ  mannaz-business-bootcamp git:(master) โœ— 

After some debugging, i think is cause be this 4 lines. (https://github.com/Shopify/identity_cache/blob/master/lib/identity_cache/query_api.rb#L29-L33)
As fetch is not been called a second time..

Test for the gem works fine...
DB: PostgreSQL
Ruby: 1.9.3-p385

expire_primary_index now private?

It was useful when expire_primary_index could be manually expired. If for any reason the object is updated in a mass way that does not directly trigger the cache to expire... doing this manually is very helpful. Is there a new interface from 0.6 to 0.7 that provides something similar?

Support for non-standard primary keys?

Hey guys,

This looks like an extraordinary tool for improving Rails' performance. I'm having a few issues testing it out, one of them is caused by our database using non standard primary keys for objects (such as itemId instead of id). Is there any way of configuring identity_cache to work with this setup?

Fetch on relations ignore relation data

If I call

site.pages.fetch_by_position(2)

The id of the site is ignored. The result will be all pages with position 2, for all sites.

site.pages.fetch_by_position_and_site_id(2, site.id)

Is working, but is not really nice.

Not sure this is a Bug.

But I would find it great if it would be possible to use it in connection with relations.

Object that isn't fetched doesn't pull associations from cache

(Using master branch)

# first time - no cache at all
> Product.first.fetch_main_category
  Product Load (0.2ms)  SELECT products.* FROM products LIMIT 1
  Category Load (0.2ms)  SELECT categories.* FROM categories WHERE categories.id = 1 LIMIT 1
 => #<Category id: 1 name: "x">

# second time, not using #fetch - no cache for fetch_main_category
> Product.first.fetch_main_category
  Product Load (0.3ms)  SELECT products.* FROM products LIMIT 1
  Category Load (0.3ms)  SELECT categories.* FROM categories WHERE categories.id = 1 LIMIT 1
 => #<Category id: 1 name: "x">

# using #fetch - no cache for fetch_main_category
> Product.fetch(1).fetch_main_category
  Product Load (0.3ms)  SELECT products.* FROM products WHERE products.id = 1 LIMIT 1
  Category Load (0.2ms)  SELECT categories.* FROM categories WHERE categories.id IN (1)
 => #<Category id: 1 name: "x">

# using #fetch - everything's cached
> Product.fetch(1).fetch_main_category
 => #<Category id: 1 name: "x">

Product class has

belongs_to :main_category, class_name: 'Category'
cache_has_one :main_category, inverse_name: :products

Expected: even when Product.first is called, the calls of fetch_main_category will get it from DB, put the Category object in cache and next calls of Product.first.fetch_main_category will read it from cache.

has_many cache returning raw cache entries on subsequent loads

Hello,

I have a has_many cache defined in an object -

has_many :tags
cache_has_many :tags

In a Rails console, I test this out (starting with an empty cache)

ob = MyModel.find(1).fetch_tags # correctly queries the db and returns the results
ob.fetch_tags # correctly *doesn't* query the db, and returns array of cached results

if I reload this object after it has populated its cache, however, I get raw cache entries instead -

MyModel.find(1).fetch_tags # correctly queries the db and returns the results
MyModel.find(1).fetch_tags # correctly does not query the db, but returns array of ActiveSupport::Cache::Entry objects

This same phenomenon happens when hitting an endpoint in a browser as well - the first load returns correct results, but raw cache entries are returned on all subsequent requests.

This is happening in my dev environment (rails_env == :development), using the master branch pulled from github a couple of hours ago.

TypeError: can't convert Hash into String when using serialize on attributes

I have the following class:

class Project < ActiveRecord::Base
  include IdentityCache
  attr_accessible :metadata,
  serialize :metadata, JSON
end

When you fetch the object for the first time, it works fine. If you fetch it a second time, it throws the following error:

TypeError: can't convert Hash into String
from ~/.rvm/gems/ruby-1.9.3-p484/gems/json-1.8.1/lib/json/common.rb:155:in `initialize'

Any way to get it to respect the JSON serialization of attributes?

Thanks!

Support normalized has_one caching

Currently throws a not implemented error. There are a number of scenarios where it is not desirable to embed a has_one association in the model, mostly due to not wanting to invalidate the object embedding it, for example this scenario I outlined here: #129

Would you accept a pull request for this, or are there plans to implement it?

Deleting cached records after migration

I have a model Movie and later on added some columns to it

but the cached version of movies does not have the updated version of schema and throws errors

Is there a way to clear model specific records cache or individual records cache for identity_cache?

multi_fetch bugs using redis/memcached

Using multi_fetch on top of redis is returning ActiveSupport::Cache:Entry objects (can be corrected by calling results.map(&:value))
Thought this may have been particular to redis, but using dalli for memcached returns a RuntimeError: can't modify frozen String

Redis:
redis

Memcached:
memcached

Although I see there are tests for multi_fetch that are passing- is this perhaps a configuration or client issue with redis/memcached? Anyone seen this behavior before?

has_and_belongs_to_many not working

I can't get a basic has_and_belongs_to_many association to work. The setup is pretty simple:

class Site < ActiveRecord::Base
  include IdentityCache
  has_and_belongs_to_many :articles
  cache_has_many :articles, :inverse_name => :site

The inverse_name is there because I get a IdentityCache::InverseAssociationError but it doesn't make any difference. Any ideas?

Gabriel

NoMethodError - undefined method `fetch_multi' for #<Class:0x0000010778d158>:

dev environment:

rails 3.2.14
dalli 2.6.4
identity_cache 0.0.4

scenario:
Product.rb

has_many :galleries,:as=>:item
cache_has_many :galleries, :inverse_name => :item

Exception:

IdentityCache cache hit for IDC:3:blob:Product:6158224318824924048:24
Completed 500 Internal Server Error in 312ms

NoMethodError - undefined method fetch_multi' for #<Class:0x0000010778d158>: activerecord (3.2.14) lib/active_record/dynamic_matchers.rb:55:inmethod_missing'
identity_cache (0.0.4) lib/identity_cache/configuration_dsl.rb:253:in `fetch_galleries'

Any idea? Thanks.

fetch multi on belongs_to

Is there some way to load the belongs_to record at the same time that the main record is fetched with cache_belongs_to?

There is no documentation for it and embed: true gives a NotImplemented error.

Does is support Model.fetch_by_column1_or_column2 ?

I have been using friendly_id in my application so by default it searches with slug or fallbacks to id

In Identifty_cache it always fetches by id so i have to cache both id and slug and separate keys and give two calls to cache to check if either exists.

record = Model.fetch_by_slug || Model.fetch_by_id(id)

can I optimize it to one call to cache

something like

Model.fetch_by_slug_or_id(id)

Plans to incorporate method caching?

I've been maintaining Simple Cacheable which has a similar feature set. You guys are doing an amazing job of running down the really hard bugs. Especially the marshalling errors.

Are there any plans to incorporate model method/class method caching and the like? If so, I'd like to start helping out. I haven't wanted to separate my focus between two caching libraries.

Undefined method `was_new_record?'

Testing out identity_cache with Rails 4.0.1 (development, SQLite3), using the master branch I ran into this when creating a new record from the console, seems to be coming from line 28 in parent_model_expiration

The only reference I can find to the was_new_record? method is from this gem, not from Rails itself, and it's not listed as a dependency for identity_cache.

Testing examples

Strategies for testing low level SQL caching aren't often discussed in the Rails community (at least from what I've been able to find).

At Shopify, do you write tests for the behavior when you add identity_cache to a model? Is checking cache invalidation something you cover in integration tests?

I would be really interested to hear more on this, I think a brief mention in the docs would be very helpful.

Identity cache does not play nice with https://github.com/influitive/apartment

In our case where we have multiple countries, that resides in multiple postgresql schemas, the cache key does not include the current country. Would you prefer that identity_cache was specifically patched to work with apartment, or that an extra option to cache_index is added? Say e.g. cache_index :email, :unique => true, :scope => #some_method_call e.g. country in our case that then is taken into account in hashing/creating the object's cache key...

ArgumentError: dump format error for symbol(0x6f)

have you seen this error before?

i just tried to run the tests and one of them is blowing up on me:

rake
1.9.3-p429

Run options: --seed 24847

# Running tests:

......................................................E...........................................................................

Finished tests in 16.957051s, 7.6664 tests/s, 16.5713 assertions/s.

  1) Error:
test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_many_associations(FetchMultiTest):
ArgumentError: dump format error for symbol(0x6f)
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache.rb:587:in `load'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache.rb:587:in `value'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache/mem_cache_store.rb:83:in `block in read_multi'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache/mem_cache_store.rb:81:in `each'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache/mem_cache_store.rb:81:in `read_multi'
    /Users/paule/Documents/ruby/identity_cache/test/test_helper.rb:25:in `block in read_multi_with_instrumentation'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache.rb:518:in `block in instrument'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `block in instrument'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/notifications.rb:123:in `instrument'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache.rb:518:in `instrument'
    /Users/paule/Documents/ruby/identity_cache/test/test_helper.rb:24:in `read_multi_with_instrumentation'
    /Users/paule/Documents/ruby/identity_cache/lib/identity_cache/memoized_cache_proxy.rb:55:in `read_multi'
    /Users/paule/Documents/ruby/identity_cache/lib/identity_cache.rb:94:in `fetch_multi'
    /Users/paule/Documents/ruby/identity_cache/lib/identity_cache/query_api.rb:55:in `block in fetch_multi'
    /Users/paule/Documents/ruby/identity_cache/lib/identity_cache/query_api.rb:75:in `require_if_necessary'
    /Users/paule/Documents/ruby/identity_cache/lib/identity_cache/query_api.rb:51:in `fetch_multi'
    /Users/paule/Documents/ruby/identity_cache/test/fetch_multi_with_batched_associations_test.rb:165:in `block in test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_many_associations'
    /Users/paule/Documents/ruby/identity_cache/test/test_helper.rb:72:in `assert_memcache_operations'
    /Users/paule/Documents/ruby/identity_cache/test/fetch_multi_with_batched_associations_test.rb:164:in `test_fetch_multi_batch_fetches_non_embedded_second_level_associations_through_embedded_first_level_has_many_associations'
    /Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/mocha-0.14.0/lib/mocha/integration/mini_test/version_230_to_2101.rb:36:in `run'

130 tests, 281 assertions, 0 failures, 1 errors, 0 skips
rake aborted!
Command failed with status (1): [ruby -I"lib:lib:test" -I"/Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/rake-10.0.4/lib" "/Users/paule/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/rake-10.0.4/lib/rake/rake_test_loader.rb" "test/**/*_test.rb" ]

Tasks: TOP => default => test
(See full trace by running task with --trace)

this is my runtime:

rbenv local
1.9.3-p429
mysql --version
mysql  Ver 14.14 Distrib 5.6.10, for osx10.8 (x86_64) using  EditLine wrapper
memcached -h
memcached 1.4.15

i tried several ruby 1.9.3 versions, just to be sure. all of them crashed.

fetch_cache_attribute_by_id throughs error in sqlserver-adapter

Hi
I've added cache_attribute to Zipcode model. when I call fetch_state_by_id(1) it throws error in sqlserver adapter, because of hard coded LIMIT clause in sql
Sql server has different keyword to LIMIT record ie) TOP clause.

class Zipcode < ActiveRecord::Base
include IdentityCache
cache_attribute :state, :by => [:id]

end

Zipcode.fetch_state_by_id(1)

below is the error details

(2.4ms) EXEC sp_executesql N'SELECT [state] FROM [zipcodes] WHERE [id] = 1 LIMIT 1'
ActiveRecord::StatementInvalid: ODBC::Error: 37000 (102) [unixODBC][FreeTDS][SQL Server]Incorrect syntax near 'LIMIT'.: EXEC sp_executesql N'SELECT [state] FROM [zipcodes] WHERE [id] = 1 LIMIT 1'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:389:in run' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:389:inblock in raw_connection_run'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver_adapter.rb:505:in with_sqlserver_error_handling' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:384:inraw_connection_run'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:376:in _raw_select' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:371:inblock in raw_select'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-3.2.14/lib/active_record/connection_adapters/abstract_adapter.rb:280:in block in log' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activesupport-3.2.14/lib/active_support/notifications/instrumenter.rb:20:ininstrument'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-3.2.14/lib/active_record/connection_adapters/abstract_adapter.rb:275:in log' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:371:inraw_select'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:354:in do_exec_query' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:24:inexec_query'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-sqlserver-adapter-3.2.12/lib/active_record/connection_adapters/sqlserver/database_statements.rb:297:in select' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-3.2.14/lib/active_record/connection_adapters/abstract/database_statements.rb:18:inselect_all'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-3.2.14/lib/active_record/connection_adapters/abstract/query_cache.rb:63:in select_all' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-3.2.14/lib/active_record/connection_adapters/abstract/database_statements.rb:24:inselect_one'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/activerecord-3.2.14/lib/active_record/connection_adapters/abstract/database_statements.rb:30:in select_value' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/identity_cache-0.0.4/lib/identity_cache/configuration_dsl.rb:271:inblock in attribute_dynamic_fetcher'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/identity_cache-0.0.4/lib/identity_cache.rb:73:in fetch' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/identity_cache-0.0.4/lib/identity_cache/configuration_dsl.rb:271:inattribute_dynamic_fetcher'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/identity_cache-0.0.4/lib/identity_cache/configuration_dsl.rb:174:in fetch_state_by_id' from (irb):3 from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/railties-3.2.14/lib/rails/commands/console.rb:47:instart'
from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/railties-3.2.14/lib/rails/commands/console.rb:8:in start' from /home/mdind15/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/railties-3.2.14/lib/rails/commands.rb:41:in<top (required)>'
from script/rails:6:in require' from script/rails:6:in

'irb(main):004:0>

PG::Error: ERROR: syntax error

Just updated identity_cache on a project to 0.0.3

Received following error (Worked fine with 0.0.2):

/gems/activerecord-3.2.13/lib/active_record/connection_adapters/postgresql_adapter.rb:1161:in `exec': PG::Error: ERROR:  syntax error at or near "`" (ActiveRecord::StatementInvalid)
LINE 1: SELECT `id` FROM `b56_survey_surveys` WHERE `name` = 'busine...
                         ^
: SELECT `id` FROM `b56_survey_surveys` WHERE `name` = 'business_bootcamp' LIMIT 1
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/postgresql_adapter.rb:1161:in `exec_no_cache'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/postgresql_adapter.rb:660:in `block in exec_query'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract_adapter.rb:280:in `block in log'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activesupport-3.2.13/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract_adapter.rb:275:in `log'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/postgresql_adapter.rb:659:in `exec_query'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/postgresql_adapter.rb:1256:in `select'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/database_statements.rb:18:in `select_all'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/query_cache.rb:63:in `select_all'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/database_statements.rb:24:in `select_one'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/database_statements.rb:30:in `select_value'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/identity_cache-0.0.3/lib/identity_cache/configuration_dsl.rb:188:in `block in identity_cache_single_value_dynamic_fetcher'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/identity_cache-0.0.3/lib/identity_cache.rb:60:in `fetch'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/identity_cache-0.0.3/lib/identity_cache/configuration_dsl.rb:188:in `identity_cache_single_value_dynamic_fetcher'
  from /home/row/.rvm/gems/ruby-1.9.3-p385@mannaz-bb/gems/identity_cache-0.0.3/lib/identity_cache/configuration_dsl.rb:56:in `fetch_by_name'

using with dalli but occurred an error

I updated bundle and now the identity_cache version is v0.2.0 and dalli version is v2.7.2
this error was not occurred before I bundle update.

my rails code is just like this.

Textbook.fetch(params[:textbook_id])
undefined method `cas' for #<ActiveSupport::Cache::DalliStore:0x000001058f0038>
IdentityCache::CacheFetcher#fetch
identity_cache (0.2.0) lib/identity_cache/cache_fetcher.rb, line 30

How can I avoid this error?

Thanks

Does it work with dalli store?

Sorry if I posted it at the wrong place, but I tried posting on stackoverflow and received no answer. I'd like to use identity_cache with dalli store.

Could not find the documentation to configure identity_cache to work with dalli. Could someone tell me how that's done?

I tried this in my config/environments/development.rb (I wanted to try this in development environment first):

config.identity_cache_store = :mem_cache_store, ActiveSupport::Cache::DalliStore.new(servers: ["mem"])

When I started rails, I saw this error:

uninitialized constant ActiveSupport::Cache::DalliStore (NameError)

deprecate warning when use 'fetch' on Rails 4.0.2

on Rails 4.0.2

when I use this

Model.fetch(params[:id])

I met this.

DEPRECATION WARNING: Calling find_by or find_by! methods with options is deprecated. e.g. User.where('age > 21').find_by_name('Jon')

this is not a severe problem, but you need to know this.

Thanks.

Support self-referential associations

It does not look like identity_cache currently supports self-referential associations.

If I have a

class Person < ActiveRecord::Base
  include IdentityCache

  has_many :children, class_name: name, foreign_key: :mother_id 
  belongs_to :mother, class_name: name, foreign_key: :mother_id

  cache_has_many :children, embed: true, inverse_name: :mother  

Then when in cache_key_generation.rb#denormalized_schema_hash:

    def self.denormalized_schema_hash(klass)
      schema_string = schema_to_string(klass.columns)
      if !(associations = embedded_associations(klass)).empty?
        embedded_schema = associations.map do |name, options|
          "#{name}:(#{denormalized_schema_hash(options[:association_class])})"
        end.sort.join(',')
        schema_string << "," << embedded_schema
      end
      IdentityCache.memcache_hash(schema_string)
    end

the klass of the association is Person, so it will call denormalized_schema_hash on Person, encounter the same association again and end up in an infinite loop and a Stack level too deep exception.

Is it possible to add this functionality?

Remove :idc_cached_nil

@dylanahsmith : "I think the only thing that prevented us from storing nil was that cache.read would return nil for a cache miss as well as if the value cached was nil. I think we may even be able to get rid of map_cached_nil_for now that we don't use cache.read."

Follow-up from #154

Note that support for cache.read was re-added in #161

Reduce Memcached deletes when expiring objects

When we have multiple children expiring parents or if we have a touch along with an update multiple delete calls will be sent to Memcached. This doesn't appear to be that expensive, but we should eliminate any extra round trips that we can.

fetch_multi fails when using memory_store

Initializer:

Rails.configuration.identity_cache_store = :memory_store, {}
IdentityCache.cache_backend = ActiveSupport::Cache.lookup_store(*Rails.configuration.identity_cache_store)

Backtrace:

NoMethodError - undefined method `cas_multi' for #<ActiveSupport::Cache::MemoryStore:0x000001051f3fa0>:
 <..>/identity_cache-a7260bb86e91/lib/identity_cache/cache_fetcher.rb:49:in `cas_multi'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache/cache_fetcher.rb:22:in `fetch_multi'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache/memoized_cache_proxy.rb:94:in `fetch_multi'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache.rb:128:in `block in fetch_in_batches'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache.rb:127:in `fetch_in_batches'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache.rb:108:in `fetch_multi'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache/query_api.rb:59:in `block in fetch_multi'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache/query_api.rb:148:in `require_if_necessary'
 <..>/identity_cache-a7260bb86e91/lib/identity_cache/query_api.rb:54:in `fetch_multi'

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.