Skip to content
This repository

Allow alternate adapters to be used for serializing/deserializing #145

Open
wants to merge 7 commits into from

8 participants

Matt Huggins Luca Guidi Don't Add Me To Your Organization a.k.a The Travis Bot Matthew Horan Geoff Hayes Alain Bloch Steven Yue Eugen
Matt Huggins

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 (along with updated documentation), but I wanted to ensure you're open to the idea before moving forward with coding anything more into this branch. Thanks!

Luca Guidi
Owner

Thanks for opening the discussion, this is an intersting topic. Here my considerations: first of all I don't like the idea of having ActiveSupport as dependency, just for #constantize. This will force developers to install this gem for a secondary feature, and also could prevent them to decide the Rails version the want to use (in the patch you're automatically excluding all the < 3.2.2 apps). A solution can be borrowing that code and consider it as a Ruby ext.

Second, does this JSON serializer guarantees ActiveRecord or Ruby objects to be transparently serialized/unserialized, including their internal state? Could be convenient to implement something similar to MongoId, where there is a more structured representations of the documents? I'm afraid that just invoking #to_json, isn't enough.

Third, by implementing this feature, I would like to see a little refactoring. Marshalling shound became an internal adapter (strartegy, if you will) of a more generic Serializer class. The _extend_marshalling should disappear, all the data will pass thru the Serializer, that by default will have a NoOp strategy, or, according to the conf, will change in marshalling/json/yaml/whatever.

Fourth, how to specify the adapter in the case of the configuration URI? Maybe redis+json:// ?

Matt Huggins

@jodosha - Thanks for the quick feedback, I really appreciate it.

After submitting this pull request and thinking about it in bed last night, I had some of the same thoughts about requiring ActiveSupport, as you mentioned. I starting thinking that it would make more sense to take that part out, not worry about constantizing strings, and only allow actual classes/objects that support dump/load to be supplied as values for the adapter param. This would allow the ActiveSupport dependency to be removed altogether.

There is a concern with this JSON serializer that if you have a Hash where the keys are strings, then you serialize/deserialize, you'll end up with a Hash where the keys are symbols. Strings will not have internal state information such as @html_safe instance variables being saved. Other objects that are extensions of Hash or Array will also lose any internal state they have prior to internal serialization with the current implementation. I''m not sure if there's really a way around this, unless a fake value is added to the serialized Hash/Array that can be interpreted back. I feel like that's kind of a hack though, personally.

I was wondering while coding this if the _extend_marshalling call should disappear, so I'm glad you mentioned that. Could you supply a little info on what you have in mind for the Serializer? Will it just be a placeholder class/module with dump/load methods that the other classes will extend or include? Also, why include a NoOp strategy as default when users currently expect Marshaling by default?

With regards to the configuration URI, I'm not familiar with this at all. Is this something Redis supports by default, or is it exposed by the gem? I haven't worked with Redis in enough detail to know more about it, unfortunately.

Thanks! :)

Matt Huggins

Alright, I went ahead and made some additional changes to this from what you suggested. It's using the verbiage "strategy" instead of "adapter" now. There are four possible options built in:

  • :marshal (default): uses Ruby's built-in Marshal dump/load methods
  • :json: builds and parses JSON in the manner I described in my original comment
  • :yaml. builds and parses YAML
  • false: acts the same as :marshalling => false previously did. (I removed the :marshalling option in the process.)

I removed the ActiveSupport dependency, and I also added tests for the new strategies.

Let me know what you think so far, and also if you could provide more insight about configuration URI's, that would be great so I could maybe take a look at that as well.

Lastly, if you decide you want to pull this in after all is said and done, I can squash the commits first if needed. Just let me know! Thanks again :)

Matt Huggins

Hmm, I seem to be getting a "Session collision" error quite a bit now, even though I didn't change any of the underlying code. I'll make sure it's nothing I've changed, but if you see anything that might have caused an issue in my changes, please let me know!

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged ca5079a into 6644a83).

Matt Huggins

