artemk-cache-money 0.2.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +201 -0
- data/README +210 -0
- data/README.markdown +210 -0
- data/TODO +17 -0
- data/UNSUPPORTED_FEATURES +12 -0
- data/config/environment.rb +16 -0
- data/config/memcached.yml +6 -0
- data/db/schema.rb +18 -0
- data/init.rb +1 -0
- data/lib/cache_money.rb +86 -0
- data/lib/cash/accessor.rb +83 -0
- data/lib/cash/buffered.rb +129 -0
- data/lib/cash/config.rb +82 -0
- data/lib/cash/fake.rb +83 -0
- data/lib/cash/finders.rb +38 -0
- data/lib/cash/index.rb +214 -0
- data/lib/cash/local.rb +76 -0
- data/lib/cash/lock.rb +63 -0
- data/lib/cash/mock.rb +154 -0
- data/lib/cash/query/abstract.rb +197 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +50 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/request.rb +3 -0
- data/lib/cash/transactional.rb +43 -0
- data/lib/cash/util/active_record.rb +5 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/util/marshal.rb +19 -0
- data/lib/cash/write_through.rb +69 -0
- data/lib/mem_cached_session_store.rb +50 -0
- data/lib/mem_cached_support_store.rb +141 -0
- data/lib/memcached_wrapper.rb +261 -0
- data/spec/cash/accessor_spec.rb +186 -0
- data/spec/cash/active_record_spec.rb +224 -0
- data/spec/cash/buffered_spec.rb +9 -0
- data/spec/cash/calculations_spec.rb +67 -0
- data/spec/cash/finders_spec.rb +408 -0
- data/spec/cash/local_buffer_spec.rb +9 -0
- data/spec/cash/local_spec.rb +9 -0
- data/spec/cash/lock_spec.rb +108 -0
- data/spec/cash/marshal_spec.rb +60 -0
- data/spec/cash/order_spec.rb +172 -0
- data/spec/cash/transactional_spec.rb +578 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/write_through_spec.rb +245 -0
- data/spec/memcached_wrapper_test.rb +209 -0
- data/spec/spec_helper.rb +68 -0
- metadata +168 -0
    
        data/TODO
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            TOP PRIORITY
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            REFACTOR
         | 
| 4 | 
            +
            * Clarify terminology around cache/key/index, etc.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            INFRASTRUCTURE
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            NEW FEATURES
         | 
| 9 | 
            +
            * transactional get multi isn't really multi
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            BUGS
         | 
| 12 | 
            +
            * Handle append strategy (using add rather than set?) to avoid race condition
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            MISSING TESTS:
         | 
| 15 | 
            +
            * missing tests for Klass.transaction do ... end
         | 
| 16 | 
            +
            * non "id" pks work but lack test coverage
         | 
| 17 | 
            +
            * expire_cache
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            * does not work with :dependent => nullify because
         | 
| 2 | 
            +
              def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
         | 
| 3 | 
            +
                association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
              This does not trigger callbacks
         | 
| 6 | 
            +
            * update_all, delete, update_counter, increment_counter, decrement_counter, counter_caches in general - counter caches are replaced by this gem, bear that in mind.
         | 
| 7 | 
            +
            * attr_readonly - no technical obstacle, just not yet supported
         | 
| 8 | 
            +
            * attributes before typecast behave unpredictably - hard to support
         | 
| 9 | 
            +
            * printf style binds: :conditions => ["name = '%s'", "37signals!"] - not too hard to support
         | 
| 10 | 
            +
            * objects as attributes that are serialized. story.title = {:foo => :bar}; customer.balance = Money.new(...) - these could be coerced using Column#type_cast?
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            With a lot of these features the issue is not technical but performance. Every special case costs some overhead.
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            gem 'activesupport', '~> 2.3.0'
         | 
| 3 | 
            +
            gem 'activerecord', '~> 2.3.0'
         | 
| 4 | 
            +
            gem 'actionpack', '~> 2.3.0'
         | 
| 5 | 
            +
            gem 'rspec', '>= 1.3.0'
         | 
| 6 | 
            +
            gem 'jeweler', '~> 1.4.0'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require 'action_controller'
         | 
| 9 | 
            +
            require 'active_record'
         | 
| 10 | 
            +
            require 'active_record/session_store'
         | 
| 11 | 
            +
            require 'jeweler'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ActiveRecord::Base.establish_connection(
         | 
| 14 | 
            +
              :adapter => 'sqlite3',
         | 
| 15 | 
            +
              :database => ':memory:'
         | 
| 16 | 
            +
            )
         | 
    
        data/db/schema.rb
    ADDED
    
    | @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            ActiveRecord::Schema.define(:version => 2) do
         | 
| 2 | 
            +
              create_table "stories", :force => true do |t|
         | 
| 3 | 
            +
                t.string "title", "subtitle"
         | 
