familia 1.2.3 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +68 -0
- data/.github/workflows/docs.yml +64 -0
- data/.gitignore +3 -0
- data/.pre-commit-config.yaml +3 -1
- data/.rubocop.yml +16 -9
- data/.rubocop_todo.yml +177 -31
- data/.yardopts +9 -0
- data/CLAUDE.md +141 -0
- data/Gemfile +15 -2
- data/Gemfile.lock +61 -61
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +317 -0
- data/familia.gemspec +8 -5
- data/lib/familia/base.rb +19 -9
- data/lib/familia/connection.rb +232 -65
- data/lib/familia/core_ext.rb +1 -1
- data/lib/familia/datatype/commands.rb +59 -0
- data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
- data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
- data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
- data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
- data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
- data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
- data/lib/familia/datatype.rb +243 -0
- data/lib/familia/errors.rb +5 -2
- data/lib/familia/features/expiration.rb +33 -34
- data/lib/familia/features/quantization.rb +9 -3
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features.rb +2 -2
- data/lib/familia/horreum/class_methods.rb +97 -130
- data/lib/familia/horreum/commands.rb +46 -51
- data/lib/familia/horreum/connection.rb +82 -0
- data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
- data/lib/familia/horreum/serialization.rb +61 -198
- data/lib/familia/horreum/settings.rb +6 -17
- data/lib/familia/horreum/utils.rb +11 -10
- data/lib/familia/horreum.rb +69 -60
- data/lib/familia/logging.rb +12 -12
- data/lib/familia/multi_result.rb +72 -0
- data/lib/familia/refinements.rb +7 -44
- data/lib/familia/settings.rb +11 -11
- data/lib/familia/utils.rb +123 -90
- data/lib/familia/version.rb +4 -21
- data/lib/familia.rb +17 -12
- data/lib/middleware/database_middleware.rb +150 -0
- data/try/configuration/scenarios_try.rb +65 -0
- data/try/core/connection_try.rb +58 -0
- data/try/core/errors_try.rb +93 -0
- data/try/core/extensions_try.rb +26 -0
- data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
- data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
- data/try/core/middleware_try.rb +68 -0
- data/try/core/refinements_try.rb +39 -0
- data/try/core/settings_try.rb +76 -0
- data/try/core/tools_try.rb +54 -0
- data/try/core/utils_try.rb +189 -0
- data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
- data/try/datatypes/datatype_base_try.rb +69 -0
- data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
- data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
- data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
- data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
- data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
- data/try/edge_cases/empty_identifiers_try.rb +48 -0
- data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -8
- data/try/edge_cases/json_serialization_try.rb +85 -0
- data/try/edge_cases/race_conditions_try.rb +60 -0
- data/try/edge_cases/reserved_keywords_try.rb +59 -0
- data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +63 -60
- data/try/edge_cases/ttl_side_effects_try.rb +51 -0
- data/try/features/expiration_try.rb +86 -0
- data/try/features/quantization_try.rb +90 -0
- data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
- data/try/features/safe_dump_try.rb +137 -0
- data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
- data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
- data/try/horreum/class_methods_try.rb +41 -0
- data/try/horreum/commands_try.rb +49 -0
- data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
- data/try/horreum/relations_try.rb +146 -0
- data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
- data/try/horreum/settings_try.rb +43 -0
- data/try/integration/cross_component_try.rb +46 -0
- data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
- data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
- data/try/models/datatype_base_try.rb +101 -0
- data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
- data/try/performance/benchmarks_try.rb +55 -0
- data/try/pooling/README.md +20 -0
- data/try/pooling/configurable_stress_test_try.rb +435 -0
- data/try/pooling/connection_pool_test_try.rb +273 -0
- data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- data/try/pooling/lib/connection_pool_metrics.rb +372 -0
- data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
- data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
- data/try/pooling/lib/visualize_stress_results.rb +434 -0
- data/try/pooling/pool_siege_try.rb +509 -0
- data/try/pooling/run_stress_tests_try.rb +482 -0
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
- data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
- data/try/prototypes/atomic_saves_v4.rb +105 -0
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- metadata +124 -38
- data/.github/workflows/ruby.yml +0 -71
- data/VERSION.yml +0 -4
- data/lib/familia/redistype/commands.rb +0 -59
- data/lib/familia/redistype.rb +0 -228
- data/lib/familia/tools.rb +0 -68
- data/lib/redis_middleware.rb +0 -109
- data/try/20_redis_type_try.rb +0 -70
- data/try/91_json_bug_try.rb +0 -86
@@ -0,0 +1,243 @@
|
|
1
|
+
# lib/familia/datatype.rb
|
2
|
+
|
3
|
+
require_relative 'datatype/commands'
|
4
|
+
require_relative 'datatype/serialization'
|
5
|
+
|
6
|
+
module Familia
|
7
|
+
|
8
|
+
# DataType - Base class for Database data type wrappers
|
9
|
+
#
|
10
|
+
# This class provides common functionality for various Database data types
|
11
|
+
# such as String, List, Set, SortedSet, and HashKey.
|
12
|
+
#
|
13
|
+
# @abstract Subclass and implement Database data type specific methods
|
14
|
+
class DataType
|
15
|
+
include Familia::Base
|
16
|
+
extend Familia::Features
|
17
|
+
|
18
|
+
@registered_types = {}
|
19
|
+
@valid_options = %i[class parent default_expiration default logical_database dbkey dbclient suffix prefix]
|
20
|
+
@logical_database = 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 :logical_database, :uri
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# To be called inside every class that inherits DataType
|
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 logical_database(val = nil)
|
42
|
+
@logical_database = val unless val.nil?
|
43
|
+
@logical_database || parent&.logical_database
|
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 :DATATYPE, nil, "#{obj} is my kinda type", caller(1..1) if Familia.debug?
|
53
|
+
obj.logical_database = logical_database
|
54
|
+
obj.default_expiration = default_expiration # 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, _| DataType.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 dbkey. 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 the database to unmarshal/marshal the class.
|
82
|
+
#
|
83
|
+
# :parent => The Familia object that this datatype object belongs
|
84
|
+
# to. This can be a class that includes Familia or an instance.
|
85
|
+
#
|
86
|
+
# :default_expiration => the time to live in seconds. When not nil, this will
|
87
|
+
# set the default expiration for this dbkey whenever #save is called.
|
88
|
+
# You can also call it explicitly via #update_expiration.
|
89
|
+
#
|
90
|
+
# :default => the default value (String-only)
|
91
|
+
#
|
92
|
+
# :logical_database => the logical database index to use (ignored if :dbclient is used).
|
93
|
+
#
|
94
|
+
# :dbclient => an instance of database client.
|
95
|
+
#
|
96
|
+
# :dbkey => 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 database connection of the parent or the
|
103
|
+
# value of opts[:dbclient] or Familia.dbclient (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 = DataType.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 dbclient
|
128
|
+
return Fiber[:familia_transaction] if Fiber[:familia_transaction]
|
129
|
+
return @dbclient if @dbclient
|
130
|
+
|
131
|
+
parent? ? parent.dbclient : Familia.dbclient(opts[:logical_database])
|
132
|
+
end
|
133
|
+
|
134
|
+
# Produces the full dbkey for this object.
|
135
|
+
#
|
136
|
+
# @return [String] The full dbkey.
|
137
|
+
#
|
138
|
+
# This method determines the appropriate dbkey based on the context of the DataType object:
|
139
|
+
#
|
140
|
+
# 1. If a hardcoded key is set in the options, it returns that key.
|
141
|
+
# 2. For instance-level DataType objects, it uses the parent instance's dbkey method.
|
142
|
+
# 3. For class-level DataType objects, it uses the parent class's dbkey method.
|
143
|
+
# 4. For standalone DataType objects, it uses the keystring as the full dbkey.
|
144
|
+
#
|
145
|
+
# For class-level DataType objects (parent_class? == true):
|
146
|
+
# - The suffix is optional and used to differentiate between different types of objects.
|
147
|
+
# - If no suffix is provided, the class's default suffix is used (via the self.suffix method).
|
148
|
+
# - If a nil suffix is explicitly passed, it won't appear in the resulting dbkey.
|
149
|
+
# - Passing nil as the suffix is how class-level DataType objects are created without
|
150
|
+
# the global default 'object' suffix.
|
151
|
+
#
|
152
|
+
# @example Instance-level DataType
|
153
|
+
# user_instance.some_datatype.dbkey # => "user:123:some_datatype"
|
154
|
+
#
|
155
|
+
# @example Class-level DataType
|
156
|
+
# User.some_datatype.dbkey # => "user:some_datatype"
|
157
|
+
#
|
158
|
+
# @example Standalone DataType
|
159
|
+
# DataType.new("mykey").dbkey # => "mykey"
|
160
|
+
#
|
161
|
+
# @example Class-level DataType with explicit nil suffix
|
162
|
+
# User.dbkey("123", nil) # => "user:123"
|
163
|
+
#
|
164
|
+
def dbkey
|
165
|
+
# Return the hardcoded key if it's set. This is useful for
|
166
|
+
# support legacy keys that aren't derived in the same way.
|
167
|
+
return opts[:dbkey] if opts[:dbkey]
|
168
|
+
|
169
|
+
if parent_instance?
|
170
|
+
# This is an instance-level datatype object so the parent instance's
|
171
|
+
# dbkey method is defined in Familia::Horreum::InstanceMethods.
|
172
|
+
parent.dbkey(keystring)
|
173
|
+
elsif parent_class?
|
174
|
+
# This is a class-level datatype object so the parent class' dbkey
|
175
|
+
# method is defined in Familia::Horreum::ClassMethods.
|
176
|
+
parent.dbkey(keystring, nil)
|
177
|
+
else
|
178
|
+
# This is a standalone DataType object where it's keystring
|
179
|
+
# is the full database key (dbkey).
|
180
|
+
keystring
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def class?
|
185
|
+
!@opts[:class].to_s.empty? && @opts[:class].is_a?(Familia)
|
186
|
+
end
|
187
|
+
|
188
|
+
def parent_instance?
|
189
|
+
parent.is_a?(Familia::Horreum)
|
190
|
+
end
|
191
|
+
|
192
|
+
def parent_class?
|
193
|
+
parent.is_a?(Class) && parent <= Familia::Horreum
|
194
|
+
end
|
195
|
+
|
196
|
+
def parent?
|
197
|
+
parent_class? || parent_instance?
|
198
|
+
end
|
199
|
+
|
200
|
+
def parent
|
201
|
+
@opts[:parent]
|
202
|
+
end
|
203
|
+
|
204
|
+
def logical_database
|
205
|
+
@opts[:logical_database] || self.class.logical_database
|
206
|
+
end
|
207
|
+
|
208
|
+
def uri
|
209
|
+
# If a specific URI is set in opts, use it
|
210
|
+
return @opts[:uri] if @opts[:uri]
|
211
|
+
|
212
|
+
# If parent has a DB set, create a URI with that DB
|
213
|
+
if parent? && parent.respond_to?(:logical_database) && parent.logical_database
|
214
|
+
base_uri = self.class.uri || Familia.uri
|
215
|
+
if base_uri
|
216
|
+
uri_with_db = base_uri.dup
|
217
|
+
uri_with_db.db = parent.logical_database
|
218
|
+
return uri_with_db
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Otherwise fall back to class URI
|
223
|
+
self.class.uri
|
224
|
+
end
|
225
|
+
|
226
|
+
def dump_method
|
227
|
+
@dump_method || self.class.dump_method
|
228
|
+
end
|
229
|
+
|
230
|
+
def load_method
|
231
|
+
@load_method || self.class.load_method
|
232
|
+
end
|
233
|
+
|
234
|
+
include Commands
|
235
|
+
include Serialization
|
236
|
+
end
|
237
|
+
|
238
|
+
require_relative 'datatype/types/list'
|
239
|
+
require_relative 'datatype/types/unsorted_set'
|
240
|
+
require_relative 'datatype/types/sorted_set'
|
241
|
+
require_relative 'datatype/types/hashkey'
|
242
|
+
require_relative 'datatype/types/string'
|
243
|
+
end
|
data/lib/familia/errors.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# lib/familia/errors.rb
|
2
|
+
#
|
3
3
|
module Familia
|
4
4
|
class Problem < RuntimeError; end
|
5
5
|
class NoIdentifier < Problem; end
|
@@ -31,6 +31,9 @@ module Familia
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
# Set Familia.connection_provider or use middleware to provide connections.
|
35
|
+
class NoConnectionAvailable < Problem; end
|
36
|
+
|
34
37
|
# Raised when attempting to refresh an object whose key doesn't exist in Redis
|
35
38
|
class KeyNotFoundError < Problem
|
36
39
|
attr_reader :key
|
@@ -1,19 +1,18 @@
|
|
1
|
-
#
|
2
|
-
# frozen_string_literal: true
|
1
|
+
# lib/familia/features/expiration.rb
|
3
2
|
|
4
3
|
|
5
4
|
module Familia::Features
|
6
5
|
|
7
6
|
module Expiration
|
8
|
-
@
|
7
|
+
@default_expiration = nil
|
9
8
|
|
10
9
|
module ClassMethods
|
11
10
|
|
12
|
-
attr_writer :
|
11
|
+
attr_writer :default_expiration
|
13
12
|
|
14
|
-
def
|
15
|
-
@
|
16
|
-
@
|
13
|
+
def default_expiration(v = nil)
|
14
|
+
@default_expiration = v.to_f unless v.nil?
|
15
|
+
@default_expiration || parent&.default_expiration || Familia.default_expiration
|
17
16
|
end
|
18
17
|
|
19
18
|
end
|
@@ -22,51 +21,51 @@ module Familia::Features
|
|
22
21
|
Familia.ld "[#{base}] Loaded #{self}"
|
23
22
|
base.extend ClassMethods
|
24
23
|
|
25
|
-
# Optionally define
|
24
|
+
# Optionally define default_expiration in the class to make
|
26
25
|
# sure we always have an array to work with.
|
27
|
-
unless base.instance_variable_defined?(:@
|
28
|
-
base.instance_variable_set(:@
|
26
|
+
unless base.instance_variable_defined?(:@default_expiration)
|
27
|
+
base.instance_variable_set(:@default_expiration, @default_expiration) # set above
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
|
-
def
|
33
|
-
@
|
31
|
+
def default_expiration=(v)
|
32
|
+
@default_expiration = v.to_f
|
34
33
|
end
|
35
34
|
|
36
|
-
def
|
37
|
-
@
|
35
|
+
def default_expiration
|
36
|
+
@default_expiration || self.class.default_expiration
|
38
37
|
end
|
39
38
|
|
40
|
-
# Sets an expiration time for the
|
39
|
+
# Sets an expiration time for the Database data associated with this object.
|
41
40
|
#
|
42
41
|
# This method allows setting a Time To Live (TTL) for the data in Redis,
|
43
42
|
# after which it will be automatically removed.
|
44
43
|
#
|
45
|
-
# @param
|
44
|
+
# @param default_expiration [Integer, nil] The Time To Live in seconds. If nil, the default
|
46
45
|
# TTL will be used.
|
47
46
|
#
|
48
47
|
# @return [Boolean] Returns true if the expiration was set successfully,
|
49
48
|
# false otherwise.
|
50
49
|
#
|
51
50
|
# @example Setting an expiration of one day
|
52
|
-
# object.update_expiration(
|
51
|
+
# object.update_expiration(default_expiration: 86400)
|
53
52
|
#
|
54
|
-
# @note If
|
53
|
+
# @note If Default expiration is set to zero, the expiration will be removed, making the
|
55
54
|
# data persist indefinitely.
|
56
55
|
#
|
57
|
-
# @raise [Familia::Problem] Raises an error if the
|
56
|
+
# @raise [Familia::Problem] Raises an error if the default expiration is not a non-negative
|
58
57
|
# integer.
|
59
58
|
#
|
60
|
-
def update_expiration(
|
61
|
-
|
59
|
+
def update_expiration(default_expiration: nil)
|
60
|
+
default_expiration ||= self.default_expiration
|
62
61
|
|
63
62
|
if self.class.has_relations?
|
64
|
-
Familia.ld "[update_expiration] #{self.class} has relations: #{self.class.
|
65
|
-
self.class.
|
66
|
-
next if definition.opts[:
|
63
|
+
Familia.ld "[update_expiration] #{self.class} has relations: #{self.class.related_fields.keys}"
|
64
|
+
self.class.related_fields.each do |name, definition|
|
65
|
+
next if definition.opts[:default_expiration].nil?
|
67
66
|
obj = send(name)
|
68
|
-
Familia.ld "[update_expiration] Updating expiration for #{name} (#{obj.
|
69
|
-
obj.update_expiration(
|
67
|
+
Familia.ld "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{default_expiration}"
|
68
|
+
obj.update_expiration(default_expiration: default_expiration)
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
@@ -75,23 +74,23 @@ module Familia::Features
|
|
75
74
|
# retention issues (e.g. not removed in a timely fashion).
|
76
75
|
#
|
77
76
|
# For the same reason, we don't want to default to 0 bc there's not a
|
78
|
-
# good reason for the
|
79
|
-
# class doesn't have a
|
77
|
+
# good reason for the default_expiration to not be set in the first place. If the
|
78
|
+
# class doesn't have a default_expiration, the default comes from Familia.default_expiration (which
|
80
79
|
# is 0).
|
81
|
-
unless
|
82
|
-
raise Familia::Problem, "
|
80
|
+
unless default_expiration.is_a?(Numeric)
|
81
|
+
raise Familia::Problem, "Default expiration must be a number (#{default_expiration.class} in #{self.class})"
|
83
82
|
end
|
84
83
|
|
85
|
-
if
|
86
|
-
return Familia.ld "[update_expiration] No expiration for #{self.class} (#{
|
84
|
+
if default_expiration.zero?
|
85
|
+
return Familia.ld "[update_expiration] No expiration for #{self.class} (#{dbkey})"
|
87
86
|
end
|
88
87
|
|
89
|
-
Familia.ld "[update_expiration] Expires #{
|
88
|
+
Familia.ld "[update_expiration] Expires #{dbkey} in #{default_expiration} seconds"
|
90
89
|
|
91
90
|
# Redis' EXPIRE command returns 1 if the timeout was set, 0 if key does
|
92
91
|
# not exist or the timeout could not be set. Via redis-rb here, it's
|
93
92
|
# a bool.
|
94
|
-
expire(
|
93
|
+
expire(default_expiration)
|
95
94
|
end
|
96
95
|
|
97
96
|
extend ClassMethods
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/features/quantization.rb
|
2
2
|
|
3
3
|
module Familia::Features
|
4
4
|
|
@@ -28,7 +28,13 @@ module Familia::Features
|
|
28
28
|
if quantum.is_a?(Array)
|
29
29
|
quantum, pattern = quantum
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
|
+
# Previously we erronously included `@opts.fetch(:quantize, nil)` in
|
33
|
+
# the list of default values here, but @opts is for horreum instances
|
34
|
+
# not at the class level. This method `qstamp` is part of the initial
|
35
|
+
# definition for whatever horreum subclass we're in right now. That's
|
36
|
+
# why default_expiration works (e.g. `class Plop; feature :quantization; default_expiration 90; end`).
|
37
|
+
quantum ||= default_expiration || 10.minutes
|
32
38
|
|
33
39
|
# Validate quantum
|
34
40
|
unless quantum.is_a?(Numeric) && quantum.positive?
|
@@ -46,7 +52,7 @@ module Familia::Features
|
|
46
52
|
end
|
47
53
|
|
48
54
|
def qstamp(quantum = nil, pattern: nil, time: nil)
|
49
|
-
self.class.qstamp(quantum || self.class.
|
55
|
+
self.class.qstamp(quantum || self.class.default_expiration, pattern: pattern, time: time)
|
50
56
|
end
|
51
57
|
|
52
58
|
extend ClassMethods
|
@@ -1,5 +1,4 @@
|
|
1
|
-
#
|
2
|
-
# frozen_string_literal: true
|
1
|
+
# lib/familia/features/safe_dump.rb
|
3
2
|
|
4
3
|
|
5
4
|
module Familia::Features
|
@@ -87,7 +86,7 @@ module Familia::Features
|
|
87
86
|
field_name = el
|
88
87
|
callable = lambda { |obj|
|
89
88
|
if obj.respond_to?(:[]) && obj[field_name]
|
90
|
-
obj[field_name] # Familia::
|
89
|
+
obj[field_name] # Familia::DataType classes
|
91
90
|
elsif obj.respond_to?(field_name)
|
92
91
|
obj.send(field_name) # Onetime::Models::RedisHash classes via method_missing 😩
|
93
92
|
end
|
data/lib/familia/features.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/features.rb
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
|
@@ -28,7 +28,7 @@ module Familia
|
|
28
28
|
# Extend the Familia::Base subclass (e.g. Customer) with the feature module
|
29
29
|
include klass
|
30
30
|
|
31
|
-
# NOTE: We may also want to extend Familia::
|
31
|
+
# NOTE: We may also want to extend Familia::DataType here so that we can
|
32
32
|
# call safe_dump on relations fields (e.g. list, set, zset, hashkey). Or
|
33
33
|
# maybe that only makes sense for hashk/object relations.
|
34
34
|
#
|