Well, I fixed the errors I had previously, which related to the session collisions. However, I now have an issue with 'json' only being available in Ruby 1.9+, but not Ruby 1.8 or non-MRI Rubies it seems. I suppose I could do the following, though I could use some input from you on how you might want this to go.

In the redis-store.gemspec file:

s.add_dependency 'json',  '>= 1.7.0' if RUBY_VERSION < '1.9'

In the json.rb strategy module:

require 'rubygems'
require 'json'

Thoughts?

Matt Huggins

@jodosha - Just wanted to follow up and see if you have any input regarding my above questions. I would love to follow through on this and ideally have it pulled into the redis-store gem if that's an option (after ironing out the questions and json gem issue).

Matthew Horan

@mhuggins, the problem with the RUBY_VERSION requirement in the gemspec is that the requirement gets compiled when the gem is pushed, and we'll only push the gem once so it will get compiled with whatever Ruby we're running at build time. Perhaps you could look into how Rails works around this?

I'm not sure we need to be too concerned about the loss of internal state resulting from serialization. This should be left as a caveat to those choosing an alternative marshaling strategy. The default state, however, should maintain internal state to preserve backwards compatibility.

Regarding URI configuration, perhaps we could pass the strategy (and possibly future options) as "query parameters" in the URI. For example, "redis://localhost/namespace?strategy=json".

Luca Guidi
Owner

Sorry guys, just back from holidays.
I think that specifying the strategy as query string is an elegant solution, but I'm still not convinced about the loss of internal the state. For instance flash messages which are containing some markup declared secure with html_safe or memoized vars for object cached with Rails.cache, will be lost after the JSON is being loaded from the store.

Matt Huggins

@jodosha - Given that feedback, I wonder then if a simpler approach could be implemented compared to what I have here. Consider this:

  • remove all strategy classes from my current pull request
  • keep the idea of a strategy, but instead of passing in a symbol that represents the strategy to use, pass in an object reference that responds to dump/load (e.g.: pass Marshal instead of :marshal).

That way, redis-store does not have to be concerned with issues such as the html_safe issue when it comes to serializing JSON, as you pointed out. I can optionally create my own JSON encoder and pass that as the strategy option if I prefer.

Matt Huggins

Then again, that would make it difficult to handle the "query parameter" idea in the URI configuration.

Geoff Hayes

Just FYI, I created a NodeJS module a while ago to unmarshall Ruby serialized data. This could be a quick-fix alternative to the problem presented in this thread: https://gist.github.com/4417808

Alain Bloch

im surprised this wasn't accepted.

@jodosha - im wondering why you care so much if JSON doesn't properly de/serialize objects in session. People shouldn't be saving objects into session anyways. The REAL problem is that redis-store only uses ruby Marshal which is a non-standard way of serializing data. You shouldn't care what strategy/adapter people use to load/dump into redis. If a developer creates a crazy adapter and it breaks their app, then it breaks their app.

Alain Bloch alainbloch commented on the diff March 29, 2013
redis-store/lib/redis/store/strategy/json.rb
((26 lines not shown))
  26
+              object.each_with_index { |v, i| object[i] = _marshal(v) }
  27
+            when *SERIALIZABLE
  28
+              object
  29
+            else
  30
+              ::Marshal.dump(object)
  31
+            end
  32
+          end
  33
+          
  34
+          def _unmarshal(object)
  35
+            case object
  36
+            when Hash
  37
+              object.each { |k,v| object[k] = _unmarshal(v) }
  38
+            when Array
  39
+              object.each_with_index { |v, i| object[i] = _unmarshal(v) }
  40
+            when String
  41
+              object.start_with?(MARSHAL_INDICATORS) ? ::Marshal.load(object) : object
4
Alain Bloch
alainbloch added a note March 29, 2013

This won't work. You need to add an asterisk so the MARSHAL_INDICATORS Array is used as multiple arguments for the star_with? method :

object.start_with?(*MARSHAL_INDICATORS)

I suggest adding a test for it. I noticed that there wasn't a test covering marshalling Ruby Marshalled strings from the store.

Matt Huggins
mhuggins added a note March 29, 2013

You're right, good catch. It could use some more tests, but it doesn't seem like this pull request is going anywhere for me to commit the time.

