jodosha/redis-store — pull request #145 by mhuggins (2012-08-04 20:08:33+00:00)
Title: Allow alternate adapters to be used for serializing/deserializing

I'm working on an app that is intended to work with session between Rails and Node.js. The problem is, Node.js can't read a Ruby marshaled object from the session with redis-store's current implementation.

Looking through old issues, I see I'm not the only one with this problem, as issues #77 and #79 were both opened with a similar purpose. The problem is that both of those implementations could not properly unmarshal Ruby objects.

This pull request does two things. First, it creates the concept of an "adapter". An adapter is anything that can respond to `load` and `dump`, and it defaults to a new wrapper class that just uses Marshal. (This is to allow for backward compatibility without existing users having to worry about changing their app code.)

Second, it introduces a new JSON adapter. This adapter serializes `Hash`, `Array`, `String`, `Numeric`, `TrueClass`, `FalseClass`, and `NilClass` objects into their appropriate JSON representations. Any other non-native JSON class is converted to a `String` via `Marshal.dump`. This allows other languages that are sharing the session to parse the JSON session data itself, simply ignoring the native Ruby objects that aren't needed.

Example usage:

# each of the following will create a new redis store object that uses the new Marshal adapter
Redis::Store.new
Redis::Store.new(:adapter => :marshal) # symbol shorthand
Redis::Store.new(:adapter => Redis::Store::Adapters::Marshal) # actual class
Redis::Store.new(:adapter => "Redis::Store::Adapters::Marshal") # string name representing class

# each of the following will create a new redis store object that uses the new Json adapter
Redis::Store.new(:adapter => :json) # symbol shorthand
Redis::Store.new(:adapter => Redis::Store::Adapters::Json) # actual class
Redis::Store.new(:adapter => "Redis::Store::Adapters::Json") # string name representing class

Here is an example of how easy it is to create a custom adapter:

module RedisYamlAdapter
def self.dump(object)
::YAML.dump(object)
end

def self.load(string)
::YAML.load(string)
end
end

Redis::Store.new(:adapter => RedisYamlAdapter)

Of course, we didn't really even need to create an adapter for this very simple scenario. We could use the YAML class as is since it already responds to `load`/`dump`:

Redis::Store.new(:adapter => YAML)

And it's just as easy to integrate into your Rails app:

# Rails 3 example
MyApp::Application.config.session_store :redis_store, :servers => { :adapter => :json }

I would love to hear feedback on the approach. Some new unit tests could also be used, but I wanted to ensure you're open to the idea before moving forward with coding anything more into this branch. Thanks!