Module: Interlock

require ‘ehcache‘

Child modules and classes

Module Interlock::Config
Module Interlock::Finders
Module Interlock::Lock

Constants

NameValue
DEFAULTS { :ttl => 1.day, :namespace => 'app', :servers => ['127.0.0.1:11211'], :client => 'memcache-client', :with_finders => false
CLIENT_KEYS [ :prefix_key, :distribution, :verify_key, :tcp_nodelay, :hash, :hash_with_prefix_key, :show_backtraces, :default_ttl, :ketama_weighted, :retry_timeout, :default_weight, :buffer_requests, :timeout, :sort_hosts, :cache_lookups, :connect_timeout, :no_block, :failover, :support_cas, :namespace
SCOPE_KEYS [:controller, :action, :id]
KEY_LENGTH_LIMIT 250
ILLEGAL_KEY_CHARACTERS_PATTERN /\s/

Public Class Methods


caching_key (controller, action, id, tag)

Build a fragment key for an explicitly passed context. Shouldn‘t be called unless you need to write your own fine-grained invalidation rules. Make sure the default ones are really unacceptable before you go to the trouble of rolling your own.

     # File lib/interlock/interlock.rb, line 128
128:     def caching_key(controller, action, id, tag)
129:       raise ArgumentError, 'Both controller and action must be specified' unless controller and action
130:       
131:       id = (id or 'all').to_interlock_tag
132:       tag = tag.to_interlock_tag
133:       
134:       key = "interlock:#{ENV['RAILS_ASSET_ID']}:#{controller}:#{action}:#{id}:#{tag}"
135:       
136:       if key.length > KEY_LENGTH_LIMIT
137:         old_key = key
138:         key = key[0..KEY_LENGTH_LIMIT-1]
139:         say old_key, "truncated to #{key}"
140:       end
141:       
142:       key.gsub ILLEGAL_KEY_CHARACTERS_PATTERN, ''
143:     end

dependency_key (klass, scope, key)

Get the Memcached key for a class‘s dependency list. We store per-class to reduce lock contention.

     # File lib/interlock/interlock.rb, line 117
117:     def dependency_key(klass, scope, key) 
118:       id = (scope == :id ? ":#{key.field(4)}" : nil)
119:       "interlock:#{ENV['RAILS_ASSET_ID']}:dependency:#{klass.name}#{id}"
120:     end

extract_options_and_dependencies (dependencies, default = nil)

Extract the dependencies from the rest of the arguments and registers them with the appropriate models.

    # File lib/interlock/interlock.rb, line 40
40:     def extract_options_and_dependencies(dependencies, default = nil) 
41:       options = dependencies.extract_options!
42:       
43:       # Hook up the dependencies nested array.
44:       dependencies.map! { |klass| [klass, :all] }
45:       options.each do |klass, scope| 
46:         if klass.is_a? Class 
47:           #
48:           # Beware! Scoping to :id means that a request's params[:id] must equal 
49:           # klass#id or the rule will not trigger. This is because params[:id] is the
50:           # only record-specific scope include in the key. 
51:           #
52:           # If you want fancier invalidation, think hard about whether it really 
53:           # matters. Over-invalidation is rarely a problem, but under-invalidation
54:           # frequently is. 
55:           #
56:           # "But I need it!" you say. All right, then start using key tags.
57:           #
58:           raise Interlock::DependencyError, "#{scope.inspect} is not a valid scope" unless [:all, :id].include?(scope)
59:           dependencies << [klass, scope.to_sym]
60:         end
61:       end    
62: 
63:       unless dependencies.any?
64:         # Use the conventional controller/model association if none are provided
65:         # Can be skipped by calling caching(nil)
66:         dependencies = [[default, :all]]
67:       end
68:       
69:       # Remove nils
70:       dependencies.reject! {|klass, scope| klass.nil? }
71:       
72:       [options.indifferentiate, dependencies]
73:     end

invalidate (key)

Invalidate a particular key.

     # File lib/interlock/interlock.rb, line 148
148:     def invalidate(key)
149:       # Console and tests do not install the local cache
150:       Interlock.local_cache.delete(key) if Interlock.local_cache
151:       ActionController::Base.cache_store.delete key
152:     end

log (msg)

     # File lib/interlock/interlock.rb, line 102
102:     def log(msg)
103:       case Interlock.config[:log_level]
104:       when 'debug', 'info', 'warn', 'error'
105:         log_method = Interlock.config[:log_level]
106:       else
107:         log_method = :debug
108:       end
109:       
110:       RAILS_DEFAULT_LOGGER.send( log_method, msg )
111:     end

register_dependencies (dependencies, key)

Add each key with scope to the appropriate dependencies array.

    # File lib/interlock/interlock.rb, line 78
78:     def register_dependencies(dependencies, key)
79:       return if Interlock.config[:disabled]
80:  
81:       Array(dependencies).each do |klass, scope|
82:         dep_key = dependency_key(klass, scope, key)
83:       
84:         # Get the value for this class/key out of the global store.
85:         this = (CACHE.get(dep_key) || {})[key]
86: 
87:         # Make sure to not overwrite broader scopes.
88:         unless this == :all or this == scope
89:           # We need to write, so acquire the lock.            
90:           CACHE.lock(dep_key) do |hash|
91:             Interlock.say key, "registered a dependency on #{klass} -> #{scope.inspect}."
92:             (hash || {}).merge({key => scope})
93:           end
94:         end        
95:       end
96:     end