| 4 | 
            +
                t.string  "type"
         | 
| 5 | 
            +
                t.boolean "published"
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              create_table "characters", :force => true do |t|
         | 
| 9 | 
            +
                t.integer "story_id"
         | 
| 10 | 
            +
                t.string "name"
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
              
         | 
| 13 | 
            +
              create_table :sessions, :force => true do |t|
         | 
| 14 | 
            +
                t.string :session_id
         | 
| 15 | 
            +
                t.text   :data
         | 
| 16 | 
            +
                t.timestamps
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        data/init.rb
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'cache_money'
         | 
    
        data/lib/cache_money.rb
    ADDED
    
    | @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            require 'active_support'
         | 
| 2 | 
            +
            require 'active_record'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'cash/lock'
         | 
| 5 | 
            +
            require 'cash/transactional'
         | 
| 6 | 
            +
            require 'cash/write_through'
         | 
| 7 | 
            +
            require 'cash/finders'
         | 
| 8 | 
            +
            require 'cash/buffered'
         | 
| 9 | 
            +
            require 'cash/index'
         | 
| 10 | 
            +
            require 'cash/config'
         | 
| 11 | 
            +
            require 'cash/accessor'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            require 'cash/request'
         | 
| 14 | 
            +
            require 'cash/fake'
         | 
| 15 | 
            +
            require 'cash/local'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            require 'cash/query/abstract'
         | 
| 18 | 
            +
            require 'cash/query/select'
         | 
| 19 | 
            +
            require 'cash/query/primary_key'
         | 
| 20 | 
            +
            require 'cash/query/calculation'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            require 'cash/util/array'
         | 
| 23 | 
            +
            require 'cash/util/marshal'
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            class ActiveRecord::Base
         | 
| 26 | 
            +
              def self.is_cached(options = {})
         | 
| 27 | 
            +
                if options == false
         | 
| 28 | 
            +
                  include NoCash
         | 
| 29 | 
            +
                else
         | 
| 30 | 
            +
                  options.assert_valid_keys(:ttl, :repository, :version)
         | 
| 31 | 
            +
                  include Cash unless ancestors.include?(Cash)
         | 
| 32 | 
            +
                  Cash::Config.create(self, options)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              def <=>(other)
         | 
| 37 | 
            +
                if self.id == other.id then 
         | 
| 38 | 
            +
                  0
         | 
| 39 | 
            +
                else
         | 
| 40 | 
            +
                  self.id < other.id ? -1 : 1
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            module Cash
         | 
| 46 | 
            +
              def self.included(active_record_class)
         | 
| 47 | 
            +
                active_record_class.class_eval do
         | 
| 48 | 
            +
                  include Config, Accessor, WriteThrough, Finders
         | 
| 49 | 
            +
                  extend ClassMethods
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              module ClassMethods
         | 
| 54 | 
            +
                def self.extended(active_record_class)
         | 
| 55 | 
            +
                  class << active_record_class
         | 
| 56 | 
            +
                    alias_method_chain :transaction, :cache_transaction
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def transaction_with_cache_transaction(&block)
         | 
| 61 | 
            +
                  if cache_config
         | 
| 62 | 
            +
                    transaction_without_cache_transaction do
         | 
| 63 | 
            +
                      repository.transaction(&block)
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  else
         | 
| 66 | 
            +
                    transaction_without_cache_transaction(&block)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def cacheable?(*args)
         | 
| 71 | 
            +
                  true
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
            end
         | 
| 75 | 
            +
            module NoCash
         | 
| 76 | 
            +
              def self.included(active_record_class)
         | 
| 77 | 
            +
                active_record_class.class_eval do
         | 
| 78 | 
            +
                  extend ClassMethods
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
              module ClassMethods
         | 
| 82 | 
            +
                def cachable?(*args)
         | 
| 83 | 
            +
                  false
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              module Accessor
         | 
| 3 | 
            +
                def self.included(a_module)
         | 
| 4 | 
            +
                  a_module.module_eval do
         | 
| 5 | 
            +
                    extend ClassMethods
         | 
| 6 | 
            +
                    include InstanceMethods
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                module ClassMethods
         | 
| 11 | 
            +
                  def fetch(keys, options = {}, &block)
         | 
| 12 | 
            +
                    case keys
         | 
| 13 | 
            +
                    when Array
         | 
| 14 | 
            +
                      return {} if keys.empty?
         | 
| 15 | 
            +
                      
         | 
| 16 | 
            +
                      keys = keys.collect { |key| cache_key(key) }
         | 
| 17 | 
            +
                      hits = repository.get_multi(*keys)
         | 
| 18 | 
            +
                      if (missed_keys = keys - hits.keys).any?
         | 
| 19 | 
            +
                        missed_values = block.call(missed_keys)
         | 
