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/lib/cash/mock.rb
    ADDED
    
    | @@ -0,0 +1,154 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              class Mock < HashWithIndifferentAccess
         | 
| 3 | 
            +
                attr_accessor :servers
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                class CacheEntry
         | 
| 6 | 
            +
                  attr_reader :value
         | 
| 7 | 
            +
                  
         | 
| 8 | 
            +
                  def self.default_ttl
         | 
| 9 | 
            +
                    1_000_000
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def self.now
         | 
| 13 | 
            +
                    Time.now
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                  
         | 
| 16 | 
            +
                  def initialize(value, raw, ttl)
         | 
| 17 | 
            +
                    if raw
         | 
| 18 | 
            +
                      @value = value.to_s
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      @value = Marshal.dump(value)
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                    
         | 
| 23 | 
            +
                    if ttl.zero?
         | 
| 24 | 
            +
                      @ttl = self.class.default_ttl
         | 
| 25 | 
            +
                    else
         | 
| 26 | 
            +
                      @ttl = ttl
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                    
         | 
| 29 | 
            +
                    @expires_at = self.class.now + @ttl
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                  
         | 
| 32 | 
            +
                  
         | 
| 33 | 
            +
                  def expired?
         | 
| 34 | 
            +
                    self.class.now > @expires_at
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                  
         | 
| 37 | 
            +
                  def increment(amount = 1)
         | 
| 38 | 
            +
                    @value = (@value.to_i + amount).to_s
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                  
         | 
| 41 | 
            +
                  def decrement(amount = 1)
         | 
| 42 | 
            +
                    @value = (@value.to_i - amount).to_s
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                  
         | 
| 45 | 
            +
                  def unmarshal
         | 
| 46 | 
            +
                    Marshal.load(@value)
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                  
         | 
| 49 | 
            +
                  def to_i
         | 
| 50 | 
            +
                    @value.to_i
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                
         | 
| 54 | 
            +
                attr_accessor :logging
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                def initialize
         | 
| 57 | 
            +
                  @logging = false
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
                
         | 
| 60 | 
            +
                def get_multi(keys)
         | 
| 61 | 
            +
                  slice(*keys).collect { |k,v| [k, v.unmarshal] }.to_hash_without_nils
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def set(key, value, ttl = CacheEntry.default_ttl, raw = false)
         | 
| 65 | 
            +
                  log "< set #{key} #{ttl}"
         | 
| 66 | 
            +
                  self[key] = CacheEntry.new(value, raw, ttl)
         | 
| 67 | 
            +
                  log('> STORED')
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def get(key, raw = false)
         | 
| 71 | 
            +
                  log "< get #{key}"
         | 
| 72 | 
            +
                  unless self.has_unexpired_key?(key)
         | 
| 73 | 
            +
                    log('> END')
         | 
| 74 | 
            +
                    return nil
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                  
         | 
| 77 | 
            +
                  log("> sending key #{key}")
         | 
| 78 | 
            +
                  log('> END')
         | 
| 79 | 
            +
                  if raw
         | 
| 80 | 
            +
                    self[key].value
         | 
| 81 | 
            +
                  else
         | 
| 82 | 
            +
                    self[key].unmarshal
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
                
         | 
| 86 | 
            +
                def delete(key, options = {})
         | 
| 87 | 
            +
                  log "< delete #{key}"
         | 
| 88 | 
            +
                  if self.has_unexpired_key?(key)
         | 
| 89 | 
            +
                    log "> DELETED"
         | 
| 90 | 
            +
                    super(key)
         | 
| 91 | 
            +
                  else
         | 
| 92 | 
            +
                    log "> NOT FOUND"
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def incr(key, amount = 1)
         | 
| 97 | 
            +
                  if self.has_unexpired_key?(key)
         | 
| 98 | 
            +
                    self[key].increment(amount)
         | 
| 99 | 
            +
                    self[key].to_i
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def decr(key, amount = 1)
         | 
| 104 | 
            +
                  if self.has_unexpired_key?(key)
         | 
| 105 | 
            +
                    self[key].decrement(amount)
         | 
| 106 | 
            +
                    self[key].to_i
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def add(key, value, ttl = CacheEntry.default_ttl, raw = false)
         | 
| 111 | 
            +
                  if self.has_unexpired_key?(key)
         | 
| 112 | 
            +
                    "NOT_STORED\r\n"
         | 
| 113 | 
            +
                  else
         | 
| 114 | 
            +
                    set(key, value, ttl, raw)
         | 
| 115 | 
            +
                    "STORED\r\n"
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def append(key, value)
         | 
| 120 | 
            +
                  set(key, get(key, true).to_s + value.to_s, nil, true)
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                def namespace
         | 
