Class: ActionController::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/interlock/action_controller.rb

Instance Method Summary collapse

Instance Method Details

#behavior_cache(*args) ⇒ Object Also known as: caching

behavior_cache marks a controller block for caching. It accepts a list of class dependencies for invalidation, as well as as :tag and :ignore keys for explicit fragment scoping. It does not accept a :ttl key.

Please note that the behavior of nested behavior_cache blocks is undefined.

Declaring dependencies

You can declare non-default invalidation dependencies by passing models to behavior_cache (you can also pass them to view_cache, but you should only do that if you are caching a fragment without an associated behavior block in the controller).

No dependencies (cache never invalidates):

behavior_cache nil do
end

Invalidate on any Media change:

behavior_cache Media do
end

Invalidate on any Media or Item change:

behavior_cache Media, Item do
end

Invalidate on Item changes if the Item id matches the current params[:id] value:

behavior_cache Item => :id do
end

You do not have to pass the same dependencies to behavior_cache and view_cache even for the same action. The set union of both dependency lists will be used.

Narrowing scope and caching multiple blocks

Sometimes you need to cache multiple blocks in a controller, or otherwise get a more fine-grained scope. Interlock provides the :tag key for this purpose. :tag accepts either an array of symbols, which are mapped to params values, or an arbitrary object, which is converted to a string identifier. Your corresponding behavior caches and view caches must have identical :tag values for the interlocking to take effect.

Note that :tag can be used to scope caches. You can simultaneously cache different versions of the same block, differentiating based on params or other logic. This is great for caching per-user, for example:

def profile
  @user = current_user
  behavior_cache :tag => @user do
    @items = @user.items
  end
end

In the view, use the same :tag value (@user). Note that @user must be set outside of the behavior block in the controller, because its contents are used to decide whether to run the block in the first place.

This way each user will see only their own cache. Pretty neat.

Broadening scope

Sometimes the default scope (controller, action, params[:id]) is too narrow. For example, you might share a partial across actions, and set up its data via a filter. By default, Interlock will cache a separate version of it for each action. To avoid this, you can use the :ignore key, which lets you list parts of the default scope to ignore:

before_filter :recent

private

def recent
  behavior_cache :ignore => :action do
    @recent = Item.find(:all, :limit => 5, :order => 'updated_at DESC')
  end
end

Valid values for :ignore are :controller, :action, :id, and :all. You can pass an array of multiple values. Just like with :tag, your corresponding behavior caches and view caches must have identical :ignore values. Note that cache blocks with :ignore values still obey the regular invalidation rules.

A good way to get started is to just use the default scope. Then grep in the production log for interlock and see what keys are being set and read. If you see lots of different keys go by for data that you know is the same, then set some :ignore values.

Skipping caching

You can pass :perform => false to disable caching, for example, in a preview action. Note that :perform only responds to false, not nil. This allows for handier view reuse because you can set :perform to an instance variable and it will still cache if the instance variable is not set:

def preview
  @perform = false
  behavior_cache :perform => @perform do
  end
  render :action => 'show'
end

And in the show.html.erb view:

<% view_cache :perform => @perform do %>
<% end %>


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/interlock/action_controller.rb', line 120

def behavior_cache(*args)  
  conventional_class = begin; controller_name.classify.constantize; rescue NameError; end
  options, dependencies = Interlock.extract_options_and_dependencies(args, conventional_class)
  
  raise Interlock::UsageError, ":ttl has no effect in a behavior_cache block" if options[:ttl]

  Interlock.say "key", "yo: #{options.inspect} -- #{dependencies.inspect}"

  key = caching_key(options.value_for_indifferent_key(:ignore), options.value_for_indifferent_key(:tag))      

  if options[:perform] == false || Interlock.config[:disabled]
    Interlock.say key, "is not cached"
    yield
  else
    Interlock.register_dependencies(dependencies, key)
        
    # See if the fragment exists, and run the block if it doesn't.
    unless read_fragment(key, :assign_content_for => false)
      Interlock.say key, "is running the controller block"
      yield
    end
  end
end

#caching_key(ignore = nil, tag = nil) ⇒ Object

Build the fragment key from a particular context. This must be deterministic and stateful except for the tag. We can’t scope the key to arbitrary params because the view doesn’t have access to which are relevant and which are not.

Note that the tag can be pretty much any object. Define to_interlock_tag if you need custom tagging for some class. ActiveRecord::Base already has it defined appropriately.

If you pass an Array of symbols as the tag, it will get value-mapped onto params and sorted. This makes granular scoping easier, although it doesn’t sidestep the normal blanket invalidations.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/interlock/action_controller.rb', line 14

def caching_key(ignore = nil, tag = nil)
  ignore = Array(ignore)
  ignore = Interlock::SCOPE_KEYS if ignore.include? :all    
  
  if (Interlock::SCOPE_KEYS - ignore).empty? and !tag
    raise Interlock::UsageError, "You must specify a :tag if you are ignoring the entire default scope."
  end
    
  if tag.is_a? Array and tag.all? {|x| x.is_a? Symbol}
    tag = tag.sort_by do |key|
      key.to_s
    end.map do |key| 
      params[key].to_interlock_tag
    end.join(";")
  end
  
  Interlock.caching_key(      
    ignore.include?(:controller) ? 'any' : controller_name,
    ignore.include?(:action) ? 'any' : action_name,
    ignore.include?(:id) ? 'all' : params[:id],
    tag
  )
end