sideroo 1.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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sideroo"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,32 @@
1
+ require "sideroo/version"
2
+ require "sideroo/enumerator"
3
+ require "sideroo/key_builder"
4
+ require "sideroo/types/base"
5
+ require "sideroo/types/bitmap"
6
+ require "sideroo/types/hash"
7
+ require "sideroo/types/hyper_log_log"
8
+ require "sideroo/types/list"
9
+ require "sideroo/types/set"
10
+ require "sideroo/types/sorted_set"
11
+ require "sideroo/types/string"
12
+
13
+ module Sideroo
14
+ class Error < StandardError; end
15
+ class MissingKeys < ArgumentError; end
16
+ class UnexpectedKeys < ArgumentError; end
17
+
18
+ class Configuration
19
+ attr_accessor :redis_client
20
+ end
21
+
22
+ class << self
23
+ def configure
24
+ @config = Configuration.new
25
+ yield(@config)
26
+ end
27
+
28
+ def redis_client
29
+ @config.redis_client
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,83 @@
1
+ module Sideroo
2
+ class Enumerator
3
+ attr_reader :type_klass
4
+ attr_reader :limit
5
+ attr_reader :redis_client
6
+
7
+ attr_reader :filters
8
+ attr_reader :search_pattern
9
+
10
+ def initialize(type_klass:, filters:, limit: -1)
11
+ @type_klass = type_klass
12
+ @limit = limit
13
+ @redis_client = redis_client || Sideroo.redis_client
14
+ @filters = stringify_keys(filters)
15
+
16
+ @search_pattern = build_search_pattern
17
+ end
18
+
19
+ def each
20
+ cursor = nil
21
+ count = 0
22
+
23
+ until cursor.to_s == '0'
24
+ cursor ||= 0
25
+ cursor, keys = redis_client.scan(cursor, match: search_pattern)
26
+
27
+ keys.each do |key|
28
+ break if exceed_limit?(count)
29
+ next unless regex_matched?(key)
30
+
31
+ count += 1
32
+ item = type_klass.new(key)
33
+ yield(item)
34
+ end
35
+ end
36
+ end
37
+
38
+ def map
39
+ output = []
40
+ each do |obj|
41
+ output.push yield(obj)
42
+ end
43
+ output
44
+ end
45
+
46
+ def count
47
+ count = 0
48
+ each { count += 1 }
49
+ count
50
+ end
51
+
52
+ def to_a
53
+ map { |item| item }
54
+ end
55
+
56
+ private
57
+
58
+ def exceed_limit?(count)
59
+ limit >= 0 && count > limit
60
+ end
61
+
62
+ def regex_matched?(key)
63
+ key =~ type_klass.key_regex
64
+ end
65
+
66
+ def build_search_pattern
67
+ search_pattern = type_klass.key_pattern
68
+
69
+ type_klass.key_attributes.each do |attr|
70
+ value = filters[attr]
71
+ value = value.nil? ? '*' : value.to_s
72
+
73
+ term = "{#{attr}}"
74
+ search_pattern = search_pattern.gsub(term, value.to_s)
75
+ end
76
+ search_pattern
77
+ end
78
+
79
+ def stringify_keys(raw_attr_map)
80
+ raw_attr_map.map { |k, v| [k.to_s, v] }.to_h
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,53 @@
1
+ module Sideroo
2
+ class KeyBuilder
3
+ attr_reader :attr_map
4
+ attr_reader :key_pattern
5
+
6
+ class << self
7
+ def key_attributes(key_pattern)
8
+ regex = /\{([^\{\}]+)\}/
9
+ key_pattern.scan(regex).map(&:first)
10
+ end
11
+ end
12
+
13
+ def initialize(attr_map:, key_pattern:)
14
+ @attr_map = attr_map
15
+ @key_pattern = key_pattern
16
+ end
17
+
18
+ def build
19
+ validate_attrs!
20
+ populate_key
21
+ end
22
+
23
+ private
24
+
25
+ def validate_attrs!
26
+ provided_attrs = attr_map.keys.map(&:to_s)
27
+ key_attributes = self.class.key_attributes(key_pattern)
28
+
29
+ missing_attrs = key_attributes - provided_attrs
30
+ unexpected_attrs = provided_attrs - key_attributes
31
+
32
+ if missing_attrs.any?
33
+ msg = "Missing attributes: #{missing_attrs.join(', ')}"
34
+ raise MissingKeys, msg
35
+ end
36
+
37
+ if unexpected_attrs.any?
38
+ msg = "Unexpected attributes: #{unexpected_attrs.join(', ')}"
39
+ raise UnexpectedKeys, msg
40
+ end
41
+ end
42
+
43
+ def populate_key
44
+ key = key_pattern
45
+ attr_map.each do |attr, value|
46
+ term = "{#{attr}}"
47
+ key = key.gsub(term, value.to_s)
48
+ end
49
+
50
+ key
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,137 @@
1
+ module Sideroo
2
+ class Base
3
+ class << self
4
+ def redis_methods(method_names)
5
+ method_names.each do |method_name|
6
+ define_redis_method(method_name)
7
+ end
8
+ end
9
+
10
+ # USAGE DEFINTIONS
11
+ #
12
+ def data_type(*args)
13
+ @data_type = args.first if args.count > 0
14
+ @data_type
15
+ end
16
+
17
+ def key_pattern(*args)
18
+ @key_pattern = args.first if args.count > 0
19
+ @key_pattern
20
+ end
21
+
22
+ def key_regex(*args)
23
+ @key_regex = args.first if args.count > 0
24
+ @key_regex || default_key_regex
25
+ end
26
+
27
+ ## METADATA
28
+ #
29
+ def example(*args)
30
+ @example = args.first if args.count > 0
31
+ @example
32
+ end
33
+
34
+ def description(*args)
35
+ @description = args.first if args.count > 0
36
+ @description
37
+ end
38
+
39
+ def key_attributes
40
+ KeyBuilder.key_attributes(key_pattern)
41
+ end
42
+
43
+ def redis_client(*args)
44
+ @redis_client = args.first if args.count > 0
45
+ @redis_client || Sideroo.redis_client
46
+ end
47
+
48
+ ## SEARCH
49
+ #
50
+ def where(attr_map)
51
+ Sideroo::Enumerator.new(
52
+ type_klass: self,
53
+ filters: attr_map,
54
+ )
55
+ end
56
+
57
+ def all
58
+ where({})
59
+ end
60
+
61
+ def example_valid?
62
+ example.nil? || example =~ key_regex
63
+ end
64
+
65
+ private
66
+
67
+ def define_redis_method(method_name)
68
+ define_method method_name do |*args|
69
+ redis_args = [key].concat(args)
70
+ # forward key and args to corresponding redis method
71
+ redis_client.send(method_name, *redis_args)
72
+ end
73
+ end
74
+
75
+ def default_key_regex
76
+ regex_str = key_pattern
77
+
78
+ key_attributes.each do |attr|
79
+ term = "{#{attr}}"
80
+ value = '(.+)'
81
+ regex_str = regex_str.gsub(term, value)
82
+ end
83
+
84
+ Regexp.new("^#{regex_str}$")
85
+ end
86
+ end
87
+
88
+ # Methods applied to all types
89
+ redis_methods %w[
90
+ del
91
+ dump
92
+ exists
93
+ expire
94
+ expireat
95
+ persist
96
+ pexpire
97
+ pexpireat
98
+ pttl
99
+ rename
100
+ renamenx
101
+ restore
102
+ touch
103
+ ttl
104
+ type
105
+ unlink
106
+ ]
107
+
108
+ attr_reader :key
109
+
110
+ def initialize(arg = {})
111
+ @key =
112
+ case arg
113
+ when ::String
114
+ message = "Expected pattern #{self.class.key_pattern}, got #{arg}"
115
+ raise(ArgumentError, message) if arg !~ self.class.key_regex
116
+ arg
117
+ when ::Hash
118
+ attr_map = arg
119
+ KeyBuilder.new(
120
+ attr_map: attr_map,
121
+ key_pattern: self.class.key_pattern,
122
+ ).build
123
+ else
124
+ message = "Hash or String expected. #{arg.class} given."
125
+ raise ArgumentError, message
126
+ end
127
+ end
128
+
129
+ def redis_client=(client)
130
+ @redis_client = client
131
+ end
132
+
133
+ def redis_client
134
+ @redis_client || self.class.redis_client
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,8 @@
1
+ module Sideroo
2
+ class Bitmap < Base
3
+ redis_methods %w[
4
+ getbit
5
+ setbit
6
+ ]
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ module Sideroo
2
+ class Hash < Base
3
+ redis_methods %w[
4
+ hdel
5
+ hexists
6
+ hget
7
+ hgetall
8
+ hincrby
9
+ hincrbyfloat
10
+ hkeys
11
+ hlen
12
+ hmget
13
+ hmset
14
+ hscan
15
+ hscan_each
16
+ hset
17
+ hsetnx
18
+ hvals
19
+ mapped_hmget
20
+ mapped_hmset
21
+ ]
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Sideroo
2
+ class HyperLogLog < Base
3
+ redis_methods %w[
4
+ pfadd
5
+ pfcount
6
+ ]
7
+
8
+ def pfmerge(destination, *other_keys)
9
+ redis_client.pfmerge(destination, key, *other_keys)
10
+ end
11
+
12
+ # Use `self.key` as destination
13
+ def pfmerge!(*other_keys)
14
+ redis_client.pfmerge(key, *other_keys)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Sideroo
2
+ class List < Base
3
+ redis_methods %w[
4
+ blpop
5
+ brpop
6
+ brpoplpush
7
+ lindex
8
+ linsert
9
+ llen
10
+ lpop
11
+ lpush
12
+ lpushx
13
+ lrange
14
+ lrem
15
+ lset
16
+ ltrim
17
+ rpop
18
+ rpoplpush
19
+ rpush
20
+ rpushx
21
+ ]
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ module Sideroo
2
+ class Set < Base
3
+ redis_methods %w[
4
+ sadd
5
+ scard
6
+ sdiff
7
+ sinter
8
+ sismember
9
+ smembers
10
+ smove
11
+ spop
12
+ srandmember
13
+ srem
14
+ sscan
15
+ sscan_each
16
+ sunion
17
+ ]
18
+ end
19
+
20
+ def sdiffstore(destination, *other_keys)
21
+ redis_client.sdiffstore(destination, key, *other_keys)
22
+ end
23
+
24
+ # Use `self.key` as destination
25
+ def sdiffstore!(*other_keys)
26
+ redis_client.sdiffstore(key, *other_keys)
27
+ end
28
+
29
+ def sinterstore(destination, *other_keys)
30
+ redis_client.sinterstore(destination, key, *other_keys)
31
+ end
32
+
33
+ # Use `self.key` as destination
34
+ def sinterstore!(*other_keys)
35
+ redis_client.sinterstore(key, *other_keys)
36
+ end
37
+
38
+ def sunionstore(destination, *other_keys)
39
+ redis_client.sunionstore(destination, key, *other_keys)
40
+ end
41
+
42
+ # Use `self.key` as destination
43
+ def sunionstore!(*other_keys)
44
+ redis_client.sunionstore(key, *other_keys)
45
+ end
46
+ end