| 124 | 
            +
                  nil
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                def flush_all
         | 
| 128 | 
            +
                  log('< flush_all')
         | 
| 129 | 
            +
                  clear
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def stats
         | 
| 133 | 
            +
                  {}
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def reset_runtime
         | 
| 137 | 
            +
                  [0, Hash.new(0)]
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def has_unexpired_key?(key)
         | 
| 141 | 
            +
                  self.has_key?(key) && !self[key].expired?
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
                
         | 
| 144 | 
            +
                def log(message)
         | 
| 145 | 
            +
                  return unless logging
         | 
| 146 | 
            +
                  logger.debug(message)
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
                
         | 
| 149 | 
            +
                def logger
         | 
| 150 | 
            +
                  @logger ||= ActiveSupport::BufferedLogger.new(Rails.root.join('log/cash_mock.log'))
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
                
         | 
| 153 | 
            +
              end
         | 
| 154 | 
            +
            end
         | 
| @@ -0,0 +1,197 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class Abstract
         | 
| 4 | 
            +
                  delegate :with_exclusive_scope, :get, :table_name, :indices, :find_from_ids_without_cache, :cache_key, :columns_hash, :logger, :to => :@active_record
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def self.perform(*args)
         | 
| 7 | 
            +
                    new(*args).perform
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(active_record, options1, options2)
         | 
| 11 | 
            +
                    @active_record, @options1, @options2 = active_record, options1, options2 || {}
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    # if @options2.empty? and active_record.base_class != active_record
         | 
| 14 | 
            +
                    #   @options2 = { :conditions => { active_record.inheritance_column => active_record.to_s }}
         | 
| 15 | 
            +
                    # end
         | 
| 16 | 
            +
                    # if active_record.base_class != active_record
         | 
| 17 | 
            +
                    #   @options2[:conditions] = active_record.merge_conditions(
         | 
| 18 | 
            +
                    #     @options2[:conditions], { active_record.inheritance_column => active_record.to_s }
         | 
| 19 | 
            +
                    #   )
         | 
| 20 | 
            +
                    # end
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def perform(find_options = {}, get_options = {})
         | 
| 24 | 
            +
                    if cache_config = cacheable?(@options1, @options2, find_options)
         | 
| 25 | 
            +
                      cache_keys, index = cache_keys(cache_config[0]), cache_config[1]
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                      misses, missed_keys, objects = hit_or_miss(cache_keys, index, get_options)
         | 
| 28 | 
            +
                      format_results(cache_keys, choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects))
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      logger.debug("  \e[1;4;31mUNCACHEABLE\e[0m #{table_name} - #{find_options.inspect} - #{get_options.inspect} - #{@options1.inspect} - #{@options2.inspect}") if logger
         | 
| 31 | 
            +
                      uncacheable
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  DESC = /DESC/i
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def order
         | 
| 38 | 
            +
                    @order ||= begin
         | 
| 39 | 
            +
                      if order_sql = @options1[:order] || @options2[:order]
         | 
| 40 | 
            +
                        matched, table_name, column_name, direction = *(ORDER.match(order_sql.to_s))
         | 
| 41 | 
            +
                        [column_name, direction =~ DESC ? :desc : :asc]
         | 
| 42 | 
            +
                      else
         | 
| 43 | 
            +
                        ['id', :asc]
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  rescue TypeError
         | 
| 47 | 
            +
                    ['id', :asc]
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def limit
         | 
| 51 | 
            +
                    @limit ||= @options1[:limit] || @options2[:limit]
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def offset
         | 
| 55 | 
            +
                    @offset ||= @options1[:offset] || @options2[:offset] || 0
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def calculation?
         | 
| 59 | 
            +
                    false
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  private
         | 
| 63 | 
            +
                  def cacheable?(*optionss)
         | 
| 64 | 
            +
                    return false if @active_record.respond_to?(:cachable?) && ! @active_record.cachable?(*optionss)
         | 
| 65 | 
            +
                    optionss.each { |options| return unless safe_options_for_cache?(options) }
         | 
| 66 | 
            +
                    partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
         | 
| 67 | 
            +
                    return if partial_indices.include?(nil)
         | 
| 68 | 
            +
                    attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    # attribute_value_pairs.each do |attribute_value_pair|
         | 
| 71 | 
            +
                    #   return false if attribute_value_pair.last.is_a?(Array)
         | 
| 72 | 
            +
                    # end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
         | 
| 75 | 
            +
                      if index.matches?(self)
         | 
| 76 | 
            +
                        [attribute_value_pairs, index]
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def hit_or_miss(cache_keys, index, options)
         | 
| 82 | 
            +
                    misses, missed_keys = nil, nil
         | 
