resque/resque — pull request #1210 by davetron5000 (2014-04-27 17:35:44+00:00)
Title: Create a class between Resque classes and what they do in redis

## Problem

I want to write code to access information about what's going on in Resque, but the code needs to work for multiple Resque instances in the same Ruby VM. Because `Resque.redis` is global, it is very difficult (impossible in some cases) to use the Resque API directly.

## Solution

Provide an API that does not rely on a global variable that encapsulates all the ways in which Resque interacts with Redis, namely the names of keys and what sort of data structure is expected in those keys.

Consider a call like this:

```ruby
decode redis.lpop("queue:#{queue}")
```

This should mean "decode the job on queue `queue`", but it actually means "decode whatever is in redis under the key `"queue:#{queue}"` which just so happens to be how we store queues, but don't worry about that right now, just go in Redis and do it".

With this PR, it turns into this:

```ruby
decode(@data_store.pop_from_queue(queue))
```
which is saying "get me the job in queue `queue`, however that's done, and decode it.

Which means that someone _else_ can do this without knowing how to construct the redis key for queue.

And because that knowledge is now centralized in one class (`DataStore`) instead of littered throughout the codebase, one could perform these operations on multiple resque queues from the same Ruby VM, e.g. for monitoring:

```ruby
resques = {
www: '10.0.3.4:2345',
admin: '10.1.4.5:8765',
ops: '10.1.4.5:8766',
}

data_stores = Hash[resques.map { |name,location|
[name,Resque::DataStore.new(Redis.new(location))]
}]

data_stores[:www].num_failed # => how many are failed in www's Resque
data_stores[:admin].num_failed # => what about admin?
stuck_workers = data_stores[:ops].workers.select { |worker|
data_stores[:ops].worker_start_time(worker) > 1.hour.ago
}
```
And so forth.

This is not an ideal design, but it solves the problem without breaking backwards compatibility and is better than what exists now, since it at least centralizes how Resque's data structures are stored in Redis. It could also, in theory, allow a different backing store than Redis.

I hacked a `concerning` concept to demonstrate which calls were relevant to what—this could be split into further classes. It's also possible that versions of the major objects (`Resque`, `Worker`, and `Job`) could be created to not use a global for `redis`, but that is for another day.