| 20 | 
            +
                        hits.merge!(missed_keys.zip(Array(missed_values)).to_hash_without_nils)
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                      hits
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      repository.get(cache_key(keys), options[:raw]) || (block ? block.call : nil)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def get(keys, options = {}, &block)
         | 
| 29 | 
            +
                    case keys
         | 
| 30 | 
            +
                    when Array
         | 
| 31 | 
            +
                      fetch(keys, options, &block)
         | 
| 32 | 
            +
                    else
         | 
| 33 | 
            +
                      fetch(keys, options) do
         | 
| 34 | 
            +
                        if block_given?
         | 
| 35 | 
            +
                          add(keys, result = yield(keys), options)
         | 
| 36 | 
            +
                          result
         | 
| 37 | 
            +
                        end
         | 
| 38 | 
            +
                      end
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def add(key, value, options = {})
         | 
| 43 | 
            +
                    if repository.add(cache_key(key), value, options[:ttl] || cache_config.ttl, options[:raw]) == "NOT_STORED\r\n"
         | 
| 44 | 
            +
                      yield if block_given?
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def set(key, value, options = {})
         | 
| 49 | 
            +
                    repository.set(cache_key(key), value, options[:ttl] || cache_config.ttl, options[:raw])
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def incr(key, delta = 1, ttl = nil)
         | 
| 53 | 
            +
                    ttl ||= cache_config.ttl
         | 
| 54 | 
            +
                    repository.incr(cache_key = cache_key(key), delta) || begin
         | 
| 55 | 
            +
                      repository.add(cache_key, (result = yield).to_s, ttl, true) { repository.incr(cache_key) }
         | 
| 56 | 
            +
                      result
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def decr(key, delta = 1, ttl = nil)
         | 
| 61 | 
            +
                    ttl ||= cache_config.ttl
         | 
| 62 | 
            +
                    repository.decr(cache_key = cache_key(key), delta) || begin
         | 
| 63 | 
            +
                      repository.add(cache_key, (result = yield).to_s, ttl, true) { repository.decr(cache_key) }
         | 
| 64 | 
            +
                      result
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def expire(key)
         | 
| 69 | 
            +
                    repository.delete(cache_key(key))
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def cache_key(key)
         | 
| 73 | 
            +
                    "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                module InstanceMethods
         | 
| 78 | 
            +
                  def expire
         | 
| 79 | 
            +
                    self.class.expire(id)
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         | 
| @@ -0,0 +1,129 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              class Buffered
         | 
| 3 | 
            +
                def self.push(cache, lock)
         | 
| 4 | 
            +
                  if cache.is_a?(Buffered)
         | 
| 5 | 
            +
                    cache.push
         | 
| 6 | 
            +
                  else
         | 
| 7 | 
            +
                    Buffered.new(cache, lock)
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(memcache, lock)
         | 
| 12 | 
            +
                  @buffer = {}
         | 
| 13 | 
            +
                  @commands = []
         | 
| 14 | 
            +
                  @cache = memcache
         | 
| 15 | 
            +
                  @lock = lock
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def pop
         | 
| 19 | 
            +
                  @cache
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def push
         | 
| 23 | 
            +
                  NestedBuffered.new(self, @lock)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def get(key, *options)
         | 
| 27 | 
            +
                  if @buffer.has_key?(key)
         | 
| 28 | 
            +
                    @buffer[key]
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    @buffer[key] = @cache.get(key, *options)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def set(key, value, *options)
         | 
| 35 | 
            +
                  @buffer[key] = value
         | 
| 36 | 
            +
                  buffer_command Command.new(:set, key, value, *options)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def incr(key, amount = 1)
         | 
| 40 | 
            +
                  return unless value = get(key, true)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  @buffer[key] = value.to_i + amount
         | 
| 43 | 
            +
                  buffer_command Command.new(:incr, key, amount)
         | 
| 44 | 
            +
                  @buffer[key]
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def decr(key, amount = 1)
         | 
| 48 | 
            +
                  return unless value = get(key, true)
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  @buffer[key] = [value.to_i - amount, 0].max
         | 
| 51 | 
            +
                  buffer_command Command.new(:decr, key, amount)
         | 
| 52 | 
            +
                  @buffer[key]
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def add(key, value, *options)
         | 
| 56 | 
            +
                  @buffer[key] = value
         | 
| 57 | 
            +
                  buffer_command Command.new(:add, key, value, *options)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def delete(key, *options)
         | 
| 61 | 
            +
                  @buffer[key] = nil
         | 
| 62 | 
            +
                  buffer_command Command.new(:delete, key, *options)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def get_multi(*keys)
         | 
| 66 | 
            +
                  values = keys.collect { |key| get(key) }
         | 
| 67 | 
            +
                  keys.zip(values).to_hash_without_nils
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def flush
         | 
