identity_cache 0.0.2 → 0.0.3
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.
- checksums.yaml +7 -0
 - data/.travis.yml +1 -0
 - data/CHANGELOG +7 -0
 - data/README.md +8 -0
 - data/Rakefile +19 -0
 - data/identity_cache.gemspec +2 -1
 - data/lib/{belongs_to_caching.rb → identity_cache/belongs_to_caching.rb} +12 -8
 - data/lib/identity_cache/cache_key_generation.rb +58 -0
 - data/lib/identity_cache/configuration_dsl.rb +301 -0
 - data/lib/identity_cache/memoized_cache_proxy.rb +118 -0
 - data/lib/identity_cache/parent_model_expiration.rb +34 -0
 - data/lib/identity_cache/query_api.rb +312 -0
 - data/lib/identity_cache/version.rb +1 -1
 - data/lib/identity_cache.rb +35 -631
 - data/performance/cache_runner.rb +123 -0
 - data/performance/cpu.rb +28 -0
 - data/performance/externals.rb +45 -0
 - data/performance/profile.rb +26 -0
 - data/test/attribute_cache_test.rb +3 -3
 - data/test/fetch_multi_test.rb +13 -39
 - data/test/fetch_multi_with_batched_associations_test.rb +236 -0
 - data/test/fetch_test.rb +1 -1
 - data/test/helpers/active_record_objects.rb +43 -0
 - data/test/helpers/cache.rb +3 -12
 - data/test/helpers/database_connection.rb +2 -1
 - data/test/index_cache_test.rb +7 -0
 - data/test/memoized_cache_proxy_test.rb +46 -1
 - data/test/normalized_has_many_test.rb +13 -0
 - data/test/recursive_denormalized_has_many_test.rb +17 -2
 - data/test/save_test.rb +2 -2
 - data/test/schema_change_test.rb +8 -28
 - data/test/test_helper.rb +49 -43
 - metadata +76 -76
 - data/lib/memoized_cache_proxy.rb +0 -71
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: ba4de54fb34aecd2a44702ea1bb950c718cdc715
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 752b53a7d7399f5273fd7083152c4b28ee5cbdeb
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: ffa0486c327789a3d975ea768ffe63ed73878f4916aa54b24fa06c1771ece940b4e8bd67a2b8838e224d996a9088b2bd6696237209040608b30c10ed28bb0438
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 903fc321ed4092d797d230302cfb8a09bfb3a4f47e58f0e67b605391ca0ea6dbc5916dd801ed06ee58e65ffae8793e6f25d8f7e94a875d2dd35d1fa427e62825
         
     | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/CHANGELOG
    CHANGED
    
    | 
         @@ -1,3 +1,10 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            0.0.3
         
     | 
| 
      
 2 
     | 
    
         
            +
            * Fix: memoization for multi hits actually work
         
     | 
| 
      
 3 
     | 
    
         
            +
            * Fix: quotes SELECT projection elements on cache misses
         
     | 
| 
      
 4 
     | 
    
         
            +
            * Add CPU performance benchmark
         
     | 
| 
      
 5 
     | 
    
         
            +
            * Fix: table names are not hardcoded anymore
         
     | 
| 
      
 6 
     | 
    
         
            +
            * Logger now differentiates memoized vs non memoized hits
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
       1 
8 
     | 
    
         
             
            0.0.2
         
     | 
| 
       2 
9 
     | 
    
         
             
            * Fix: Existent embedded entries will no longer raise when ActiveModel::MissingAttributeError when accessing a newly created attribute.
         
     | 
| 
       3 
10 
     | 
    
         
             
            * Fix: Do not marshal raw AcriveRecord associations
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -16,6 +16,8 @@ gem 'identity_cache' 
     | 
|
| 
       16 
16 
     | 
    
         
             
            And then execute:
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                $ bundle
         
     | 
| 
      
 19 
     | 
    
         
            +
                
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
       19 
21 
     | 
    
         | 
| 
       20 
22 
     | 
    
         
             
            Add the following to your environment/production.rb:
         
     | 
| 
       21 
23 
     | 
    
         | 
| 
         @@ -23,6 +25,12 @@ Add the following to your environment/production.rb: 
     | 
|
| 
       23 
25 
     | 
    
         
             
            config.identity_cache_store = :mem_cache_store, Memcached::Rails.new(:servers => ["mem1.server.com"])
         
     | 
| 
       24 
26 
     | 
    
         
             
            ```
         
     | 
| 
       25 
27 
     | 
    
         | 
| 
      
 28 
     | 
    
         
            +
            Add an initializer with this code:
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 31 
     | 
    
         
            +
            IdentityCache.cache_backend = ActiveSupport::Cache.lookup_store(*Rails.configuration.identity_cache_store)
         
     | 
| 
      
 32 
     | 
    
         
            +
            ```
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
       26 
34 
     | 
    
         
             
            ## Usage
         
     | 
| 
       27 
35 
     | 
    
         | 
| 
       28 
36 
     | 
    
         
             
            ### Basic Usage
         
     | 
    
        data/Rakefile
    CHANGED
    
    | 
         @@ -14,3 +14,22 @@ Rake::TestTask.new(:test) do |t| 
     | 
|
| 
       14 
14 
     | 
    
         
             
              t.pattern = 'test/**/*_test.rb'
         
     | 
| 
       15 
15 
     | 
    
         
             
              t.verbose = true
         
     | 
| 
       16 
