familia 0.10.2 → 1.0.0.pre.rc1
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 +11 -12
- 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 +175 -0
- data/lib/familia/features.rb +51 -0
- data/lib/familia/horreum/class_methods.rb +240 -0
- data/lib/familia/horreum/commands.rb +59 -0
- data/lib/familia/horreum/relations_management.rb +141 -0
- data/lib/familia/horreum/serialization.rb +154 -0
- data/lib/familia/horreum/settings.rb +63 -0
- data/lib/familia/horreum/utils.rb +43 -0
- data/lib/familia/horreum.rb +198 -0
- data/lib/familia/logging.rb +249 -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/settings.rb +38 -0
- data/lib/familia/types/hashkey.rb +108 -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 +129 -0
- data/lib/familia/version.rb +25 -0
- data/lib/familia.rb +56 -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 +40 -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 +186 -0
- metadata +50 -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,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
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
require_relative '../lib/familia'
|
3
|
+
require_relative '../lib/familia/features/quantizer'
|
4
|
+
require_relative './test_helpers'
|
5
|
+
|
6
|
+
#Familia.apiversion = 'v1'
|
7
|
+
|
8
|
+
@limiter1 = Limiter.new :requests
|
9
|
+
|
10
|
+
|
11
|
+
## Redis Types are unique per instance of a Familia class
|
12
|
+
@a = Bone.new 'atoken1', :name1
|
13
|
+
@b = Bone.new 'atoken2', :name2
|
14
|
+
p [@a.object_id, @b.object_id]
|
15
|
+
p [@a.owners.parent.class, @b.owners.parent.class]
|
16
|
+
p [@a.owners.parent.object_id, @b.owners.parent.object_id]
|
17
|
+
p [@a.owners.rediskey, @b.owners.rediskey]
|
18
|
+
p [@a.token, @b.token]
|
19
|
+
p [@a.name, @b.name]
|
20
|
+
@a.owners.rediskey.eql?(@b.owners.rediskey)
|
21
|
+
#=> false
|
22
|
+
|
23
|
+
## Redis Types are frozen
|
24
|
+
@a.owners.frozen?
|
25
|
+
#=> true
|
26
|
+
|
27
|
+
|
28
|
+
## Limiter#qstamp
|
29
|
+
@limiter1.counter.qstamp(10.minutes, '%H:%M', 1302468980)
|
30
|
+
##=> '20:50'
|
31
|
+
|
32
|
+
## Redis Types can be stored to quantized stamp suffix
|
33
|
+
@limiter1.counter.rediskey
|
34
|
+
##=> "v1:limiter:requests:counter:20:50"
|
35
|
+
|
36
|
+
## Limiter#qstamp as a number
|
37
|
+
@limiter2 = Limiter.new :requests
|
38
|
+
@limiter2.counter.qstamp(10.minutes, pattern=nil, now=1302468980)
|
39
|
+
##=> 1302468600
|
40
|
+
|
41
|
+
## Redis Types can be stored to quantized numeric suffix. This
|
42
|
+
## tryouts is disabled b/c `RedisType#rediskey` takes no args
|
43
|
+
## and relies on the `class Limiter` definition in test_helpers.rb
|
44
|
+
## for the `:quantize` option. The quantized suffix for the Limiter
|
45
|
+
## class is `'%H:%M'` so its redis keys will always look like that.
|
46
|
+
@limiter2.counter.rediskey
|
47
|
+
##=> "v1:limiter:requests:counter:1302468600"
|
48
|
+
|
49
|
+
## Increment counter
|
50
|
+
@limiter1.counter.clear
|
51
|
+
@limiter1.counter.increment
|
52
|
+
#=> 1
|
53
|
+
|
54
|
+
## Check ttl
|
55
|
+
@limiter1.counter.ttl
|
56
|
+
#=> 3600.0
|
57
|
+
|
58
|
+
## Check ttl for a different instance
|
59
|
+
## (this exists to make sure options are cloned for each instance)
|
60
|
+
@limiter3 = Limiter.new :requests
|
61
|
+
@limiter3.counter.ttl
|
62
|
+
#=> 3600.0
|
63
|
+
|
64
|
+
## Check realttl
|
65
|
+
sleep 1 # Redis ttls are in seconds so we can't wait any less time than this (without mocking)
|
66
|
+
@limiter1.counter.realttl
|
67
|
+
#=> 3600-1
|
@@ -1,13 +1,13 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative '../lib/familia'
|
2
|
+
require_relative './test_helpers'
|
3
3
|
|
4
|
-
Familia.apiversion = 'v1'
|
4
|
+
#Familia.apiversion = 'v1'
|
5
5
|
|
6
6
|
@a = Bone.new 'atoken2', 'akey'
|
7
7
|
|
8
8
|
## Bone#rediskey
|
9
9
|
@a.rediskey
|
10
|
-
#=> '
|
10
|
+
#=> 'bone:atoken2:akey:object'
|
11
11
|
|
12
12
|
## Familia::String#value should give default value
|
13
13
|
@a.value.value
|
@@ -39,7 +39,7 @@ Familia.apiversion = 'v1'
|
|
39
39
|
#=> '1000'
|
40
40
|
|
41
41
|
## Familia::String#increment
|
42
|
-
@ret.increment
|
42
|
+
@ret.increment
|
43
43
|
#=> 1001
|
44
44
|
|
45
45
|
## Familia::String#incrementby
|
@@ -47,7 +47,7 @@ Familia.apiversion = 'v1'
|
|
47
47
|
#=> 1100
|
48
48
|
|
49
49
|
## Familia::String#decrement
|
50
|
-
@ret.decrement
|
50
|
+
@ret.decrement
|
51
51
|
#=> 1099
|
52
52
|
|
53
53
|
## Familia::String#decrementby
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative '../lib/familia'
|
2
|
+
require_relative './test_helpers'
|
3
3
|
|
4
4
|
@a = Bone.new 'atoken', 'akey'
|
5
5
|
|
@@ -21,7 +21,7 @@ require 'familia/test_helpers'
|
|
21
21
|
@a.props.has_key? 'fieldA'
|
22
22
|
#=> true
|
23
23
|
|
24
|
-
## Familia::HashKey#all
|
24
|
+
## Familia::HashKey#all
|
25
25
|
@a.props.all.class
|
26
26
|
#=> Hash
|
27
27
|
|
data/try/26_redis_bool_try.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative '../lib/familia'
|
2
|
+
require_relative './test_helpers'
|
3
3
|
|
4
4
|
Familia.debug = true
|
5
5
|
|
@@ -17,10 +17,10 @@ Familia.debug = true
|
|
17
17
|
## Trying to store a boolean value to a hash key raises an exception
|
18
18
|
begin
|
19
19
|
@hashkey["test"] = true
|
20
|
-
rescue
|
20
|
+
rescue Familia::HighRiskFactor => e
|
21
21
|
e.message
|
22
22
|
end
|
23
|
-
#=> "
|
23
|
+
#=> "High risk factor for serialization bugs: true<TrueClass>"
|
24
24
|
|
25
25
|
## Boolean values are returned as strings
|
26
26
|
@hashkey["test"]
|
@@ -29,11 +29,15 @@ end
|
|
29
29
|
## Trying to store a nil value to a hash key raises an exception
|
30
30
|
begin
|
31
31
|
@hashkey["test"] = nil
|
32
|
-
rescue
|
32
|
+
rescue Familia::HighRiskFactor => e
|
33
33
|
e.message
|
34
34
|
end
|
35
|
-
#=> "
|
35
|
+
#=> "High risk factor for serialization bugs: <NilClass>"
|
36
36
|
|
37
37
|
## The exceptions prevented the hash from being updated
|
38
38
|
@hashkey["test"]
|
39
39
|
#=> "true"
|
40
|
+
|
41
|
+
## Clear the hash key
|
42
|
+
@hashkey.clear
|
43
|
+
#=> 1
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../lib/familia'
|
2
|
+
require_relative './test_helpers'
|
3
|
+
|
4
|
+
Familia.debug = true
|
5
|
+
|
6
|
+
@identifier = 'tryouts-27@onetimesecret.com'
|
7
|
+
@customer = Customer.new @identifier
|
8
|
+
@hashkey = Familia::HashKey.new 'tryouts-27'
|
9
|
+
|
10
|
+
## Customer passed as value is returned as string identifier, on assignment as string
|
11
|
+
@hashkey["test1"] = @customer.identifier
|
12
|
+
#=> @identifier
|
13
|
+
|
14
|
+
## Customer passed as value is returned as string identifier
|
15
|
+
@hashkey["test1"]
|
16
|
+
#=> @identifier
|
17
|
+
|
18
|
+
## Trying to store a customer to a hash key implicitly converts it to string identifier
|
19
|
+
## we can't tell from the return value this way either. Here the store method return value
|
20
|
+
## is either 1 (if the key is new) or 0 (if the key already exists).
|
21
|
+
@hashkey.store "test2", @customer
|
22
|
+
#=> 1
|
23
|
+
|
24
|
+
## Trying to store a customer to a hash key implicitly converts it to string identifier
|
25
|
+
## but we can't tell that from the return value. Here the hash syntax return value
|
26
|
+
## is the value that is being assigned.
|
27
|
+
@hashkey["test2"] = @customer
|
28
|
+
#=> @customer
|
29
|
+
|
30
|
+
## Trying store again with the same key returns 0
|
31
|
+
@hashkey.store "test2", @customer
|
32
|
+
#=> 0
|
33
|
+
|
34
|
+
## Customer passed as value is returned as string identifier
|
35
|
+
@hashkey["test2"]
|
36
|
+
#=> @identifier
|
37
|
+
|
38
|
+
## Remove the key
|
39
|
+
@hashkey.clear
|
40
|
+
#=> 1
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
Familia.apiversion = 'v1'
|
1
|
+
require_relative '../lib/familia'
|
2
|
+
require_relative './test_helpers'
|
3
|
+
#Familia.apiversion = 'v1'
|
4
4
|
|
5
5
|
@a = Bone.new 'atoken', 'akey'
|
6
6
|
|
@@ -8,8 +8,8 @@ Familia.apiversion = 'v1'
|
|
8
8
|
Bone.prefix
|
9
9
|
#=> :bone
|
10
10
|
|
11
|
-
## Familia#
|
12
|
-
@a.
|
11
|
+
## Familia#identifier
|
12
|
+
@a.identifier
|
13
13
|
#=> 'atoken:akey'
|
14
14
|
|
15
15
|
## Familia.suffix
|
@@ -18,32 +18,34 @@ Bone.suffix
|
|
18
18
|
|
19
19
|
## Familia#rediskey
|
20
20
|
@a.rediskey
|
21
|
-
#=> '
|
21
|
+
#=> 'bone:atoken:akey:object'
|
22
22
|
|
23
23
|
## Familia#rediskey
|
24
24
|
@a.rediskey
|
25
|
-
#=> '
|
25
|
+
#=> 'bone:atoken:akey:object'
|
26
26
|
|
27
27
|
## Familia#save
|
28
28
|
@cust = Customer.new :delano, "Delano Mandelbaum"
|
29
29
|
@cust.save
|
30
30
|
#=> true
|
31
31
|
|
32
|
-
## Customer.
|
33
|
-
|
34
|
-
|
32
|
+
## Customer.values (Updateing this sorted set of instance IDs is a Onetime
|
33
|
+
# Secret feature. Disabled here b/c there's no code in Familia or the test
|
34
|
+
# helpers that replicates this behaviour.) Leaving this here for reference.
|
35
|
+
Customer.values.all.collect(&:custid)
|
36
|
+
##=> ['delano']
|
35
37
|
|
36
38
|
## Familia.from_redis
|
37
39
|
obj = Customer.from_redis :delano
|
38
|
-
obj.custid
|
39
|
-
#=>
|
40
|
+
[obj.class, obj.custid]
|
41
|
+
#=> [Customer, 'delano']
|
40
42
|
|
41
43
|
## Customer.destroy
|
42
44
|
@cust.destroy!
|
43
|
-
#=>
|
45
|
+
#=> true
|
44
46
|
|
45
47
|
## Customer.instances
|
46
|
-
Customer.
|
48
|
+
Customer.values.size
|
47
49
|
#=> 0
|
48
50
|
|
49
51
|
## Familia#save with an object that expires
|
@@ -57,7 +59,7 @@ Customer.customers.class
|
|
57
59
|
|
58
60
|
## Familia class rediskey
|
59
61
|
Customer.customers.rediskey
|
60
|
-
#=> '
|
62
|
+
#=> 'customer:customers'
|
61
63
|
|
62
64
|
## Familia.class_list
|
63
65
|
Customer.customers << :delano << :tucker << :morton
|
@@ -69,19 +71,18 @@ Customer.customers.clear
|
|
69
71
|
#=> 1
|
70
72
|
|
71
73
|
|
72
|
-
## Familia class replace 1
|
74
|
+
## Familia class replace 1 of 4
|
73
75
|
Customer.message.value = "msg1"
|
74
76
|
#=> "msg1"
|
75
77
|
|
76
|
-
## Familia class replace 2
|
78
|
+
## Familia class replace 2 of 4
|
77
79
|
Customer.message.value
|
78
80
|
#=> "msg1"
|
79
81
|
|
80
|
-
## Familia class replace 3
|
82
|
+
## Familia class replace 3 of 4
|
81
83
|
Customer.message = "msg2"
|
82
84
|
#=> "msg2"
|
83
85
|
|
84
|
-
## Familia class replace 4
|
86
|
+
## Familia class replace 4 of 4
|
85
87
|
Customer.message.value
|
86
88
|
#=> "msg2"
|
87
|
-
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# These tryouts test the safe dumping functionality.
|
4
|
+
|
5
|
+
require_relative '../lib/familia'
|
6
|
+
require_relative './test_helpers'
|
7
|
+
|
8
|
+
## By default Familia::Base has no safe_dump_fields method
|
9
|
+
Familia::Base.respond_to?(:safe_dump_fields)
|
10
|
+
#=> false
|
11
|
+
|
12
|
+
## Implementing models like Customer can define safe dump fields
|
13
|
+
Customer.safe_dump_fields
|
14
|
+
#=> [:custid, :role, :verified, :updated, :created, :secrets_created, :active]
|
15
|
+
|
16
|
+
## Implementing models like Customer can safely dump their fields
|
17
|
+
@cust = Customer.new
|
18
|
+
@cust.custid = "test@example.com"
|
19
|
+
@cust.role = "user"
|
20
|
+
@cust.verified = true
|
21
|
+
@cust.created = Time.now.to_i
|
22
|
+
@cust.updated = Time.now.to_i
|
23
|
+
@safe_dump = @cust.safe_dump
|
24
|
+
@safe_dump.keys.sort
|
25
|
+
#=> [:active, :created, :custid, :role, :secrets_created, :updated, :verified]
|
26
|
+
|
27
|
+
## Implementing models like Customer do have other fields
|
28
|
+
## that are by default considered not safe to dump.
|
29
|
+
@cust1 = Customer.new
|
30
|
+
@cust1.email = "test@example.com"
|
31
|
+
|
32
|
+
@all_non_safe_fields = @cust1.instance_variables.map { |el|
|
33
|
+
el.to_s[1..-1].to_sym # slice off the leading @
|
34
|
+
}.sort
|
35
|
+
|
36
|
+
(@all_non_safe_fields - Customer.safe_dump_fields).sort
|
37
|
+
#=> [:custom_domains, :email, :password_reset, :sessions, :stripe_customer, :timeline]
|
38
|
+
|
39
|
+
## Implementing models like Customer can rest assured knowing
|
40
|
+
## any other field not in the safe list will not be dumped.
|
41
|
+
@cust2 = Customer.new
|
42
|
+
@cust2.email = "test@example.com"
|
43
|
+
@cust2.custid = "test@example.com"
|
44
|
+
@all_safe_fields = @cust2.safe_dump.keys.sort
|
45
|
+
|
46
|
+
@all_non_safe_fields = @cust2.instance_variables.map { |el|
|
47
|
+
el.to_s[1..-1].to_sym # slice off the leading @
|
48
|
+
}.sort
|
49
|
+
p [1, all_non_safe_fields: @all_non_safe_fields]
|
50
|
+
# Check if any of the non-safe fields are in the safe dump
|
51
|
+
(@all_non_safe_fields & @all_safe_fields) - [:custid, :role, :verified, :updated, :created, :secrets_created]
|
52
|
+
#=> []
|
53
|
+
|
54
|
+
## Bone does not have safe_dump feature enabled
|
55
|
+
Bone.respond_to?(:safe_dump_fields)
|
56
|
+
#=> false
|
57
|
+
|
58
|
+
## Bone instances do not have safe_dump method
|
59
|
+
@bone = Bone.new(name: "Rex", age: 3)
|
60
|
+
@bone.respond_to?(:safe_dump)
|
61
|
+
#=> false
|
62
|
+
|
63
|
+
## Blone has safe_dump feature enabled
|
64
|
+
Blone.respond_to?(:safe_dump_fields)
|
65
|
+
#=> true
|
66
|
+
|
67
|
+
## Blone has empty safe_dump_fields
|
68
|
+
Blone.safe_dump_fields
|
69
|
+
#=> []
|
70
|
+
|
71
|
+
## Blone instances have safe_dump method
|
72
|
+
@blone = Blone.new(name: "Fido", age: 5)
|
73
|
+
@blone.respond_to?(:safe_dump)
|
74
|
+
#=> true
|
75
|
+
|
76
|
+
## Blone safe_dump returns an empty hash
|
77
|
+
@blone.safe_dump
|
78
|
+
#=> {}
|
79
|
+
|
80
|
+
# Teardown
|
81
|
+
@cust2.destroy!
|
82
|
+
@bone = nil
|
83
|
+
@blone = nil
|