<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -39,9 +39,7 @@ Running `script/generator users details` will create a UsersComponent with a &quot;de
 
 Any component action may be cached using the fragment caching you've configured on ActionController::Base. The command to cache a component action must come after the definition of the action itself. This is because the caching method wraps the action, which makes the caching work even if you call the action directly.
 
-Cache expiration must either be handled through some TTL option (Rails' :mem_cache_store calls it :expires_in) or a version argument. Support for the latter has not yet been optimized in this plugin, but you can always pass a version counter in the component arguments themselves. Direct key expiration via some method like expire_component is not practical since the keys are so variable and since cache stores like memcache don't keep a list of known keys in memory to iterate through and pattern match against.
-
-For example:
+Example:
 
   class UsersComponent &lt; Components::Base
     def details(user_id)
@@ -51,7 +49,7 @@ For example:
     cache :details, :expires_in =&gt; 15.minutes
   end
 
-This will cache the returns from UsersComponent#details using a cache key like &quot;users/details/5&quot;, where 5 is the user_id. The cache will only be good for fifteen minutes.
+This will cache the returns from UsersComponent#details using a cache key like &quot;users/details/5&quot;, where 5 is the user_id. The cache will only be good for fifteen minutes. See Components::Caching for more information.
 
 === Helpers
 </diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -1,4 +1,4 @@
-# Component caching is at a very fine-grained level - a component is cached based on all
+# Component caching is very fine-grained - a component is cached based on all
 # of its arguments. Any more or less would result in inaccurate cache hits.
 #
 # === Howto
@@ -28,23 +28,58 @@
 #
 # I know of three general methods to expire caches:
 #
-#  * TTL: expires a cache after some number of seconds. This works well for content that
-#    is hit frequently but can stand to be a bit stale at times.
-#  * Versioning: caches are never actually expired, rather, they are eventually ignored
-#    when all new cache requests are for a new version. This only works with cache stores
-#    that have some kind of limited cache space, otherwise cache consumption will go
-#    through the metaphorical roof.
-#  * Direct Expiration: caches are expired by name. This does not work when cache keys
-#    have variable elements, or when the complete list of cache keys is not available
-#    or a brute-force regular expression approach.
+# * TTL: expires a cache after some number of seconds. This works well for content that
+#   is hit frequently but can stand to be a bit stale at times.
+# * Versioning: caches are never actually expired, rather, they are eventually ignored
+#   when all new cache requests are for a new version. This only works with cache stores
+#   that have some kind of limited cache space, otherwise cache consumption will go
+#   through the metaphorical roof.
+# * Direct Expiration: caches are expired by name. This does not work when cache keys
+#   have variable elements, or when the complete list of cache keys is not available
+#   or a brute-force regular expression approach.
 #
 # Of those three, direct expiration is not a viable option due to the variable nature of
 # component cache keys. And since both of the remaining methods are best supported by some
 # variation of memcache, that is the officially recommended cache store.
 #
-# TODO: describe how to implement TTL/Versioned expiration
+# ==== TTL Expiration
+#
+# If you are using Rails' :mem_cache_store for fragments, then you can set up TTL-style
+# expiration by specifying an :expires_in option, like so:
+#
+#   class UserComponent &lt; Components::Base
+#     def show(user_id)
+#       @user = User.find(user_id)
+#       render
+#     end
+#     cache :show, :expires_in =&gt; 15.minutes
+#   end
+#
+# === Versioned Expiration
+#
+# Maintaining and incrementing version numbers may be implemented any number of ways. To
+# use the version numbers, though, you can specify a :version option, which may either name
+# a method (use a Symbol) or provide a proc. In either case, the method or proc should
+# receive all of the same arguments as the action itself, and should return the version
+# string.
+#
+#   class UserComponent &lt; Components::Base
+#     def show(user_id)
+#       @user = User.find(user_id)
+#       render
+#     end
+#     cache :show, :version =&gt; :show_cache_version
+#
+#     protected
+#
+#     def show_cache_version(user_id)
+#       # you may want to find your version from a model object, from memcache, or whereever.
+#       Version.for(&quot;users/show&quot;, user_id)
+#     end
+#   end
+#
 module Components::Caching
-  def self.included(base)
+  def self.included(base) #:nodoc:
     base.class_eval do
       extend ClassMethods
     end
@@ -71,7 +106,7 @@ module Components::Caching
       self.send(&quot;#{action}_cache_options=&quot;, cache_options)
     end
 
-    def cache_store
+    def cache_store #:nodoc:
       @cache_store ||= if ActionController::Base.respond_to?(:cache_store)
         # Rails 2.1
         ActionController::Base.cache_store
@@ -84,26 +119,30 @@ module Components::Caching
 
   protected
 
-  def with_caching(action, args, &amp;block)
+  def with_caching(action, args, &amp;block) #:nodoc:
     key = cache_key(action, args)
     cache_options = self.send(&quot;#{action}_cache_options&quot;)
     read_fragment(key, cache_options) || returning(block.call) { |fragment| write_fragment(key, fragment, cache_options) }
   end
 
-  def read_fragment(key, cache_options = nil)
+  def read_fragment(key, cache_options = nil) #:nodoc:
     returning self.class.cache_store.read(key, cache_options) do |content|
       logger.info &quot;Component Cache hit: #{key}&quot; unless content.blank?
     end
   end
 
-  def write_fragment(key, content, cache_options = nil)
+  def write_fragment(key, content, cache_options = nil) #:nodoc:
     logger.info &quot;Component Cache miss: #{key}&quot;
     self.class.cache_store.write(key, content, cache_options)
   end
 
-  # TODO: test for consistency in cache key even w. options hashes at the end of args
-  def cache_key(action, args = [])
-    key = ([self.class.path, action] + args).collect do |arg|
+  # generates the cache key for the given action/args
+  def cache_key(action, args = []) #:nodoc:
+    key_pieces = [self.class.path, action] + args
+    if v = cache_version(action, args)
+      key_pieces &lt;&lt; &quot;v#{v}&quot;
+    end
+    key = key_pieces.collect do |arg|
       case arg
         when Hash:                arg.to_query # Rails 2.0 compat
         when ActiveRecord::Base:  &quot;#{arg.class.to_s.underscore}#{arg.id}&quot; # note: doesn't apply to record sets
@@ -119,4 +158,18 @@ module Components::Caching
       key
     end
   end
+
+  # returns the versioning configuration for the given action, if any
+  def versioning(action) #:nodoc:
+    (self.send(&quot;#{action}_cache_options&quot;) || {})[:version]
+  end
+
+  # returns the actual version for a given action/args by calling either
+  # the named method or the configured proc.
+  def cache_version(action, args) #:nodoc:
+    case version = versioning(action)
+      when Proc:   version.call(*args)
+      when Symbol: send(version, *args)
+    end
+  end
 end</diff>
      <filename>lib/components/caching.rb</filename>
    </modified>
    <modified>
      <diff>@@ -32,4 +32,16 @@ class CachingTest &lt; Test::Unit::TestCase
     @component.expects(:write_fragment).with(&quot;hello_world/say_it/frumpamumpa&quot;, &quot;frumpamumpa&quot;, nil)
     assert_equal &quot;frumpamumpa&quot;, @component.say_it(&quot;frumpamumpa&quot;)
   end
+
+  def test_expires_in_passthrough
+    @component.say_it_cache_options = {:expires_in =&gt; 15.minutes}
+    @component.expects(:write_fragment).with(&quot;hello_world/say_it/ninnanana&quot;, &quot;ninnanana&quot;, {:expires_in =&gt; 15.minutes})
+    assert_equal &quot;ninnanana&quot;, @component.say_it(&quot;ninnanana&quot;)
+  end
+
+  def test_versioned_keys
+    @component.say_it_cache_options = {:version =&gt; :some_named_method}
+    @component.expects(:some_named_method).with(&quot;rangleratta&quot;).returns(314)
+    assert_equal &quot;hello_world/say_it/rangleratta/v314&quot;, @component.send(:cache_key, :say_it, [&quot;rangleratta&quot;])
+  end
 end
\ No newline at end of file</diff>
      <filename>test/caching_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>1002a74f387c136ecc3476e45d8a1fdc5348b0e2</id>
    </parent>
  </parents>
  <author>
    <name>Lance Ivy</name>
    <email>lance@cainlevy.net</email>
  </author>
  <url>http://github.com/cainlevy/components/commit/547d73c6ffe5a619665df4e54ad9fba2137cb4ea</url>
  <id>547d73c6ffe5a619665df4e54ad9fba2137cb4ea</id>
  <committed-date>2008-05-23T12:21:19-07:00</committed-date>
  <authored-date>2008-05-23T12:21:19-07:00</authored-date>
  <message>version-style cache expiration support, and rdoc cleanup</message>
  <tree>91a896e55d2e5e3e230448912e86295830790007</tree>
  <committer>
    <name>Lance Ivy</name>
    <email>lance@cainlevy.net</email>
  </committer>
</commit>
