nebulous_stomp 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.hgignore +2 -0
  3. data/.hgtags +1 -0
  4. data/README.md +225 -28
  5. data/feature/connection_example.yaml +24 -0
  6. data/feature/feature_test_spec.rb +247 -0
  7. data/feature/gimme.rb +91 -0
  8. data/lib/nebulous_stomp/listener.rb +107 -0
  9. data/lib/nebulous_stomp/message.rb +132 -265
  10. data/lib/nebulous_stomp/msg/body.rb +169 -0
  11. data/lib/nebulous_stomp/msg/header.rb +98 -0
  12. data/lib/nebulous_stomp/param.rb +16 -35
  13. data/lib/nebulous_stomp/redis_handler.rb +19 -29
  14. data/lib/nebulous_stomp/redis_handler_null.rb +12 -11
  15. data/lib/nebulous_stomp/redis_helper.rb +110 -0
  16. data/lib/nebulous_stomp/request.rb +212 -0
  17. data/lib/nebulous_stomp/stomp_handler.rb +30 -96
  18. data/lib/nebulous_stomp/stomp_handler_null.rb +8 -22
  19. data/lib/nebulous_stomp/target.rb +52 -0
  20. data/lib/nebulous_stomp/version.rb +1 -1
  21. data/lib/nebulous_stomp.rb +63 -50
  22. data/md/LICENSE.txt +20 -2
  23. data/md/nebulous_protocol.md +25 -18
  24. data/spec/listener_spec.rb +104 -0
  25. data/spec/message_spec.rb +227 -116
  26. data/spec/nebulous_spec.rb +44 -9
  27. data/spec/param_spec.rb +16 -33
  28. data/spec/redis_handler_null_spec.rb +0 -2
  29. data/spec/redis_handler_spec.rb +0 -2
  30. data/spec/redis_helper_spec.rb +107 -0
  31. data/spec/request_spec.rb +249 -0
  32. data/spec/stomp_handler_null_spec.rb +33 -34
  33. data/spec/stomp_handler_spec.rb +1 -74
  34. data/spec/target_spec.rb +97 -0
  35. metadata +20 -11
  36. data/lib/nebulous_stomp/nebrequest.rb +0 -259
  37. data/lib/nebulous_stomp/nebrequest_null.rb +0 -37
  38. data/spec/nebrequest_null_spec.rb +0 -219
  39. data/spec/nebrequest_spec.rb +0 -239
  40. data/spec/through_test_spec.rb +0 -80