Alain Bloch
alainbloch added a note March 29, 2013

Well, if it means anything to you, Im using your code on a fairly large website. ;) Thanks for taking the time those months ago to do this.

Matt Huggins
mhuggins added a note March 29, 2013

:smile::+1:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Steven Yue

+1

This is a good feature, JSON is a standard way when the session is not used only in rails/ruby

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
1  redis-store/lib/redis-store.rb
@@ -3,7 +3,6 @@
3 3
 require 'redis/factory'
4 4
 require 'redis/distributed_store'
5 5
 require 'redis/store/namespace'
6  
-require 'redis/store/marshalling'
7 6
 require 'redis/store/version'
8 7
 
9 8
 class Redis
21  redis-store/lib/redis/store.rb
... ...
@@ -1,14 +1,21 @@
1 1
 require 'redis/store/ttl'
2 2
 require 'redis/store/interface'
  3
+require 'redis/store/strategy'
3 4
 
4 5
 class Redis
5 6
   class Store < self
6 7
     include Ttl, Interface
7 8
 
  9
+    STRATEGIES = {
  10
+      :marshal => Strategy::Marshal,
  11
+      :json    => Strategy::Json,
  12
+      :yaml    => Strategy::Yaml,
  13
+    }.freeze
  14
+
8 15
     def initialize(options = { })
9 16
       super
10  
-      _extend_marshalling options
11  
-      _extend_namespace   options
  17
+      _extend_strategy  options
  18
+      _extend_namespace options
12 19
     end
13 20
 
14 21
     def reconnect
@@ -20,9 +27,13 @@ def to_s
20 27
     end
21 28
 
22 29
     private
23  
-      def _extend_marshalling(options)
24  
-        @marshalling = !(options[:marshalling] === false) # HACK - TODO delegate to Factory
25  
-        extend Marshalling if @marshalling
  30
+      def _extend_strategy(options)
  31
+        strategy = options[:strategy]
  32
+
  33
+        unless strategy === false
  34
+          strategy_class = STRATEGIES[strategy] || STRATEGIES[:marshal]
  35
+          extend Strategy, strategy_class
  36
+        end
26 37
       end
27 38
 
28 39
       def _extend_namespace(options)
30  redis-store/lib/redis/store/marshalling.rb → redis-store/lib/redis/store/strategy.rb
... ...
@@ -1,44 +1,48 @@
  1
+require 'redis/store/strategy/json'
  2
+require 'redis/store/strategy/marshal'
  3
+require 'redis/store/strategy/yaml'
  4
+
1 5
 class Redis
2 6
   class Store < self
3  
-    module Marshalling
  7
+    module Strategy
4 8
       def set(key, value, options = nil)
5  
-        _marshal(value, options) { |value| super encode(key), encode(value), options }
  9
+        dump(value, options) { |value| super encode(key), encode(value), options }
6 10
       end
7 11
 
8 12
       def setnx(key, value, options = nil)
9  
-        _marshal(value, options) { |value| super encode(key), encode(value), options }
  13
+        dump(value, options) { |value| super encode(key), encode(value), options }
10 14
       end
11 15
 
12 16
       def setex(key, expiry, value, options = nil)
13  
-        _marshal(value, options) { |value| super encode(key), expiry, encode(value), options }
  17
+        dump(value, options) { |value| super encode(key), expiry, encode(value), options }
14 18
       end
15 19
 
16 20
       def get(key, options = nil)
17  
-        _unmarshal super(key), options
  21
+        load super(key), options
18 22
       end
19 23
 
20 24
       def mget(*keys)
21 25
         options = keys.flatten.pop if keys.flatten.last.is_a?(Hash)
22 26
         super(*keys).map do |result|
23  
-          _unmarshal result, options
  27
+          load result, options
24 28
         end
25 29
       end
26 30
 
27 31
       private
28  
-        def _marshal(val, options)
29  
-          yield marshal?(options) ? Marshal.dump(val) : val
  32
+        def dump(val, options)
  33
+          yield dump?(options) ? _dump(val) : val
30 34
         end
31 35
 
