artsy/garner — pull request #38 by fancyremarker (2013-06-25 16:05:35+00:00)
Title: Fix: Stale cache on :destroy for class-bound results

to: @joeyAghion

This fixes a bug in binding to classes via the `SafeCacheKey` and `Touch` strategy pair, where class-bound results would not be invalidated when an instance of the class was destroyed.

A spec demonstrating the problem case is:

```ruby
def cached_object_name_concatenator
garner.bind(Cheese) { Cheese.all.order_by(:_id => :asc).map(&:name).join(", ") }
end

it "invalidates on destroy" do
m1 = Cheese.create({ :name => "M1" })
m3 = Cheese.create({ :name => "M3" })
cached_object_name_concatenator.call.should == "M1, M3"
m1.destroy
cached_object_name_concatenator.call.should == "M3"
end
```

Because the `Cheese` class uses its most recently updated document as its "proxy binding," the destruction of any document other than the most recently updated one causes `Cheese`-bound results to go stale. I'm not convinced that my solution is ideal, especially since:

1. It introduces an assumption about how class and instance bindings interact, and
2. It requires updating the `:updated_at` of an unmodified document.

But I couldn't think of a more elegant alternative. Any ideas? Here's the crux of the solution, in touch.rb:

```ruby
if binding.respond_to?(:destroyed?) &&
binding.destroyed? &&
binding.class.respond_to?(:proxy_binding)
# Binding is destroyed, but we must ensure that its class's
# proxy_binding is touched, if necessary.
binding = binding.class.proxy_binding
binding.touch if binding.respond_to?(:touch)
end
```