16 
     | 
    
         
             
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            namespace :benchmark do
         
     | 
| 
      
 19 
     | 
    
         
            +
              desc "Run the identity cache CPU benchmark"
         
     | 
| 
      
 20 
     | 
    
         
            +
              task :cpu do
         
     | 
| 
      
 21 
     | 
    
         
            +
                ruby "./performance/cpu.rb"
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              task :externals do
         
     | 
| 
      
 25 
     | 
    
         
            +
                ruby "./performance/externals.rb"
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            namespace :profile do
         
     | 
| 
      
 30 
     | 
    
         
            +
              desc "Profile IDC code"
         
     | 
| 
      
 31 
     | 
    
         
            +
              task :run do
         
     | 
| 
      
 32 
     | 
    
         
            +
                ruby "./performance/profile.rb"
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
    
        data/identity_cache.gemspec
    CHANGED
    
    | 
         @@ -22,7 +22,8 @@ Gem::Specification.new do |gem| 
     | 
|
| 
       22 
22 
     | 
    
         
             
              gem.add_dependency('cityhash', '0.6.0')
         
     | 
| 
       23 
23 
     | 
    
         
             
              gem.add_development_dependency('memcache-client')
         
     | 
| 
       24 
24 
     | 
    
         
             
              gem.add_development_dependency('rake')
         
     | 
| 
       25 
     | 
    
         
            -
              gem.add_development_dependency('mocha')
         
     | 
| 
      
 25 
     | 
    
         
            +
              gem.add_development_dependency('mocha', '0.14.0')
         
     | 
| 
       26 
26 
     | 
    
         
             
              gem.add_development_dependency('mysql2')
         
     | 
| 
       27 
27 
     | 
    
         
             
              gem.add_development_dependency('debugger')
         
     | 
| 
      
 28 
     | 
    
         
            +
              gem.add_development_dependency('ruby-prof')
         
     | 
| 
       28 
29 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,21 +1,21 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module IdentityCache
         
     | 
| 
       2 
2 
     | 
    
         
             
              module BelongsToCaching
         
     | 
| 
      
 3 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
                 
     | 
| 
       5 
     | 
    
         
            -
                  base.send(:extend, ClassMethods)
         
     | 
| 
      
 5 
     | 
    
         
            +
                included do |base|
         
     | 
| 
       6 
6 
     | 
    
         
             
                  base.class_attribute :cached_belongs_tos
         
     | 
| 
      
 7 
     | 
    
         
            +
                  base.cached_belongs_tos = {}
         
     | 
| 
       7 
8 
     | 
    
         
             
                end
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
                module ClassMethods
         
     | 
| 
       10 
11 
     | 
    
         
             
                  def cache_belongs_to(association, options = {})
         
     | 
| 
       11 
     | 
    
         
            -
                    self.cached_belongs_tos ||= {}
         
     | 
| 
       12 
12 
     | 
    
         
             
                    self.cached_belongs_tos[association] = options
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                    options[:embed] ||= false
         
     | 
| 
       15 
     | 
    
         
            -
                    options[:cached_accessor_name] 
     | 
| 
       16 
     | 
    
         
            -
                    options[:foreign_key] 
     | 
| 
       17 
     | 
    
         
            -
                    options[: 
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
      
 15 
     | 
    
         
            +
                    options[:cached_accessor_name]    ||= "fetch_#{association}"
         
     | 
| 
      
 16 
     | 
    
         
            +
                    options[:foreign_key]             ||= reflect_on_association(association).foreign_key
         
     | 
| 
      
 17 
     | 
    
         
            +
                    options[:association_class]       ||= reflect_on_association(association).klass
         
     | 
| 
      
 18 
     | 
    
         
            +
                    options[:prepopulate_method_name] ||= "prepopulate_fetched_#{association}"
         
     | 
| 
       19 
19 
     | 
    
         
             
                    if options[:embed]
         
     | 
| 
       20 
20 
     | 
    
         
             
                      raise NotImplementedError
         
     | 
| 
       21 
21 
     | 
    
         
             
                    else
         
     | 
| 
         @@ -27,11 +27,15 @@ module IdentityCache 
     | 
|
| 
       27 
27 
     | 
    
         
             
                    self.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
       28 
28 
     | 
    
         
             
                      def #{options[:cached_accessor_name]}
         
     | 
| 
       29 
29 
     | 
    
         
             
                        if IdentityCache.should_cache? && #{options[:foreign_key]}.present? && !association(:#{association}).loaded?
         
     | 
| 
       30 
     | 
    
         
            -
                          self.#{association} = #{options[: 
     | 
| 
      
 30 
     | 
    
         
            +
                          self.#{association} = #{options[:association_class]}.fetch_by_id(#{options[:foreign_key]})
         
     | 
| 
       31 
31 
     | 
    
         
             
                        else
         
     | 
| 
       32 
32 
     | 
    
         
             
                          #{association}
         
     | 
| 
       33 
33 
     | 
    
         
             
                        end
         
     | 
| 
       34 
34 
     | 
    
         
             
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                      def #{options[:prepopulate_method_name]}(record)
         
     | 
| 
      
 37 
     | 
    
         
            +
                        self.#{association} = record
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
       35 
39 
     | 
    
         
             
                    CODE
         
     | 
| 
       36 
40 
     | 
    
         
             
                  end
         
     | 
| 
       37 
41 
     | 
    
         
             
                end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module IdentityCache
         
     | 
| 
      
 2 
     | 
    
         
            +
              module CacheKeyGeneration
         
     | 
| 
      
 3 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def rails_cache_key(id)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    rails_cache_key_prefix + id.to_s
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def rails_cache_key_prefix
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @rails_cache_key_prefix ||= begin
         
     | 
| 
      
 12 
     | 
    
         
            +
                      "IDC:blob:#{base_class.name}:#{IdentityCache.denormalized_schema_hash(self)}:"
         
     | 
| 
      
 13 
     | 
    
         
            +
                    end
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def rails_cache_index_key_for_fields_and_values(fields, values)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    "IDC:index:#{base_class.name}:#{rails_cache_string_for_fields_and_values(fields, values)}"
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    "IDC:attribute:#{base_class.name}:#{attribute}:#{rails_cache_string_for_fields_and_values(fields, values)}"
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def rails_cache_string_for_fields_and_values(fields, values)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    "#{fields.join('/')}:#{IdentityCache.memcache_hash(values.join('/'))}"
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def primary_cache_index_key # :nodoc:
         
     | 
| 
      
 30 
     | 
    
         
            +
                  self.class.rails_cache_key(id)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def secondary_cache_index_key_for_current_values(fields) # :nodoc:
         
     | 
| 
      
 34 
     | 
    
         
            +
                  self.class.rails_cache_index_key_for_fields_and_values(fields, fields.collect {|field| self.send(field)})
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def secondary_cache_index_key_for_previous_values(fields) # :nodoc:
         
     | 
| 
      
 38 
     | 
    
         
            +
                  self.class.rails_cache_index_key_for_fields_and_values(fields, old_values_for_fields(fields))
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def attribute_cache_key_for_attribute_and_previous_values(attribute, fields) # :nodoc:
         
     | 
| 
      
 42 
     | 
    
         
            +
                  self.class.rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, old_values_for_fields(fields))
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def old_values_for_fields(fields) # :nodoc:
         
     | 
