restruct 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 348743cb28cb1159c9d59162d3f39233424afc66
4
- data.tar.gz: ec7f662e9b67b0762f0cd5dd7f5314e37cafecca
3
+ metadata.gz: 220d7c61ca9fe2daff93071982a18b7934967c71
4
+ data.tar.gz: bc038fb7d2126b6d30d770ab1d28687b031111a0
5
5
  SHA512:
6
- metadata.gz: be6d4f14461f91b75265eef5a327709c192f966d26a56dec4ff83ec0c8563a7ad5070653a240c8e96f2c8fb5222d3473d83aae10eee72617c795cc9fb7577cd7
7
- data.tar.gz: 3543ef892445534d84dee4aca23430a7a7c0804dbeb45c7a681018152715ae14b29e3cefce3fdb71a7416628b95b7f25f628e75ee19cb9a2f55ae95a17b338a8
6
+ metadata.gz: 3357b7800bb6da26bdb54675d097300cd9f33b652f63ad7c5e9d1a0a71f4038459f20e8bade86f12d2b6562f57e08af5ed74400139307cbaea20d35fa5b2273f
7
+ data.tar.gz: 5740e1bc9699cef63a9c5632c2c6198a48164b99aaf78e3b60bef61983352b5fcc6d48ce3a8b8524af50b673a905caa65c1655e97c3bc4500ee9e0e21fc5b4f5
@@ -1 +1 @@
1
- ruby 2.1
1
+ ruby 2.0
@@ -4,23 +4,28 @@ require 'forwardable'
4
4
  require 'securerandom'
5
5
 
6
6
  require_relative 'restruct/version'
7
+ require_relative 'restruct/errors'
7
8
  require_relative 'restruct/structure'
8
9
  require_relative 'restruct/id'
9
10
  require_relative 'restruct/array'
10
11
  require_relative 'restruct/set'
11
12
  require_relative 'restruct/hash'
13
+ require_relative 'restruct/queue'
12
14
  require_relative 'restruct/nested_hash'
13
15
  require_relative 'restruct/marshalizable'
14
16
  require_relative 'restruct/marshal_array'
15
17
  require_relative 'restruct/marshal_set'
16
18
  require_relative 'restruct/marshal_hash'
17
- require_relative 'restruct/batch'
19
+ require_relative 'restruct/marshal_queue'
20
+ require_relative 'restruct/locker'
21
+ require_relative 'restruct/connection'
22
+
18
23
 
19
24
  module Restruct
20
25
 
21
26
  extend ClassConfig
22
27
 
23
- attr_config :redis, Redic.new
28
+ attr_config :connection, Connection.new
24
29
  attr_config :id_separator, ':'
25
30
  attr_config :id_generator, ->() { Id.new(:restruct)[SecureRandom.uuid] }
26
31
 
@@ -7,7 +7,7 @@ module Restruct
7
7
  def_delegators :to_a, :uniq, :join, :reverse, :+, :-, :&, :|
8
8
 
9
9
  def at(index)
10
- deserialize redis.call('LINDEX', id, index)
10
+ deserialize connection.call('LINDEX', id, index)
11
11
  end
12
12
 
13
13
  def values_at(*args)
@@ -48,11 +48,11 @@ module Restruct
48
48
  validate_index_type! index
49
49
  validate_index_bounds! index
50
50
 
51
- redis.call 'LSET', id, index, serialize(element)
51
+ connection.lazy 'LSET', id, index, serialize(element)
52
52
  end
53
53
 
54
54
  def push(*elements)
55
- redis.call 'RPUSH', id, *elements.map { |e| serialize e }
55
+ connection.lazy 'RPUSH', id, *elements.map { |e| serialize e }
56
56
  self
57
57
  end
58
58
  alias_method :<<, :push
@@ -70,7 +70,7 @@ module Restruct
70
70
 
71
71
  def pop(count=1)
72
72
  if count == 1
73
- deserialize redis.call('RPOP', id)
73
+ deserialize connection.lazy('RPOP', id)
74
74
  else
