familia 0.10.2 → 1.0.0.pre.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.pre-commit-config.yaml +1 -1
- data/.rubocop.yml +75 -0
- data/.rubocop_todo.yml +63 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +47 -15
- data/README.md +65 -13
- data/VERSION.yml +4 -3
- data/familia.gemspec +18 -13
- data/lib/familia/base.rb +33 -0
- data/lib/familia/connection.rb +87 -0
- data/lib/familia/core_ext.rb +119 -124
- data/lib/familia/errors.rb +33 -0
- data/lib/familia/features/api_version.rb +19 -0
- data/lib/familia/features/atomic_saves.rb +8 -0
- data/lib/familia/features/quantizer.rb +35 -0
- data/lib/familia/features/safe_dump.rb +194 -0
- data/lib/familia/features.rb +51 -0
- data/lib/familia/horreum/class_methods.rb +292 -0
- data/lib/familia/horreum/commands.rb +106 -0
- data/lib/familia/horreum/relations_management.rb +141 -0
- data/lib/familia/horreum/serialization.rb +193 -0
- data/lib/familia/horreum/settings.rb +63 -0
- data/lib/familia/horreum/utils.rb +44 -0
- data/lib/familia/horreum.rb +248 -0
- data/lib/familia/logging.rb +232 -0
- data/lib/familia/redistype/commands.rb +56 -0
- data/lib/familia/redistype/serialization.rb +110 -0
- data/lib/familia/redistype.rb +185 -0
- data/lib/familia/refinements.rb +88 -0
- data/lib/familia/settings.rb +38 -0
- data/lib/familia/types/hashkey.rb +107 -0
- data/lib/familia/types/list.rb +155 -0
- data/lib/familia/types/sorted_set.rb +234 -0
- data/lib/familia/types/string.rb +115 -0
- data/lib/familia/types/unsorted_set.rb +123 -0
- data/lib/familia/utils.rb +125 -0
- data/lib/familia/version.rb +25 -0
- data/lib/familia.rb +57 -161
- data/lib/redis_middleware.rb +109 -0
- data/try/00_familia_try.rb +5 -4
- data/try/10_familia_try.rb +21 -17
- data/try/20_redis_type_try.rb +67 -0
- data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
- data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
- data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
- data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
- data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
- data/try/26_redis_bool_try.rb +10 -6
- data/try/27_redis_horreum_try.rb +93 -0
- data/try/30_familia_object_try.rb +21 -20
- data/try/35_feature_safedump_try.rb +83 -0
- data/try/40_customer_try.rb +140 -0
- data/try/41_customer_safedump_try.rb +86 -0
- data/try/test_helpers.rb +194 -0
- metadata +51 -47
- data/lib/familia/helpers.rb +0 -70
- data/lib/familia/object.rb +0 -533
- data/lib/familia/redisobject.rb +0 -1017
- data/lib/familia/test_helpers.rb +0 -40
- data/lib/familia/tools.rb +0 -67
- data/try/20_redis_object_try.rb +0 -44
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class Set < RedisType
|
5
|
+
def size
|
6
|
+
redis.scard rediskey
|
7
|
+
end
|
8
|
+
alias length size
|
9
|
+
|
10
|
+
def empty?
|
11
|
+
size.zero?
|
12
|
+
end
|
13
|
+
|
14
|
+
def add *values
|
15
|
+
values.flatten.compact.each { |v| redis.sadd? rediskey, to_redis(v) }
|
16
|
+
update_expiration
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(v)
|
21
|
+
add v
|
22
|
+
end
|
23
|
+
|
24
|
+
def members
|
25
|
+
echo :members, caller(1..1).first if Familia.debug
|
26
|
+
el = membersraw
|
27
|
+
multi_from_redis(*el)
|
28
|
+
end
|
29
|
+
alias all members
|
30
|
+
alias to_a members
|
31
|
+
|
32
|
+
def membersraw
|
33
|
+
redis.smembers(rediskey)
|
34
|
+
end
|
35
|
+
|
36
|
+
def each(&blk)
|
37
|
+
members.each(&blk)
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_with_index(&blk)
|
41
|
+
members.each_with_index(&blk)
|
42
|
+
end
|
43
|
+
|
44
|
+
def collect(&blk)
|
45
|
+
members.collect(&blk)
|
46
|
+
end
|
47
|
+
|
48
|
+
def select(&blk)
|
49
|
+
members.select(&blk)
|
50
|
+
end
|
51
|
+
|
52
|
+
def eachraw(&blk)
|
53
|
+
membersraw.each(&blk)
|
54
|
+
end
|
55
|
+
|
56
|
+
def eachraw_with_index(&blk)
|
57
|
+
membersraw.each_with_index(&blk)
|
58
|
+
end
|
59
|
+
|
60
|
+
def collectraw(&blk)
|
61
|
+
membersraw.collect(&blk)
|
62
|
+
end
|
63
|
+
|
64
|
+
def selectraw(&blk)
|
65
|
+
membersraw.select(&blk)
|
66
|
+
end
|
67
|
+
|
68
|
+
def member?(val)
|
69
|
+
redis.sismember rediskey, to_redis(val)
|
70
|
+
end
|
71
|
+
alias include? member?
|
72
|
+
|
73
|
+
def delete(val)
|
74
|
+
redis.srem rediskey, to_redis(val)
|
75
|
+
end
|
76
|
+
alias remove delete
|
77
|
+
alias rem delete
|
78
|
+
alias del delete
|
79
|
+
|
80
|
+
def intersection *setkeys
|
81
|
+
# TODO
|
82
|
+
end
|
83
|
+
|
84
|
+
def pop
|
85
|
+
redis.spop rediskey
|
86
|
+
end
|
87
|
+
|
88
|
+
def move(dstkey, val)
|
89
|
+
redis.smove rediskey, dstkey, val
|
90
|
+
end
|
91
|
+
|
92
|
+
def random
|
93
|
+
from_redis randomraw
|
94
|
+
end
|
95
|
+
|
96
|
+
def randomraw
|
97
|
+
redis.srandmember(rediskey)
|
98
|
+
end
|
99
|
+
|
100
|
+
## Make the value stored at KEY identical to the given list
|
101
|
+
# define_method :"#{name}_sync" do |*latest|
|
102
|
+
# latest = latest.flatten.compact
|
103
|
+
# # Do nothing if we're given an empty Array.
|
104
|
+
# # Otherwise this would clear all current values
|
105
|
+
# if latest.empty?
|
106
|
+
# false
|
107
|
+
# else
|
108
|
+
# # Convert to a list of index values if we got the actual objects
|
109
|
+
# latest = latest.collect { |obj| obj.index } if klass === latest.first
|
110
|
+
# current = send("#{name_plural}raw")
|
111
|
+
# added = latest-current
|
112
|
+
# removed = current-latest
|
113
|
+
# #Familia.info "#{self.index}: adding: #{added}"
|
114
|
+
# added.each { |v| self.send("add_#{name_singular}", v) }
|
115
|
+
# #Familia.info "#{self.index}: removing: #{removed}"
|
116
|
+
# removed.each { |v| self.send("remove_#{name_singular}", v) }
|
117
|
+
# true
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
|
121
|
+
Familia::RedisType.register self, :set
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Familia
|
6
|
+
DIGEST_CLASS = Digest::SHA256
|
7
|
+
|
8
|
+
module Utils
|
9
|
+
|
10
|
+
def debug?
|
11
|
+
@debug == true
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_id
|
15
|
+
input = SecureRandom.hex(32) # 16=128 bits, 32=256 bits
|
16
|
+
Digest::SHA256.hexdigest(input).to_i(16).to_s(36) # base-36 encoding
|
17
|
+
end
|
18
|
+
|
19
|
+
def join(*val)
|
20
|
+
val.compact.join(Familia.delim)
|
21
|
+
end
|
22
|
+
|
23
|
+
def split(val)
|
24
|
+
val.split(Familia.delim)
|
25
|
+
end
|
26
|
+
|
27
|
+
def rediskey(*val)
|
28
|
+
join(*val)
|
29
|
+
end
|
30
|
+
|
31
|
+
def redisuri(uri)
|
32
|
+
generic_uri = URI.parse(uri.to_s)
|
33
|
+
|
34
|
+
# Create a new URI::Redis object
|
35
|
+
redis_uri = URI::Redis.build(
|
36
|
+
scheme: generic_uri.scheme,
|
37
|
+
userinfo: generic_uri.userinfo,
|
38
|
+
host: generic_uri.host,
|
39
|
+
port: generic_uri.port,
|
40
|
+
path: generic_uri.path,
|
41
|
+
query: generic_uri.query,
|
42
|
+
fragment: generic_uri.fragment
|
43
|
+
)
|
44
|
+
|
45
|
+
redis_uri
|
46
|
+
end
|
47
|
+
|
48
|
+
def now(name = Time.now)
|
49
|
+
name.utc.to_f
|
50
|
+
end
|
51
|
+
|
52
|
+
# A quantized timestamp
|
53
|
+
# e.g. 12:32 -> 12:30
|
54
|
+
#
|
55
|
+
def qnow(quantum = 10.minutes, now = Familia.now)
|
56
|
+
rounded = now - (now % quantum)
|
57
|
+
Time.at(rounded).utc.to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
def qstamp(quantum = nil, pattern = nil, now = Familia.now)
|
61
|
+
quantum ||= ttl || 10.minutes
|
62
|
+
pattern ||= '%H%M'
|
63
|
+
rounded = now - (now % quantum)
|
64
|
+
Time.at(rounded).utc.strftime(pattern)
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_sha_hash(*elements)
|
68
|
+
concatenated_string = Familia.join(*elements)
|
69
|
+
DIGEST_CLASS.hexdigest(concatenated_string)
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method determines the appropriate value to return based on the class of the input argument.
|
73
|
+
# It uses a case statement to handle different classes:
|
74
|
+
# - For Symbol, String, Integer, and Float classes, it traces the operation and converts the value to a string.
|
75
|
+
# - For Familia::Horreum class, it traces the operation and returns the identifier of the value.
|
76
|
+
# - For TrueClass, FalseClass, and NilClass, it traces the operation and converts the value to a string ("true", "false", or "").
|
77
|
+
# - For any other class, it traces the operation and returns nil.
|
78
|
+
#
|
79
|
+
# Alternative names for `value_to_distinguish` could be `input_value`, `value`, or `object`.
|
80
|
+
def distinguisher(value_to_distinguish, strict_values = true)
|
81
|
+
case value_to_distinguish
|
82
|
+
when ::Symbol, ::String, ::Integer, ::Float
|
83
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "string", caller(1..1) if Familia.debug?
|
84
|
+
# Symbols and numerics are naturally serializable to strings
|
85
|
+
# so it's a relatively low risk operation.
|
86
|
+
value_to_distinguish.to_s
|
87
|
+
|
88
|
+
when ::TrueClass, ::FalseClass, ::NilClass
|
89
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "true/false/nil", caller(1..1) if Familia.debug?
|
90
|
+
# TrueClass, FalseClass, and NilClass are high risk because we can't
|
91
|
+
# reliably determine the original type of the value from the serialized
|
92
|
+
# string. This can lead to unexpected behavior when deserializing. For
|
93
|
+
# example, if a TrueClass value is serialized as "true" and then later
|
94
|
+
# deserialized as a String, it can cause errors in the application. Worse
|
95
|
+
# still, if a NilClass value is serialized as an empty string we lose the
|
96
|
+
# ability to distinguish between a nil value and an empty string when
|
97
|
+
#
|
98
|
+
raise Familia::HighRiskFactor, value_to_distinguish if strict_values
|
99
|
+
value_to_distinguish.to_s #=> "true", "false", ""
|
100
|
+
|
101
|
+
when Familia::Base, Class
|
102
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "base", caller(1..1) if Familia.debug?
|
103
|
+
if value_to_distinguish.is_a?(Class)
|
104
|
+
value_to_distinguish.name
|
105
|
+
else
|
106
|
+
value_to_distinguish.identifier
|
107
|
+
end
|
108
|
+
|
109
|
+
else
|
110
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "else1 #{strict_values}", caller(1..1) if Familia.debug?
|
111
|
+
|
112
|
+
if value_to_distinguish.class.ancestors.member?(Familia::Base)
|
113
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "isabase", caller(1..1) if Familia.debug?
|
114
|
+
value_to_distinguish.identifier
|
115
|
+
|
116
|
+
else
|
117
|
+
Familia.trace :TOREDIS_DISTINGUISHER, redis, "else2 #{strict_values}", caller(1..1) if Familia.debug?
|
118
|
+
raise Familia::HighRiskFactor, value_to_distinguish if strict_values
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Familia
|
6
|
+
module VERSION
|
7
|
+
def self.to_s
|
8
|
+
load_config
|
9
|
+
version = [@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
|
10
|
+
version += "-#{@version[:PRE]}" if @version[:PRE]
|
11
|
+
version
|
12
|
+
end
|
13
|
+
alias inspect to_s
|
14
|
+
|
15
|
+
def self.version
|
16
|
+
@version ||= load_config
|
17
|
+
@version
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.load_config
|
21
|
+
version_file_path = File.join(__dir__, '..', '..', 'VERSION.yml')
|
22
|
+
@version = YAML.load_file(version_file_path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/familia.rb
CHANGED
@@ -1,177 +1,73 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# rubocop:disable all
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'redis'
|
3
5
|
require 'uri/redis'
|
4
|
-
require 'gibbler'
|
5
|
-
require 'familia/core_ext'
|
6
|
-
require 'multi_json'
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
[@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
|
13
|
-
end
|
14
|
-
alias_method :inspect, :to_s
|
15
|
-
def self.load_config
|
16
|
-
require 'yaml'
|
17
|
-
@version ||= YAML.load_file(File.join(FAMILIA_LIB_HOME, '..', 'VERSION.yml'))
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
7
|
+
require_relative 'familia/core_ext'
|
8
|
+
require_relative 'familia/refinements'
|
9
|
+
require_relative 'familia/errors'
|
10
|
+
require_relative 'familia/version'
|
21
11
|
|
12
|
+
# Familia - A family warehouse for Redis
|
13
|
+
#
|
14
|
+
# Familia provides a way to organize and store Ruby objects in Redis.
|
15
|
+
# It includes various modules and classes to facilitate object-Redis interactions.
|
16
|
+
#
|
17
|
+
# @example Basic usage
|
18
|
+
# class Flower < Familia::Horreum
|
19
|
+
#
|
20
|
+
# identifier :my_identifier_method
|
21
|
+
# field :token
|
22
|
+
# field :name
|
23
|
+
# list :owners
|
24
|
+
# set :tags
|
25
|
+
# zset :metrics
|
26
|
+
# hash :props
|
27
|
+
# string :value, :default => "GREAT!"
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# @see https://github.com/delano/familia
|
31
|
+
#
|
22
32
|
module Familia
|
23
|
-
|
24
|
-
@secret = '1-800-AWESOME' # Should be modified via Familia.secret = ''
|
25
|
-
@apiversion = nil
|
26
|
-
@uri = URI.parse 'redis://127.0.0.1'
|
27
|
-
@delim = ':'
|
28
|
-
@clients = {}
|
29
|
-
@classes = []
|
30
|
-
@suffix = :object.freeze
|
31
|
-
@index = :id.freeze
|
33
|
+
|
32
34
|
@debug = false
|
33
|
-
@
|
34
|
-
@load_method = :from_json
|
35
|
+
@members = []
|
35
36
|
|
36
37
|
class << self
|
37
|
-
|
38
|
-
|
39
|
-
attr_writer :apiversion
|
38
|
+
attr_accessor :debug
|
39
|
+
attr_reader :members
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
def info *msg
|
44
|
-
STDERR.puts *msg
|
45
|
-
end
|
46
|
-
def classes with_redis_objects=false
|
47
|
-
with_redis_objects ? [@classes, RedisObject.classes].flatten : @classes
|
48
|
-
end
|
49
|
-
def ld *msg
|
50
|
-
info *msg if debug?
|
51
|
-
end
|
52
|
-
def trace label, redis_instance, ident, context=nil
|
53
|
-
return unless Familia.debug?
|
54
|
-
codeline = if context
|
55
|
-
context = [context].flatten
|
56
|
-
context.reject! { |line| line =~ /lib\/familia/ }
|
57
|
-
context.first
|
58
|
-
end
|
59
|
-
info "[%s] -> %s <- %s %s" % [label, codeline, redis_instance.id, ident]
|
60
|
-
end
|
61
|
-
def uri= v
|
62
|
-
v = URI.parse v unless URI === v
|
63
|
-
@uri = v
|
64
|
-
end
|
65
|
-
# A convenience method for returning the appropriate Redis
|
66
|
-
# connection. If +uri+ is an Integer, we'll treat it as a
|
67
|
-
# database number. If it's a String, we'll treat it as a
|
68
|
-
# full URI (e.g. redis://1.2.3.4/15).
|
69
|
-
# Otherwise we'll return the default connection.
|
70
|
-
def redis(uri=nil)
|
71
|
-
if Integer === uri
|
72
|
-
tmp = Familia.uri
|
73
|
-
tmp.db = uri
|
74
|
-
uri = tmp
|
75
|
-
elsif String === uri
|
76
|
-
uri &&= URI.parse uri
|
77
|
-
end
|
78
|
-
uri ||= Familia.uri
|
79
|
-
connect(uri) unless @clients[uri.serverid]
|
80
|
-
@clients[uri.serverid]
|
81
|
-
end
|
82
|
-
def log(level, path)
|
83
|
-
logger = Log4r::Logger.new('familia')
|
84
|
-
logger.outputters = Log4r::FileOutputter.new 'familia', :filename => path
|
85
|
-
logger.level = Log4r.const_get(level)
|
86
|
-
logger
|
87
|
-
end
|
88
|
-
def connect(uri=nil)
|
89
|
-
uri &&= URI.parse uri if String === uri
|
90
|
-
uri ||= Familia.uri
|
91
|
-
conf = uri.conf
|
92
|
-
redis = Redis.new conf
|
93
|
-
Familia.trace(:CONNECT, redis, conf.inspect, caller[0..3])
|
94
|
-
@clients[uri.serverid] = redis
|
95
|
-
end
|
96
|
-
def reconnect_all!
|
97
|
-
Familia.classes.each do |klass|
|
98
|
-
klass.redis.client.reconnect
|
99
|
-
Familia.info "#{klass} ping: #{klass.redis.ping}" if debug?
|
100
|
-
end
|
101
|
-
end
|
102
|
-
def connected?(uri=nil)
|
103
|
-
uri &&= URI.parse uri if String === uri
|
104
|
-
@clients.has_key?(uri.serverid)
|
105
|
-
end
|
106
|
-
def default_suffix(a=nil) @suffix = a if a; @suffix end
|
107
|
-
def default_suffix=(a) @suffix = a end
|
108
|
-
def index(r=nil) @index = r if r; @index end
|
109
|
-
def index=(r) @index = r; r end
|
110
|
-
def join(*r) r.join(Familia.delim) end
|
111
|
-
def split(r) r.split(Familia.delim) end
|
112
|
-
def rediskey *args
|
113
|
-
el = args.flatten.compact
|
114
|
-
el.unshift @apiversion unless @apiversion.nil?
|
115
|
-
el.join(Familia.delim)
|
41
|
+
def included(member)
|
42
|
+
raise Problem, "#{member} should subclass Familia::Horreum"
|
116
43
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@apiversion
|
127
|
-
end
|
128
|
-
def now n=Time.now
|
129
|
-
n.utc.to_i
|
130
|
-
end
|
131
|
-
# A quantized timestamp
|
132
|
-
# e.g. 12:32 -> 12:30
|
44
|
+
|
45
|
+
# A convenience pattern for configuring Familia.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# Familia.configure do |config|
|
49
|
+
# config.debug = true
|
50
|
+
# config.enable_redis_logging = true
|
51
|
+
# end
|
52
|
+
#
|
133
53
|
#
|
134
|
-
def
|
135
|
-
|
136
|
-
Time.at(rounded).utc.to_i
|
54
|
+
def configure
|
55
|
+
yield self
|
137
56
|
end
|
138
57
|
end
|
139
58
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
attr_reader :uri
|
145
|
-
def initialize uri
|
146
|
-
@uri = uri
|
147
|
-
end
|
148
|
-
def message
|
149
|
-
"No client for #{uri.serverid}"
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def self.included(obj)
|
154
|
-
obj.send :include, Familia::InstanceMethods
|
155
|
-
obj.send :include, Gibbler::Complex
|
156
|
-
obj.extend Familia::ClassMethods
|
157
|
-
obj.class_zset :instances, :class => obj, :reference => true
|
158
|
-
Familia.classes << obj
|
159
|
-
end
|
160
|
-
|
161
|
-
require 'familia/object'
|
162
|
-
require 'familia/helpers'
|
59
|
+
require_relative 'familia/logging'
|
60
|
+
require_relative 'familia/connection'
|
61
|
+
require_relative 'familia/settings'
|
62
|
+
require_relative 'familia/utils'
|
163
63
|
|
64
|
+
extend Logging
|
65
|
+
extend Connection
|
66
|
+
extend Settings
|
67
|
+
extend Utils
|
164
68
|
end
|
165
69
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
@klasses ||= []
|
171
|
-
@klasses
|
172
|
-
end
|
173
|
-
def included(obj)
|
174
|
-
self.klasses << obj
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
70
|
+
require_relative 'familia/base'
|
71
|
+
require_relative 'familia/features'
|
72
|
+
require_relative 'familia/redistype'
|
73
|
+
require_relative 'familia/horreum'
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RedisLogger is RedisClient middleware.
|
4
|
+
#
|
5
|
+
# This middleware addresses the need for detailed Redis command logging, which
|
6
|
+
# was removed from the redis-rb gem due to performance concerns. However, in
|
7
|
+
# many development and debugging scenarios, the ability to log Redis commands
|
8
|
+
# can be invaluable.
|
9
|
+
#
|
10
|
+
# @example Enable Redis command logging
|
11
|
+
# RedisLogger.logger = Logger.new(STDOUT)
|
12
|
+
# RedisClient.register(RedisLogger)
|
13
|
+
#
|
14
|
+
# @see https://github.com/redis-rb/redis-client?tab=readme-ov-file#instrumentation-and-middlewares
|
15
|
+
#
|
16
|
+
# @note While there were concerns about the performance impact of logging in
|
17
|
+
# the redis-rb gem, this middleware is designed to be optional and can be
|
18
|
+
# easily enabled or disabled as needed. The performance impact is minimal
|
19
|
+
# when logging is disabled, and the benefits during development and debugging
|
20
|
+
# often outweigh the slight performance cost when enabled.
|
21
|
+
module RedisLogger
|
22
|
+
@logger = nil
|
23
|
+
|
24
|
+
class << self
|
25
|
+
# Gets/sets the logger instance used by RedisLogger.
|
26
|
+
# @return [Logger, nil] The current logger instance or nil if not set.
|
27
|
+
attr_accessor :logger
|
28
|
+
end
|
29
|
+
|
30
|
+
# Logs the Redis command and its execution time.
|
31
|
+
#
|
32
|
+
# This method is called for each Redis command when the middleware is active.
|
33
|
+
# It logs the command and its execution time only if a logger is set.
|
34
|
+
#
|
35
|
+
# @param command [Array] The Redis command and its arguments.
|
36
|
+
# @param redis_config [Hash] The configuration options for the Redis
|
37
|
+
# connection.
|
38
|
+
# @return [Object] The result of the Redis command execution.
|
39
|
+
#
|
40
|
+
# @note The performance impact of this logging is negligible when no logger
|
41
|
+
# is set, as it quickly returns control to the Redis client. When a logger
|
42
|
+
# is set, the minimal overhead is often offset by the valuable insights
|
43
|
+
# gained during development and debugging.
|
44
|
+
def call(command, redis_config)
|
45
|
+
return yield unless RedisLogger.logger
|
46
|
+
|
47
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
48
|
+
result = yield
|
49
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond) - start
|
50
|
+
RedisLogger.logger.debug("Redis: #{command.inspect} (#{duration}µs)")
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# RedisCommandCounter is RedisClient middleware.
|
56
|
+
#
|
57
|
+
# This middleware counts the number of Redis commands executed. It can be
|
58
|
+
# useful for performance monitoring and debugging, allowing you to track
|
59
|
+
# the volume of Redis operations in your application.
|
60
|
+
#
|
61
|
+
# @example Enable Redis command counting
|
62
|
+
# RedisCommandCounter.reset
|
63
|
+
# RedisClient.register(RedisCommandCounter)
|
64
|
+
#
|
65
|
+
# @see https://github.com/redis-rb/redis-client?tab=readme-ov-file#instrumentation-and-middlewares
|
66
|
+
module RedisCommandCounter
|
67
|
+
@count = 0
|
68
|
+
@mutex = Mutex.new
|
69
|
+
|
70
|
+
class << self
|
71
|
+
# Gets the current count of Redis commands executed.
|
72
|
+
# @return [Integer] The number of Redis commands executed.
|
73
|
+
attr_reader :count
|
74
|
+
|
75
|
+
# Resets the command count to zero.
|
76
|
+
# This method is thread-safe.
|
77
|
+
# @return [Integer] The reset count (always 0).
|
78
|
+
def reset
|
79
|
+
@mutex.synchronize { @count = 0 }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Increments the command count.
|
83
|
+
# This method is thread-safe.
|
84
|
+
# @return [Integer] The new count after incrementing.
|
85
|
+
def increment
|
86
|
+
@mutex.synchronize { @count += 1 }
|
87
|
+
end
|
88
|
+
|
89
|
+
def count_commands
|
90
|
+
start_count = count
|
91
|
+
yield
|
92
|
+
end_count = count
|
93
|
+
end_count - start_count
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Counts the Redis command and delegates its execution.
|
98
|
+
#
|
99
|
+
# This method is called for each Redis command when the middleware is active.
|
100
|
+
# It increments the command count and then yields to execute the actual command.
|
101
|
+
#
|
102
|
+
# @param command [Array] The Redis command and its arguments.
|
103
|
+
# @param redis_config [Hash] The configuration options for the Redis connection.
|
104
|
+
# @return [Object] The result of the Redis command execution.
|
105
|
+
def call(command, redis_config)
|
106
|
+
RedisCommandCounter.increment
|
107
|
+
yield
|
108
|
+
end
|
109
|
+
end
|
data/try/00_familia_try.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
require 'familia'
|
2
|
-
require 'familia/test_helpers'
|
3
1
|
|
4
|
-
|
2
|
+
require_relative '../lib/familia'
|
3
|
+
require_relative './test_helpers'
|
4
|
+
|
5
|
+
#Familia.apiversion = 'v1'
|
5
6
|
|
6
7
|
|
7
8
|
## Check for help class
|
8
|
-
Bone.
|
9
|
+
Bone.redis_types.keys # consistent b/c hashes are ordered
|
9
10
|
#=> [:owners, :tags, :metrics, :props, :value]
|
10
11
|
|
11
12
|
## Familia has a uri
|
data/try/10_familia_try.rb
CHANGED
@@ -1,25 +1,28 @@
|
|
1
|
-
require 'familia'
|
2
|
-
require 'familia/test_helpers'
|
3
1
|
|
4
|
-
|
5
|
-
redis_objects = Familia::RedisObject.registration.keys
|
6
|
-
redis_objects.collect(&:to_s).sort
|
7
|
-
#=> ["hash", "list", "set", "string", "zset"]
|
2
|
+
require 'time'
|
8
3
|
|
9
|
-
|
10
|
-
|
4
|
+
require_relative '../lib/familia'
|
5
|
+
require_relative './test_helpers'
|
6
|
+
|
7
|
+
## Has all redistype relativess
|
8
|
+
registered_types = Familia::RedisType.registered_types.keys
|
9
|
+
registered_types.collect(&:to_s).sort
|
10
|
+
#=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
|
11
|
+
|
12
|
+
## Familia created class methods for redistype list class
|
13
|
+
Familia::Horreum::ClassMethods.public_method_defined? :list?
|
11
14
|
#=> true
|
12
15
|
|
13
|
-
## Familia created class methods for
|
14
|
-
Familia::ClassMethods.public_method_defined? :list
|
16
|
+
## Familia created class methods for redistype list class
|
17
|
+
Familia::Horreum::ClassMethods.public_method_defined? :list
|
15
18
|
#=> true
|
16
19
|
|
17
|
-
## Familia created class methods for
|
18
|
-
Familia::ClassMethods.public_method_defined? :lists
|
20
|
+
## Familia created class methods for redistype list class
|
21
|
+
Familia::Horreum::ClassMethods.public_method_defined? :lists
|
19
22
|
#=> true
|
20
23
|
|
21
|
-
## A Familia object knows its
|
22
|
-
Bone.
|
24
|
+
## A Familia object knows its redistype relativess
|
25
|
+
Bone.redis_types.is_a?(Hash) && Bone.redis_types.has_key?(:owners)
|
23
26
|
#=> true
|
24
27
|
|
25
28
|
## A Familia object knows its lists
|
@@ -30,14 +33,15 @@ Bone.lists.size
|
|
30
33
|
Bone.list? :owners
|
31
34
|
#=> true
|
32
35
|
|
33
|
-
## A Familia object can get a specific
|
36
|
+
## A Familia object can get a specific redistype relatives def
|
34
37
|
definition = Bone.list :owners
|
35
38
|
definition.klass
|
36
39
|
#=> Familia::List
|
37
40
|
|
38
41
|
## Familia.now
|
39
|
-
Familia.now
|
40
|
-
|
42
|
+
parsed_time = Familia.now(Time.parse('2011-04-10 20:56:20 UTC').utc)
|
43
|
+
[parsed_time, parsed_time.is_a?(Numeric), parsed_time.is_a?(Float)]
|
44
|
+
#=> [1302468980.0, true, true]
|
41
45
|
|
42
46
|
## Familia.qnow
|
43
47
|
Familia.qnow 10.minutes, 1302468980
|