| 
      
 46 
     | 
    
         
            +
                  fields.map do |field|
         
     | 
| 
      
 47 
     | 
    
         
            +
                    field_string = field.to_s
         
     | 
| 
      
 48 
     | 
    
         
            +
                    if destroyed? && transaction_changed_attributes.has_key?(field_string)
         
     | 
| 
      
 49 
     | 
    
         
            +
                      transaction_changed_attributes[field_string]
         
     | 
| 
      
 50 
     | 
    
         
            +
                    elsif persisted? && transaction_changed_attributes.has_key?(field_string)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      transaction_changed_attributes[field_string]
         
     | 
| 
      
 52 
     | 
    
         
            +
                    else
         
     | 
| 
      
 53 
     | 
    
         
            +
                      self.send(field)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,301 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module IdentityCache
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ConfigurationDSL
         
     | 
| 
      
 3 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                included do |base|
         
     | 
| 
      
 6 
     | 
    
         
            +
                  base.class_attribute :cache_indexes
         
     | 
| 
      
 7 
     | 
    
         
            +
                  base.class_attribute :cache_attributes
         
     | 
| 
      
 8 
     | 
    
         
            +
                  base.class_attribute :cached_has_manys
         
     | 
| 
      
 9 
     | 
    
         
            +
                  base.class_attribute :cached_has_ones
         
     | 
| 
      
 10 
     | 
    
         
            +
                  base.class_attribute :primary_cache_index_enabled
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  base.cached_has_manys = {}
         
     | 