75
75
  [count, size].min.times.map { pop }.reverse
76
76
  end
@@ -78,15 +78,15 @@ module Restruct
78
78
 
79
79
  def shift(count=1)
80
80
  if count == 1
81
- deserialize redis.call('LPOP', id)
81
+ deserialize connection.lazy('LPOP', id)
82
82
  else
83
83
  [count, size].min.times.map { shift }
84
84
  end
85
85
  end
86
86
 
87
87
  def delete(element)
88
- removed_count = redis.call 'LREM', id, 0, serialize(element)
89
- removed_count > 0 ? element : nil
88
+ removed_count = connection.lazy 'LREM', id, 0, serialize(element)
89
+ removed_count && removed_count > 0 ? element : nil
90
90
  end
91
91
 
92
92
  def delete_at(index)
@@ -117,7 +117,7 @@ module Restruct
117
117
  end
118
118
 
119
119
  def size
120
- redis.call 'LLEN', id
120
+ connection.call 'LLEN', id
121
121
  end
122
122
  alias_method :count, :size
123
123
  alias_method :length, :size
@@ -161,7 +161,7 @@ module Restruct
161
161
 
162
162
  def range(start, stop)
163
163
  return nil if start > size
164
- redis.call('LRANGE', id, start, stop).map { |e| deserialize e }
164
+ connection.call('LRANGE', id, start, stop).map { |e| deserialize e }
165
165
  end
166
166
 
167
167
  def validate_index_type!(index)
@@ -0,0 +1,71 @@
1
+ module Restruct
2
+ class Connection
3
+
4
+ def initialize(*args)
5
+ @redis = Redic.new *args
6
+ @scripts = {}
7
+ @nesting = ::Hash.new { |h,k| h[k] = 0 }
8
+ end
9
+
10
+ def call(*args)
11
+ raise ArgumentError if args.empty?
12
+ redis.call! *args
13
+ rescue RuntimeError => ex
14
+ raise ConnectionErrorFactory.create(ex)
15
+ end
16
+
17
+ def lazy(*args)
18
+ if nested?
19
+ redis.queue *args
20
+ nil
21
+ else
22
+ call *args
23
+ end
24
+ end
25
+
26
+ def script(lua_src, *args)
27
+ scripts[lua_src] ||= call 'SCRIPT', 'LOAD', lua_src
28
+ call 'EVALSHA', scripts[lua_src], *args
29
+ rescue NoScriptError
30
+ scripts.delete lua_src
31
+ retry
32
+ end
33
+
34
+ def batch
35
+ incr_nesting
36
+ begin
37
+ result = yield
38
+ ensure
39
+ decr_nesting
40
+ end
41
+ commit unless nested?
42
+ rescue => ex
43
+ redis.clear unless nested?
44
+ raise ex
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :redis, :scripts
50
+
51
+ def nested?
52
+ @nesting[Thread.current.object_id] > 0
53
+ end
54
+
55
+ def incr_nesting
56
+ @nesting[Thread.current.object_id] += 1
57
+ end
58
+
59
+ def decr_nesting
60
+ @nesting[Thread.current.object_id] -= 1
61
+ end
62
+
63
+ def commit
64
+ results = redis.commit
65
+ error = results.detect { |r| r === RuntimeError }
66
+ raise ConnectionErrorFactory.create(error) if error
67
+ results
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,54 @@
1
+ module Restruct
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ConnectionErrorFactory
6
+ def self.create(exception)
7
+ error_type = NoScriptError.match?(exception) ? NoScriptError : ConnectionError
8
+ error_type.new exception
9
+ end
10
+ end
11
+
12
+ class ConnectionError < Error
13
+
14
+ def initialize(inner_exception)
15
+ @inner_exception = inner_exception
16
+ end
17
+
18
+ def message
19
+ @inner_exception.message
20
+ end
21
+
22
+ def backtrace
23
+ @inner_exception.backtrace
24
+ end
25
+
26
+ end
27
+
28
+ class NoScriptError < ConnectionError
29
+ def self.match?(exception)
30
+ exception.message.start_with? 'NOSCRIPT No matching script'
31
+ end
32
+ end
33
+
34
+ class LockerError < Error
35
+
36
+ def initialize(inner_exception)
37
+ @inner_exception = inner_exception
38
+ end
39
+
40
+ def message
41
+ @parsed_message ||= parse_message @inner_exception.message
42
+ end
43
+
44
+ def backtrace
45
+ @inner_exception.backtrace
46
+ end
47
+
48
+ def parse_message(message)
49
+ match_data = /^(ERR Error running script.*@user_script.*user_script.*: )/.match message
50
+ match_data ? message.gsub(match_data.captures.first,'').strip : message
51
+ end
52
+ end
53
+
54
+ end
@@ -7,7 +7,7 @@ module Restruct
7
7
  def_delegators :to_h, :merge, :flatten, :invert
