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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.pre-commit-config.yaml +1 -1
  4. data/.rubocop.yml +75 -0
  5. data/.rubocop_todo.yml +63 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +47 -15
  8. data/README.md +11 -12
  9. data/VERSION.yml +4 -3
  10. data/familia.gemspec +18 -13
  11. data/lib/familia/base.rb +33 -0
  12. data/lib/familia/connection.rb +87 -0
  13. data/lib/familia/core_ext.rb +119 -124
  14. data/lib/familia/errors.rb +33 -0
  15. data/lib/familia/features/api_version.rb +19 -0
  16. data/lib/familia/features/atomic_saves.rb +8 -0
  17. data/lib/familia/features/quantizer.rb +35 -0
  18. data/lib/familia/features/safe_dump.rb +175 -0
  19. data/lib/familia/features.rb +51 -0
  20. data/lib/familia/horreum/class_methods.rb +240 -0
  21. data/lib/familia/horreum/commands.rb +59 -0
  22. data/lib/familia/horreum/relations_management.rb +141 -0
  23. data/lib/familia/horreum/serialization.rb +154 -0
  24. data/lib/familia/horreum/settings.rb +63 -0
  25. data/lib/familia/horreum/utils.rb +43 -0
  26. data/lib/familia/horreum.rb +198 -0
  27. data/lib/familia/logging.rb +249 -0
  28. data/lib/familia/redistype/commands.rb +56 -0
  29. data/lib/familia/redistype/serialization.rb +110 -0
  30. data/lib/familia/redistype.rb +185 -0
  31. data/lib/familia/settings.rb +38 -0
  32. data/lib/familia/types/hashkey.rb +108 -0
  33. data/lib/familia/types/list.rb +155 -0
  34. data/lib/familia/types/sorted_set.rb +234 -0
  35. data/lib/familia/types/string.rb +115 -0
  36. data/lib/familia/types/unsorted_set.rb +123 -0
  37. data/lib/familia/utils.rb +129 -0
  38. data/lib/familia/version.rb +25 -0
  39. data/lib/familia.rb +56 -161
  40. data/lib/redis_middleware.rb +109 -0
  41. data/try/00_familia_try.rb +5 -4
  42. data/try/10_familia_try.rb +21 -17
  43. data/try/20_redis_type_try.rb +67 -0
  44. data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
  45. data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
  46. data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
  47. data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
  48. data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
  49. data/try/26_redis_bool_try.rb +10 -6
  50. data/try/27_redis_horreum_try.rb +40 -0
  51. data/try/30_familia_object_try.rb +21 -20
  52. data/try/35_feature_safedump_try.rb +83 -0
  53. data/try/40_customer_try.rb +140 -0
  54. data/try/41_customer_safedump_try.rb +86 -0
  55. data/try/test_helpers.rb +186 -0
  56. metadata +50 -47
  57. data/lib/familia/helpers.rb +0 -70
  58. data/lib/familia/object.rb +0 -533
  59. data/lib/familia/redisobject.rb +0 -1017
  60. data/lib/familia/test_helpers.rb +0 -40
  61. data/lib/familia/tools.rb +0 -67
  62. 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
@@ -1,11 +1,12 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
3
1
 
4
- Familia.apiversion = 'v1'
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.redis_objects.keys # consistent b/c hashes are ordered
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
@@ -1,25 +1,28 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
3
1
 
4
- ## Has all redis objects
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
- ## Familia created class methods for redis object class
10
- Familia::ClassMethods.public_method_defined? :list?
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 redis object class
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 redis object class
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 redis objects
22
- Bone.redis_objects.is_a?(Hash) && Bone.redis_objects.has_key?(:owners)
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 redis object def
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 Time.parse('2011-04-10 20:56:20 UTC').utc
40
- #=> 1302468980
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,5 +1,5 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
1
+ require_relative '../lib/familia'
2
+ require_relative './test_helpers'
3
3
 
4
4
  # Familia.debug = true
5
5
 
@@ -1,6 +1,6 @@
1
1
 
2
- require 'familia'
3
- require 'familia/test_helpers'
2
+ require_relative '../lib/familia'
3
+ require_relative './test_helpers'
4
4
 
5
5
  @a = Bone.new 'atoken', 'akey'
6
6
 
@@ -1,6 +1,6 @@
1
1
 
2
- require 'familia'
3
- require 'familia/test_helpers'
2
+ require_relative '../lib/familia'
3
+ require_relative './test_helpers'
4
4
 
5
5
  @a = Bone.new 'atoken', 'akey'
6
6
 
@@ -1,13 +1,13 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
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
- #=> 'v1:bone:atoken2:akey:object'
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
- require 'familia'
2
- require 'familia/test_helpers'
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
 
@@ -1,5 +1,5 @@
1
- require 'familia'
2
- require 'familia/test_helpers'
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 TypeError => e
20
+ rescue Familia::HighRiskFactor => e
21
21
  e.message
22
22
  end
23
- #=> "Cannot store test => true (TrueClass) in key"
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 TypeError => e
32
+ rescue Familia::HighRiskFactor => e
33
33
  e.message
34
34
  end
35
- #=> "Cannot store test => nil (NilClass) in key"
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
- require 'familia'
2
- require 'familia/test_helpers'
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#index
12
- @a.index
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
- #=> 'v1:bone:atoken:akey:object'
21
+ #=> 'bone:atoken:akey:object'
22
22
 
23
23
  ## Familia#rediskey
24
24
  @a.rediskey
25
- #=> 'v1:bone:atoken:akey:object'
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.instances
33
- Customer.instances.all.collect(&:custid)
34
- #=> [:delano]
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
- #=> :delano
40
+ [obj.class, obj.custid]
41
+ #=> [Customer, 'delano']
40
42
 
41
43
  ## Customer.destroy
42
44
  @cust.destroy!
43
- #=> 1
45
+ #=> true
44
46
 
45
47
  ## Customer.instances
46
- Customer.instances.size
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
- #=> 'v1:customer:customers'
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