@@ -0,0 +1,169 @@
1
+ require 'json'
2
+
3
+ require_relative '../stomp_handler'
4
+
5
+
6
+ module NebulousStomp
7
+ module Msg
8
+
9
+
10
+ ##
11
+ # A class to encapsulate a Nebulous message body - helper class for Message
12
+ #
13
+ class Body
14
+
15
+ # Might be nil: only caught on messages that came directly from STOMP.
16
+ attr_reader :stomp_body
17
+
18
+ # The Nebulous Protocol
19
+ attr_reader :verb, :params, :desc
20
+
21
+ # Will be a hash for messages that follow The Protocol; anything at all otherwise.
22
+ attr_reader :body
23
+
24
+ ##
25
+ # is_json should be a boolean, true if the body is JSON-encoded.
26
+ # If it is false then we assume that we are coded like STOMP headers, in lines of text.
27
+ #
28
+ def initialize(is_json, hash)
29
+ @is_json = !!is_json
30
+ @stomp_body = hash[:stompBody]
31
+ @body = hash[:body]
32
+ @verb = hash[:verb]
33
+ @params = hash[:parameters] || hash[:params]
34
+ @desc = hash[:description] || hash[:desc]
35
+
36
+ fill_from_stomp
37
+ end
38
+
39
+ ##
40
+ # Output a the body part of the hash for serialization to the cache.
41
+ #
42
+ # Since the body could be quite large we only set :body if there is no @stomp_body. We
43
+ # recreate the one from the other anyway.
44
+ #
45
+ def to_h
46
+ { stompBody: @stomp_body,
47
+ body: @stomp_body ? nil : body,
48
+ verb: @verb,
49
+ params: @params.kind_of?(Enumerable) ? @params.dup : @params,
50
+ desc: @desc }
51
+
52
+ end
53
+
54
+ ##
55
+ # Return a body object for the Stomp gem
56
+ #
57
+ def body_for_stomp
58
+
59
+ case
60
+ when @is_json
61
+ @body.to_json
62
+ when @body.is_a?(Hash)
63
+ @body.map{|k,v| "#{k}: #{v}" }.join("\n") << "\n\n"
64
+ else
65
+ @body.to_s
66
+ end
67
+
68
+ end
69
+
70
+ ##
71
+ # Return the message body formatted for The Protocol, in JSON.
72
+ #
73
+ # Raise an exception if the message body doesn't follow the protocol.
74
+ #
75
+ # (We use this as the key for the Redis cache)
76
+ #
77
+ def protocol_json
78
+ fail NebulousError, "no protocol in this message!" unless @verb
79
+ protocol_hash.to_json
80
+ end
81
+
82
+ private
83
+
84
+ ##
85
+ # Return The Protocol of the message as a hash.
86
+ #
87
+ def protocol_hash
88
+ { verb: @verb,
89
+ parameters: @params,
90
+ description: @desc }.delete_if{|_,v| v.nil? }
91
+
92
+ end
93
+
94
+ ##
95
+ # Fill all the other attributes, if we can.
96
+ #
97
+ # Note that we confusingly store @verb, @params, & @desc (The Protocol, which if present is
98
+ # the message body); @body (the message body); and @stomp_body (the original body from the
99
+ # stomp message if we were created from one). Any of these could be passed in the initialize
100
+ # hash.
101
+ #
102
+ # The rule is that we always prioritize them in that order. If we are passed a @verb, then
103
+ # the Protocol fields go into @body; otherwise if set we take @body as it stands; otherwise
104
+ # we try and decode @stomp_body and set that in @body.
105
+ #
106
+ # NB: We never overwrite @stomp_body. If not passed to us, it stays nil. It's only stored in
107
+ # case we can't decode it, as a fallback.
108
+ #
109
+ def fill_from_stomp
110
+
111
+ if @verb
112
+ @body = protocol_hash
113
+ elsif @body.nil? || @body.respond_to?(:empty?) && @body.empty?
114
+ @body = parse_stomp_body
115
+ end
116
+
117
+ parse_body
118
+
119
+ # Assume that if verb is missing, the other two are just part of a
120
+ # response which has nothing to do with the protocol
121
+ @params = @desc = nil unless @verb
122
+
123
+ self
124
+ end
125
+
126
+ def parse_stomp_body
127
+ case
128
+ when @stomp_body.nil? then nil
129
+ when @is_json then stomp_body_from_json
130
+ else
131
+ stomp_body_from_text
132
+
133
+ end
134
+ end
135
+
136
+ def stomp_body_from_json
137
+ JSON.parse(@stomp_body)
138
+ rescue JSON::ParserError, TypeError
139
+ # If we can't parse it, fine, take it as a text blob
140
+ @stomp_body.to_s
141
+ end
142
+
143
+ def stomp_body_from_text
144
+ lines = @stomp_body.to_s.split("\n").reject{|s| s =~ /^\s*$/ }
145
+ hash = {}
146
+
147
+ lines.each do |line|
148
+ k,v = line.split(':', 2).each{|x| x.strip! }
149
+ hash[k] = v if line.include?(':')
150
+ end
151
+
152
+ # If there are any lines we could not parse, forget the whole thing and return a text blob
153
+ (lines.size > hash.keys.size) ? @stomp_body.to_s : hash
154
+ end
155
+
156
+ def parse_body
157
+ if @body.is_a?(Hash)
158
+ @verb ||= @body["verb"]
159
+ @params ||= @body["parameters"] || @body["params"]
160
+ @desc ||= @body["description"] || @body["desc"]
161
+ end
162
+ end
163
+
164
+
165
+ end # Message::Body
166
+
167
+
168
+ end
169
+ end
@@ -0,0 +1,98 @@
1
+ require 'json'
2
+
3
+ require_relative '../stomp_handler'
4
+
5
+
6
+ module NebulousStomp
7
+ module Msg
8
+
9
+
10
+ ##
11
+ # A class to encapsulate a Nebulous message header - a helper class for Message
12
+ #
13
+ class Header
14
+
15
+ # Might be nil: parsed from incoming messages; set by StompHandler on send
16
+ attr_accessor :reply_id
17
+
18
+ # Might be nil: only caught on messages that came directly from STOMP
19
+ attr_reader :stomp_headers
20
+
21
+ # The content type of the message
22
+ attr_reader :content_type
23
+
24
+ # From The Nebulous Protocol
25
+ attr_reader :reply_to, :in_reply_to
26
+
27
+ ##
28
+ #
29
+ def initialize(hash)
30
+ @stomp_headers = hash[:stompHeaders]
31
+ @reply_to = hash[:replyTo]
32
+ @reply_id = hash[:replyId]
33
+ @in_reply_to = hash[:inReplyTo]
34
+ @content_type = hash[:contentType]
35
+
36
+ # If we have no stomp headers then we (probably correctly) assume that this is a user
37
+ # created message, and default the content type to JSON.
38
+ @content_type = 'application/json' if @stomp_headers.nil? && @content_type.nil?
39
+
40
+ fill_from_stomp
41
+ end
42
+
43
+ ##
44
+ # true if the content type appears to be JSON-y
45
+ #
46
+ def content_is_json?
47
+ @content_type =~ /json$/i ? true : false
48
+ end
49
+
50
+ ##
51
+ # Output a the header part of the hash for serialization to the cache.
52
+ #
53
+ def to_h
54
+ { stompHeaders: @stomp_headers,
55
+ replyTo: @reply_to,
56
+ replyId: @reply_id,
57
+ inReplyTo: @in_reply_to,
58
+ contentType: @content_type }
59
+
60
+ end
61
+
62
+ ##
63
+ # Return the hash of additional headers for the Stomp gem
64
+ #
65
+ def headers_for_stomp
66
+ { "content-type" => @content_type,
67
+ "neb-reply-id" => @reply_id,
68
+ "neb-reply-to" => @reply_to,
69
+ "neb-in-reply-to" => @in_reply_to }
70
+
71
+ end
72
+
73
+ private
74
+
75
+ ##
76
+ # Fill all the other attributes, if you can, from @stomp_headers
77
+ #
78
+ def fill_from_stomp
79
+ return unless @stomp_headers
80
+
81
+ type = @stomp_headers["content-type"] || @stomp_headers[:'content-type'] \
82
+ || @stomp_headers["content_type"] || @stomp_headers[:content_type] \
83
+ || @stomp_headers["contentType"] || @stomp_headers[:contentType]
84
+
85
+ @content_type ||= type
86
+ @reply_id ||= @stomp_headers['neb-reply-id']
87
+ @reply_to ||= @stomp_headers['neb-reply-to']
88
+ @in_reply_to ||= @stomp_headers['neb-in-reply-to']
89
+
90
+ self
91
+ end
92
+
93
+ end # of Header
94
+
95
+
96
+ end
97
+ end
98
+
@@ -1,3 +1,6 @@
1
+ require_relative 'target'
2
+
3
+
1
4
  module NebulousStomp
