familia 1.2.1 → 2.0.0.pre.pre

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +68 -0
  3. data/.github/workflows/docs.yml +64 -0
  4. data/.gitignore +4 -0
  5. data/.pre-commit-config.yaml +3 -1
  6. data/.rubocop.yml +16 -9
  7. data/.rubocop_todo.yml +177 -31
  8. data/.yardopts +9 -0
  9. data/CLAUDE.md +141 -0
  10. data/Gemfile +15 -2
  11. data/Gemfile.lock +76 -34
  12. data/README.md +39 -23
  13. data/bin/irb +3 -0
  14. data/docs/connection_pooling.md +317 -0
  15. data/familia.gemspec +9 -5
  16. data/lib/familia/base.rb +19 -9
  17. data/lib/familia/connection.rb +232 -65
  18. data/lib/familia/core_ext.rb +1 -1
  19. data/lib/familia/datatype/commands.rb +59 -0
  20. data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
  21. data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
  22. data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
  23. data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
  24. data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
  25. data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
  26. data/lib/familia/datatype.rb +243 -0
  27. data/lib/familia/errors.rb +5 -2
  28. data/lib/familia/features/expiration.rb +33 -34
  29. data/lib/familia/features/quantization.rb +9 -3
  30. data/lib/familia/features/safe_dump.rb +2 -3
  31. data/lib/familia/features.rb +2 -2
  32. data/lib/familia/horreum/class_methods.rb +97 -110
  33. data/lib/familia/horreum/commands.rb +46 -51
  34. data/lib/familia/horreum/connection.rb +82 -0
  35. data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
  36. data/lib/familia/horreum/serialization.rb +61 -198
  37. data/lib/familia/horreum/settings.rb +6 -17
  38. data/lib/familia/horreum/utils.rb +11 -10
  39. data/lib/familia/horreum.rb +69 -60
  40. data/lib/familia/logging.rb +12 -12
  41. data/lib/familia/multi_result.rb +72 -0
  42. data/lib/familia/refinements.rb +7 -44
  43. data/lib/familia/settings.rb +11 -11
  44. data/lib/familia/utils.rb +123 -90
  45. data/lib/familia/version.rb +4 -21
  46. data/lib/familia.rb +17 -12
  47. data/lib/middleware/database_middleware.rb +150 -0
  48. data/try/configuration/scenarios_try.rb +65 -0
  49. data/try/core/connection_try.rb +58 -0
  50. data/try/core/errors_try.rb +93 -0
  51. data/try/core/extensions_try.rb +26 -0
  52. data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
  53. data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
  54. data/try/core/middleware_try.rb +68 -0
  55. data/try/core/refinements_try.rb +39 -0
  56. data/try/core/settings_try.rb +76 -0
  57. data/try/core/tools_try.rb +54 -0
  58. data/try/core/utils_try.rb +189 -0
  59. data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
  60. data/try/datatypes/datatype_base_try.rb +69 -0
  61. data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
  62. data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
  63. data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
  64. data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
  65. data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
  66. data/try/edge_cases/empty_identifiers_try.rb +48 -0
  67. data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -7
  68. data/try/edge_cases/json_serialization_try.rb +85 -0
  69. data/try/edge_cases/race_conditions_try.rb +60 -0
  70. data/try/edge_cases/reserved_keywords_try.rb +59 -0
  71. data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +60 -59
  72. data/try/edge_cases/ttl_side_effects_try.rb +51 -0
  73. data/try/features/expiration_try.rb +86 -0
  74. data/try/features/quantization_try.rb +90 -0
  75. data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
  76. data/try/features/safe_dump_try.rb +137 -0
  77. data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
  78. data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
  79. data/try/horreum/class_methods_try.rb +41 -0
  80. data/try/horreum/commands_try.rb +49 -0
  81. data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
  82. data/try/horreum/relations_try.rb +146 -0
  83. data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
  84. data/try/horreum/settings_try.rb +43 -0
  85. data/try/integration/cross_component_try.rb +46 -0
  86. data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
  87. data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
  88. data/try/models/datatype_base_try.rb +101 -0
  89. data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
  90. data/try/performance/benchmarks_try.rb +55 -0
  91. data/try/pooling/README.md +20 -0
  92. data/try/pooling/configurable_stress_test_try.rb +435 -0
  93. data/try/pooling/connection_pool_test_try.rb +273 -0
  94. data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  95. data/try/pooling/lib/connection_pool_metrics.rb +372 -0
  96. data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
  97. data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
  98. data/try/pooling/lib/visualize_stress_results.rb +434 -0
  99. data/try/pooling/pool_siege_try.rb +509 -0
  100. data/try/pooling/run_stress_tests_try.rb +482 -0
  101. data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
  102. data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
  103. data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
  104. data/try/prototypes/atomic_saves_v4.rb +105 -0
  105. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
  106. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
  107. metadata +140 -43
  108. data/.github/workflows/ruby.yml +0 -71
  109. data/VERSION.yml +0 -4
  110. data/lib/familia/redistype/commands.rb +0 -59
  111. data/lib/familia/redistype.rb +0 -228
  112. data/lib/familia/tools.rb +0 -68
  113. data/lib/redis_middleware.rb +0 -109
  114. data/try/20_redis_type_try.rb +0 -70
  115. data/try/91_json_bug_try.rb +0 -86
