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
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/horreum/serialization.rb
|
2
2
|
#
|
3
3
|
module Familia
|
4
4
|
|
@@ -6,9 +6,9 @@ module Familia
|
|
6
6
|
# Familia::Horreum
|
7
7
|
#
|
8
8
|
class Horreum
|
9
|
-
# The Sacred Scrolls of
|
9
|
+
# The Sacred Scrolls of Database Responses
|
10
10
|
#
|
11
|
-
# Behold! The mystical runes that
|
11
|
+
# Behold! The mystical runes that Database whispers back to us:
|
12
12
|
#
|
13
13
|
# "OK" - The sweet sound of success, like a tiny "ding!" from the depths of data.
|
14
14
|
# true - The boolean Buddha nods in agreement.
|
@@ -16,27 +16,27 @@ module Familia
|
|
16
16
|
# 0 - The silent hero. It tried its best, bless its heart.
|
17
17
|
# nil - The zen master of responses. It's not nothing, it's... enlightenment!
|
18
18
|
#
|
19
|
-
# These sacred signs are our guide through the
|
19
|
+
# These sacred signs are our guide through the Database wilderness. When we cast
|
20
20
|
# our spells (er, commands), we seek these friendly faces in the returned
|
21
21
|
# smoke signals.
|
22
22
|
#
|
23
|
-
# Should our
|
23
|
+
# Should our Database rituals summon anything else, we pause. We ponder. We
|
24
24
|
# possibly panic. For the unexpected in Redis-land is like finding a penguin
|
25
25
|
# in your pasta - delightfully confusing, but probably not what you ordered.
|
26
26
|
#
|
27
|
-
# May your
|
27
|
+
# May your Database returns be ever valid, and your data ever flowing!
|
28
28
|
#
|
29
|
-
@valid_command_return_values = ["OK", true, 1, 0, nil]
|
29
|
+
@valid_command_return_values = ["OK", true, 1, 0, nil].freeze
|
30
30
|
|
31
31
|
class << self
|
32
|
-
|
32
|
+
attr_reader :valid_command_return_values
|
33
33
|
end
|
34
34
|
|
35
35
|
# Serialization: Where Objects Go to Become Strings (and Vice Versa)!
|
36
36
|
#
|
37
37
|
# This module is chock-full of methods that'll make your head spin (in a
|
38
38
|
# good way)! We've got loaders, dumpers, and refreshers galore. It's like
|
39
|
-
# a laundromat for your data, but instead of quarters, it runs on
|
39
|
+
# a laundromat for your data, but instead of quarters, it runs on Database commands.
|
40
40
|
#
|
41
41
|
# A Note on Our Refreshing Refreshers:
|
42
42
|
# In the wild world of Ruby, '!' usually means "Watch out! I'm dangerous!"
|
@@ -47,7 +47,7 @@ module Familia
|
|
47
47
|
#
|
48
48
|
# Remember: In Familia, refreshing isn't just a chore, it's a chance to
|
49
49
|
# dance with data! Whether you bang(!) or not, you're still invited to
|
50
|
-
# the
|
50
|
+
# the Database disco.
|
51
51
|
#
|
52
52
|
# (P.S. If you're reading these docs, lol sorry. I asked Claude 3.5 to
|
53
53
|
# write in the style of _why the lucky stiff today and got this uncanny
|
@@ -56,59 +56,17 @@ module Familia
|
|
56
56
|
#
|
57
57
|
# (Ahem! What I meant to say was that if you're reading this, congratulations!
|
58
58
|
# You've stumbled upon the secret garden of documentation. Feel free to smell
|
59
|
-
# the Ruby roses, but watch out for the
|
59
|
+
# the Ruby roses, but watch out for the Database thorns!)
|
60
60
|
#
|
61
61
|
module Serialization
|
62
62
|
|
63
|
-
attr_writer :redis
|
64
|
-
|
65
|
-
# Summon the mystical Redis connection from the depths of instance or class.
|
66
|
-
#
|
67
|
-
# This method is like a magical divining rod, always pointing to the nearest
|
68
|
-
# source of Redis goodness. It first checks if we have a personal Redis
|
69
|
-
# connection (@redis), and if not, it borrows the class's connection.
|
70
|
-
#
|
71
|
-
# @return [Redis] A shimmering Redis connection, ready for your bidding.
|
72
|
-
#
|
73
|
-
# @example Finding your Redis way
|
74
|
-
# puts object.redis
|
75
|
-
# # => #<Redis client v4.5.1 for redis://localhost:6379/0>
|
76
|
-
#
|
77
|
-
def redis
|
78
|
-
@redis || self.class.redis
|
79
|
-
end
|
80
|
-
|
81
|
-
# Perform a sacred Redis transaction ritual.
|
82
|
-
#
|
83
|
-
# This method creates a protective circle around your Redis operations,
|
84
|
-
# ensuring they all succeed or fail together. It's like a group hug for your
|
85
|
-
# data operations, but with more ACID properties.
|
86
|
-
#
|
87
|
-
# @yield [conn] A block where you can perform your Redis incantations.
|
88
|
-
# @yieldparam conn [Redis] A Redis connection in multi mode.
|
89
|
-
#
|
90
|
-
# @example Performing a Redis rain dance
|
91
|
-
# transaction do |conn|
|
92
|
-
# conn.set("weather", "rainy")
|
93
|
-
# conn.set("mood", "melancholic")
|
94
|
-
# end
|
95
|
-
#
|
96
|
-
# @note This method temporarily replaces your Redis connection with a multi
|
97
|
-
# connection. Don't worry, it puts everything back where it found it when it's done.
|
98
|
-
#
|
99
|
-
def transaction
|
100
|
-
redis.multi do |conn|
|
101
|
-
yield(conn)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
63
|
# Save our precious data to Redis, with a sprinkle of timestamp magic!
|
106
64
|
#
|
107
65
|
# This method is like a conscientious historian, not only recording your
|
108
66
|
# object's current state but also meticulously timestamping when it was
|
109
67
|
# created and last updated. It's the record keeper of your data's life story!
|
110
68
|
#
|
111
|
-
# @return [Boolean] true if the save was successful, false if
|
69
|
+
# @return [Boolean] true if the save was successful, false if Database was grumpy.
|
112
70
|
#
|
113
71
|
# @example Preserving your pet rock for posterity
|
114
72
|
# rocky = PetRock.new(name: "Dwayne")
|
@@ -119,26 +77,23 @@ module Familia
|
|
119
77
|
# It's like Hansel and Gretel, but for data operations!
|
120
78
|
#
|
121
79
|
def save update_expiration: true
|
122
|
-
Familia.trace :SAVE,
|
80
|
+
Familia.trace :SAVE, dbclient, uri, caller(1..1) if Familia.debug?
|
123
81
|
|
124
|
-
#
|
125
|
-
# key field in sync with the field that is the chosen identifier.
|
126
|
-
self.key = self.identifier
|
82
|
+
# No longer need to sync computed identifier with a cache field
|
127
83
|
self.created ||= Familia.now.to_i if respond_to?(:created)
|
128
84
|
self.updated = Familia.now.to_i if respond_to?(:updated)
|
129
85
|
|
130
|
-
# Commit our tale to the
|
86
|
+
# Commit our tale to the Database chronicles
|
131
87
|
#
|
132
|
-
# e.g. `ret` # => MultiResult.new(true, ["OK", "OK"])
|
133
88
|
ret = commit_fields(update_expiration: update_expiration)
|
134
89
|
|
135
|
-
Familia.ld "[save] #{self.class} #{
|
90
|
+
Familia.ld "[save] #{self.class} #{dbkey} #{ret} (update_expiration: #{update_expiration})"
|
136
91
|
|
137
|
-
# Did
|
138
|
-
ret.
|
92
|
+
# Did Database accept our offering?
|
93
|
+
!ret.nil?
|
139
94
|
end
|
140
95
|
|
141
|
-
# Updates multiple fields atomically in a
|
96
|
+
# Updates multiple fields atomically in a Database transaction.
|
142
97
|
#
|
143
98
|
# @param fields [Hash] Field names and values to update. Special key :update_expiration
|
144
99
|
# controls whether to update key expiration (default: true)
|
@@ -154,19 +109,19 @@ module Familia
|
|
154
109
|
update_expiration = kwargs.delete(:update_expiration) { true }
|
155
110
|
fields = kwargs
|
156
111
|
|
157
|
-
Familia.trace :BATCH_UPDATE,
|
112
|
+
Familia.trace :BATCH_UPDATE, dbclient, fields.keys, caller(1..1) if Familia.debug?
|
158
113
|
|
159
114
|
command_return_values = transaction do |conn|
|
160
115
|
fields.each do |field, value|
|
161
116
|
prepared_value = serialize_value(value)
|
162
|
-
conn.hset
|
117
|
+
conn.hset dbkey, field, prepared_value
|
163
118
|
# Update instance variable to keep object in sync
|
164
119
|
send("#{field}=", value) if respond_to?("#{field}=")
|
165
120
|
end
|
166
121
|
end
|
167
122
|
|
168
123
|
# Update expiration if requested and supported
|
169
|
-
self.update_expiration(
|
124
|
+
self.update_expiration(default_expiration: nil) if update_expiration && respond_to?(:update_expiration)
|
170
125
|
|
171
126
|
# Return same MultiResult format as other methods
|
172
127
|
summary_boolean = command_return_values.all? { |ret| %w[OK 0 1].include?(ret.to_s) }
|
@@ -204,12 +159,12 @@ module Familia
|
|
204
159
|
# if applicable, updating the expiration time.
|
205
160
|
#
|
206
161
|
# @param update_expiration [Boolean] Whether to update the expiration time
|
207
|
-
# of the
|
162
|
+
# of the dbkey. This is true by default, but can be disabled if you
|
208
163
|
# don't want to mess with the cosmic balance of your key's lifespan.
|
209
164
|
#
|
210
165
|
# @return [MultiResult] A mystical object containing:
|
211
|
-
# - success: A boolean indicating if all
|
212
|
-
# - results: An array of strings, cryptic messages from the
|
166
|
+
# - success: A boolean indicating if all Database commands succeeded
|
167
|
+
# - results: An array of strings, cryptic messages from the Database gods
|
213
168
|
#
|
214
169
|
# The MultiResult object responds to:
|
215
170
|
# - successful?: Returns the boolean success value
|
@@ -218,60 +173,43 @@ module Familia
|
|
218
173
|
# @note Be warned, young programmer! This method dabbles in the arcane
|
219
174
|
# art of transactions. Side effects may include data persistence and a
|
220
175
|
# slight tingling sensation. The method does not raise exceptions for
|
221
|
-
# unexpected
|
176
|
+
# unexpected Database responses, but logs warnings and returns a failure status.
|
222
177
|
#
|
223
|
-
# @example Offering your changes to the
|
178
|
+
# @example Offering your changes to the Database deities
|
224
179
|
# unicorn.name = "Charlie"
|
225
180
|
# unicorn.horn_length = "magnificent"
|
226
181
|
# result = unicorn.commit_fields
|
227
182
|
# if result.successful?
|
228
|
-
# puts "The
|
183
|
+
# puts "The Database gods are pleased with your offering"
|
229
184
|
# p result.results # => ["OK", "OK"]
|
230
185
|
# else
|
231
|
-
# puts "The
|
186
|
+
# puts "The Database gods frown upon your offering"
|
232
187
|
# p result.results # Examine the unexpected values
|
233
188
|
# end
|
234
189
|
#
|
235
190
|
# @see Familia::Horreum.valid_command_return_values for the list of
|
236
|
-
# acceptable
|
191
|
+
# acceptable Database command return values.
|
237
192
|
#
|
238
193
|
# @note This method performs logging at various levels:
|
239
|
-
# - Debug: Logs the object's class,
|
240
|
-
# - Warn: Logs any unexpected return values from
|
194
|
+
# - Debug: Logs the object's class, dbkey, and current state before committing
|
195
|
+
# - Warn: Logs any unexpected return values from Database commands
|
241
196
|
# - Debug: Logs the final result, including success status and all return values
|
242
197
|
#
|
243
198
|
# @note The expiration update is only performed for classes that have
|
244
199
|
# the expiration feature enabled. For others, it's a no-op.
|
245
200
|
#
|
246
201
|
def commit_fields update_expiration: true
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
202
|
+
prepared_value = to_h
|
203
|
+
Familia.ld "[commit_fields] Begin #{self.class} #{dbkey} #{prepared_value} (exp: #{update_expiration})"
|
204
|
+
|
205
|
+
result = self.hmset(prepared_value)
|
206
|
+
|
251
207
|
# Only classes that have the expiration ferature enabled will
|
252
208
|
# actually set an expiration time on their keys. Otherwise
|
253
209
|
# this will be a no-op that simply logs the attempt.
|
254
|
-
self.update_expiration(
|
255
|
-
|
256
|
-
# The acceptable redis command return values are defined in the
|
257
|
-
# Horreum class. This is to ensure that all commands return values
|
258
|
-
# are validated against a consistent set of values.
|
259
|
-
acceptable_values = Familia::Horreum.valid_command_return_values
|
260
|
-
|
261
|
-
# Check if all return values are valid
|
262
|
-
summary_boolean = command_return_values.uniq.all? { |value|
|
263
|
-
acceptable_values.include?(value)
|
264
|
-
}
|
265
|
-
|
266
|
-
# Log the unexpected
|
267
|
-
unless summary_boolean
|
268
|
-
unexpected_values = command_return_values.reject { |value| acceptable_values.include?(value) }
|
269
|
-
Familia.warn "[commit_fields] Unexpected return values: #{unexpected_values.inspect}"
|
270
|
-
end
|
210
|
+
self.update_expiration(default_expiration: nil) if update_expiration
|
271
211
|
|
272
|
-
|
273
|
-
|
274
|
-
MultiResult.new(summary_boolean, command_return_values)
|
212
|
+
result
|
275
213
|
end
|
276
214
|
|
277
215
|
# Dramatically vanquish this object from the face of Redis! (ed: delete it)
|
@@ -289,9 +227,9 @@ module Familia
|
|
289
227
|
# # => *poof* Rocky is no more. A moment of silence, please.
|
290
228
|
#
|
291
229
|
# This method is part of Familia's high-level object lifecycle management. While `delete!`
|
292
|
-
# operates directly on
|
230
|
+
# operates directly on dbkeys, `destroy!` operates at the object level and is used for
|
293
231
|
# ORM-style operations. Use `destroy!` when removing complete objects from the system, and
|
294
|
-
# `delete!` when working directly with
|
232
|
+
# `delete!` when working directly with dbkeys.
|
295
233
|
#
|
296
234
|
# @note If debugging is enabled, this method will leave a trace of its
|
297
235
|
# destructive path, like breadcrumbs for future data archaeologists.
|
@@ -299,7 +237,7 @@ module Familia
|
|
299
237
|
# @see #delete! The actual hitman carrying out the deed.
|
300
238
|
#
|
301
239
|
def destroy!
|
302
|
-
Familia.trace :DESTROY,
|
240
|
+
Familia.trace :DESTROY, dbclient, uri, caller(1..1) if Familia.debug?
|
303
241
|
delete!
|
304
242
|
end
|
305
243
|
|
@@ -323,7 +261,7 @@ module Familia
|
|
323
261
|
self.class.fields.each { |field| send("#{field}=", nil) }
|
324
262
|
end
|
325
263
|
|
326
|
-
# The Great
|
264
|
+
# The Great Database Refresh-o-matic 3000
|
327
265
|
#
|
328
266
|
# Imagine your object as a forgetful time traveler. This method is like
|
329
267
|
# zapping it with a memory ray from Redis-topia. ZAP! New memories!
|
@@ -339,33 +277,33 @@ module Familia
|
|
339
277
|
# Remember: In the game of Redis-Refresh, you win or you... well, you
|
340
278
|
# always win, but sometimes you forget why you played in the first place.
|
341
279
|
#
|
342
|
-
# @raise [Familia::KeyNotFoundError] If the
|
280
|
+
# @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
|
343
281
|
#
|
344
282
|
# @example
|
345
283
|
# object.refresh!
|
346
284
|
def refresh!
|
347
|
-
Familia.trace :REFRESH,
|
348
|
-
raise Familia::KeyNotFoundError,
|
285
|
+
Familia.trace :REFRESH, dbclient, uri, caller(1..1) if Familia.debug?
|
286
|
+
raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
|
349
287
|
fields = hgetall
|
350
|
-
Familia.ld "[refresh!] #{self.class} #{
|
288
|
+
Familia.ld "[refresh!] #{self.class} #{dbkey} fields:#{fields.keys}"
|
351
289
|
optimistic_refresh(**fields)
|
352
290
|
end
|
353
291
|
|
354
292
|
# Ah, the magical refresh dance! It's like giving your object a
|
355
293
|
# sip from the fountain of youth.
|
356
294
|
#
|
357
|
-
# This method twirls your object around, dips it into the
|
295
|
+
# This method twirls your object around, dips it into the Database pool,
|
358
296
|
# and brings it back sparkling clean and up-to-date. It's using the
|
359
|
-
# refresh! spell behind the scenes, so expect some
|
297
|
+
# refresh! spell behind the scenes, so expect some Database whispering.
|
360
298
|
#
|
361
299
|
# @note Caution, young Rubyist! While this method loves to play
|
362
300
|
# chain-tag with other methods, it's still got that refresh! kick.
|
363
301
|
# It'll update your object faster than you can say "matz!"
|
364
302
|
#
|
365
|
-
# @return [self] Your object, freshly bathed in
|
303
|
+
# @return [self] Your object, freshly bathed in Database waters, ready
|
366
304
|
# to dance with more methods in a conga line of Ruby joy!
|
367
305
|
#
|
368
|
-
# @raise [Familia::KeyNotFoundError] If the
|
306
|
+
# @raise [Familia::KeyNotFoundError] If the dbkey does not exist.
|
369
307
|
#
|
370
308
|
def refresh
|
371
309
|
refresh!
|
@@ -385,7 +323,7 @@ module Familia
|
|
385
323
|
# dragon.to_h
|
386
324
|
# # => {"name"=>"Puff", "breathes"=>"fire", "age"=>1000}
|
387
325
|
#
|
388
|
-
# @note Watch in awe as each field is lovingly prepared for its
|
326
|
+
# @note Watch in awe as each field is lovingly prepared for its Database adventure!
|
389
327
|
#
|
390
328
|
def to_h
|
391
329
|
self.class.fields.inject({}) do |hsh, field|
|
@@ -402,7 +340,7 @@ module Familia
|
|
402
340
|
# Line up all our attributes in a neat little array parade!
|
403
341
|
#
|
404
342
|
# This method marshals all our object's attributes into an orderly procession,
|
405
|
-
# ready to march into
|
343
|
+
# ready to march into Database in perfect formation. It's like a little data army,
|
406
344
|
# but friendlier and less prone to conquering neighboring databases.
|
407
345
|
#
|
408
346
|
# @return [Array] A splendid array of Redis-ready values, in the order of our fields.
|
@@ -411,7 +349,7 @@ module Familia
|
|
411
349
|
# unicorn.to_a
|
412
350
|
# # => ["Charlie", "magnificent", 5]
|
413
351
|
#
|
414
|
-
# @note Each value is carefully disguised in its
|
352
|
+
# @note Each value is carefully disguised in its Database costume
|
415
353
|
# before joining the parade.
|
416
354
|
#
|
417
355
|
def to_a
|
@@ -424,21 +362,21 @@ module Familia
|
|
424
362
|
end
|
425
363
|
|
426
364
|
# Behold, the grand tale of two serialization sorcerers:
|
427
|
-
# Familia::
|
365
|
+
# Familia::DataType and Familia::Horreum!
|
428
366
|
#
|
429
367
|
# These twin wizards, though cut from the same magical cloth,
|
430
368
|
# have their own unique spells for turning Ruby objects into
|
431
369
|
# Redis-friendly potions. Let's peek into their spell books:
|
432
370
|
#
|
433
371
|
# Shared Incantations:
|
434
|
-
# - Both transform various data creatures for
|
372
|
+
# - Both transform various data creatures for Database safekeeping
|
435
373
|
# - They tame wild Strings, Symbols, and those slippery Numerics
|
436
374
|
# - Secret rituals (aka custom serialization) are welcome
|
437
375
|
#
|
438
376
|
# Mystical Differences:
|
439
|
-
# -
|
377
|
+
# - DataType reads the future in opts[:class] tea leaves
|
440
378
|
# - Horreum prefers to interrogate types more thoroughly
|
441
|
-
# -
|
379
|
+
# - DataType leaves a trail of debug breadcrumbs
|
442
380
|
#
|
443
381
|
# But wait! Enter the wise Familia.distinguisher,
|
444
382
|
# a grand unifier of serialization magic!
|
@@ -447,7 +385,7 @@ module Familia
|
|
447
385
|
# 1. Juggles a circus of data types from both realms
|
448
386
|
# 2. Offers a 'strict_values' toggle for the type-obsessed
|
449
387
|
# 3. Welcomes custom spells via dump_method
|
450
|
-
# 4. Sprinkles debug fairy dust à la
|
388
|
+
# 4. Sprinkles debug fairy dust à la DataType
|
451
389
|
#
|
452
390
|
# By channeling the Familia.distinguisher, we've created a
|
453
391
|
# harmonious serialization symphony, flexible enough to dance
|
@@ -474,14 +412,13 @@ module Familia
|
|
474
412
|
|
475
413
|
prepared
|
476
414
|
end
|
477
|
-
alias to_redis serialize_value
|
478
415
|
|
479
|
-
# Converts a
|
416
|
+
# Converts a Database string value back to its original Ruby type
|
480
417
|
#
|
481
418
|
# This method attempts to deserialize JSON strings back to their original
|
482
419
|
# Hash or Array types. Simple string values are returned as-is.
|
483
420
|
#
|
484
|
-
# @param val [String] The string value from
|
421
|
+
# @param val [String] The string value from Database to deserialize
|
485
422
|
# @param symbolize_keys [Boolean] Whether to symbolize hash keys (default: true for compatibility)
|
486
423
|
# @return [Object] The deserialized value (Hash, Array, or original string)
|
487
424
|
#
|
@@ -500,82 +437,8 @@ module Familia
|
|
500
437
|
|
501
438
|
val
|
502
439
|
end
|
503
|
-
alias from_redis deserialize_value
|
504
440
|
|
505
441
|
end
|
506
|
-
# End of Serialization module
|
507
|
-
|
508
|
-
# The magical MultiResult, keeper of Redis's deepest secrets!
|
509
|
-
#
|
510
|
-
# This quirky little class wraps up the outcome of a Redis "transaction"
|
511
|
-
# (or as I like to call it, a "Redis dance party") with a bow made of
|
512
|
-
# pure Ruby delight. It knows if your commands were successful and
|
513
|
-
# keeps the results safe in its pocket dimension.
|
514
|
-
#
|
515
|
-
# @attr_reader success [Boolean] The golden ticket! True if all your
|
516
|
-
# Redis wishes came true in the transaction.
|
517
|
-
# @attr_reader results [Array<String>] A mystical array of return values,
|
518
|
-
# each one a whisper from the Redis gods.
|
519
|
-
#
|
520
|
-
# @example Summoning a MultiResult from the void
|
521
|
-
# result = MultiResult.new(true, ["OK", "OK"])
|
522
|
-
#
|
523
|
-
# @example Divining the success of your Redis ritual
|
524
|
-
# if result.successful?
|
525
|
-
# puts "Huzzah! The Redis spirits smile upon you!"
|
526
|
-
# else
|
527
|
-
# puts "Alas! The Redis gremlins have conspired against us!"
|
528
|
-
# end
|
529
|
-
#
|
530
|
-
# @example Peering into the raw essence of results
|
531
|
-
# result.results.each_with_index do |value, index|
|
532
|
-
# puts "Command #{index + 1} whispered back: #{value}"
|
533
|
-
# end
|
534
|
-
#
|
535
|
-
class MultiResult
|
536
|
-
# @return [Boolean] true if all commands in the transaction succeeded,
|
537
|
-
# false otherwise
|
538
|
-
attr_reader :success
|
539
|
-
|
540
|
-
# @return [Array<String>] The raw return values from the Redis commands
|
541
|
-
attr_reader :results
|
542
|
-
|
543
|
-
# Creates a new MultiResult instance.
|
544
|
-
#
|
545
|
-
# @param success [Boolean] Whether all commands succeeded
|
546
|
-
# @param results [Array<String>] The raw results from Redis commands
|
547
|
-
def initialize(success, results)
|
548
|
-
@success = success
|
549
|
-
@results = results
|
550
|
-
end
|
551
|
-
|
552
|
-
# Returns a tuple representing the result of the transaction.
|
553
|
-
#
|
554
|
-
# @return [Array] A tuple containing the success status and the raw results.
|
555
|
-
# The success status is a boolean indicating if all commands succeeded.
|
556
|
-
# The raw results is an array of return values from the Redis commands.
|
557
|
-
#
|
558
|
-
# @example
|
559
|
-
# [true, ["OK", true, 1]]
|
560
|
-
#
|
561
|
-
def tuple
|
562
|
-
[successful?, results]
|
563
|
-
end
|
564
|
-
alias to_a tuple
|
565
|
-
|
566
|
-
def to_h
|
567
|
-
{ success: successful?, results: results }
|
568
|
-
end
|
569
|
-
|
570
|
-
# Convenient method to check if the commit was successful.
|
571
|
-
#
|
572
|
-
# @return [Boolean] true if all commands succeeded, false otherwise
|
573
|
-
def successful?
|
574
|
-
@success
|
575
|
-
end
|
576
|
-
alias success? successful?
|
577
|
-
end
|
578
|
-
# End of MultiResult class
|
579
442
|
|
580
443
|
include Serialization # these become Horreum instance methods
|
581
444
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/horreum/settings.rb
|
2
2
|
#
|
3
3
|
module Familia
|
4
4
|
# InstanceMethods - Module containing instance-level methods for Familia
|
5
5
|
#
|
6
6
|
# This module is included in classes that include Familia, providing
|
7
|
-
# instance-level functionality for
|
7
|
+
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
10
|
|
@@ -18,23 +18,12 @@ module Familia
|
|
18
18
|
@opts
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
23
|
-
uri: self.class.uri,
|
24
|
-
db: self.class.db,
|
25
|
-
key: rediskey,
|
26
|
-
type: redistype,
|
27
|
-
ttl: ttl,
|
28
|
-
realttl: realttl
|
29
|
-
}
|
21
|
+
def logical_database=(v)
|
22
|
+
@logical_database = v.to_i
|
30
23
|
end
|
31
24
|
|
32
|
-
def
|
33
|
-
@
|
34
|
-
end
|
35
|
-
|
36
|
-
def db
|
37
|
-
@db || self.class.db
|
25
|
+
def logical_database
|
26
|
+
@logical_database || self.class.logical_database
|
38
27
|
end
|
39
28
|
|
40
29
|
def suffix
|
@@ -1,10 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/horreum/utils.rb
|
2
2
|
#
|
3
3
|
module Familia
|
4
4
|
# InstanceMethods - Module containing instance-level methods for Familia
|
5
5
|
#
|
6
6
|
# This module is included in classes that include Familia, providing
|
7
|
-
# instance-level functionality for
|
7
|
+
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
10
|
|
@@ -12,30 +12,31 @@ module Familia
|
|
12
12
|
#
|
13
13
|
module Utils
|
14
14
|
|
15
|
-
def
|
16
|
-
u = Familia.
|
17
|
-
u.
|
18
|
-
u.key =
|
15
|
+
def uri(suffix = nil)
|
16
|
+
u = Familia.uri(self.class.uri) # returns URI::Redis
|
17
|
+
u.logical_database = logical_database if logical_database # override the logical_database if we have one
|
18
|
+
u.key = dbkey(suffix)
|
19
19
|
u
|
20
20
|
end
|
21
21
|
|
22
|
-
# +suffix+ is the value to be used at the end of the
|
22
|
+
# +suffix+ is the value to be used at the end of the db key
|
23
23
|
# (e.g. `customer:customer_id:scores` would have `scores` as the suffix
|
24
24
|
# and `customer_id` would have been the identifier in that case).
|
25
25
|
#
|
26
26
|
# identifier is the value that distinguishes this object from others.
|
27
|
-
# Whether this is a Horreum or
|
27
|
+
# Whether this is a Horreum or DataType object, the value is taken
|
28
28
|
# from the `identifier` method).
|
29
29
|
#
|
30
|
-
def
|
30
|
+
def dbkey(suffix = nil, ignored = nil)
|
31
31
|
raise Familia::NoIdentifier, "No identifier for #{self.class}" if identifier.to_s.empty?
|
32
32
|
suffix ||= self.suffix # use the instance method to get the default suffix
|
33
|
-
self.class.
|
33
|
+
self.class.dbkey identifier, suffix
|
34
34
|
end
|
35
35
|
|
36
36
|
def join(*args)
|
37
37
|
Familia.join(args.map { |field| send(field) })
|
38
38
|
end
|
39
|
+
|
39
40
|
end
|
40
41
|
|
41
42
|
include Utils # these become Horreum instance methods
|