2
5
 
3
6
 
@@ -7,7 +10,6 @@ module NebulousStomp
7
10
  module Param
8
11
  extend self
9
12
 
10
-
11
13
  # Default parameters hash
12
14
  ParamDefaults = { stompConnectHash: {},
13
15
  redisConnectHash: {},
@@ -21,66 +23,50 @@ module NebulousStomp
21
23
  receiveQueue: nil,
22
24
  messageTimeout: nil }
23
25
 
24
-
25
26
  ##
26
- # Set the initial parameter string. This also has the effect of resetting
27
- # everything.
27
+ # Set the initial parameter string. This also has the effect of resetting everything.
28
28
  #
29
- # Parameters default to Param::ParamDefaults. keys passed in parameter p to
30
- # ovveride those defaults must match, or a NebulousError will result.
29
+ # Parameters default to Param::ParamDefaults. keys passed in parameter p to override those
30
+ # defaults must match, or a NebulousError will result.
31
31
  #
32
32
  # This method is only called by Nebulous::init().
33
33
  #
34
34
  def set(p={})
35
- raise NebulousError, "Invalid initialisation hash" unless p.kind_of?(Hash)
35
+ fail NebulousError, "Invalid initialisation hash" unless p.kind_of?(Hash)
36
36
 
37
37
  validate(ParamDefaults, p, "Unknown initialisation hash")