32  
-        def _unmarshal(val, options)
33  
-          unmarshal?(val, options) ? Marshal.load(val) : val
  36
+        def load(val, options)
  37
+          load?(val, options) ? _load(val) : val
34 38
         end
35 39
 
36  
-        def marshal?(options)
  40
+        def dump?(options)
37 41
           !(options && options[:raw])
38 42
         end
39 43
 
40  
-        def unmarshal?(result, options)
41  
-          result && result.size > 0 && marshal?(options)
  44
+        def load?(result, options)
  45
+          result && result.size > 0 && dump?(options)
42 46
         end
43 47
 
44 48
         if defined?(Encoding)
49  redis-store/lib/redis/store/strategy/json.rb
... ...
@@ -0,0 +1,49 @@
  1
+require 'json'
  2
+
  3
+class Redis
  4
+  class Store < self
  5
+    module Strategy
  6
+      module Json
  7
+        private
  8
+          SERIALIZABLE = [String, TrueClass, FalseClass, NilClass, Numeric, Date, Time].freeze
  9
+          MARSHAL_INDICATORS = ["\x04", "\004", "\u0004"].freeze
  10
+          
  11
+          def _dump(object)
  12
+            object = _marshal(object)
  13
+            object.to_json
  14
+          end
  15
+          
  16
+          def _load(string)
  17
+            object = JSON.parse(string, :symbolize_names => true)
  18
+            _unmarshal(object)
  19
+          end
  20
+          
  21
+          def _marshal(object)
  22
+            case object
  23
+            when Hash
  24
+              object.each { |k,v| object[k] = _marshal(v) }
  25
+            when Array
  26
+              object.each_with_index { |v, i| object[i] = _marshal(v) }
  27
+            when *SERIALIZABLE
  28
+              object
  29
+            else
  30
+              ::Marshal.dump(object)
  31
+            end
  32
+          end
  33
+          
  34
+          def _unmarshal(object)
  35
+            case object
  36
+            when Hash
  37
+              object.each { |k,v| object[k] = _unmarshal(v) }
  38
+            when Array
  39
+              object.each_with_index { |v, i| object[i] = _unmarshal(v) }
  40
+            when String
  41
+              object.start_with?(MARSHAL_INDICATORS) ? ::Marshal.load(object) : object
  42
+            else
  43
+              object
  44
+            end
  45
+          end
  46
+      end
  47
+    end
  48
+  end
  49
+end
16  redis-store/lib/redis/store/strategy/marshal.rb
... ...
@@ -0,0 +1,16 @@
  1
+class Redis
  2
+  class Store < self
  3
+    module Strategy
  4
+      module Marshal
  5
+        private
  6
+          def _dump(object)
  7
+            ::Marshal.dump(object)
  8
+          end
  9
+          
  10
+          def _load(string)
  11
+            ::Marshal.load(string)
  12
+          end
  13
+      end
  14
+    end
  15
+  end
  16
+end
16  redis-store/lib/redis/store/strategy/yaml.rb
... ...
@@ -0,0 +1,16 @@
  1
+class Redis
  2
+  class Store < self
  3
+    module Strategy
  4
+      module Yaml
  5
+        private
  6
+          def _dump(object)
  7
+            YAML.dump(object)
  8
+          end
  9
+          
  10
+          def _load(string)
  11
+            YAML.load(string)
  12
+          end
  13
+      end
  14
+    end
  15
+  end
  16
+end
28  redis-store/test/redis/factory_test.rb
@@ -41,9 +41,31 @@
41 41
         store.instance_variable_get(:@client).password.must_equal("secret")
42 42
       end
43 43
 
44  
-      it "allows/disable marshalling" do
45  
-        store = Redis::Factory.create :marshalling => false
46  
-        store.instance_variable_get(:@marshalling).must_equal(false)
  44
+      it "allows json strategy option" do
  45
+        store = Redis::Factory.create :strategy => :json
  46
+        store.must_be_kind_of(Redis::Store::Strategy::Json)
  47
+      end
  48
+
  49
+      it "allows marshal strategy option" do
  50
+        store = Redis::Factory.create :strategy => :marshal
  51