8
8
 
9
9
  def [](key)
10
- deserialize redis.call('HGET', id, key)
10
+ deserialize connection.call('HGET', id, key)
11
11
  end
12
12
 
13
13
  def fetch(key, default=nil, &block)
@@ -25,7 +25,7 @@ module Restruct
25
25
  end
26
26
 
27
27
  def store(key, value)
28
- redis.call 'HSET', id, key, serialize(value)
28
+ connection.lazy 'HSET', id, key, serialize(value)
29
29
  value
30
30
  end
31
31
  alias_method :[]=, :store
@@ -38,7 +38,7 @@ module Restruct
38
38
 
39
39
  def delete(key)
40
40
  value = self[key]
41
- redis.call 'HDEL', id, key
41
+ connection.lazy 'HDEL', id, key
42
42
  value
43
43
  end
44
44
 
@@ -59,11 +59,11 @@ module Restruct
59
59
  end
60
60
 
61
61
  def keys
62
- redis.call 'HKEYS', id
62
+ connection.call 'HKEYS', id
63
63
  end
64
64
 
65
65
  def values
66
- redis.call('HVALS', id).map { |v| deserialize v }
66
+ connection.call('HVALS', id).map { |v| deserialize v }
67
67
  end
68
68
 
69
69
  def values_at(*keys)
@@ -71,7 +71,7 @@ module Restruct
71
71
  end
72
72
 
73
73
  def key?(key)
74
- redis.call('HEXISTS', id, key) == 1
74
+ connection.call('HEXISTS', id, key) == 1
75
75
  end
76
76
  alias_method :has_key?, :key?
77
77
 
@@ -81,7 +81,7 @@ module Restruct
81
81
  alias_method :has_value?, :value?
82
82
 
83
83
  def size
84
- redis.call 'HLEN', id
84
+ connection.call 'HLEN', id
85
85
  end
86
86
  alias_method :count, :size
87
87
  alias_method :length, :size
@@ -104,7 +104,7 @@ module Restruct
104
104
  end
105
105
 
106
106
  def to_h
107
- redis.call('HGETALL', id).each_slice(2).each_with_object({}) do |(k,v), hash|
107
+ connection.call('HGETALL', id).each_slice(2).each_with_object({}) do |(k,v), hash|
108
108
  hash[k] = deserialize v
109
109
  end
110
110
  end