38
38
 
39
39
  @params = ParamDefaults.merge(p)
40
40
  end
41
41
 
42
-
43
42
  ##
44
43
  # Add a Nebulous target. Raises NebulousError if anything looks screwy.
45
44
  #
46
45
  # Parameters:
47
- # n -- target name
48
- # t -- hash, which must follow the pattern set by Param::TargetDefaults
46
+ # * t -- a Target
49
47
  #
50
48
  # Used only by Nebulous::init
51
49
  #
52
- def add_target(n, t)
53
- raise NebulousError, "Invalid target hash" unless t.kind_of?(Hash)
54
-
55
- validate(TargetDefaults, t, "Unknown target hash")
56
-
57
- raise NebulousError, "Config Problem - Target missing 'send'" \
58
- if t[:sendQueue].nil?
59
-
60
- raise NebulousError, "Config Problem - Target missing 'receive'" \
61
- if t[:receiveQueue].nil?
50
+ def add_target(t)
51
+ fail NebulousError, "Invalid target" unless t.kind_of?(Target)
62
52
 
63
53
  @params ||= ParamDefaults
64
- @params[:targets][n.to_sym] = TargetDefaults.merge(t)
54
+ @params[:targets][t.name.to_sym] = t
65
55
  end
66
56
 
67
-
68
-
69
57
  ##
70
58
  # Set a logger instance
71
59
  #
72
60
  def set_logger(lg)
73
- raise NebulousError unless lg.kind_of?(Logger) || lg.nil?
61
+ fail NebulousError unless lg.kind_of?(Logger) || lg.nil?
74
62
  @logger = lg
75
63
  end
76
64
 
77
-
78
65
  ##
79
66
  # Get the logger instance
80
67
  #
81
68
  def get_logger; @logger; end
82
69
 
83
-
84
70
  ##
85
71
  # Get the whole parameter hash. Probably only useful for testing.
86
72
  #
@@ -88,7 +74,6 @@ module NebulousStomp
88
74
  @params
89
75
  end
90
76
 
91
-
92
77
  ##
93
78
  # Get a the value of the parameter with the key p.
94
79
  #
@@ -97,18 +82,15 @@ module NebulousStomp
97
82
  @params[p.to_sym]
98
83
  end
99
84
 
100
-
101
85
  ##
102
- # Given a target name, return the corresponding target hash
86
+ # Given a target name, return the corresponding Target object
103
87
  #
104
88
  def get_target(name)
105
89
  t = Param.get(:targets)
106
- x = (t && t.kind_of?(Hash)) ? t[name.to_sym] : nil
107
-
108
- raise NebulousError, "Config problem - unknown target #{name}" if x.nil?
109
- return x
90
+ (t && t.kind_of?(Hash)) ? t[name.to_s.to_sym] : nil
110
91
  end
111
92
 
93
+ private
112
94
 
113
95
  ##
114
96
  # Raise an exception if a hash has any keys not found in an exemplar
@@ -117,11 +99,10 @@ module NebulousStomp
117
99
  #
118
100
  def validate(exemplar, hash, message)
119
101
  hash.each_key do |k|
120
- raise NebulousError, "#{message} key '#{k}'" unless exemplar.include?(k)
102
+ fail NebulousError, "#{message} key '#{k}'" unless exemplar.include?(k)
121
103
  end
122
104
  end
123
105
 
124
-
125
106
  ##
126
107
  # reset all parameters -- probably only useful for testing
127
108
  #
@@ -6,48 +6,44 @@ module NebulousStomp
6
6
 
7
7
  ##
8
8
  # A class to deal with talking to Redis via the Redis gem
9
+ #
10
+ # You shouldn't need to instantiate this yourself. If you want to use NebulousStomp to talk to
11
+ # redis directly, try NebulousStomp::RedisHelper.
9
12
  #
10
13
  class RedisHandler
11
14
 
12
- # This is most likely useless to anything except spec/redis_handler_spec --
13
- # at least, I hope so...
15
+ # This is most likely useless to anything except spec/redis_handler_spec -- at least, I hope
16
+ # so...
14
17
  attr_reader :redis
15
18
 
16
-
17
19
  ##
18
20
  # Initialise an instance of the handler by passing it the connection hash
19
21
  #
