familia 2.0.0.pre4 → 2.0.0.pre5
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/.gitignore +3 -0
- data/.rubocop_todo.yml +17 -17
- data/CLAUDE.md +3 -3
- data/Gemfile +5 -1
- data/Gemfile.lock +18 -3
- data/README.md +36 -157
- data/TEST_COVERAGE.md +40 -0
- data/docs/overview.md +359 -0
- data/docs/wiki/API-Reference.md +270 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +64 -0
- data/docs/wiki/Home.md +49 -0
- data/docs/wiki/Implementation-Guide.md +183 -0
- data/docs/wiki/Security-Model.md +143 -0
- data/lib/familia/base.rb +18 -27
- data/lib/familia/connection.rb +6 -5
- data/lib/familia/{datatype → data_type}/commands.rb +2 -5
- data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
- data/lib/familia/{datatype → data_type}/types/hashkey.rb +2 -2
- data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
- data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
- data/lib/familia/{datatype → data_type}/types/string.rb +2 -1
- data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
- data/lib/familia/{datatype.rb → data_type.rb} +10 -12
- data/lib/familia/encryption/manager.rb +102 -0
- data/lib/familia/encryption/provider.rb +49 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +103 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +118 -0
- data/lib/familia/encryption/registry.rb +50 -0
- data/lib/familia/encryption.rb +178 -0
- data/lib/familia/encryption_request_cache.rb +68 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +153 -0
- data/lib/familia/features/encrypted_fields.rb +28 -0
- data/lib/familia/features/expiration.rb +107 -77
- data/lib/familia/features/quantization.rb +5 -9
- data/lib/familia/features/relatable_objects.rb +2 -4
- data/lib/familia/features/safe_dump.rb +14 -17
- data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
- data/lib/familia/features/transient_fields.rb +47 -0
- data/lib/familia/features.rb +40 -24
- data/lib/familia/field_type.rb +270 -0
- data/lib/familia/horreum/connection.rb +8 -11
- data/lib/familia/horreum/{commands.rb → database_commands.rb} +7 -19
- data/lib/familia/horreum/definition_methods.rb +453 -0
- data/lib/familia/horreum/{class_methods.rb → management_methods.rb} +19 -243
- data/lib/familia/horreum/serialization.rb +46 -18
- data/lib/familia/horreum/settings.rb +10 -2
- data/lib/familia/horreum/utils.rb +9 -10
- data/lib/familia/horreum.rb +18 -10
- data/lib/familia/logging.rb +14 -14
- data/lib/familia/settings.rb +39 -3
- data/lib/familia/utils.rb +45 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -1
- data/try/core/base_enhancements_try.rb +115 -0
- data/try/core/connection_try.rb +0 -1
- data/try/core/errors_try.rb +0 -1
- data/try/core/familia_extended_try.rb +3 -4
- data/try/core/familia_try.rb +0 -1
- data/try/core/pools_try.rb +2 -2
- data/try/core/secure_identifier_try.rb +0 -1
- data/try/core/settings_try.rb +0 -1
- data/try/core/utils_try.rb +0 -1
- data/try/{datatypes → data_types}/boolean_try.rb +1 -2
- data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
- data/try/{datatypes → data_types}/hash_try.rb +1 -2
- data/try/{datatypes → data_types}/list_try.rb +1 -2
- data/try/{datatypes → data_types}/set_try.rb +1 -2
- data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
- data/try/{datatypes → data_types}/string_try.rb +1 -2
- data/try/debugging/README.md +32 -0
- data/try/debugging/cache_behavior_tracer.rb +91 -0
- data/try/debugging/encryption_method_tracer.rb +138 -0
- data/try/debugging/provider_diagnostics.rb +110 -0
- data/try/edge_cases/hash_symbolization_try.rb +0 -1
- data/try/edge_cases/json_serialization_try.rb +0 -1
- data/try/edge_cases/reserved_keywords_try.rb +42 -11
- data/try/encryption/config_persistence_try.rb +192 -0
- data/try/encryption/encryption_core_try.rb +328 -0
- data/try/encryption/instance_variable_scope_try.rb +31 -0
- data/try/encryption/module_loading_try.rb +28 -0
- data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
- data/try/encryption/roundtrip_validation_try.rb +28 -0
- data/try/encryption/secure_memory_handling_try.rb +125 -0
- data/try/features/encrypted_fields_core_try.rb +117 -0
- data/try/features/encrypted_fields_integration_try.rb +220 -0
- data/try/features/encrypted_fields_no_cache_security_try.rb +205 -0
- data/try/features/encrypted_fields_security_try.rb +370 -0
- data/try/features/encryption_fields/aad_protection_try.rb +53 -0
- data/try/features/encryption_fields/context_isolation_try.rb +120 -0
- data/try/features/encryption_fields/error_conditions_try.rb +116 -0
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +122 -0
- data/try/features/encryption_fields/fresh_key_try.rb +163 -0
- data/try/features/encryption_fields/key_rotation_try.rb +117 -0
- data/try/features/encryption_fields/memory_security_try.rb +37 -0
- data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +54 -0
- data/try/features/encryption_fields/thread_safety_try.rb +199 -0
- data/try/features/expiration_try.rb +0 -1
- data/try/features/feature_dependencies_try.rb +159 -0
- data/try/features/quantization_try.rb +0 -1
- data/try/features/real_feature_integration_try.rb +148 -0
- data/try/features/relatable_objects_try.rb +0 -1
- data/try/features/safe_dump_advanced_try.rb +0 -1
- data/try/features/safe_dump_try.rb +0 -1
- data/try/features/transient_fields/redacted_string_try.rb +248 -0
- data/try/features/transient_fields/refresh_reset_try.rb +164 -0
- data/try/features/transient_fields/simple_refresh_test.rb +50 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
- data/try/features/transient_fields_core_try.rb +181 -0
- data/try/features/transient_fields_integration_try.rb +260 -0
- data/try/helpers/test_helpers.rb +42 -0
- data/try/horreum/base_try.rb +157 -3
- data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
- data/try/horreum/field_categories_try.rb +118 -0
- data/try/horreum/field_definition_try.rb +96 -0
- data/try/horreum/initialization_try.rb +0 -1
- data/try/horreum/relations_try.rb +0 -1
- data/try/horreum/serialization_persistent_fields_try.rb +165 -0
- data/try/horreum/serialization_try.rb +2 -3
- data/try/memory/memory_basic_test.rb +73 -0
- data/try/memory/memory_detailed_test.rb +121 -0
- data/try/memory/memory_docker_ruby_dump.sh +80 -0
- data/try/memory/memory_search_for_string.rb +83 -0
- data/try/memory/test_actual_redactedstring_protection.rb +38 -0
- data/try/models/customer_safe_dump_try.rb +0 -1
- data/try/models/customer_try.rb +0 -1
- data/try/models/datatype_base_try.rb +1 -2
- data/try/models/familia_object_try.rb +0 -1
- metadata +85 -18
@@ -0,0 +1,270 @@
|
|
1
|
+
# lib/familia/field_type.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
# Base class for all field types in Familia
|
5
|
+
#
|
6
|
+
# Field types encapsulate the behavior for different kinds of fields,
|
7
|
+
# including how their getter/setter methods are defined and how values
|
8
|
+
# are serialized/deserialized.
|
9
|
+
#
|
10
|
+
# @example Creating a custom field type
|
11
|
+
# class TimestampFieldType < Familia::FieldType
|
12
|
+
# def define_setter(klass)
|
13
|
+
# field_name = @name
|
14
|
+
# klass.define_method :"#{@method_name}=" do |value|
|
15
|
+
# timestamp = value.is_a?(Time) ? value.to_i : value
|
16
|
+
# instance_variable_set(:"@#{field_name}", timestamp)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def define_getter(klass)
|
21
|
+
# field_name = @name
|
22
|
+
# klass.define_method @method_name do
|
23
|
+
# timestamp = instance_variable_get(:"@#{field_name}")
|
24
|
+
# timestamp ? Time.at(timestamp) : nil
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
class FieldType
|
30
|
+
attr_reader :name, :options, :method_name, :fast_method_name, :on_conflict
|
31
|
+
|
32
|
+
# Initialize a new field type
|
33
|
+
#
|
34
|
+
# @param name [Symbol] The field name
|
35
|
+
# @param as [Symbol, String, false] The method name (defaults to field name)
|
36
|
+
# If false, no accessor methods are created
|
37
|
+
# @param fast_method [Symbol, String, false] The fast method name
|
38
|
+
# (defaults to "#{name}!"). If false, no fast method is created
|
39
|
+
# @param on_conflict [Symbol] Conflict resolution strategy when method
|
40
|
+
# already exists (:raise, :skip, :warn, :overwrite)
|
41
|
+
# @param options [Hash] Additional options for the field type
|
42
|
+
#
|
43
|
+
def initialize(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, **options)
|
44
|
+
@name = name.to_sym
|
45
|
+
@method_name = as == false ? nil : as.to_sym
|
46
|
+
@fast_method_name = fast_method == false ? nil : fast_method&.to_sym
|
47
|
+
|
48
|
+
# Validate fast method name format
|
49
|
+
if @fast_method_name && !@fast_method_name.to_s.end_with?('!')
|
50
|
+
raise ArgumentError, "Fast method name must end with '!' (got: #{@fast_method_name})"
|
51
|
+
end
|
52
|
+
|
53
|
+
@on_conflict = on_conflict
|
54
|
+
@options = options
|
55
|
+
end
|
56
|
+
|
57
|
+
# Install this field type on a class
|
58
|
+
#
|
59
|
+
# This method defines all necessary methods on the target class
|
60
|
+
# and registers the field type for later reference.
|
61
|
+
#
|
62
|
+
# @param klass [Class] The class to install this field type on
|
63
|
+
#
|
64
|
+
def install(klass)
|
65
|
+
if @method_name
|
66
|
+
# For skip strategy, check for any method conflicts first
|
67
|
+
if @on_conflict == :skip
|
68
|
+
has_getter_conflict = klass.method_defined?(@method_name) || klass.private_method_defined?(@method_name)
|
69
|
+
has_setter_conflict = klass.method_defined?(:"#{@method_name}=") || klass.private_method_defined?(:"#{@method_name}=")
|
70
|
+
|
71
|
+
# If either getter or setter conflicts, skip the whole field
|
72
|
+
return if has_getter_conflict || has_setter_conflict
|
73
|
+
end
|
74
|
+
|
75
|
+
define_getter(klass)
|
76
|
+
define_setter(klass)
|
77
|
+
end
|
78
|
+
|
79
|
+
define_fast_writer(klass) if @fast_method_name
|
80
|
+
end
|
81
|
+
|
82
|
+
# Define the getter method on the target class
|
83
|
+
#
|
84
|
+
# Subclasses can override this to customize getter behavior.
|
85
|
+
# The default implementation creates a simple attr_reader equivalent.
|
86
|
+
#
|
87
|
+
# @param klass [Class] The class to define the method on
|
88
|
+
#
|
89
|
+
def define_getter(klass)
|
90
|
+
field_name = @name
|
91
|
+
method_name = @method_name
|
92
|
+
|
93
|
+
handle_method_conflict(klass, method_name) do
|
94
|
+
klass.define_method method_name do
|
95
|
+
instance_variable_get(:"@#{field_name}")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Define the setter method on the target class
|
101
|
+
#
|
102
|
+
# Subclasses can override this to customize setter behavior.
|
103
|
+
# The default implementation creates a simple attr_writer equivalent.
|
104
|
+
#
|
105
|
+
# @param klass [Class] The class to define the method on
|
106
|
+
#
|
107
|
+
def define_setter(klass)
|
108
|
+
field_name = @name
|
109
|
+
method_name = @method_name
|
110
|
+
|
111
|
+
handle_method_conflict(klass, :"#{method_name}=") do
|
112
|
+
klass.define_method :"#{method_name}=" do |value|
|
113
|
+
instance_variable_set(:"@#{field_name}", value)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Define the fast writer method on the target class
|
119
|
+
#
|
120
|
+
# Fast methods provide direct database access for immediate persistence.
|
121
|
+
# Subclasses can override this to customize fast method behavior.
|
122
|
+
#
|
123
|
+
# @param klass [Class] The class to define the method on
|
124
|
+
#
|
125
|
+
def define_fast_writer(klass)
|
126
|
+
return unless @fast_method_name&.to_s&.end_with?('!')
|
127
|
+
|
128
|
+
field_name = @name
|
129
|
+
method_name = @method_name
|
130
|
+
fast_method_name = @fast_method_name
|
131
|
+
|
132
|
+
handle_method_conflict(klass, fast_method_name) do
|
133
|
+
klass.define_method fast_method_name do |*args|
|
134
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0 or 1)" if args.size > 1
|
135
|
+
|
136
|
+
val = args.first
|
137
|
+
|
138
|
+
# If no value provided, return current stored value
|
139
|
+
return hget(field_name) if val.nil?
|
140
|
+
|
141
|
+
begin
|
142
|
+
# Trace the operation if debugging is enabled
|
143
|
+
Familia.trace :FAST_WRITER, dbclient, "#{field_name}: #{val.inspect}", caller(1..1) if Familia.debug?
|
144
|
+
|
145
|
+
# Convert value for database storage
|
146
|
+
prepared = serialize_value(val)
|
147
|
+
Familia.ld "[FieldType#define_fast_writer] #{fast_method_name} val: #{val.class} prepared: #{prepared.class}"
|
148
|
+
|
149
|
+
# Use the setter method to update instance variable
|
150
|
+
send(:"#{method_name}=", val) if method_name
|
151
|
+
|
152
|
+
# Persist to database immediately
|
153
|
+
ret = hset(field_name, prepared)
|
154
|
+
ret.zero? || ret.positive?
|
155
|
+
rescue Familia::Problem => e
|
156
|
+
raise "#{fast_method_name} method failed: #{e.message}", e.backtrace
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# Whether this field should be persisted to the database
|
163
|
+
#
|
164
|
+
# @return [Boolean] true if field should be persisted
|
165
|
+
#
|
166
|
+
def persistent?
|
167
|
+
true
|
168
|
+
end
|
169
|
+
|
170
|
+
def transient?
|
171
|
+
!persistent?
|
172
|
+
end
|
173
|
+
|
174
|
+
# The category for this field type (used for filtering)
|
175
|
+
#
|
176
|
+
# @return [Symbol] the field category
|
177
|
+
#
|
178
|
+
def category
|
179
|
+
:field
|
180
|
+
end
|
181
|
+
|
182
|
+
# Serialize a value for database storage
|
183
|
+
#
|
184
|
+
# Subclasses can override this to customize serialization.
|
185
|
+
# The default implementation passes values through unchanged.
|
186
|
+
#
|
187
|
+
# @param value [Object] The value to serialize
|
188
|
+
# @param record [Object] The record instance (for context)
|
189
|
+
# @return [Object] The serialized value
|
190
|
+
#
|
191
|
+
def serialize(value, _record = nil)
|
192
|
+
value
|
193
|
+
end
|
194
|
+
|
195
|
+
# Deserialize a value from database storage
|
196
|
+
#
|
197
|
+
# Subclasses can override this to customize deserialization.
|
198
|
+
# The default implementation passes values through unchanged.
|
199
|
+
#
|
200
|
+
# @param value [Object] The value to deserialize
|
201
|
+
# @param record [Object] The record instance (for context)
|
202
|
+
# @return [Object] The deserialized value
|
203
|
+
#
|
204
|
+
def deserialize(value, _record = nil)
|
205
|
+
value
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns all method names generated for this field (used for conflict detection)
|
209
|
+
#
|
210
|
+
# @return [Array<Symbol>] Array of method names this field type generates
|
211
|
+
#
|
212
|
+
def generated_methods
|
213
|
+
[@method_name, @fast_method_name].compact
|
214
|
+
end
|
215
|
+
|
216
|
+
# Enhanced inspection output for debugging
|
217
|
+
#
|
218
|
+
# @return [String] Human-readable representation
|
219
|
+
#
|
220
|
+
def inspect
|
221
|
+
attributes = [
|
222
|
+
"name=#{@name}",
|
223
|
+
"method_name=#{@method_name}",
|
224
|
+
"fast_method_name=#{@fast_method_name}",
|
225
|
+
"on_conflict=#{@on_conflict}",
|
226
|
+
"category=#{category}"
|
227
|
+
]
|
228
|
+
"#<#{self.class.name} #{attributes.join(' ')}>"
|
229
|
+
end
|
230
|
+
alias to_s inspect
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
# Handle method name conflicts during definition
|
235
|
+
#
|
236
|
+
# @param klass [Class] The target class
|
237
|
+
# @param method_name [Symbol] The method name to define
|
238
|
+
# @yield Block that defines the method
|
239
|
+
#
|
240
|
+
def handle_method_conflict(klass, method_name)
|
241
|
+
case @on_conflict
|
242
|
+
when :skip
|
243
|
+
return if klass.method_defined?(method_name) || klass.private_method_defined?(method_name)
|
244
|
+
when :warn
|
245
|
+
if klass.method_defined?(method_name) || klass.private_method_defined?(method_name)
|
246
|
+
warn <<~WARNING
|
247
|
+
|
248
|
+
WARNING: Method >>> #{method_name} <<< already exists on #{klass}.
|
249
|
+
Field functionality may be broken. Consider using a different name
|
250
|
+
with field(:#{@name}, as: :other_name)
|
251
|
+
|
252
|
+
Called from:
|
253
|
+
#{Familia.pretty_stack(limit: 3)}
|
254
|
+
|
255
|
+
WARNING
|
256
|
+
end
|
257
|
+
when :raise
|
258
|
+
if klass.method_defined?(method_name) || klass.private_method_defined?(method_name)
|
259
|
+
raise ArgumentError, "Method >>> #{method_name} <<< already defined for #{klass}"
|
260
|
+
end
|
261
|
+
when :overwrite
|
262
|
+
# Proceed silently - allow overwrite
|
263
|
+
else
|
264
|
+
raise ArgumentError, "Unknown conflict resolution strategy: #{@on_conflict}"
|
265
|
+
end
|
266
|
+
|
267
|
+
yield
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
class Horreum
|
5
|
-
|
6
5
|
# Familia::Horreum::Connection
|
7
6
|
#
|
8
7
|
module Connection
|
@@ -47,36 +46,34 @@ module Familia
|
|
47
46
|
#
|
48
47
|
# @note This method works with the global Familia.transaction context when available
|
49
48
|
#
|
50
|
-
def transaction
|
49
|
+
def transaction(&)
|
51
50
|
# If we're already in a Familia.transaction context, just yield the multi connection
|
52
51
|
if Fiber[:familia_transaction]
|
53
52
|
yield(Fiber[:familia_transaction])
|
54
53
|
else
|
55
54
|
# Otherwise, create a local transaction
|
56
|
-
block_result = dbclient.multi
|
57
|
-
yield(conn)
|
58
|
-
end
|
55
|
+
block_result = dbclient.multi(&)
|
59
56
|
end
|
60
57
|
block_result
|
61
58
|
end
|
62
59
|
alias multi transaction
|
63
60
|
|
64
|
-
def pipeline
|
61
|
+
def pipeline(&)
|
65
62
|
# If we're already in a Familia.pipeline context, just yield the pipeline connection
|
66
63
|
if Fiber[:familia_pipeline]
|
67
64
|
yield(Fiber[:familia_pipeline])
|
68
65
|
else
|
69
66
|
# Otherwise, create a local transaction
|
70
|
-
block_result = dbclient.pipeline
|
71
|
-
yield(conn)
|
72
|
-
end
|
67
|
+
block_result = dbclient.pipeline(&)
|
73
68
|
end
|
74
69
|
block_result
|
75
70
|
end
|
76
|
-
|
77
71
|
end
|
78
72
|
|
79
|
-
#
|
73
|
+
# include for instance methods after it's loaded. Note that Horreum::Utils
|
74
|
+
# are also included and at one time also has a uri method. This connection
|
75
|
+
# module is also extended for the class level methods. It will require some
|
76
|
+
# disambiguation at some point.
|
80
77
|
include Familia::Horreum::Connection
|
81
78
|
end
|
82
79
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# lib/familia/horreum/
|
1
|
+
# lib/familia/horreum/database_commands.rb
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
# InstanceMethods - Module containing instance-level methods for Familia
|
@@ -7,7 +7,6 @@ module Familia
|
|
7
7
|
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
|
-
|
11
10
|
# Methods that call Database commands (InstanceMethods)
|
12
11
|
#
|
13
12
|
# NOTE: There is no hgetall for Horreum. This is because Horreum
|
@@ -16,8 +15,7 @@ module Familia
|
|
16
15
|
# emphasize this, instead of "refreshing" the object with hgetall,
|
17
16
|
# just load the object again.
|
18
17
|
#
|
19
|
-
module
|
20
|
-
|
18
|
+
module DatabaseCommands
|
21
19
|
def move(logical_database)
|
22
20
|
dbclient.move dbkey, logical_database
|
23
21
|
end
|
@@ -41,6 +39,7 @@ module Familia
|
|
41
39
|
def exists?(check_size: true)
|
42
40
|
key_exists = self.class.dbclient.exists?(dbkey)
|
43
41
|
return key_exists unless check_size
|
42
|
+
|
44
43
|
key_exists && !size.zero?
|
45
44
|
end
|
46
45
|
|
@@ -83,21 +82,11 @@ module Familia
|
|
83
82
|
end
|
84
83
|
alias remove remove_field # deprecated
|
85
84
|
|
86
|
-
def
|
85
|
+
def data_type
|
87
86
|
Familia.trace :DATATYPE, dbclient, uri, caller(1..1) if Familia.debug?
|
88
87
|
dbclient.type dbkey(suffix)
|
89
88
|
end
|
90
89
|
|
91
|
-
|
92
|
-
# Retrieves the prefix for the current instance by delegating to its class.
|
93
|
-
#
|
94
|
-
# @return [String] The prefix associated with the class of the current instance.
|
95
|
-
# @example
|
96
|
-
# instance.prefix
|
97
|
-
def prefix
|
98
|
-
self.class.prefix
|
99
|
-
end
|
100
|
-
|
101
90
|
# For parity with DataType#hgetall
|
102
91
|
def hgetall
|
103
92
|
Familia.trace :HGETALL, dbclient, uri, caller(1..1) if Familia.debug?
|
@@ -117,8 +106,8 @@ module Familia
|
|
117
106
|
dbclient.hset dbkey, field, value
|
118
107
|
end
|
119
108
|
|
120
|
-
def hmset(hsh={})
|
121
|
-
hsh ||=
|
109
|
+
def hmset(hsh = {})
|
110
|
+
hsh ||= to_h
|
122
111
|
Familia.trace :HMSET, dbclient, hsh, caller(1..1) if Familia.debug?
|
123
112
|
dbclient.hmset dbkey(suffix), hsh
|
124
113
|
end
|
@@ -175,9 +164,8 @@ module Familia
|
|
175
164
|
ret.positive?
|
176
165
|
end
|
177
166
|
alias clear delete!
|
178
|
-
|
179
167
|
end
|
180
168
|
|
181
|
-
include
|
169
|
+
include DatabaseCommands # these become Familia::Horreum instance methods
|
182
170
|
end
|
183
171
|
end
|