+        store.must_be_kind_of(Redis::Store::Strategy::Marshal)
  52
+      end
  53
+
  54
+      it "allows yaml strategy option" do
  55
+        store = Redis::Factory.create :strategy => :yaml
  56
+        store.must_be_kind_of(Redis::Store::Strategy::Yaml)
  57
+      end
  58
+
  59
+      it "allows false strategy option" do
  60
+        store = Redis::Factory.create :strategy => false
  61
+        store.wont_be_kind_of(Redis::Store::Strategy::Json)
  62
+        store.wont_be_kind_of(Redis::Store::Strategy::Marshal)
  63
+        store.wont_be_kind_of(Redis::Store::Strategy::Yaml)
  64
+      end
  65
+
  66
+      it "defaults to marshal strategy" do
  67
+        store = Redis::Factory.create
  68
+        store.must_be_kind_of(Redis::Store::Strategy::Marshal)
47 69
       end
48 70
 
49 71
       it "should instantiate a Redis::DistributedStore store" do
2  redis-store/test/redis/store/namespace_test.rb
@@ -3,7 +3,7 @@
3 3
 describe "Redis::Store::Namespace" do
4 4
   def setup
5 5
     @namespace = "theplaylist"
6  
-    @store  = Redis::Store.new :namespace => @namespace, :marshalling => false # TODO remove mashalling option
  6
+    @store  = Redis::Store.new :namespace => @namespace, :strategy => false
7 7
     @client = @store.instance_variable_get(:@client)
8 8
     @rabbit = "bunny"
9 9
   end
108  redis-store/test/redis/store/strategy/json_test.rb
... ...
@@ -0,0 +1,108 @@
  1
+require 'test_helper'
  2
+
  3
+describe "Redis::Store::Strategy::Json" do
  4
+  def setup
  5
+    @store = Redis::Store.new :strategy => :json
  6
+    @rabbit = OpenStruct.new :name => 'rabbit', :legs => 4
  7
+    @peter     = { :name => "Peter Cottontail",
  8
+                   :race => @rabbit }
  9
+    @bunnicula = { :name    => "Bunnicula",
  10
+                   :race    => @rabbit,
  11
+                   :friends => [@peter],
  12
+                   :age     => 3.1,
  13
+                   :alive   => true }
  14
+    @store.set "rabbit", @bunnicula
  15
+    @store.del "rabbit2"
  16
+  end
  17
+
  18
+  def teardown
  19
+    @store.quit
  20
+  end
  21
+
  22
+  it "unmarshals on get" do
  23
+    @store.get("rabbit").must_equal(@bunnicula)
  24
+  end
  25
+
  26
+  it "marshals on set" do
  27
+    @store.set "rabbit", @peter
  28
+    @store.get("rabbit").must_equal(@peter)
  29
+  end
  30
+
  31
+  it "doesn't unmarshal on get if raw option is true" do
  32
+    race = Marshal.dump(@rabbit).to_json
  33