| 
      
 13 
     | 
    
         
            +
                  base.cached_has_ones = {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                  base.cache_attributes = []
         
     | 
| 
      
 15 
     | 
    
         
            +
                  base.cache_indexes = []
         
     | 
| 
      
 16 
     | 
    
         
            +
                  base.primary_cache_index_enabled = true
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  base.private_class_method :build_normalized_has_many_cache, :build_denormalized_association_cache,
         
     | 
| 
      
 19 
     | 
    
         
            +
                                            :add_parent_expiry_hook, :identity_cache_multiple_value_dynamic_fetcher,
         
     | 
| 
      
 20 
     | 
    
         
            +
                                            :identity_cache_single_value_dynamic_fetcher, :identity_cache_sql_conditions
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # Declares a new index in the cache for the class where IdentityCache was
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # included.
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # IdentityCache will add a fetch_by_field1_and_field2_and_...field for every
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # index.
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # == Example:
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #
         
     | 
| 
      
 32 
     | 
    
         
            +
                  #  class Product
         
     | 
| 
      
 33 
     | 
    
         
            +
                  #    include IdentityCache
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #    cache_index :name, :vendor
         
     | 
| 
      
 35 
     | 
    
         
            +
                  #  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # Will add Product.fetch_by_name_and_vendor
         
     | 
| 
      
 38 
     | 
    
         
            +
                  #
         
     | 
| 
      
 39 
     | 
    
         
            +
                  # == Parameters
         
     | 
| 
      
 40 
     | 
    
         
            +
                  #
         
     | 
| 
      
 41 
     | 
    
         
            +
                  # +fields+ Array of symbols or strings representing the fields in the index
         
     | 
| 
      
 42 
     | 
    
         
            +
                  #
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # == Options
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # * unique: if the index would only have unique values
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #
         
     | 
| 
      
 46 
     | 
    
         
            +
                  def cache_index(*fields)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    raise NotImplementedError, "Cache indexes need an enabled primary index" unless primary_cache_index_enabled
         
     | 
| 
      
 48 
     | 
    
         
            +
                    options = fields.extract_options!
         
     | 
| 
      
 49 
     | 
    
         
            +
                    self.cache_indexes.push fields
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    field_list = fields.join("_and_")
         
     | 
| 
      
 52 
     | 
    
         
            +
                    arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    if options[:unique]
         
     | 
| 
      
 55 
     | 
    
         
            +
                      self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
      
 56 
     | 
    
         
            +
                        def fetch_by_#{field_list}(#{arg_list})
         
     | 
| 
      
 57 
     | 
    
         
            +
                          identity_cache_single_value_dynamic_fetcher(#{fields.inspect}, [#{arg_list}])
         
     | 
| 
      
 58 
     | 
    
         
            +
                        end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                        # exception throwing variant
         
     | 
| 
      
 61 
     | 
    
         
            +
                        def fetch_by_#{field_list}!(#{arg_list})
         
     | 
| 
      
 62 
     | 
    
         
            +
                          fetch_by_#{field_list}(#{arg_list}) or raise ActiveRecord::RecordNotFound
         
     | 
| 
      
 63 
     | 
    
         
            +
                        end
         
     | 
| 
      
 64 
     | 
    
         
            +
                      CODE
         
     | 
| 
      
 65 
     | 
    
         
            +
                    else
         
     | 
| 
      
 66 
     | 
    
         
            +
                      self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        def fetch_by_#{field_list}(#{arg_list})
         
     | 
| 
      
 68 
     | 
    
         
            +
                          identity_cache_multiple_value_dynamic_fetcher(#{fields.inspect}, [#{arg_list}])
         
     | 
| 
      
 69 
     | 
    
         
            +
                        end
         
     | 
| 
      
 70 
     | 
    
         
            +
                      CODE
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                  # Will cache an association to the class including IdentityCache.
         
     | 
| 
      
 76 
     | 
    
         
            +
                  # The embed option, if set, will make IdentityCache keep the association
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # values in the same cache entry as the parent.
         
     | 
| 
      
 78 
     | 
    
         
            +
                  #
         
     | 
| 
      
 79 
     | 
    
         
            +
                  # Embedded associations are more effective in offloading database work,
         
     | 
| 
      
 80 
     | 
    
         
            +
                  # however they will increase the size of the cache entries and make the
         
     | 
| 
      
 81 
     | 
    
         
            +
                  # whole entry expire when any of the embedded members change.
         
     | 
| 
      
 82 
     | 
    
         
            +
                  #
         
     | 
| 
      
 83 
     | 
    
         
            +
                  # == Example:
         
     | 
| 
      
 84 
     | 
    
         
            +
                  #   class Product
         
     | 
| 
      
 85 
     | 
    
         
            +
                  #    cached_has_many :options, :embed => false
         
     | 
| 
      
 86 
     | 
    
         
            +
                  #    cached_has_many :orders
         
     | 
| 
      
 87 
     | 
    
         
            +
                  #    cached_has_many :buyers, :inverse_name => 'line_item'
         
     | 
| 
      
 88 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 89 
     | 
    
         
            +
                  #
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # == Parameters
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # +association+ Name of the association being cached as a symbol
         
     | 
| 
      
 92 
     | 
    
         
            +
                  #
         
     | 
| 
      
 93 
     | 
    
         
            +
                  # == Options
         
     | 
| 
      
 94 
     | 
    
         
            +
                  #
         
     | 
| 
      
 95 
     | 
    
         
            +
                  # * embed: If set will cause IdentityCache to keep the values for this
         
     | 
| 
      
 96 
     | 
    
         
            +
                  #   association in the same cache entry as the parent, instead of its own.
         
     | 
| 
      
 97 
     | 
    
         
            +
                  # * inverse_name: The name of the parent in the association if the name is
         
     | 
| 
      
 98 
     | 
    
         
            +
                  #   not the lowercase pluralization of the parent object's class
         
     | 
| 
      
 99 
     | 
    
         
            +
                  def cache_has_many(association, options = {})
         
     | 
| 
      
 100 
     | 
    
         
            +
                    options[:embed] ||= false
         
     | 
| 
      
 101 
     | 
    
         
            +
                    options[:inverse_name] ||= self.name.underscore.to_sym
         
     | 
| 
      
 102 
     | 
    
         
            +
                    raise InverseAssociationError unless self.reflect_on_association(association)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    self.cached_has_manys[association] = options
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                    if options[:embed]
         
     | 
| 
      
 106 
     | 
    
         
            +
                      build_denormalized_association_cache(association, options)
         
     | 
| 
      
 107 
     | 
    
         
            +
                    else
         
     | 
| 
      
 108 
     | 
    
         
            +
                      build_normalized_has_many_cache(association, options)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                  # Will cache an association to the class including IdentityCache.
         
     | 
| 
      
 113 
     | 
    
         
            +
                  # The embed option if set will make IdentityCache keep the association
         
     | 
| 
      
 114 
     | 
    
         
            +
                  # values in the same cache entry as the parent.
         
     | 
| 
      
 115 
     | 
    
         
            +
                  #
         
     | 
| 
      
 116 
     | 
    
         
            +
                  # Embedded associations are more effective in offloading database work,
         
     | 
| 
      
 117 
     | 
    
         
            +
                  # however they will increase the size of the cache entries and make the
         
     | 
| 
      
 118 
     | 
    
         
            +
                  # whole entry expire with the change of any of the embedded members
         
     | 
| 
      
 119 
     | 
    
         
            +
                  #
         
     | 
| 
      
 120 
     | 
    
         
            +
                  # == Example:
         
     | 
| 
      
 121 
     | 
    
         
            +
                  #   class Product
         
     | 
| 
      
 122 
     | 
    
         
            +
                  #    cached_has_one :store, :embed => false
         
     | 
| 
      
 123 
     | 
    
         
            +
                  #    cached_has_one :vendor
         
     | 
| 
      
 124 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 125 
     | 
    
         
            +
                  #
         
     | 
| 
      
 126 
     | 
    
         
            +
                  # == Parameters
         
     | 
| 
      
 127 
     | 
    
         
            +
                  # +association+ Symbol with the name of the association being cached
         
     | 
| 
      
 128 
     | 
    
         
            +
                  #
         
     | 
| 
      
 129 
     | 
    
         
            +
                  # == Options
         
     | 
| 
      
 130 
     | 
    
         
            +
                  #
         
     | 
| 
      
 131 
     | 
    
         
            +
                  # * embed: If set will cause IdentityCache to keep the values for this
         
     | 
| 
      
 132 
     | 
    
         
            +
                  #   association in the same cache entry as the parent, instead of its own.
         
     | 
| 
      
 133 
     | 
    
         
            +
                  # * inverse_name: The name of the parent in the association ( only
         
     | 
| 
      
 134 
     | 
    
         
            +
                  #   necessary if the name is not the lowercase pluralization of the
         
     | 
| 
      
 135 
     | 
    
         
            +
                  #   parent object's class)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  def cache_has_one(association, options = {})
         
     | 
| 
      
 137 
     | 
    
         
            +
                    options[:embed] ||= true
         
     | 
| 
      
 138 
     | 
    
         
            +
                    options[:inverse_name] ||= self.name.underscore.to_sym
         
     | 
| 
      
 139 
     | 
    
         
            +
                    raise InverseAssociationError unless self.reflect_on_association(association)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    self.cached_has_ones[association] = options
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                    if options[:embed]
         
     | 
| 
      
 143 
     | 
    
         
            +
                      build_denormalized_association_cache(association, options)
         
     | 
| 
      
 144 
     | 
    
         
            +
                    else
         
     | 
| 
      
 145 
     | 
    
         
            +
                      raise NotImplementedError
         
     | 
| 
      
 146 
     | 
    
         
            +
                    end
         
     | 
| 
      
 147 
     | 
    
         
            +
                  end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                  # Will cache a single attribute on its own blob, it will add a
         
     | 
| 
      
 150 
     | 
    
         
            +
                  # fetch_attribute_by_id (or the value of the by option).
         
     | 
| 
      
 151 
     | 
    
         
            +
                  #
         
     | 
| 
      
 152 
     | 
    
         
            +
                  # == Example:
         
     | 
| 
      
 153 
     | 
    
         
            +
                  #   class Product
         
     | 
| 
      
 154 
     | 
    
         
            +
                  #    cache_attribute :quantity, :by => :name
         
     | 
| 
      
 155 
     | 
    
         
            +
                  #    cache_attribute :quantity  :by => [:name, :vendor]
         
     | 
| 
      
 156 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 157 
     | 
    
         
            +
                  #
         
     | 
| 
      
 158 
     | 
    
         
            +
                  # == Parameters
         
     | 
| 
      
 159 
     | 
    
         
            +
                  # +attribute+ Symbol with the name of the attribute being cached
         
     | 
| 
      
 160 
     | 
    
         
            +
                  #
         
     | 
| 
      
 161 
     | 
    
         
            +
                  # == Options
         
     | 
| 
      
 162 
     | 
    
         
            +
                  #
         
     | 
| 
      
 163 
     | 
    
         
            +
                  # * by: Other attribute or attributes in the model to keep values indexed. Default is :id
         
     | 
| 
      
 164 
     | 
    
         
            +
                  def cache_attribute(attribute, options = {})
         
     | 
| 
      
 165 
     | 
    
         
            +
                    options[:by] ||= :id
         
     | 
| 
      
 166 
     | 
    
         
            +
                    fields = Array(options[:by])
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                    self.cache_attributes.push [attribute, fields]
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                    field_list = fields.join("_and_")
         
     | 
| 
      
 171 
     | 
    
         
            +
                    arg_list = (0...fields.size).collect { |i| "arg#{i}" }.join(',')
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                    self.instance_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
      
 174 
     | 
    
         
            +
                      def fetch_#{attribute}_by_#{field_list}(#{arg_list})
         
     | 
| 
      
 175 
     | 
    
         
            +
                        attribute_dynamic_fetcher(#{attribute.inspect}, #{fields.inspect}, [#{arg_list}])
         
     | 
| 
      
 176 
     | 
    
         
            +
                      end
         
     | 
| 
      
 177 
     | 
    
         
            +
                    CODE
         
     | 
| 
      
 178 
     | 
    
         
            +
                  end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                  def disable_primary_cache_index
         
     | 
| 
      
 181 
     | 
    
         
            +
                    raise NotImplementedError, "Secondary indexes rely on the primary index to function. You must either remove the secondary indexes or don't disable the primary" if self.cache_indexes.size > 0
         
     | 
| 
      
 182 
     | 
    
         
            +
                    self.primary_cache_index_enabled = false
         
     | 
| 
      
 183 
     | 
    
         
            +
                  end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
                  def identity_cache_single_value_dynamic_fetcher(fields, values) # :nodoc:
         
     | 
| 
      
 186 
     | 
    
         
            +
                    sql_on_miss = "SELECT `id` FROM `#{table_name}` WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1"
         
     | 
| 
      
 187 
     | 
    
         
            +
                    cache_key = rails_cache_index_key_for_fields_and_values(fields, values)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    id = IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) }
         
     | 
| 
      
 189 
     | 
    
         
            +
                    unless id.nil?
         
     | 
| 
      
 190 
     | 
    
         
            +
                      record = fetch_by_id(id.to_i)
         
     | 
| 
      
 191 
     | 
    
         
            +
                      IdentityCache.cache.delete(cache_key) unless record
         
     | 
| 
      
 192 
     | 
    
         
            +
                    end
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
                    record
         
     | 
| 
      
 195 
     | 
    
         
            +
                  end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                  def identity_cache_multiple_value_dynamic_fetcher(fields, values) # :nodoc
         
     | 
| 
      
 198 
     | 
    
         
            +
                    sql_on_miss = "SELECT `id` FROM `#{table_name}` WHERE #{identity_cache_sql_conditions(fields, values)}"
         
     | 
| 
      
 199 
     | 
    
         
            +
                    cache_key = rails_cache_index_key_for_fields_and_values(fields, values)
         
     | 
| 
      
 200 
     | 
    
         
            +
                    ids = IdentityCache.fetch(cache_key) { connection.select_values(sql_on_miss) }
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                    ids.empty? ? [] : fetch_multi(*ids)
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                  def build_denormalized_association_cache(association, options) #:nodoc:
         
     | 
| 
      
 206 
     | 
    
         
            +
                    options[:association_class]      ||= reflect_on_association(association).klass
         
     | 
| 
      
 207 
     | 
    
         
            +
                    options[:cached_accessor_name]   ||= "fetch_#{association}"
         
     | 
| 
      
 208 
     | 
    
         
            +
                    options[:records_variable_name]     ||= "cached_#{association}"
         
     | 
| 
      
 209 
     | 
    
         
            +
                    options[:population_method_name] ||= "populate_#{association}_cache"
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                    unless instance_methods.include?(options[:cached_accessor_name].to_sym)
         
     | 
| 
      
 213 
     | 
    
         
            +
                      self.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
      
 214 
     | 
    
         
            +
                        def #{options[:cached_accessor_name]}
         
     | 
| 
      
 215 
     | 
    
         
            +
                          fetch_denormalized_cached_association('#{options[:records_variable_name]}', :#{association})
         
     | 
| 
      
 216 
     | 
    
         
            +
                        end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                        def #{options[:population_method_name]}
         
     | 
| 
      
 219 
     | 
    
         
            +
                          populate_denormalized_cached_association('#{options[:records_variable_name]}', :#{association})
         
     | 
| 
      
 220 
     | 
    
         
            +
                        end
         
     | 
| 
      
 221 
     | 
    
         
            +
                      CODE
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                      add_parent_expiry_hook(options.merge(:only_on_foreign_key_change => false))
         
     | 
| 
      
 224 
     | 
    
         
            +
                    end
         
     | 
| 
      
 225 
     | 
    
         
            +
                  end
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                  def build_normalized_has_many_cache(association, options) #:nodoc:
         
     | 
| 
      
 228 
     | 
    
         
            +
                    singular_association = association.to_s.singularize
         
     | 
| 
      
 229 
     | 
    
         
            +
                    options[:association_class]       ||= reflect_on_association(association).klass
         
     | 
| 
      
 230 
     | 
    
         
            +
                    options[:cached_accessor_name]    ||= "fetch_#{association}"
         
     | 
| 
      
 231 
     | 
    
         
            +
                    options[:ids_name]                ||= "#{singular_association}_ids"
         
     | 
| 
      
 232 
     | 
    
         
            +
                    options[:cached_ids_name]         ||= "fetch_#{options[:ids_name]}"
         
     | 
| 
      
 233 
     | 
    
         
            +
                    options[:ids_variable_name]       ||= "cached_#{options[:ids_name]}"
         
     | 
| 
      
 234 
     | 
    
         
            +
                    options[:records_variable_name]   ||= "cached_#{association}"
         
     | 
| 
      
 235 
     | 
    
         
            +
                    options[:population_method_name]  ||= "populate_#{association}_cache"
         
     | 
| 
      
 236 
     | 
    
         
            +
                    options[:prepopulate_method_name] ||= "prepopulate_fetched_#{association}"
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
                    self.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
      
 239 
     | 
    
         
            +
                      attr_reader :#{options[:ids_variable_name]}
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                      def #{options[:cached_ids_name]}
         
     | 
| 
      
 242 
     | 
    
         
            +
                        #{options[:population_method_name]} unless @#{options[:ids_variable_name]}
         
     | 
| 
      
 243 
     | 
    
         
            +
                        @#{options[:ids_variable_name]}
         
     | 
| 
      
 244 
     | 
    
         
            +
                      end
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
                      def #{options[:population_method_name]}
         
     | 
| 
      
 247 
     | 
    
         
            +
                        @#{options[:ids_variable_name]} = #{options[:ids_name]}
         
     | 
| 
      
 248 
     | 
    
         
            +
                        association_cache.delete(:#{association})
         
     | 
| 
      
 249 
     | 
    
         
            +
                      end
         
     | 
| 
      
 250 
     | 
    
         
            +
             
     | 
| 
      
 251 
     | 
    
         
            +
                      def #{options[:cached_accessor_name]}
         
     | 
| 
      
 252 
     | 
    
         
            +
                        if IdentityCache.should_cache? || #{association}.loaded?
         
     | 
| 
      
 253 
     | 
    
         
            +
                          #{options[:population_method_name]} unless @#{options[:ids_variable_name]} || @#{options[:records_variable_name]}
         
     | 
| 
      
 254 
     | 
    
         
            +
                          @#{options[:records_variable_name]} ||= #{options[:association_class]}.fetch_multi(*@#{options[:ids_variable_name]})
         
     | 
| 
      
 255 
     | 
    
         
            +
                        else
         
     | 
| 
      
 256 
     | 
    
         
            +
                          #{association}
         
     | 
| 
      
 257 
     | 
    
         
            +
                        end
         
     | 
| 
      
 258 
     | 
    
         
            +
                      end
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
                      def #{options[:prepopulate_method_name]}(records)
         
     | 
| 
      
 261 
     | 
    
         
            +
                        @#{options[:records_variable_name]} = records
         
     | 
| 
      
 262 
     | 
    
         
            +
                      end
         
     | 
| 
      
 263 
     | 
    
         
            +
                    CODE
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                    add_parent_expiry_hook(options.merge(:only_on_foreign_key_change => true))
         
     | 
| 
      
 266 
     | 
    
         
            +
                  end
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                  def attribute_dynamic_fetcher(attribute, fields, values) #:nodoc:
         
     | 
| 
      
 269 
     | 
    
         
            +
                    cache_key = rails_cache_key_for_attribute_and_fields_and_values(attribute, fields, values)
         
     | 
| 
      
 270 
     | 
    
         
            +
                    sql_on_miss = "SELECT `#{attribute}` FROM `#{table_name}` WHERE #{identity_cache_sql_conditions(fields, values)} LIMIT 1"
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
                    IdentityCache.fetch(cache_key) { connection.select_value(sql_on_miss) }
         
     | 
| 
      
 273 
     | 
    
         
            +
                  end
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
      
 275 
     | 
    
         
            +
                  def add_parent_expiry_hook(options)
         
     | 
| 
      
 276 
     | 
    
         
            +
                    child_class = options[:association_class]
         
     | 
| 
      
 277 
     | 
    
         
            +
                    child_association = child_class.reflect_on_association(options[:inverse_name])
         
     | 
| 
      
 278 
     | 
    
         
            +
                    raise InverseAssociationError unless child_association
         
     | 
| 
      
 279 
     | 
    
         
            +
                    foreign_key = child_association.association_foreign_key
         
     | 
| 
      
 280 
     | 
    
         
            +
                    parent_class ||= self.name
         
     | 
| 
      
 281 
     | 
    
         
            +
                    new_parent = options[:inverse_name]
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
                    child_class.send(:include, ArTransactionChanges) unless child_class.include?(ArTransactionChanges)
         
     | 
| 
      
 284 
     | 
    
         
            +
                    child_class.send(:include, ParentModelExpiration) unless child_class.include?(ParentModelExpiration)
         
     | 
| 
      
 285 
     | 
    
         
            +
             
     | 
| 
      
 286 
     | 
    
         
            +
                    child_class.class_eval(ruby = <<-CODE, __FILE__, __LINE__)
         
     | 
| 
      
 287 
     | 
    
         
            +
                      after_commit :expire_parent_cache
         
     | 
| 
      
 288 
     | 
    
         
            +
                      after_touch  :expire_parent_cache
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
                      def expire_parent_cache
         
     | 
| 
      
 291 
     | 
    
         
            +
                        expire_parent_cache_on_changes(:#{options[:inverse_name]}, '#{foreign_key}', #{parent_class}, #{options[:only_on_foreign_key_change]})
         
     | 
| 
      
 292 
     | 
    
         
            +
                      end
         
     | 
| 
      
 293 
     | 
    
         
            +
                    CODE
         
     | 
| 
      
 294 
     | 
    
         
            +
                  end
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
                  def identity_cache_sql_conditions(fields, values)
         
     | 
| 
      
 297 
     | 
    
         
            +
                    fields.each_with_index.collect { |f, i| "`#{f}` = #{quote_value(values[i])}" }.join(" AND ")
         
     | 
| 
      
 298 
     | 
    
         
            +
                  end
         
     | 
| 
      
 299 
     | 
    
         
            +
                end
         
     | 
| 
      
 300 
     | 
    
         
            +
              end
         
     | 
| 
      
 301 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,118 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'monitor'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module IdentityCache
         
     | 
| 
      
 4 
     | 
    
         
            +
              class MemoizedCacheProxy
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_writer :memcache
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(memcache = nil)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @memcache = memcache || Rails.cache
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @key_value_maps = Hash.new {|h, k| h[k] = {} }
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def memoized_key_values
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @key_value_maps[Thread.current]
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def with_memoization(&block)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Thread.current[:memoizing_idc] = true
         
     | 
| 
      
 18 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 19 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 20 
     | 
    
         
            +
                  clear_memoization
         
     | 
| 
      
 21 
     | 
    
         
            +
                  Thread.current[:memoizing_idc] = false
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def write(key, value)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  memoized_key_values[key] = value if memoizing?
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @memcache.write(key, value)
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def read(key)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  used_memcached = true
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  result = if memoizing?
         
     | 
| 
      
 33 
     | 
    
         
            +
                    used_memcached = false
         
     | 
| 
      
 34 
     | 
    
         
            +
                    mkv = memoized_key_values
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                    mkv.fetch(key) do
         
     | 
| 
      
 37 
     | 
    
         
            +
                      used_memcached = true
         
     | 
| 
      
 38 
     | 
    
         
            +
                      mkv[key] = @memcache.read(key)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  else
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @memcache.read(key)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  if result
         
     | 
| 
      
 46 
     | 
    
         
            +
                    IdentityCache.logger.debug { "[IdentityCache] #{ used_memcached ? '(memcache)'  : '(memoized)'  } cache hit for #{key}" }
         
     | 
| 
      
 47 
     | 
    
         
            +
                  else
         
     | 
| 
      
 48 
     | 
    
         
            +
                    IdentityCache.logger.debug { "[IdentityCache] cache miss for #{key}" }
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  result
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def delete(key)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  memoized_key_values.delete(key) if memoizing?
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @memcache.delete(key)
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def read_multi(*keys)
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  if IdentityCache.logger.debug?
         
     | 
| 
      
 62 
     | 
    
         
            +
                    memoized_keys , memcache_keys = [], []
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  result = if memoizing?
         
     | 
| 
      
 66 
     | 
    
         
            +
                    hash = {}
         
     | 
| 
      
 67 
     | 
    
         
            +
                    mkv = memoized_key_values
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    missing_keys = keys.reject do |key|
         
     | 
| 
      
 70 
     | 
    
         
            +
                      if mkv.has_key?(key)
         
     | 
| 
      
 71 
     | 
    
         
            +
                        memoized_keys << key if IdentityCache.logger.debug?
         
     | 
| 
      
 72 
     | 
    
         
            +
                        hit = mkv[key]
         
     | 
| 
      
 73 
     | 
    
         
            +
                        hash[key] = hit unless hit.nil?
         
     | 
| 
      
 74 
     | 
    
         
            +
                        true
         
     | 
| 
      
 75 
     | 
    
         
            +
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    hits =   missing_keys.empty? ? {} : @memcache.read_multi(*missing_keys)
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    missing_keys.each do |key|
         
     | 
| 
      
 81 
     | 
    
         
            +
                      hit = hits[key]
         
     | 
| 
      
 82 
     | 
    
         
            +
                      mkv[key] = hit
         
     | 
| 
      
 83 
     | 
    
         
            +
                      hash[key] = hit unless hit.nil?
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                    hash
         
     | 
| 
      
 86 
     | 
    
         
            +
                  else
         
     | 
| 
      
 87 
     | 
    
         
            +
                    @memcache.read_multi(*keys)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  if IdentityCache.logger.debug?
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                    result.each do |k, v|
         
     | 
| 
      
 93 
     | 
    
         
            +
                      memcache_keys << k if !v.nil? && !memoized_keys.include?(k)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    memoized_keys.each{ |k| IdentityCache.logger.debug "[IdentityCache] (memoized) cache hit for #{k} (multi)" }
         
     | 
| 
      
 97 
     | 
    
         
            +
                    memcache_keys.each{ |k| IdentityCache.logger.debug "[IdentityCache] (memcache) cache hit for #{k} (multi)" }
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                  result
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 104 
     | 
    
         
            +
                  clear_memoization
         
     | 
| 
      
 105 
     | 
    
         
            +
                  @memcache.clear
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                private
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                def clear_memoization
         
     | 
| 
      
 111 
     | 
    
         
            +
                  @key_value_maps.delete(Thread.current)
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                def memoizing?
         
     | 
| 
      
 115 
     | 
    
         
            +
                  Thread.current[:memoizing_idc]
         
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module IdentityCache
         
     | 
| 
      
 2 
     | 
    
         
            +
              module ParentModelExpiration # :nodoc:
         
     | 
| 
      
 3 
     | 
    
         
            +
                def expire_parent_cache_on_changes(parent_name, foreign_key, parent_class, only_on_foreign_key_change)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  new_parent = send(parent_name)
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  if new_parent && new_parent.respond_to?(:expire_primary_index, true)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    if should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
         
     | 
| 
      
 8 
     | 
    
         
            +
                      new_parent.expire_primary_index
         
     | 
| 
      
 9 
     | 
    
         
            +
                      new_parent.expire_parent_cache if new_parent.respond_to?(:expire_parent_cache)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    end
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  if transaction_changed_attributes[foreign_key].present?
         
     | 
| 
      
 14 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 15 
     | 
    
         
            +
                      old_parent = parent_class.find(transaction_changed_attributes[foreign_key])
         
     | 
| 
      
 16 
     | 
    
         
            +
                      old_parent.expire_primary_index if old_parent.respond_to?(:expire_primary_index)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      old_parent.expire_parent_cache  if old_parent.respond_to?(:expire_parent_cache)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    rescue ActiveRecord::RecordNotFound => e
         
     | 
| 
      
 19 
     | 
    
         
            +
                      # suppress errors finding the old parent if its been destroyed since it will have expired itself in that case
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  true
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def should_expire_identity_cache_parent?(foreign_key, only_on_foreign_key_change)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if only_on_foreign_key_change
         
     | 
| 
      
 28 
     | 
    
         
            +
                    destroyed? || was_new_record? || transaction_changed_attributes[foreign_key].present?
         
     | 
| 
      
 29 
     | 
    
         
            +
                  else
         
     | 
| 
      
 30 
     | 
    
         
            +
                    true
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     |