| 83 | 
            +
                    objects = @active_record.get(cache_keys, options.merge(:ttl => index.ttl)) do |missed_keys|
         | 
| 84 | 
            +
                      misses = miss(missed_keys, @options1.merge(:limit => index.window))
         | 
| 85 | 
            +
                      serialize_objects(index, misses)
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                    [misses, missed_keys, objects]
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  def cache_keys(attribute_value_pairs)
         | 
| 91 | 
            +
                    attribute_value_pairs.flatten.join('/')
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def safe_options_for_cache?(options)
         | 
| 95 | 
            +
                    return false unless options.kind_of?(Hash)
         | 
| 96 | 
            +
                    options.except(:conditions, :readonly, :limit, :offset, :order).values.compact.empty? && !options[:readonly]
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  def attribute_value_pairs_for_conditions(conditions)
         | 
| 100 | 
            +
                    case conditions
         | 
| 101 | 
            +
                    when Hash
         | 
| 102 | 
            +
                      conditions.to_a.collect { |key, value| [key.to_s, value] }
         | 
| 103 | 
            +
                    when String
         | 
| 104 | 
            +
                      parse_indices_from_condition(conditions.gsub('1 = 1 AND ', '')) #ignore unnecessary conditions
         | 
| 105 | 
            +
                    when Array
         | 
| 106 | 
            +
                      parse_indices_from_condition(*conditions)
         | 
| 107 | 
            +
                    when NilClass
         | 
| 108 | 
            +
                      []
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  AND = /\s+AND\s+/i
         | 
| 113 | 
            +
                  TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/              # Matches: `users`.id, `users`.`id`, users.id, id
         | 
| 114 | 
            +
                  VALUE = /'?(\d+|\?|(?:(?:[^']|'')*))'?/                     # Matches: 123, ?, '123', '12''3'
         | 
| 115 | 
            +
                  KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+=\s+#{VALUE}\)?$/ # Matches: KEY = VALUE, (KEY = VALUE)
         | 
| 116 | 
            +
                  ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i              # Matches: COLUMN ASC, COLUMN DESC, COLUMN
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  def parse_indices_from_condition(conditions = '', *values)
         | 
| 119 | 
            +
                    values = values.dup
         | 
| 120 | 
            +
                    conditions.split(AND).inject([]) do |indices, condition|
         | 
| 121 | 
            +
                      matched, table_name, column_name, sql_value = *(KEY_EQ_VALUE.match(condition))
         | 
| 122 | 
            +
                      if matched
         | 
| 123 | 
            +
                        # value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
         | 
| 124 | 
            +
                        if sql_value == '?'
         | 
| 125 | 
            +
                          value = values.shift
         | 
| 126 | 
            +
                        else
         | 
| 127 | 
            +
                          column = columns_hash[column_name]
         | 
| 128 | 
            +
                          raise "could not find column #{column_name} in columns #{columns_hash.keys.join(',')}" if column.nil?
         | 
| 129 | 
            +
                          if sql_value[0..0] == ':' && values && values.count > 0 && values[0].is_a?(Hash)
         | 
| 130 | 
            +
                            symb  = sql_value[1..-1].to_sym
         | 
| 131 | 
            +
                            value = column.type_cast(values[0][symb])
         | 
| 132 | 
            +
                          else
         | 
| 133 | 
            +
                            value = column.type_cast(sql_value)
         | 
| 134 | 
            +
                          end
         | 
| 135 | 
            +
                        end
         | 
| 136 | 
            +
                        indices << [column_name, value]
         | 
| 137 | 
            +
                      else
         | 
| 138 | 
            +
                        return nil
         | 
| 139 | 
            +
                      end
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  def indexed_on?(attributes)
         | 
| 144 | 
            +
                    indices.detect { |index| index == attributes }
         | 
| 145 | 
            +
                  rescue NoMethodError
         | 
| 146 | 
            +
                    nil
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
                  alias_method :index_for, :indexed_on?
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  def format_results(cache_keys, objects)
         | 
| 151 | 
            +
                    return objects if objects.blank?
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    objects = convert_to_array(cache_keys, objects)
         | 
| 154 | 
            +
                    objects = apply_limits_and_offsets(objects, @options1)
         | 
| 155 | 
            +
                    deserialize_objects(objects)
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  def choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects)
         | 
| 159 | 
            +
                    missed_keys == cache_keys ? misses : objects
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  def serialize_objects(index, objects)
         | 
| 163 | 
            +
                    Array(objects).collect { |missed| index.serialize_object(missed) }
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  def convert_to_array(cache_keys, object)
         | 
| 167 | 
            +
                    if object.kind_of?(Hash)
         | 