| 71 | 
            +
                  sorted_keys = @commands.select(&:requires_lock?).collect(&:key).uniq.sort
         | 
| 72 | 
            +
                  sorted_keys.each do |key|
         | 
| 73 | 
            +
                    @lock.acquire_lock(key)
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                  perform_commands
         | 
| 76 | 
            +
                ensure
         | 
| 77 | 
            +
                  @buffer = {}
         | 
| 78 | 
            +
                  sorted_keys.each do |key|
         | 
| 79 | 
            +
                    @lock.release_lock(key)
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def respond_to?(method)
         | 
| 84 | 
            +
                  @cache.respond_to?(method)
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                protected
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def perform_commands
         | 
| 90 | 
            +
                  @commands.each do |command|
         | 
| 91 | 
            +
                    command.call(@cache)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def buffer_command(command)
         | 
| 96 | 
            +
                  @commands << command
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
                
         | 
| 99 | 
            +
                private
         | 
| 100 | 
            +
                
         | 
| 101 | 
            +
                def method_missing(method, *args, &block)
         | 
| 102 | 
            +
                  @cache.send(method, *args, &block)
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              class NestedBuffered < Buffered
         | 
| 107 | 
            +
                def flush
         | 
| 108 | 
            +
                  perform_commands
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              class Command
         | 
| 113 | 
            +
                attr_accessor :key
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                def initialize(name, key, *args)
         | 
| 116 | 
            +
                  @name = name
         | 
| 117 | 
            +
                  @key = key
         | 
| 118 | 
            +
                  @args = args
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                def requires_lock?
         | 
| 122 | 
            +
                  @name == :set
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                def call(cache)
         | 
| 126 | 
            +
                  cache.send @name, @key, *@args
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
            end
         | 
    
        data/lib/cash/config.rb
    ADDED
    
    | @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              module Config
         | 
| 3 | 
            +
                def self.create(active_record, options, indices = [])
         | 
| 4 | 
            +
                  active_record.cache_config = Cash::Config::Config.new(active_record, options)
         | 
| 5 | 
            +
                  indices.each { |i| active_record.index i.attributes, i.options }
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
                
         | 
| 8 | 
            +
                def self.included(a_module)
         | 
| 9 | 
            +
                  a_module.module_eval do
         | 
| 10 | 
            +
                    extend ClassMethods
         | 
| 11 | 
            +
                    delegate :repository, :to => "self.class"
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                module ClassMethods
         | 
| 16 | 
            +
                  def self.extended(a_class)
         | 
| 17 | 
            +
                    class << a_class
         | 
| 18 | 
            +
                      def cache_config
         | 
| 19 | 
            +
                        @cache_config ? @cache_config : superclass.cache_config
         | 
| 20 | 
            +
                      end
         | 
| 21 | 
            +
                      
         | 
| 22 | 
            +
                      delegate :repository, :indices, :to => :cache_config
         | 
| 23 | 
            +
                      alias_method_chain :inherited, :cache_config
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def inherited_with_cache_config(subclass)
         | 
| 28 | 
            +
                    inherited_without_cache_config(subclass)
         | 
| 29 | 
            +
                    @cache_config.inherit(subclass)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def index(attributes, options = {})
         | 
| 33 | 
            +
                    options.assert_valid_keys(:ttl, :order, :limit, :buffer, :order_column)
         | 
| 34 | 
            +
                    (@cache_config.indices.unshift(Index.new(@cache_config, self, attributes, options))).uniq!
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def version(number)
         | 
| 38 | 
            +
                    @cache_config.options[:version] = number
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def cache_config=(config)
         | 
| 42 | 
            +
                    @cache_config = config
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                class Config
         | 
| 47 | 
            +
                  attr_reader :active_record, :options
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def initialize(active_record, options = {})
         | 
| 50 | 
            +
                    @active_record, @options = active_record, options
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def repository
         | 
| 54 | 
            +
                    @options[:repository]
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def ttl
         | 
| 58 | 
            +
                    @ttl ||= @options[:ttl] || default_ttl
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def version
         | 
| 62 | 
            +
                    @options[:version] || 1
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def indices
         | 
| 66 | 
            +
                    @indices ||= active_record == ActiveRecord::Base ? [] : [Index.new(self, active_record, active_record.primary_key)]
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  def inherit(active_record)
         | 
| 70 | 
            +
                    Cash::Config.create(active_record, @options, indices)
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                  
         | 
| 73 | 
            +
                  private
         | 
| 74 | 
            +
                  
         | 
| 75 | 
            +
                    def default_ttl
         | 
| 76 | 
            +
                      default = repository.default_ttl if repository.respond_to?(:default_ttl)
         | 
| 77 | 
            +
                      default ||= 1.day
         | 
| 78 | 
            +
                      default
         | 
| 79 | 
            +
                    end 
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end
         |