+    @store.get("rabbit", :raw => true).must_equal(%({"name":"Bunnicula","race":#{race},"friends":[{"name":"Peter Cottontail","race":#{race}}],"age":3.1,"alive":true}))
  34
+  end
  35
+
  36
+  it "doesn't marshal on set if raw option is true" do
  37
+    race = Marshal.dump(@rabbit)
  38
+    @store.set "rabbit", @peter, :raw => true
  39
+    @store.get("rabbit", :raw => true).must_equal(%({:name=>"Peter Cottontail", :race=>#{race.inspect}}))
  40
+  end
  41
+
  42
+  it "doesn't set an object if already exist" do
  43
+    @store.setnx "rabbit", @peter
  44
+    @store.get("rabbit").must_equal(@bunnicula)
  45
+  end
  46
+
  47
+  it "marshals on set unless exists" do
  48
+    @store.setnx "rabbit2", @peter
  49
+    @store.get("rabbit2").must_equal(@peter)
  50
+  end
  51
+
  52
+  it "doesn't marshal on set unless exists if raw option is true" do
  53
+    @store.setnx "rabbit2", @peter, :raw => true
  54
+    race = Marshal.dump(@rabbit)
  55
+    @store.get("rabbit2", :raw => true).must_equal(%({:name=>"Peter Cottontail", :race=>#{race.inspect}}))
  56
+  end
  57
+
  58
+  it "marshals on set expire" do
  59
+    @store.setex "rabbit2", 1, @peter
  60
+    @store.get("rabbit2").must_equal(@peter)
  61
+    sleep 2
  62
+    @store.get("rabbit2").must_be_nil
  63
+  end
  64
+
  65
+  it "doesn't unmarshal on multi get" do
  66
+    @store.set "rabbit2", @peter
  67
+    rabbit, rabbit2 = @store.mget "rabbit", "rabbit2"
  68
+    rabbit.must_equal(@bunnicula)
  69
+    rabbit2.must_equal(@peter)
  70
+  end
  71
+
  72
+  it "doesn't unmarshal on multi get if raw option is true" do
  73
+    @store.set "rabbit", @bunnicula
  74
+    @store.set "rabbit2", @peter
  75
+    rabbit, rabbit2 = @store.mget "rabbit", "rabbit2", :raw => true
  76
+    race = Marshal.dump(@rabbit).to_json
  77
+    rabbit.must_equal(%({"name":"Bunnicula","race":#{race},"friends":[{"name":"Peter Cottontail","race":#{race}}],"age":3.1,"alive":true}))
  78
+    rabbit2.must_equal(%({"name":"Peter Cottontail","race":#{race}}))
  79
+  end
  80
+
  81
+  describe "binary safety" do
  82
+    before do
  83
+      @utf8_key = [51339].pack("U*")
  84
+      @ascii_string = [128].pack("C*")
  85
+      @ascii_rabbit = OpenStruct.new(:name => @ascii_string)
  86
+    end
  87
+
  88
+    it "marshals objects"
  89
+      # @store.set(@utf8_key, @ascii_rabbit)
  90
+      # @store.get(@utf8_key).must_equal(@ascii_rabbit)
  91
+
  92
+    it "gets and sets raw values" do
  93
+      @store.set(@utf8_key, @ascii_string, :raw => true)
  94
+      @store.get(@utf8_key, :raw => true).bytes.to_a.must_equal(@ascii_string.bytes.to_a)
  95
+    end
  96
+
  97
+    it "marshals objects on setnx"
  98
+      # @store.del(@utf8_key)
  99
+      # @store.setnx(@utf8_key, @ascii_rabbit)
  100
+      # @store.get(@utf8_key).must_equal(@ascii_rabbit)
  101
+
  102
+    it "gets and sets raw values on setnx" do
  103
+      @store.del(@utf8_key)
  104
+      @store.setnx(@utf8_key, @ascii_string, :raw => true)
  105
+      @store.get(@utf8_key, :raw => true).bytes.to_a.must_equal(@ascii_string.bytes.to_a)
  106
+    end
  107
+  end if defined?(Encoding)
  108
+end
44  redis-store/test/redis/store/marshalling_test.rb → ...s-store/test/redis/store/strategy/marshal_test.rb
... ...
@@ -1,8 +1,8 @@
1 1
 require 'test_helper'
2 2
 
3  
-describe "Redis::Marshalling" do
  3
+describe "Redis::Store::Strategy::Marshal" do
4 4
   def setup
5  
-    @store = Redis::Store.new :marshalling => true
  5
+    @store = Redis::Store.new :strategy => :marshal
6 6
     @rabbit = OpenStruct.new :name => "bunny"
7 7
     @white_rabbit = OpenStruct.new :color => "white"
8 8
     @store.set "rabbit", @rabbit
@@ -32,7 +32,7 @@ def teardown
32 32
     end
33 33
   end
34 34
 
35  
-  it "doesn't marshal set if raw option is true" do
  35
+  it "doesn't marshal on set if raw option is true" do
36 36
     @store.set "rabbit", @white_rabbit, :raw => true
37 37
     @store.get("rabbit", :raw => true).must_equal(%(#<OpenStruct color="white">))
38 38
   end
@@ -90,38 +90,32 @@ def teardown
90 90
   end
91 91
 
92 92
   describe "binary safety" do
93  
-    it "marshals objects" do
94  
-      utf8_key = [51339].pack("U*")
95  
-      ascii_rabbit = OpenStruct.new(:name => [128].pack("C*"))
  93
+    before do
  94
+      @utf8_key = [51339].pack("U*")
  95
+      @ascii_string = [128].pack("C*")
  96
+      @ascii_rabbit = OpenStruct.new(:name => @ascii_string)
  97
+    end
96 98
 
97  
-      @store.set(utf8_key, ascii_rabbit)
98  
-      @store.get(utf8_key).must_equal(ascii_rabbit)
  99
+    it "marshals objects" do
  100
+      @store.set(@utf8_key, @ascii_rabbit)
  101
+      @store.get(@utf8_key).must_equal(@ascii_rabbit)
99 102
     end
100 103
 
101 104
     it "gets and sets raw values" do
102  
-      utf8_key = [51339].pack("U*")
103  
-      ascii_string = [128].pack("C*")
104  
-
105  
-      @store.set(utf8_key, ascii_string, :raw => true)
106  
-      @store.get(utf8_key, :raw => true).bytes.to_a.must_equal(ascii_string.bytes.to_a)
  105
+      @store.set(@utf8_key, @ascii_string, :raw => true)
  106
+      @store.get(@utf8_key, :raw => true).bytes.to_a.must_equal(@ascii_string.bytes.to_a)
107 107
     end
108 108
 
109 109
     it "marshals objects on setnx" do
110  
-      utf8_key = [51339].pack("U*")
111  
-      ascii_rabbit = OpenStruct.new(:name => [128].pack("C*"))
112  
-
113  
-      @store.del(utf8_key)
114  
-      @store.setnx(utf8_key, ascii_rabbit)
115  
-      @store.get(utf8_key).must_equal(ascii_rabbit)
  110
+      @store.del(@utf8_key)
  111
+      @store.setnx(@utf8_key, @ascii_rabbit)
  112
+      @store.get(@utf8_key).must_equal(@ascii_rabbit)
116 113
     end
117 114
 
118 115
     it "gets and sets raw values on setnx" do
119  
-      utf8_key = [51339].pack("U*")
120  
-      ascii_string = [128].pack("C*")
121  
-
122  
-      @store.del(utf8_key)
123  
-      @store.setnx(utf8_key, ascii_string, :raw => true)
124  
-      @store.get(utf8_key, :raw => true).bytes.to_a.must_equal(ascii_string.bytes.to_a)
  116
+      @store.del(@utf8_key)
  117
+      @store.setnx(@utf8_key, @ascii_string, :raw => true)
  118
+      @store.get(@utf8_key, :raw => true).bytes.to_a.must_equal(@ascii_string.bytes.to_a)
125 119
     end
126 120
   end if defined?(Encoding)
127 121
 end
105  redis-store/test/redis/store/strategy/yaml_test.rb
... ...
@@ -0,0 +1,105 @@
  1
+require 'test_helper'
  2
+
  3
+describe "Redis::Store::Strategy::Yaml" do
  4
+  def setup
  5
+    @store = Redis::Store.new :strategy => :yaml
  6
+    @rabbit = OpenStruct.new :name => "bunny"
  7
+    @white_rabbit = OpenStruct.new :color => "white"
  8
+    @store.set "rabbit", @rabbit
  9
+    @store.del "rabbit2"
  10
+  end
  11
+
  12
+  def teardown
  13
+    @store.quit
  14
+  end
  15
+
  16
+  # Psych::YAML had a bug in which it could not properly serialize binary Strings
  17
+  # in Ruby 1.9.3.  The issue was addressed in 1.9.3-p125.
  18
+  def self.binary_encodable_yaml?
  19
+    RUBY_VERSION != '1.9.3' or RUBY_PATCHLEVEL >= 125
  20
+  end
  21
+
  22
+  it "unmarshals on get" do
  23
+    @store.get("rabbit").must_equal(@rabbit)
  24
+  end
  25
+
  26
+  it "marshals on set" do
  27
+    @store.set "rabbit", @white_rabbit
  28
+    @store.get("rabbit").must_equal(@white_rabbit)
  29
+  end
  30
+
  31
+  it "doesn't unmarshal on get if raw option is true" do
  32
+    @store.get("rabbit", :raw => true).must_equal("--- !ruby/object:OpenStruct\ntable:\n  :name: bunny\n")
  33
+  end
  34
+
  35
+  it "doesn't marshal on set if raw option is true" do
  36
+    @store.set "rabbit", @white_rabbit, :raw => true
  37
+    @store.get("rabbit", :raw => true).must_equal(%(#<OpenStruct color=\"white\">))
  38
+  end
  39
+
  40
+  it "doesn't set an object if already exist" do
  41
+    @store.setnx "rabbit", @white_rabbit
  42
+    @store.get("rabbit").must_equal(@rabbit)
  43
+  end
  44
+
  45
+  it "marshals on set unless exists" do
  46
+    @store.setnx "rabbit2", @white_rabbit
  47
+    @store.get("rabbit2").must_equal(@white_rabbit)
  48
+  end
  49
+
  50
+  it "doesn't marshal on set unless exists if raw option is true" do
  51
+    @store.setnx "rabbit2", @white_rabbit, :raw => true
  52
+    @store.get("rabbit2", :raw => true).must_equal(%(#<OpenStruct color=\"white\">))
  53
+  end
  54
+
  55
+  it "marshals on set expire" do
  56
+    @store.setex "rabbit2", 1, @white_rabbit
  57
+    @store.get("rabbit2").must_equal(@white_rabbit)
  58
+    sleep 2
  59
+    @store.get("rabbit2").must_be_nil
  60
+  end
  61
+
  62
+  it "doesn't unmarshal on multi get" do
  63
+    @store.set "rabbit2", @white_rabbit
  64
+    rabbit, rabbit2 = @store.mget "rabbit", "rabbit2"
  65
+    rabbit.must_equal(@rabbit)
  66
+    rabbit2.must_equal(@white_rabbit)
  67
+  end
  68
+
  69
+  it "doesn't unmarshal on multi get if raw option is true" do
  70
+    @store.set "rabbit2", @white_rabbit
  71
+    rabbit, rabbit2 = @store.mget "rabbit", "rabbit2", :raw => true
  72
+    rabbit.must_equal("--- !ruby/object:OpenStruct\ntable:\n  :name: bunny\n")
  73
+    rabbit2.must_equal("--- !ruby/object:OpenStruct\ntable:\n  :color: white\n")
  74
+  end
  75
+
  76
+  describe "binary safety" do
  77
+    before do
  78
+      @utf8_key = [51339].pack("U*")
  79
+      @ascii_string = [128].pack("C*")
  80
+      @ascii_rabbit = OpenStruct.new(:name => @ascii_string)
  81
+    end
  82
+
  83
+    it "marshals objects" do
  84
+      @store.set(@utf8_key, @ascii_rabbit)
  85
+      @store.get(@utf8_key).must_equal(@ascii_rabbit)
  86
+    end
  87
+
  88
+    it "gets and sets raw values" do
  89
+      @store.set(@utf8_key, @ascii_string, :raw => true)
  90
+      @store.get(@utf8_key, :raw => true).bytes.to_a.must_equal(@ascii_string.bytes.to_a)
  91
+    end
  92
+
  93
+    it "marshals objects on setnx" do
  94
+      @store.del(@utf8_key)
  95
+      @store.setnx(@utf8_key, @ascii_rabbit)
  96
+      @store.get(@utf8_key).must_equal(@ascii_rabbit)
  97
+    end
  98
+
  99
+    it "gets and sets raw values on setnx" do
  100
+      @store.del(@utf8_key)
  101
+      @store.setnx(@utf8_key, @ascii_string, :raw => true)
  102
+      @store.get(@utf8_key, :raw => true).bytes.to_a.must_equal(@ascii_string.bytes.to_a)
  103
+    end
  104
+  end if defined?(Encoding) && binary_encodable_yaml?
  105
+end
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.