@@ -1,228 +0,0 @@
1
- # rubocop:disable all
2
-
3
- require_relative 'redistype/commands'
4
- require_relative 'redistype/serialization'
5
-
6
- module Familia
7
-
8
- # RedisType - Base class for Redis data type wrappers
9
- #
10
- # This class provides common functionality for various Redis data types
11
- # such as String, List, Set, SortedSet, and HashKey.
12
- #
13
- # @abstract Subclass and implement Redis data type specific methods
14
- class RedisType
15
- include Familia::Base
16
- extend Familia::Features
17
-
18
- @registered_types = {}
19
- @valid_options = %i[class parent ttl default db key redis suffix prefix]
20
- @db = nil
21
-
22
- feature :expiration
23
- feature :quantization
24
-
25
- class << self
26
- attr_reader :registered_types, :valid_options, :has_relations
27
- attr_accessor :parent
28
- attr_writer :db, :uri
29
- end
30
-
31
- module ClassMethods
32
- # To be called inside every class that inherits RedisType
33
- # +methname+ is the term used for the class and instance methods
34
- # that are created for the given +klass+ (e.g. set, list, etc)
35
- def register(klass, methname)
36
- Familia.ld "[#{self}] Registering #{klass} as #{methname.inspect}"
37
-
38
- @registered_types[methname] = klass
39
- end
40
-
41
- def db(val = nil)
42
- @db = val unless val.nil?
43
- @db || parent&.db
44
- end
45
-
46
- def uri(val = nil)
47
- @uri = val unless val.nil?
48
- @uri || (parent ? parent.uri : Familia.uri)
49
- end
50
-
51
- def inherited(obj)
52
- Familia.trace :REDISTYPE, nil, "#{obj} is my kinda type", caller(1..1) if Familia.debug?
53
- obj.db = db
54
- obj.ttl = ttl # method added via Features::Expiration
55
- obj.uri = uri
56
- obj.parent = self
57
- super(obj)
58
- end
59
-
60
- def valid_keys_only(opts)
61
- opts.select { |k, _| RedisType.valid_options.include? k }
62
- end
63
-
64
- def has_relations?
65
- @has_relations ||= false
66
- end
67
- end
68
- extend ClassMethods
69
-
70
- attr_reader :keystring, :parent, :opts
71
- attr_writer :dump_method, :load_method
72
-
73
- # +keystring+: If parent is set, this will be used as the suffix
74
- # for rediskey. Otherwise this becomes the value of the key.
75
- # If this is an Array, the elements will be joined.
76
- #
77
- # Options:
78
- #
79
- # :class => A class that responds to Familia.load_method and
80
- # Familia.dump_method. These will be used when loading and
81
- # saving data from/to redis to unmarshal/marshal the class.
82
- #
83
- # :parent => The Familia object that this redistype object belongs
84
- # to. This can be a class that includes Familia or an instance.
85
- #
86
- # :ttl => the time to live in seconds. When not nil, this will
87
- # set the redis expire for this key whenever #save is called.
88
- # You can also call it explicitly via #update_expiration.
89
- #
90
- # :default => the default value (String-only)
91
- #
92
- # :db => the redis database to use (ignored if :redis is used).
93
- #
94
- # :redis => an instance of Redis.
95
- #
96
- # :key => a hardcoded key to use instead of the deriving the from
97
- # the name and parent (e.g. a derived key: customer:custid:secret_counter).
98
- #
99
- # :suffix => the suffix to use for the key (e.g. 'scores' in customer:custid:scores).
100
- # :prefix => the prefix to use for the key (e.g. 'customer' in customer:custid:scores).
101
- #
102
- # Connection precendence: uses the redis connection of the parent or the
103
- # value of opts[:redis] or Familia.redis (in that order).
104
- def initialize(keystring, opts = {})
105
- #Familia.ld " [initializing] #{self.class} #{opts}"
106
- @keystring = keystring
107
- @keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array)
108
-
109
- # Remove all keys from the opts that are not in the allowed list
110
- @opts = opts || {}
111
- @opts = RedisType.valid_keys_only(@opts)
112
-
113
- # Apply the options to instance method setters of the same name
114
- @opts.each do |k, v|
115
- # Bewarde logging :parent instance here implicitly calls #to_s which for
116
- # some classes could include the identifier which could still be nil at
117
- # this point. This would result in a Familia::Problem being raised. So
118
- # to be on the safe-side here until we have a better understanding of
119
- # the issue, we'll just log the class name for each key-value pair.
120
- Familia.ld " [setting] #{k} #{v.class}"
121
- send(:"#{k}=", v) if respond_to? :"#{k}="
122
- end
123
-
124
- init if respond_to? :init
125
- end
126
-
127
- def redis
128
- return @redis if @redis
129
-
130
- parent? ? parent.redis : Familia.redis(opts[:db])
131
- end
132
-
133
- # Produces the full Redis key for this object.
134
- #
135
- # @return [String] The full Redis key.
136
- #
137
- # This method determines the appropriate Redis key based on the context of the RedisType object:
138
- #
139
- # 1. If a hardcoded key is set in the options, it returns that key.
140
- # 2. For instance-level RedisType objects, it uses the parent instance's rediskey method.
141
- # 3. For class-level RedisType objects, it uses the parent class's rediskey method.
142
- # 4. For standalone RedisType objects, it uses the keystring as the full Redis key.
143
- #
144
- # For class-level RedisType objects (parent_class? == true):
145
- # - The suffix is optional and used to differentiate between different types of objects.
146
- # - If no suffix is provided, the class's default suffix is used (via the self.suffix method).
147
- # - If a nil suffix is explicitly passed, it won't appear in the resulting Redis key.
148
- # - Passing nil as the suffix is how class-level RedisType objects are created without
149
- # the global default 'object' suffix.
150
- #
151
- # @example Instance-level RedisType
152
- # user_instance.some_redistype.rediskey # => "user:123:some_redistype"
153
- #
154
- # @example Class-level RedisType
155
- # User.some_redistype.rediskey # => "user:some_redistype"
156
- #
157
- # @example Standalone RedisType
158
- # RedisType.new("mykey").rediskey # => "mykey"
159
- #
160
- # @example Class-level RedisType with explicit nil suffix
161
- # User.rediskey("123", nil) # => "user:123"
162
- #
163
- def rediskey
164
- # Return the hardcoded key if it's set. This is useful for
165
- # support legacy keys that aren't derived in the same way.
166
- return opts[:key] if opts[:key]
167
-
168
- if parent_instance?
169
- # This is an instance-level redistype object so the parent instance's
170
- # rediskey method is defined in Familia::Horreum::InstanceMethods.
171
- parent.rediskey(keystring)
172
- elsif parent_class?
173
- # This is a class-level redistype object so the parent class' rediskey
174
- # method is defined in Familia::Horreum::ClassMethods.
175
- parent.rediskey(keystring, nil)
176
- else
177
- # This is a standalone RedisType object where it's keystring
178
- # is the full redis key.
179
- keystring
180
- end
181
- end
182
-
183
- def class?
184
- !@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
185
- end
186
-
187
- def parent_instance?
188
- parent.is_a?(Familia::Horreum)
189
- end
190
-
191
- def parent_class?
192
- parent.is_a?(Class) && parent <= Familia::Horreum
193
- end
194
-
195
- def parent?
196
- parent_class? || parent_instance?
197
- end
198
-
199
- def parent
200
- @opts[:parent]
201
- end
202
-
203
- def db
204
- @opts[:db] || self.class.db
205
- end
206
-
207
- def uri
208
- @opts[:uri] || self.class.uri
209
- end
210
-
211
- def dump_method
212
- @dump_method || self.class.dump_method
213
- end
214
-
215
- def load_method
216
- @load_method || self.class.load_method
217
- end
218
-
219
- include Commands
220
- include Serialization
221
- end
222
-
223
- require_relative 'redistype/types/list'
224
- require_relative 'redistype/types/unsorted_set'
225
- require_relative 'redistype/types/sorted_set'
226
- require_relative 'redistype/types/hashkey'
227
- require_relative 'redistype/types/string'
228
- end
data/lib/familia/tools.rb DELETED
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Familia
4
- module Tools
5
- extend self
6
- def move_keys(filter, source_uri, target_uri, &each_key)
7
- raise "Source and target are the same (#{target_uri})" if target_uri == source_uri
8
-
9
- Familia.connect target_uri
10
- source_keys = Familia.redis(source_uri).keys(filter)
11
- puts "Moving #{source_keys.size} keys from #{source_uri} to #{target_uri} (filter: #{filter})"
12
- source_keys.each_with_index do |key, idx|
13
- type = Familia.redis(source_uri).type key
14
- ttl = Familia.redis(source_uri).ttl key
15
- if source_uri.host == target_uri.host && source_uri.port == target_uri.port
16
- Familia.redis(source_uri).move key, target_uri.db
17
- else
18
- case type
19
- when 'string'
20
- Familia.redis(source_uri).get key
21
- when 'list'
22
- Familia.redis(source_uri).lrange key, 0, -1
23
- when 'set'
24
- Familia.redis(source_uri).smembers key
25
- else
26
- raise Familia::Problem, "unknown key type: #{type}"
27
- end
28
- raise 'Not implemented'
29
- end
30
- yield(idx, type, key, ttl) unless each_key.nil?
31
- end
32
- end
33
-
34
- # Use the return value from each_key as the new key name
35
- def rename(filter, source_uri, target_uri = nil, &each_key)
36
- target_uri ||= source_uri
37
- move_keys filter, source_uri, target_uri if source_uri != target_uri
38
- source_keys = Familia.redis(source_uri).keys(filter)
39
- puts "Renaming #{source_keys.size} keys from #{source_uri} (filter: #{filter})"
40
- source_keys.each_with_index do |key, idx|
41
- Familia.trace :RENAME1, Familia.redis(source_uri), "#{key}", ''
42
- type = Familia.redis(source_uri).type key
43
- ttl = Familia.redis(source_uri).ttl key
44
- newkey = yield(idx, type, key, ttl) unless each_key.nil?
45
- Familia.trace :RENAME2, Familia.redis(source_uri), "#{key} -> #{newkey}", caller(1..1).first
46
- Familia.redis(source_uri).renamenx key, newkey
47
- end
48
- end
49
-
50
- def get_any(keyname, uri = nil)
51
- type = Familia.redis(uri).type keyname
52
- case type
53
- when 'string'
54
- Familia.redis(uri).get keyname
55
- when 'list'
56
- Familia.redis(uri).lrange(keyname, 0, -1) || []
57
- when 'set'
58
- Familia.redis(uri).smembers(keyname) || []
59
- when 'zset'
60
- Familia.redis(uri).zrange(keyname, 0, -1) || []
61
- when 'hash'
62
- Familia.redis(uri).hgetall(keyname) || {}
63
- else
64
- nil
65
- end
66
- end
67
- end
68
- end
@@ -1,109 +0,0 @@
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,70 +0,0 @@
1
-
2
- require_relative '../lib/familia'
3
- require_relative './test_helpers'
4
-
5
-
6
- @limiter1 = Limiter.new :requests
7
-
8
-
9
- ## Redis Types are unique per instance of a Familia class
10
- @a = Bone.new 'atoken1', :name1
11
- @b = Bone.new 'atoken2', :name2
12
- p [@a.object_id, @b.object_id]
13
- p [@a.owners.parent.class, @b.owners.parent.class]
14
- p [@a.owners.parent.object_id, @b.owners.parent.object_id]
15
- p [@a.owners.rediskey, @b.owners.rediskey]
16
- p [@a.token, @b.token]
17
- p [@a.name, @b.name]
18
- @a.owners.rediskey.eql?(@b.owners.rediskey)
19
- #=> false
20
-
21
- ## Redis Types are frozen
22
- @a.owners.frozen?
23
- #=> true
24
-
25
- ## Limiter#qstamp
26
- @limiter1.counter.qstamp(10.minutes, '%H:%M', 1302468980)
27
- ##=> '20:50'
28
-
29
- ## Redis Types can be stored to quantized stamp suffix
30
- @limiter1.counter.rediskey
31
- ##=> "v1:limiter:requests:counter:20:50"
32
-
33
- ## Limiter#qstamp as a number
34
- @limiter2 = Limiter.new :requests
35
- p [@limiter1.ttl, @limiter2.ttl]
36
- p [@limiter1.counter.parent.ttl, @limiter2.counter.parent.ttl]
37
- @limiter2.counter.qstamp(10.minutes, pattern: nil, time: 1302468980)
38
- #=> 1302468600
39
-
40
- ## Redis Types can be stored to quantized numeric suffix. This
41
- ## tryouts is disabled b/c `RedisType#rediskey` takes no args
42
- ## and relies on the `class Limiter` definition in test_helpers.rb
43
- ## for the `:quantize` option. The quantized suffix for the Limiter
44
- ## class is `'%H:%M'` so its redis keys will always look like that.
45
- @limiter2.counter.rediskey
46
- ##=> "v1:limiter:requests:counter:1302468600"
47
-
48
- ## Increment counter
49
- @limiter1.counter.delete!
50
- @limiter1.counter.increment
51
- #=> 1
52
-
53
- ## Check counter ttl
54
- @limiter1.counter.ttl
55
- #=> 3600.0
56
-
57
- ## Check limiter ttl
58
- @limiter1.ttl
59
- #=> 1800.0
60
-
61
- ## Check ttl for a different instance
62
- ## (this exists to make sure options are cloned for each instance)
63
- @limiter3 = Limiter.new :requests
64
- @limiter3.counter.ttl
65
- #=> 3600.0
66
-
67
- ## Check realttl
68
- sleep 1 # Redis ttls are in seconds so we can't wait any less time than this (without mocking)
69
- @limiter1.counter.realttl
70
- #=> 3600-1
@@ -1,86 +0,0 @@
1
- # try/91_json_bug_try.rb
2
-
3
- require_relative '../lib/familia'
4
- require_relative './test_helpers'
5
-
6
- Familia.debug = false
7
-
8
- # Define a simple model with fields that should handle JSON data
9
- class JsonTest < Familia::Horreum
10
- identifier :id
11
- field :id
12
- field :config # This should be able to store Hash objects
13
- field :tags # This should be able to store Array objects
14
- field :simple # This should store simple strings as-is
15
- end
16
-
17
- # Create an instance with JSON data
18
- @test_obj = JsonTest.new
19
- @test_obj.id = "json_test_1"
20
-
21
- ## Test 1: Store a Hash - should serialize to JSON automatically
22
- @test_obj.config = { theme: "dark", notifications: true, settings: { volume: 80 } }
23
- @test_obj.config.class
24
- #=> Hash
25
-
26
- ## Test 2: Store an Array - should serialize to JSON automatically
27
- @test_obj.tags = ["ruby", "redis", "json", "familia"]
28
- @test_obj.tags.class
29
- #=> Array
30
-
31
- ## Test 3: Store a simple string - should remain as string
32
- @test_obj.simple = "just a string"
33
- @test_obj.simple.class
34
- #=> String
35
-
36
- ## Save the object - this should call serialize_value and use to_json
37
- @test_obj.save
38
- #=> true
39
-
40
- ## Verify what's actually stored in Redis (raw)
41
- raw_data = @test_obj.hgetall
42
- p [:plop, @test_obj]
43
- puts "Raw Redis data:"
44
- raw_data
45
- #=> {"id"=>"json_test_1", "config"=>"{\"theme\":\"dark\",\"notifications\":true,\"settings\":{\"volume\":80}}", "tags"=>"[\"ruby\",\"redis\",\"json\",\"familia\"]", "simple"=>"just a string", "key"=>"json_test_1"}
46
-
47
- ## BUG: After refresh, JSON data comes back as strings instead of parsed objects
48
- @test_obj.refresh!
49
-
50
- ## Test 4: Hash should be deserialized back to Hash
51
- puts "Config after refresh:"
52
- puts @test_obj.config.inspect
53
- puts "Config class: "
54
- [@test_obj.config.class, @test_obj.config.inspect]
55
- #=> [Hash, "{:theme=>\"dark\", :notifications=>true, :settings=>{:volume=>80}}"]
56
-
57
- ## Test 5: Array should be deserialized back to Array
58
- puts "Tags after refresh:"
59
- puts @test_obj.tags.inspect
60
- puts "Tags class: #{@test_obj.tags.class}"
61
- @test_obj.tags.inspect
62
- @test_obj.tags.class
63
- #=> ["ruby", "redis", "json", "familia"]
64
- #=> Array
65
-
66
- ## Test 6: Simple string should remain a string (this works correctly)
67
- puts "Simple after refresh:"
68
- puts @test_obj.simple.inspect
69
- puts "Simple class: #{@test_obj.simple.class}"
70
- @test_obj.simple.inspect
71
- @test_obj.simple.class
72
- #=> "just a string"
73
- #=> String
74
-
75
- ## Demonstrate the asymmetry:
76
- puts "\n=== ASYMMETRY DEMONSTRATION ==="
77
- puts "Before save: config is #{@test_obj.config.class}"
78
- @test_obj.config = { example: "data" }
79
- puts "After assignment: config is #{@test_obj.config.class}"
80
- @test_obj.save
81
- puts "After save: config is still #{@test_obj.config.class}"
82
- @test_obj.refresh!
83
- puts "After refresh: config is now #{@test_obj.config.class}!"
84
-
85
- ## Clean up
86
- @test_obj.destroy!