| 168 | 
            +
                      cache_keys.collect { |key| object[cache_key(key)] }.flatten.compact
         | 
| 169 | 
            +
                    else
         | 
| 170 | 
            +
                      Array(object)
         | 
| 171 | 
            +
                    end
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  def apply_limits_and_offsets(results, options)
         | 
| 175 | 
            +
                    results.slice((options[:offset] || 0), (options[:limit] || results.length))
         | 
| 176 | 
            +
                  end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  def deserialize_objects(objects)
         | 
| 179 | 
            +
                    if objects.first.kind_of?(ActiveRecord::Base)
         | 
| 180 | 
            +
                      objects
         | 
| 181 | 
            +
                    else
         | 
| 182 | 
            +
                      cache_keys = objects.collect { |id| "id/#{id}" }
         | 
| 183 | 
            +
                      objects = get(cache_keys, &method(:find_from_keys))
         | 
| 184 | 
            +
                      convert_to_array(cache_keys, objects)
         | 
| 185 | 
            +
                    end
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  def find_from_keys(*missing_keys)
         | 
| 189 | 
            +
                    missing_ids = Array(missing_keys).flatten.collect { |key| key.split('/')[2].to_i }
         | 
| 190 | 
            +
                    options = {}
         | 
| 191 | 
            +
                    order_sql = @options1[:order] || @options2[:order]
         | 
| 192 | 
            +
                    options[:order] = order_sql if order_sql
         | 
| 193 | 
            +
                    with_exclusive_scope { find_from_ids_without_cache(missing_ids, options) }
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
              end
         | 
| 197 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class Calculation < Abstract
         | 
| 4 | 
            +
                  delegate :calculate_without_cache, :incr, :to => :@active_record
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize(active_record, operation, column, options1, options2)
         | 
| 7 | 
            +
                    super(active_record, options1, options2)
         | 
| 8 | 
            +
                    @operation, @column = operation, column
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def perform
         | 
| 12 | 
            +
                    super({}, :raw => true)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def calculation?
         | 
| 16 | 
            +
                    true
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  protected
         | 
| 20 | 
            +
                  def miss(_, __)
         | 
| 21 | 
            +
                    calculate_without_cache(@operation, @column, @options1)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def uncacheable
         | 
| 25 | 
            +
                    calculate_without_cache(@operation, @column, @options1)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def format_results(_, objects)
         | 
| 29 | 
            +
                    objects.to_i
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def serialize_objects(_, objects)
         | 
| 33 | 
            +
                    objects.to_s
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def cacheable?(*optionss)
         | 
| 37 | 
            +
                    @column == :all && super(*optionss)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def cache_keys(attribute_value_pairs)
         | 
| 41 | 
            +
                    "#{super(attribute_value_pairs)}/#{@operation}"
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module Cash
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class PrimaryKey < Abstract
         | 
| 4 | 
            +
                  def initialize(active_record, ids, options1, options2)
         | 
| 5 | 
            +
                    super(active_record, options1, options2)
         | 
| 6 | 
            +
                    @expects_array = ids.first.kind_of?(Array)
         | 
| 7 | 
            +
                    @original_ids = ids
         | 
| 8 | 
            +
                    @ids = ids.flatten.compact.uniq.collect do |object|
         | 
| 9 | 
            +
                      object.respond_to?(:quoted_id) ? object.quoted_id : object.to_i
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def perform
         | 
| 14 | 
            +
                    return [] if @expects_array && @ids.empty?
         | 
| 15 | 
            +
                    raise ActiveRecord::RecordNotFound if @ids.empty?
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    super(:conditions => { :id => @ids.first })
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                  
         | 
| 20 | 
            +
                  protected
         | 
| 21 | 
            +
                  def deserialize_objects(objects)
         | 
| 22 | 
            +
                    convert_to_active_record_collection(super(objects))
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def cache_keys(attribute_value_pairs)
         | 
| 26 | 
            +
                    @ids.collect { |id| "id/#{id}" }
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def miss(missing_keys, options)
         | 
| 30 | 
            +
                    find_from_keys(*missing_keys)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def uncacheable
         | 
| 34 | 
            +
                    find_from_ids_without_cache(@original_ids, @options1)
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  private
         | 
| 38 | 
            +
                  def convert_to_active_record_collection(objects)
         | 
| 39 | 
            +
                    case objects.size
         | 
| 40 | 
            +
                    when 0
         | 
| 41 | 
            +
                      raise ActiveRecord::RecordNotFound
         | 
| 42 | 
            +
                    when 1
         | 
| 43 | 
            +
                      @expects_array ? objects : objects.first
         | 
| 44 | 
            +
                    else
         | 
| 45 | 
            +
                      objects
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         |