Module: Interlock

Defined in:
lib/interlock.rb,
lib/interlock/config.rb,
lib/interlock/interlock.rb

Defined Under Namespace

Modules: Config Classes: DependencyError, FragmentConsistencyError, InterlockError, UsageError

Constant Summary collapse

DEFAULTS =
{
  :ttl => 1.day,
  :namespace => 'app',
  :servers => ['localhost:11211']
}
SCOPE_KEYS =
[:controller, :action, :id]
KEY_LENGTH_LIMIT =

Buried value extracted from memcache.rb in the memcache-client gem. If one tries to request a key that is too long, the client throws an error. In practice, it seems better to simply avoid ever setting such long keys, so we use this value to achieve such for keys generated by Interlock.

250
ILLEGAL_KEY_CHARACTERS_PATTERN =

Similarly buried and useful: no whitespace allowed in keys.

/\s/
@@config =
DEFAULTS

Class Method Summary collapse

Class Method Details

.caching_key(controller, action, id, tag) ⇒ Object

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.

Raises:

  • (ArgumentError)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/interlock/interlock.rb', line 106

def caching_key(controller, action, id, tag)
  raise ArgumentError, 'Both controller and action must be specified' unless controller and action
  
  id = (id or 'all').to_interlock_tag
  tag = tag.to_interlock_tag
  
  key = "interlock:#{ENV['RAILS_ASSET_ID']}:#{controller}:#{action}:#{id}:#{tag}"
  
  if key.length > KEY_LENGTH_LIMIT
    old_key = key
    key = key[0..KEY_LENGTH_LIMIT-1]
    say old_key, "truncated to #{key}"
  end
  
  key.gsub ILLEGAL_KEY_CHARACTERS_PATTERN, ''
end

.dependency_key(klass) ⇒ Object

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



96
97
98
# File 'lib/interlock/interlock.rb', line 96

def dependency_key(klass) 
  "interlock:#{ENV['RAILS_ASSET_ID']}:dependency:#{klass.name}"
end

.extract_options_and_dependencies(dependencies, default = nil) ⇒ Object

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



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/interlock/interlock.rb', line 31

def extract_options_and_dependencies(dependencies, default = nil) 
  options = dependencies.extract_options!
  
  # Hook up the dependencies nested array.
  dependencies.map! { |klass| [klass, :all] }
  options.each do |klass, scope| 
    if klass.is_a? Class 
      #
      # Beware! Scoping to :id means that a request's params[:id] must equal 
      # klass#id or the rule will not trigger. This is because params[:id] is the
      # only record-specific scope include in the key. 
      #
      # If you want fancier invalidation, think hard about whether it really 
      # matters. Over-invalidation is rarely a problem, but under-invalidation
      # frequently is. 
      #
      # "But I need it!" you say. All right, then start using key tags.
      #
      raise Interlock::DependencyError, "#{scope.inspect} is not a valid scope" unless [:all, :id].include?(scope)
      dependencies << [klass, scope.to_sym]
    end
  end    

  unless dependencies.any?
    # Use the conventional controller/model association if none are provided
    # Can be skipped by calling caching(nil)
    dependencies = [[default, :all]]
  end
  
  # Remove nils
  dependencies.reject! {|klass, scope| klass.nil? }
  
  [options.indifferentiate, dependencies]
end

.invalidate(key) ⇒ Object

Invalidate a particular key.



126
127
128
# File 'lib/interlock/interlock.rb', line 126

def invalidate(key)
  ActionController::Base.fragment_cache_store.delete key
end

.register_dependencies(dependencies, key) ⇒ Object

Add each key with scope to the appropriate dependencies array.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/interlock/interlock.rb', line 69

def register_dependencies(dependencies, key)
  Array(dependencies).each do |klass, scope|
    dep_key = dependency_key(klass)
    
    # Get the value for this class/key out of the global store.
    this = (CACHE.get(dep_key) || {})[key]

    # Make sure to not overwrite broader scopes.
    unless this == :all or this == scope
      # We need to write, so acquire the lock.            
      CACHE.lock(dep_key) do |hash|
        Interlock.say key, "registered a dependency on #{klass} -> #{scope.inspect}."
        (hash || {}).merge({key => scope})
      end
    end
    
  end
end

.say(key, msg) ⇒ Object

:nodoc:



88
89
90
# File 'lib/interlock/interlock.rb', line 88

def say(key, msg) #:nodoc:
  RAILS_DEFAULT_LOGGER.warn "** fragment #{key.inspect[1..-2]} #{msg}"
end