@@ -0,0 +1,43 @@
1
+ module Restruct
2
+ class Locker < Structure
3
+
4
+ REGISTER_LUA = File.read "#{File.dirname(__FILE__)}/../../lua/register.lua"
5
+ UNREGISTER_LUA = File.read "#{File.dirname(__FILE__)}/../../lua/unregister.lua"
6
+
7
+ def lock(key, &block)
8
+ _lock key, false, &block
9
+ end
10
+
11
+ def lock!(key, &block)
12
+ _lock key, true, &block
13
+ end
14
+
15
+ alias_method :unlock!, :destroy
16
+
17
+ alias_method :locked?, :exists?
18
+
19
+ def key
20
+ connection.call('HGET', id, 'key')
21
+ end
22
+ alias_method :locked_by, :key
23
+
24
+ def to_h
25
+ ::Hash[connection.call('HGETALL', id).each_slice(2).to_a]
26
+ end
27
+ alias_method :to_primitive, :to_h
28
+
29
+ private
30
+
31
+ def _lock(key, exclusive)
32
+ connection.script REGISTER_LUA, 0, id, key, exclusive
33
+ begin
34
+ yield
35
+ ensure
36
+ connection.script UNREGISTER_LUA, 0, id
37
+ end
38
+ rescue Restruct::ConnectionError => ex
39
+ raise LockerError.new ex
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ module Restruct
2
+ class MarshalQueue < Queue
3
+ include Marshalizable
4
+ end
5
+ end
@@ -9,7 +9,7 @@ module Restruct
9
9
  const_set :TYPE, type
10
10
 
11
11
  def [](key)
12
- self.class::TYPE.new id: id[key], redis: redis, parent: self
12
+ self.class::TYPE.new id: id[key], connection: connection, parent: self
13
13
  end
14
14
 
15
15
  def fetch(key)
@@ -39,7 +39,7 @@ module Restruct
39
39
 
40
40
  def keys
41
41
  sections = id.sections.count + 1
42
- redis.call('KEYS', id['*']).map do |k|
42
+ connection.call('KEYS', id['*']).map do |k|
43
43
  Id.new(k).sections.take(sections).last
44
44
  end.uniq.sort
45
45
  end
@@ -0,0 +1,38 @@
1
+ module Restruct
2
+ class Queue < Structure
3
+
4
+ def push(object)
5
+ connection.lazy 'RPUSH', id, serialize(object)
6
+ end
7
+
8
+ def pop
9
+ deserialize connection.lazy('LPOP', id)
10
+ end
11
+
12
+ def size
13
+ connection.call 'LLEN', id
14
+ end
15
+ alias_method :count, :size
16
+ alias_method :length, :size
17
+
18
+ def empty?
19
+ size == 0
20
+ end
21
+
22
+ def to_a
23
+ connection.call('LRANGE', id, 0, -1).map { |o| deserialize o }
24
+ end
25
+ alias_method :to_primitive, :to_a
26
+
27
+ private
28
+
29
+ def serialize(string)
30
+ string
31
+ end
32
+
33
+ def deserialize(string)
34
+ string
35
+ end
36
+
37
+ end
38
+ end
@@ -57,7 +57,7 @@ module Restruct
57
57
  end
58
58
 
59
59
  def size
60
- redis.call 'SCARD', id
60
+ connection.call 'SCARD', id
61
61
  end
62
62
  alias_method :count, :size
63
63
  alias_method :length, :size
@@ -67,7 +67,7 @@ module Restruct
67
67
  end
68
68
 
69
69
  def include?(member)
70
- redis.call('SISMEMBER', id, serialize(member)) == 1
70
+ connection.call('SISMEMBER', id, serialize(member)) == 1
71
71
  end
72
72
 
73
73
  def each(&block)
@@ -75,7 +75,7 @@ module Restruct
75
75
  end
76
76
 
77
77
  def to_a
78
- redis.call('SMEMBERS', id).map { |e| deserialize e }
78
+ connection.call('SMEMBERS', id).map { |e| deserialize e }
79
79
  end
80
80
 
81
81
  def to_set
@@ -99,11 +99,11 @@ module Restruct
99
99
  private
100
100
 
101
101
  def _add(*members)
102
- redis.call 'SADD', id, *members.map { |m| serialize m }
102
+ connection.lazy 'SADD', id, *members.map { |m| serialize m }
103
103
  end
104
104
 
105
105
  def _delete(*members)
106
- redis.call 'SREM', id, *members.map { |m| serialize m }
106
+ connection.lazy 'SREM', id, *members.map { |m| serialize m }
107
107
  end
108
108
 
109
109