familia 1.2.1 → 2.0.0.pre2
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 +4 -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 +16 -2
- data/Gemfile.lock +97 -36
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +192 -0
- data/familia.gemspec +10 -6
- 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 -110
- 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 +18 -13
- 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} +7 -5
- 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 -7
- 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} +60 -59
- 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} +21 -18
- data/try/models/datatype_base_try.rb +100 -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 +143 -46
- 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,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
|
#
|
@@ -1,62 +1,61 @@
|
|
1
|
-
#
|
1
|
+
# lib/familia/horreum/class_methods.rb
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'related_fields_management'
|
4
4
|
|
5
5
|
module Familia
|
6
6
|
class Horreum
|
7
7
|
# Class-level instance variables
|
8
8
|
# These are set up as nil initially and populated later
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
9
|
+
@dbclient = nil # TODO
|
10
|
+
@identifier_field = nil
|
11
|
+
@default_expiration = nil
|
12
|
+
@logical_database = nil
|
13
13
|
@uri = nil
|
14
14
|
@suffix = nil
|
15
15
|
@prefix = nil
|
16
16
|
@fields = nil # []
|
17
|
-
@
|
18
|
-
@
|
17
|
+
@class_related_fields = nil # {}
|
18
|
+
@related_fields = nil # {}
|
19
19
|
@dump_method = nil
|
20
20
|
@load_method = nil
|
21
21
|
|
22
22
|
# ClassMethods: Provides class-level functionality for Horreum
|
23
23
|
#
|
24
24
|
# This module is extended into classes that include Familia::Horreum,
|
25
|
-
# providing methods for
|
25
|
+
# providing methods for Database operations and object management.
|
26
26
|
#
|
27
27
|
# Key features:
|
28
|
-
# * Includes
|
29
|
-
# * Defines methods for managing fields, identifiers, and
|
30
|
-
# * Provides utility methods for working with
|
28
|
+
# * Includes RelatedFieldsManagement for DataType field handling
|
29
|
+
# * Defines methods for managing fields, identifiers, and dbkeys
|
30
|
+
# * Provides utility methods for working with Database objects
|
31
31
|
#
|
32
32
|
module ClassMethods
|
33
33
|
include Familia::Settings
|
34
|
-
include Familia::Horreum::
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# This method
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# @
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@
|
59
|
-
@identifier
|
34
|
+
include Familia::Horreum::RelatedFieldsManagement
|
35
|
+
|
36
|
+
# Sets or retrieves the unique identifier field for the class.
|
37
|
+
#
|
38
|
+
# This method defines or returns the field or method that contains the unique
|
39
|
+
# identifier used to generate the dbkey for the object. If a value is provided,
|
40
|
+
# it sets the identifier field; otherwise, it returns the current identifier field.
|
41
|
+
#
|
42
|
+
# @param [Object] val the field name or method to set as the identifier field (optional).
|
43
|
+
# @return [Object] the current identifier field.
|
44
|
+
#
|
45
|
+
def identifier_field(val = nil)
|
46
|
+
if val
|
47
|
+
# Validate identifier field definition at class definition time
|
48
|
+
case val
|
49
|
+
when Symbol, String, Proc
|
50
|
+
@identifier_field = val
|
51
|
+
else
|
52
|
+
raise Problem, <<~ERROR
|
53
|
+
Invalid identifier field definition: #{val.inspect}.
|
54
|
+
Use a field name (Symbol/String) or Proc.
|
55
|
+
ERROR
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@identifier_field
|
60
59
|
end
|
61
60
|
|
62
61
|
# Defines a field for the class and creates accessor methods.
|
@@ -84,12 +83,12 @@ module Familia
|
|
84
83
|
# The dynamically defined method performs the following:
|
85
84
|
# - Acts as both a reader and a writer method.
|
86
85
|
# - When called without arguments, retrieves the current value from Redis.
|
87
|
-
# - When called with an argument, persists the value to
|
86
|
+
# - When called with an argument, persists the value to Database immediately.
|
88
87
|
# - Checks if the correct number of arguments is provided (zero or one).
|
89
|
-
# - Converts the provided value to a format suitable for
|
88
|
+
# - Converts the provided value to a format suitable for Database storage.
|
90
89
|
# - Uses the existing accessor method to set the attribute value when
|
91
90
|
# writing.
|
92
|
-
# - Persists the value to
|
91
|
+
# - Persists the value to Database immediately using the hset command when
|
93
92
|
# writing.
|
94
93
|
# - Includes custom error handling to raise an ArgumentError if the wrong
|
95
94
|
# number of arguments is given.
|
@@ -145,21 +144,21 @@ module Familia
|
|
145
144
|
val = args.first
|
146
145
|
|
147
146
|
# If no value is provided to this fast attribute method, make a call
|
148
|
-
# to
|
147
|
+
# to the db to return the current stored value of the hash field.
|
149
148
|
return hget name if val.nil?
|
150
149
|
|
151
150
|
begin
|
152
151
|
# Trace the operation if debugging is enabled.
|
153
|
-
Familia.trace :FAST_WRITER,
|
152
|
+
Familia.trace :FAST_WRITER, dbclient, "#{name}: #{val.inspect}", caller(1..1) if Familia.debug?
|
154
153
|
|
155
|
-
# Convert the provided value to a format suitable for
|
154
|
+
# Convert the provided value to a format suitable for Database storage.
|
156
155
|
prepared = serialize_value(val)
|
157
156
|
Familia.ld "[.fast_attribute!] #{name} val: #{val.class} prepared: #{prepared.class}"
|
158
157
|
|
159
158
|
# Use the existing accessor method to set the attribute value.
|
160
159
|
send :"#{name}=", val
|
161
160
|
|
162
|
-
# Persist the value to
|
161
|
+
# Persist the value to Database immediately using the hset command.
|
163
162
|
hset name, prepared
|
164
163
|
rescue Familia::Problem => e
|
165
164
|
# Raise a custom error message if an exception occurs during the execution of the method.
|
@@ -175,36 +174,24 @@ module Familia
|
|
175
174
|
@fields
|
176
175
|
end
|
177
176
|
|
178
|
-
def
|
179
|
-
@
|
180
|
-
@
|
177
|
+
def class_related_fields
|
178
|
+
@class_related_fields ||= {}
|
179
|
+
@class_related_fields
|
181
180
|
end
|
182
181
|
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
def redis_object?(name)
|
188
|
-
redis_types.key? name.to_s.to_sym
|
189
|
-
end
|
190
|
-
|
191
|
-
def redis_types
|
192
|
-
@redis_types ||= {}
|
193
|
-
@redis_types
|
182
|
+
def related_fields
|
183
|
+
@related_fields ||= {}
|
184
|
+
@related_fields
|
194
185
|
end
|
195
186
|
|
196
187
|
def has_relations?
|
197
188
|
@has_relations ||= false
|
198
189
|
end
|
199
190
|
|
200
|
-
def
|
201
|
-
@
|
202
|
-
@
|
203
|
-
|
204
|
-
|
205
|
-
def uri(v = nil)
|
206
|
-
@uri = v unless v.nil?
|
207
|
-
@uri || parent&.uri
|
191
|
+
def logical_database(v = nil)
|
192
|
+
Familia.trace :DB, Familia.dbclient, "#{@logical_database} #{v}", caller(1..1) if Familia.debug?
|
193
|
+
@logical_database = v unless v.nil?
|
194
|
+
@logical_database || parent&.logical_database
|
208
195
|
end
|
209
196
|
|
210
197
|
def all(suffix = nil)
|
@@ -217,11 +204,11 @@ module Familia
|
|
217
204
|
matching_keys_count(filter) > 0
|
218
205
|
end
|
219
206
|
|
220
|
-
# Returns the number of
|
221
|
-
# @param filter [String]
|
207
|
+
# Returns the number of dbkeys matching the given filter pattern
|
208
|
+
# @param filter [String] dbkey pattern to match (default: '*')
|
222
209
|
# @return [Integer] Number of matching keys
|
223
210
|
def matching_keys_count(filter = '*')
|
224
|
-
|
211
|
+
dbclient.keys(dbkey(filter)).compact.size
|
225
212
|
end
|
226
213
|
alias size matching_keys_count # For backwards compatibility
|
227
214
|
|
@@ -273,7 +260,7 @@ module Familia
|
|
273
260
|
#
|
274
261
|
def create *args, **kwargs
|
275
262
|
fobj = new(*args, **kwargs)
|
276
|
-
raise Familia::Problem, "#{self} already exists: #{fobj.
|
263
|
+
raise Familia::Problem, "#{self} already exists: #{fobj.dbkey}" if fobj.exists?
|
277
264
|
|
278
265
|
fobj.save
|
279
266
|
fobj
|
@@ -285,17 +272,17 @@ module Familia
|
|
285
272
|
end
|
286
273
|
|
287
274
|
def rawmultiget(*ids)
|
288
|
-
ids.collect! { |objid|
|
275
|
+
ids.collect! { |objid| dbkey(objid) }
|
289
276
|
return [] if ids.compact.empty?
|
290
277
|
|
291
|
-
Familia.trace :MULTIGET,
|
292
|
-
|
278
|
+
Familia.trace :MULTIGET, dbclient, "#{ids.size}: #{ids}", caller(1..1) if Familia.debug?
|
279
|
+
dbclient.mget(*ids)
|
293
280
|
end
|
294
281
|
|
295
|
-
# Retrieves and instantiates an object from
|
282
|
+
# Retrieves and instantiates an object from Database using the full object
|
296
283
|
# key.
|
297
284
|
#
|
298
|
-
# @param objkey [String] The full
|
285
|
+
# @param objkey [String] The full dbkey for the object.
|
299
286
|
# @return [Object, nil] An instance of the class if the key exists, nil
|
300
287
|
# otherwise.
|
301
288
|
# @raise [ArgumentError] If the provided key is empty.
|
@@ -325,10 +312,10 @@ module Familia
|
|
325
312
|
|
326
313
|
# We use a lower-level method here b/c we're working with the
|
327
314
|
# full key and not just the identifier.
|
328
|
-
does_exist =
|
315
|
+
does_exist = dbclient.exists(objkey).positive?
|
329
316
|
|
330
317
|
Familia.ld "[.find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
|
331
|
-
Familia.trace :FROM_KEY,
|
318
|
+
Familia.trace :FROM_KEY, dbclient, objkey, caller(1..1) if Familia.debug?
|
332
319
|
|
333
320
|
# This is the reason for calling exists first. We want to definitively
|
334
321
|
# and without any ambiguity know if the object exists in Redis. If it
|
@@ -337,22 +324,22 @@ module Familia
|
|
337
324
|
# the constructor, which will then be annoying to debug.
|
338
325
|
return unless does_exist
|
339
326
|
|
340
|
-
obj =
|
341
|
-
Familia.trace :FROM_KEY2,
|
327
|
+
obj = dbclient.hgetall(objkey) # horreum objects are persisted as database hashes
|
328
|
+
Familia.trace :FROM_KEY2, dbclient, "#{objkey}: #{obj.inspect}", caller(1..1) if Familia.debug?
|
342
329
|
|
343
330
|
new(**obj)
|
344
331
|
end
|
345
|
-
alias
|
332
|
+
alias from_dbkey find_by_key # deprecated
|
346
333
|
|
347
|
-
# Retrieves and instantiates an object from
|
334
|
+
# Retrieves and instantiates an object from Database using its identifier.
|
348
335
|
#
|
349
336
|
# @param identifier [String, Integer] The unique identifier for the
|
350
337
|
# object.
|
351
|
-
# @param suffix [Symbol] The suffix to use in the
|
338
|
+
# @param suffix [Symbol] The suffix to use in the dbkey (default:
|
352
339
|
# :object).
|
353
340
|
# @return [Object, nil] An instance of the class if found, nil otherwise.
|
354
341
|
#
|
355
|
-
# This method constructs the full
|
342
|
+
# This method constructs the full dbkey using the provided identifier
|
356
343
|
# and suffix, then delegates to `find_by_key` for the actual retrieval and
|
357
344
|
# instantiation.
|
358
345
|
#
|
@@ -367,10 +354,10 @@ module Familia
|
|
367
354
|
suffix ||= self.suffix
|
368
355
|
return nil if identifier.to_s.empty?
|
369
356
|
|
370
|
-
objkey =
|
357
|
+
objkey = dbkey(identifier, suffix)
|
371
358
|
|
372
359
|
Familia.ld "[.find_by_id] #{self} from key #{objkey})"
|
373
|
-
Familia.trace :FIND_BY_ID, Familia.
|
360
|
+
Familia.trace :FIND_BY_ID, Familia.dbclient(uri), objkey, caller(1..1).first if Familia.debug?
|
374
361
|
find_by_key objkey
|
375
362
|
end
|
376
363
|
alias find find_by_id
|
@@ -380,10 +367,10 @@ module Familia
|
|
380
367
|
# Checks if an object with the given identifier exists in Redis.
|
381
368
|
#
|
382
369
|
# @param identifier [String, Integer] The unique identifier for the object.
|
383
|
-
# @param suffix [Symbol, nil] The suffix to use in the
|
370
|
+
# @param suffix [Symbol, nil] The suffix to use in the dbkey (default: class suffix).
|
384
371
|
# @return [Boolean] true if the object exists, false otherwise.
|
385
372
|
#
|
386
|
-
# This method constructs the full
|
373
|
+
# This method constructs the full dbkey using the provided identifier and suffix,
|
387
374
|
# then checks if the key exists in Redis.
|
388
375
|
#
|
389
376
|
# @example
|
@@ -393,24 +380,24 @@ module Familia
|
|
393
380
|
suffix ||= self.suffix
|
394
381
|
return false if identifier.to_s.empty?
|
395
382
|
|
396
|
-
objkey =
|
383
|
+
objkey = dbkey identifier, suffix
|
397
384
|
|
398
|
-
ret =
|
399
|
-
Familia.trace :EXISTS,
|
385
|
+
ret = dbclient.exists objkey
|
386
|
+
Familia.trace :EXISTS, dbclient, "#{objkey} #{ret.inspect}", caller(1..1) if Familia.debug?
|
400
387
|
|
401
|
-
ret.positive? # differs from
|
388
|
+
ret.positive? # differs from Valkey API but I think it's okay bc `exists?` is a predicate method.
|
402
389
|
end
|
403
390
|
|
404
|
-
# Destroys an object in
|
391
|
+
# Destroys an object in Database with the given identifier.
|
405
392
|
#
|
406
393
|
# @param identifier [String, Integer] The unique identifier for the object to destroy.
|
407
|
-
# @param suffix [Symbol, nil] The suffix to use in the
|
394
|
+
# @param suffix [Symbol, nil] The suffix to use in the dbkey (default: class suffix).
|
408
395
|
# @return [Boolean] true if the object was successfully destroyed, false otherwise.
|
409
396
|
#
|
410
397
|
# This method is part of Familia's high-level object lifecycle management. While `delete!`
|
411
|
-
# operates directly on
|
398
|
+
# operates directly on dbkeys, `destroy!` operates at the object level and is used for
|
412
399
|
# ORM-style operations. Use `destroy!` when removing complete objects from the system, and
|
413
|
-
# `delete!` when working directly with
|
400
|
+
# `delete!` when working directly with dbkeys.
|
414
401
|
#
|
415
402
|
# @example
|
416
403
|
# User.destroy!(123) # Removes user:123:object from Redis
|
@@ -419,27 +406,27 @@ module Familia
|
|
419
406
|
suffix ||= self.suffix
|
420
407
|
return false if identifier.to_s.empty?
|
421
408
|
|
422
|
-
objkey =
|
409
|
+
objkey = dbkey identifier, suffix
|
423
410
|
|
424
|
-
ret =
|
425
|
-
Familia.trace :DESTROY!,
|
411
|
+
ret = dbclient.del objkey
|
412
|
+
Familia.trace :DESTROY!, dbclient, "#{objkey} #{ret.inspect}", caller(1..1) if Familia.debug?
|
426
413
|
ret.positive?
|
427
414
|
end
|
428
415
|
|
429
|
-
# Finds all keys in
|
416
|
+
# Finds all keys in Database matching the given suffix pattern.
|
430
417
|
#
|
431
418
|
# @param suffix [String] The suffix pattern to match (default: '*').
|
432
|
-
# @return [Array<String>] An array of matching
|
419
|
+
# @return [Array<String>] An array of matching dbkeys.
|
433
420
|
#
|
434
|
-
# This method searches for all
|
435
|
-
# It uses the class's
|
421
|
+
# This method searches for all dbkeys that match the given suffix pattern.
|
422
|
+
# It uses the class's dbkey method to construct the search pattern.
|
436
423
|
#
|
437
424
|
# @example
|
438
425
|
# User.find # Returns all keys matching user:*:object
|
439
426
|
# User.find('active') # Returns all keys matching user:*:active
|
440
427
|
#
|
441
428
|
def find_keys(suffix = '*')
|
442
|
-
|
429
|
+
dbclient.keys(dbkey('*', suffix)) || []
|
443
430
|
end
|
444
431
|
|
445
432
|
# +identifier+ can be a value or an Array of values used to create the index.
|
@@ -449,15 +436,15 @@ module Familia
|
|
449
436
|
# +suffix+ If a nil value is explicitly passed in, it won't appear in the redis
|
450
437
|
# key that's returned. If no suffix is passed in, the class' suffix is used
|
451
438
|
# as the default (via the class method self.suffix). It's an important
|
452
|
-
# distinction b/c passing in an explicitly nil is how
|
439
|
+
# distinction b/c passing in an explicitly nil is how DataType objects
|
453
440
|
# at the class level are created without the global default 'object'
|
454
|
-
# suffix. See
|
455
|
-
def
|
456
|
-
# Familia.ld "[.
|
441
|
+
# suffix. See DataType#dbkey "parent_class?" for more details.
|
442
|
+
def dbkey(identifier, suffix = self.suffix)
|
443
|
+
# Familia.ld "[.dbkey] #{identifier} for #{self} (suffix:#{suffix})"
|
457
444
|
raise NoIdentifier, self if identifier.to_s.empty?
|
458
445
|
|
459
446
|
identifier &&= identifier.to_s
|
460
|
-
Familia.
|
447
|
+
Familia.dbkey(prefix, identifier, suffix)
|
461
448
|
end
|
462
449
|
|
463
450
|
def dump_method
|
@@ -468,6 +455,6 @@ module Familia
|
|
468
455
|
@load_method || :from_json # Familia.load_method
|
469
456
|
end
|
470
457
|
end
|
471
|
-
|
458
|
+
|
472
459
|
end
|
473
460
|
end
|