20
22
  # If no hash is passed, we try and get it from Nebulous::Params
21
23
  #
22
- # We use the optional testRedis parameter to mock connections to Redis, for
23
- # testing. It's probably of no use to anyone else.
24
+ # We use the optional testRedis parameter to mock connections to Redis, for testing. It's
25
+ # probably of no use to anyone else.
24
26
  #
25
27
  def initialize(connectHash=nil, testRedis=nil)
26
- @redis_hash = connectHash ? connectHash.dup : nil
27
- @redis_hash ||= Param.get(:redisConnectHash)
28
-
28
+ @redis_hash = connectHash ? connectHash.dup : nil
29
29
  @test_redis = testRedis
30
30
  @redis = nil
31
31
  end
32
32
 
33
-
34
33
  ##
35
- # Connect to the Redis key/value store. Raise Nebulous error if connection
36
- # fails.
34
+ # Connect to the Redis key/value store. Raise Nebulous error if connection fails.
37
35
  #
38
36
  def connect
39
37
  @redis = @test_redis || Redis.new(@redis_hash.dup)
40
38
 
41
39
  @redis.client.connect
42
- raise ConnectionError, "Redis Connection failed" unless @redis.connected?
40
+ fail ConnectionError, "Redis Connection failed" unless @redis.connected?
43
41
 
44
42
  self
45
-
46
43
  rescue => err
47
44
  raise ConnectionError, err.to_s
48
45
  end
49
46
 
50
-
51
47
  ##
52
48
  # :call-seq:
53
49
  # handler.connected? -> (boolean)
@@ -60,7 +56,6 @@ module NebulousStomp
60
56
  self
61
57
  end
62
58
 
63
-
64
59
  ##
65
60
  # return whether we are connected to Redis.
66
61
  #
@@ -68,38 +63,33 @@ module NebulousStomp
68
63
  @redis && @redis.connected?
69
64
  end
70
65
 
71
-
72
66
  ##
73
67
  # :call-seq:
74
68
  # handler.redis_on? -> (boolean)
75
69
  #
76
- # Return whether the Redis is turned "on" in the connect hash, the config
77
- # file.
70
+ # Return whether the Redis is turned "on" in the connect hash, the config file.
78
71
  #
79
- # The rest of nebulous should just let RedisHandler worry about this
80
- # detail.
72
+ # The rest of nebulous should just let RedisHandler worry about this detail.
81
73
  #
82
74
  def redis_on?
83
75
  @redis_hash && !@redis_hash.empty?
84
76
  end
85
77
 
86
-
87
-
88
78
  ##
89
- # Cover all the other methods on @redis that we are basically forwarding to
90
- # it. I could use Forwardable here -- except that would not allow us to
91
- # raise Nebulous::ConnectionError if @redis.nil?
79
+ # Cover all the other methods on @redis that we are basically forwarding to it. I could use
80
+ # Forwardable here -- except that would not allow us to raise Nebulous::ConnectionError if
81
+ # @redis.nil?
82
+ #
83
+ # Possible candidates for future inclusion: exists, expire, ping
92
84
  #
93
85
  def method_missing(meth, *args)
94
86
  super unless [:set,:get,:del].include?(meth)
95
-
96
- raise ConnectionError, "Redis not connected, sent #{meth}" \
97
- unless connected?
87
+ fail ConnectionError, "Redis not connected, sent #{meth}" unless connected?
98
88
 
99
89
  @redis.__send__(meth, *args)
100
90
  end
101
91
 
102
- end
92
+ end # RedisHandler
103
93
 
104
94
 
105
95
  end
@@ -6,7 +6,6 @@ require_relative 'redis_handler'
6
6
 
7
7
  module NebulousStomp
8
8
 
9
-
10
9
  ##
11
10
  # Behaves just like RedisHandler, except, does nothing and expects no
12
11
  # connection to Redis.
@@ -17,41 +16,43 @@ module NebulousStomp
17
16
 
18
17
  attr_reader :fake_pair
19
18
 
20
-
21
19
  def initialize(connectHash={})
22
20
  super
23
21
  @fake_pair = {}
24
22
  end
25
23
 
26
-
27
24
  def insert_fake(key, value)
28
25
  @fake_pair = { key => value }
29
26
  end
30
27
 
31
-
32
28
  def connect
33
29
  @redis = true
34
30
  self
35
31
  end
36
32
 
37
-
38
33
  def quit
39
34
  @redis = nil
40
35
  self
41
36
  end
42
-
43
37
 
44
38
  def connected?
45
39
  @fake_pair != {}
46
40
  end
47
41
 
42
+ def set(key, value, hash=nil)
43
+ insert_fake(key, value)
44
+ "OK"
45
+ end
48
46
 
49
- def set(key, value, hash=nil); insert_fake(key, value); end
50
-
51
- def del(key); @fake_pair = {}; end
52
-
53
- def get(key); @fake_pair.values.first; end
47
+ def del(key)
48
+ x = @fake_pair.empty? ? 0 : 1
49
+ @fake_pair = {}
50
+ x
51
+ end
54
52
 
53
+ def get(key)
54
+ @fake_pair.values.first
55
+ end
55
56
 
56
57
  end
57
58
 
@@ -0,0 +1,110 @@
1
+ require 'json'
2
+
3
+ require_relative '../nebulous_stomp'
4
+
5
+
6
+ module NebulousStomp
7
+
8
+
9
+ ##
10
+ # A class to help out users who want to talk to Redis themselves; the "Redis use case".
11
+ #
12
+ # redis = NebulousStomp::RedisHelper.new
13
+ #
14
+ # redis.set(:thing, "thingy")
15
+ # redes.set(:gone_in_30_seconds, "thingy", 30)
16
+ #
17
+ # value = redis.get(:thing)
18
+ #
19
+ # redis.del(:thing)
20
+ #
21
+ class RedisHelper
22
+
23
+ # For testing only
24
+ attr_writer :redis_handler
25
+
26
+ ##
27
+ # :call-seq:
28
+ # RedisHelper.new -> (helper)
29
+ #
30
+ def initialize
31
+ @param_hash = Param.get(:redisConnectHash)
32
+
33
+ fail NebulousError, "NebulousStomp.init has not been called or Redis not configured" \
34
+ if @param_hash.nil? || @param_hash.empty?
35
+
36
+ end
37
+
38
+ ##
39
+ # :call-seq:
40
+ # redis.set(key, value)
41
+ # redis.set(key, value, timeout)
42
+ #
43
+ # Set a value in the store.
44
+ #
45
+ def set(key, value, timeout=nil)
46
+ rtimeout = (Integer(timeout.to_s, 10) rescue nil)
47
+ rvalue = value_to_json(value)
48
+ fail ArgumentError, "Timeout must be a number" if timeout && rtimeout.nil?
49
+ ensure_connected
50
+
51
+ if timeout
52
+ redis_handler.set(key.to_s, rvalue, ex: rtimeout)
53
+ else
54
+ redis_handler.set(key.to_s, rvalue)
55
+ end
56
+
57
+ self
58
+ end
59
+
60
+ ##
61
+ # :call-seq:
62
+ # redis.get(key) -> value
63
+ #
64
+ # Get a string value from the store. Return nil if there is none.
65
+ #
66
+ def get(key)
67
+ ensure_connected
68
+ json_to_value(redis_handler.get key.to_s)
69
+ end
70
+
71
+ ##
72
+ # :call-seq:
73
+ # redis.del(key)
74
+ #
75
+ # Remove a value from the store. Raise an exception if there is none.
76
+ #
77
+ def del(key)
78
+ ensure_connected
79
+ num = redis_handler.del(key.to_s)
80
+ fail ArgumentError, "Unknown key, cannot delete" if num == 0
81
+ end
82
+
83
+ private
84
+
85
+ def redis_handler
86
+ @redis_handler ||= RedisHandler.new(Param.get :redisConnectHash)
87
+ end
88
+
89
+ def ensure_connected
90
+ redis_handler.connect unless redis_handler.connected?
91
+ end
92
+
93
+ def value_to_json(value)
94
+ { value: value}.to_json
95
+ end
96
+
97
+ def json_to_value(json)
98
+ return nil if json.nil?
99
+ hash = JSON.parse(json, symbolize_names: true)
100
+
101
+ hash.is_a?(Hash) ? hash[:value] : nil
102
+ rescue JSON::ParserError
103
+ return nil
104
+ end
105
+
106
+ end # RedisHelper
107
+
108
+
109
+ end
110
+