familia 2.0.0.pre5 → 2.0.0.pre7
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/claude-code-review.yml +57 -0
- data/.github/workflows/claude.yml +71 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +3 -0
- data/CLAUDE.md +32 -10
- data/Gemfile +2 -2
- data/Gemfile.lock +4 -3
- data/docs/wiki/API-Reference.md +95 -18
- data/docs/wiki/Connection-Pooling-Guide.md +437 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +40 -3
- data/docs/wiki/Expiration-Feature-Guide.md +596 -0
- data/docs/wiki/Feature-System-Guide.md +631 -0
- data/docs/wiki/Features-System-Developer-Guide.md +892 -0
- data/docs/wiki/Field-System-Guide.md +784 -0
- data/docs/wiki/Home.md +82 -15
- data/docs/wiki/Implementation-Guide.md +126 -33
- data/docs/wiki/Quantization-Feature-Guide.md +721 -0
- data/docs/wiki/Relationships-Guide.md +684 -0
- data/docs/wiki/Security-Model.md +65 -25
- data/docs/wiki/Transient-Fields-Guide.md +280 -0
- data/examples/bit_encoding_integration.rb +237 -0
- data/examples/redis_command_validation_example.rb +231 -0
- data/examples/relationships_basic.rb +273 -0
- data/lib/familia/base.rb +1 -1
- data/lib/familia/connection.rb +3 -3
- data/lib/familia/data_type/types/counter.rb +38 -0
- data/lib/familia/data_type/types/hashkey.rb +18 -0
- data/lib/familia/data_type/types/lock.rb +43 -0
- data/lib/familia/data_type/types/string.rb +9 -2
- data/lib/familia/data_type.rb +9 -6
- data/lib/familia/encryption/encrypted_data.rb +137 -0
- data/lib/familia/encryption/manager.rb +21 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +20 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +20 -0
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +17 -3
- data/lib/familia/features/encrypted_fields/concealed_string.rb +293 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +94 -26
- data/lib/familia/features/encrypted_fields.rb +413 -4
- data/lib/familia/features/expiration.rb +319 -33
- data/lib/familia/features/quantization.rb +385 -44
- data/lib/familia/features/relationships/cascading.rb +438 -0
- data/lib/familia/features/relationships/indexing.rb +370 -0
- data/lib/familia/features/relationships/membership.rb +503 -0
- data/lib/familia/features/relationships/permission_management.rb +264 -0
- data/lib/familia/features/relationships/querying.rb +620 -0
- data/lib/familia/features/relationships/redis_operations.rb +274 -0
- data/lib/familia/features/relationships/score_encoding.rb +442 -0
- data/lib/familia/features/relationships/tracking.rb +379 -0
- data/lib/familia/features/relationships.rb +466 -0
- data/lib/familia/features/safe_dump.rb +1 -1
- data/lib/familia/features/transient_fields/redacted_string.rb +1 -1
- data/lib/familia/features/transient_fields.rb +192 -10
- data/lib/familia/features.rb +2 -1
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/{connection.rb → core/connection.rb} +2 -8
- data/lib/familia/horreum/{database_commands.rb → core/database_commands.rb} +14 -3
- data/lib/familia/horreum/core/serialization.rb +535 -0
- data/lib/familia/horreum/{utils.rb → core/utils.rb} +0 -2
- data/lib/familia/horreum/core.rb +21 -0
- data/lib/familia/horreum/{settings.rb → shared/settings.rb} +0 -2
- data/lib/familia/horreum/{definition_methods.rb → subclass/definition.rb} +45 -29
- data/lib/familia/horreum/{management_methods.rb → subclass/management.rb} +9 -8
- data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
- data/lib/familia/horreum.rb +17 -17
- data/lib/familia/validation/command_recorder.rb +336 -0
- data/lib/familia/validation/expectations.rb +519 -0
- data/lib/familia/validation/test_helpers.rb +443 -0
- data/lib/familia/validation/validator.rb +412 -0
- data/lib/familia/validation.rb +140 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +1 -1
- data/try/core/create_method_try.rb +240 -0
- data/try/core/database_consistency_try.rb +299 -0
- data/try/core/errors_try.rb +25 -4
- data/try/core/familia_try.rb +1 -1
- data/try/core/persistence_operations_try.rb +297 -0
- data/try/data_types/counter_try.rb +93 -0
- data/try/data_types/lock_try.rb +133 -0
- data/try/debugging/debug_aad_process.rb +82 -0
- data/try/debugging/debug_concealed_internal.rb +59 -0
- data/try/debugging/debug_concealed_reveal.rb +61 -0
- data/try/debugging/debug_context_aad.rb +68 -0
- data/try/debugging/debug_context_simple.rb +80 -0
- data/try/debugging/debug_cross_context.rb +62 -0
- data/try/debugging/debug_database_load.rb +64 -0
- data/try/debugging/debug_encrypted_json_check.rb +53 -0
- data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
- data/try/debugging/debug_exists_lifecycle.rb +54 -0
- data/try/debugging/debug_field_decrypt.rb +74 -0
- data/try/debugging/debug_fresh_cross_context.rb +73 -0
- data/try/debugging/debug_load_path.rb +66 -0
- data/try/debugging/debug_method_definition.rb +46 -0
- data/try/debugging/debug_method_resolution.rb +41 -0
- data/try/debugging/debug_minimal.rb +24 -0
- data/try/debugging/debug_provider.rb +68 -0
- data/try/debugging/debug_secure_behavior.rb +73 -0
- data/try/debugging/debug_string_class.rb +46 -0
- data/try/debugging/debug_test.rb +46 -0
- data/try/debugging/debug_test_design.rb +80 -0
- data/try/edge_cases/hash_symbolization_try.rb +1 -0
- data/try/edge_cases/reserved_keywords_try.rb +1 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/encryption/encryption_core_try.rb +6 -4
- data/try/features/categorical_permissions_try.rb +515 -0
- data/try/features/encrypted_fields_core_try.rb +19 -11
- data/try/features/encrypted_fields_integration_try.rb +66 -70
- data/try/features/encrypted_fields_no_cache_security_try.rb +22 -8
- data/try/features/encrypted_fields_security_try.rb +151 -144
- data/try/features/encryption_fields/aad_protection_try.rb +108 -23
- data/try/features/encryption_fields/concealed_string_core_try.rb +253 -0
- data/try/features/encryption_fields/context_isolation_try.rb +30 -8
- data/try/features/encryption_fields/error_conditions_try.rb +6 -6
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +20 -14
- data/try/features/encryption_fields/fresh_key_try.rb +27 -22
- data/try/features/encryption_fields/key_rotation_try.rb +16 -10
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +15 -13
- data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
- data/try/features/encryption_fields/thread_safety_try.rb +6 -6
- data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/relationships_edge_cases_try.rb +145 -0
- data/try/features/relationships_performance_minimal_try.rb +132 -0
- data/try/features/relationships_performance_simple_try.rb +155 -0
- data/try/features/relationships_performance_try.rb +420 -0
- data/try/features/relationships_performance_working_try.rb +144 -0
- data/try/features/relationships_try.rb +237 -0
- data/try/features/safe_dump_try.rb +3 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields_integration_try.rb +1 -1
- data/try/helpers/test_helpers.rb +26 -1
- data/try/horreum/base_try.rb +14 -8
- data/try/horreum/enhanced_conflict_handling_try.rb +3 -1
- data/try/horreum/initialization_try.rb +1 -1
- data/try/horreum/relations_try.rb +2 -2
- data/try/horreum/serialization_persistent_fields_try.rb +8 -8
- data/try/horreum/serialization_try.rb +39 -4
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +1 -1
- data/try/validation/atomic_operations_try.rb.disabled +320 -0
- data/try/validation/command_validation_try.rb.disabled +207 -0
- data/try/validation/performance_validation_try.rb.disabled +324 -0
- data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
- metadata +81 -12
- data/TEST_COVERAGE.md +0 -40
- data/lib/familia/features/relatable_objects.rb +0 -125
- data/lib/familia/horreum/serialization.rb +0 -473
- data/try/features/relatable_objects_try.rb +0 -220
@@ -4,23 +4,120 @@ require_relative 'transient_fields/redacted_string'
|
|
4
4
|
|
5
5
|
module Familia
|
6
6
|
module Features
|
7
|
-
#
|
7
|
+
# TransientFields is a feature that provides secure handling of sensitive runtime data
|
8
|
+
# that should never be persisted to Redis/Valkey. Unlike encrypted fields, transient
|
9
|
+
# fields exist only in memory and are automatically wrapped in RedactedString objects
|
10
|
+
# for security.
|
8
11
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
+
# Transient fields are ideal for:
|
13
|
+
# - API keys and tokens that change frequently
|
14
|
+
# - Temporary passwords or passphrases
|
15
|
+
# - Session-specific secrets
|
16
|
+
# - Any sensitive data that should never touch persistent storage
|
17
|
+
# - Debug or development secrets that need secure handling
|
18
|
+
#
|
19
|
+
# All transient field values are automatically wrapped in RedactedString instances
|
20
|
+
# which provide:
|
21
|
+
# - Automatic redaction in logs and string representations
|
22
|
+
# - Secure memory management with explicit cleanup
|
23
|
+
# - Safe access patterns through expose blocks
|
24
|
+
# - Protection against accidental value exposure
|
25
|
+
#
|
26
|
+
# Example:
|
27
|
+
#
|
28
|
+
# class ApiClient < Familia::Horreum
|
29
|
+
# feature :transient_fields
|
30
|
+
#
|
31
|
+
# field :endpoint # Regular persistent field
|
32
|
+
# transient_field :token # Transient field (not persisted)
|
33
|
+
# transient_field :secret, as: :api_secret # Custom accessor name
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# client = ApiClient.new(
|
37
|
+
# endpoint: 'https://api.example.com',
|
38
|
+
# token: ENV['API_TOKEN'],
|
39
|
+
# secret: ENV['API_SECRET']
|
40
|
+
# )
|
41
|
+
#
|
42
|
+
# # Regular field persists
|
43
|
+
# client.save
|
44
|
+
# client.endpoint # => "https://api.example.com"
|
45
|
+
#
|
46
|
+
# # Transient fields are RedactedString instances
|
47
|
+
# puts client.token # => "[REDACTED]"
|
48
|
+
#
|
49
|
+
# # Access the actual value safely
|
50
|
+
# client.token.expose do |token|
|
51
|
+
# response = HTTP.post(client.endpoint,
|
52
|
+
# headers: { 'Authorization' => "Bearer #{token}" }
|
53
|
+
# )
|
54
|
+
# # Token value is only available within this block
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # Explicit cleanup when done
|
58
|
+
# client.token.clear!
|
59
|
+
#
|
60
|
+
# Security Features:
|
61
|
+
#
|
62
|
+
# RedactedString automatically protects sensitive values:
|
63
|
+
# - String representation shows "[REDACTED]" instead of actual value
|
64
|
+
# - Inspect output shows "[REDACTED]" instead of actual value
|
65
|
+
# - Hash values are constant to prevent value inference
|
66
|
+
# - Equality checks work only on object identity
|
67
|
+
#
|
68
|
+
# Safe Access Patterns:
|
69
|
+
#
|
70
|
+
# # ✅ Recommended: Use .expose block
|
71
|
+
# client.token.expose do |token|
|
72
|
+
# # Use token directly without creating copies
|
73
|
+
# HTTP.auth("Bearer #{token}") # Safe
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# # ✅ Direct access (use carefully)
|
77
|
+
# raw_token = client.token.value
|
78
|
+
# # Remember to clear original source if needed
|
79
|
+
#
|
80
|
+
# # ❌ Avoid: These create uncontrolled copies
|
81
|
+
# token_copy = client.token.value.dup # Creates copy in memory
|
82
|
+
# interpolated = "Bearer #{client.token}" # Creates copy via to_s
|
83
|
+
#
|
84
|
+
# Memory Management:
|
85
|
+
#
|
86
|
+
# # Clear individual fields
|
87
|
+
# client.token.clear!
|
88
|
+
#
|
89
|
+
# # Check if cleared
|
90
|
+
# client.token.cleared? # => true
|
91
|
+
#
|
92
|
+
# # Accessing cleared values raises error
|
93
|
+
# client.token.value # => SecurityError: Value already cleared
|
94
|
+
#
|
95
|
+
# ⚠️ Important Security Limitations:
|
96
|
+
#
|
97
|
+
# Ruby provides NO memory safety guarantees for cryptographic secrets:
|
98
|
+
# - No secure wiping: .clear! is best-effort only
|
99
|
+
# - GC copying: Garbage collector may duplicate secrets
|
100
|
+
# - String operations: Every manipulation creates copies
|
101
|
+
# - Memory persistence: Secrets may remain in memory indefinitely
|
102
|
+
#
|
103
|
+
# For highly sensitive applications, consider external secrets management
|
104
|
+
# (HashiCorp Vault, AWS Secrets Manager) or languages with secure memory handling.
|
12
105
|
#
|
13
106
|
module TransientFields
|
14
107
|
def self.included(base)
|
15
|
-
Familia.
|
108
|
+
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
16
109
|
base.extend ClassMethods
|
110
|
+
|
111
|
+
# Initialize transient fields tracking
|
112
|
+
base.instance_variable_set(:@transient_fields, []) unless base.instance_variable_defined?(:@transient_fields)
|
17
113
|
end
|
18
114
|
|
19
|
-
# ClassMethods
|
20
|
-
#
|
21
115
|
module ClassMethods
|
22
116
|
# Define a transient field that automatically wraps values in RedactedString
|
23
117
|
#
|
118
|
+
# Transient fields are not persisted to Redis/Valkey and exist only in memory.
|
119
|
+
# All values are automatically wrapped in RedactedString for security.
|
120
|
+
#
|
24
121
|
# @param name [Symbol] The field name
|
25
122
|
# @param as [Symbol] The method name (defaults to field name)
|
26
123
|
# @param kwargs [Hash] Additional field options
|
@@ -31,14 +128,99 @@ module Familia
|
|
31
128
|
# transient_field :api_key
|
32
129
|
# end
|
33
130
|
#
|
131
|
+
# @example Define a transient field with custom accessor name
|
132
|
+
# class Service < Familia::Horreum
|
133
|
+
# feature :transient_fields
|
134
|
+
# transient_field :secret_key, as: :api_secret
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# service = Service.new(secret_key: 'secret123')
|
138
|
+
# service.api_secret.expose { |key| use_api_key(key) }
|
139
|
+
#
|
34
140
|
def transient_field(name, as: name, **kwargs)
|
35
|
-
|
36
|
-
|
37
|
-
|
141
|
+
@transient_fields ||= []
|
142
|
+
@transient_fields << name unless @transient_fields.include?(name)
|
143
|
+
|
144
|
+
# Use the field type system for proper integration
|
38
145
|
require_relative 'transient_fields/transient_field_type'
|
39
146
|
field_type = TransientFieldType.new(name, as: as, **kwargs.merge(fast_method: false))
|
40
147
|
register_field_type(field_type)
|
41
148
|
end
|
149
|
+
|
150
|
+
# Returns list of transient field names defined on this class
|
151
|
+
#
|
152
|
+
# @return [Array<Symbol>] Array of transient field names
|
153
|
+
#
|
154
|
+
def transient_fields
|
155
|
+
@transient_fields || []
|
156
|
+
end
|
157
|
+
|
158
|
+
# Check if a field is transient
|
159
|
+
#
|
160
|
+
# @param field_name [Symbol] The field name to check
|
161
|
+
# @return [Boolean] true if field is transient, false otherwise
|
162
|
+
#
|
163
|
+
def transient_field?(field_name)
|
164
|
+
transient_fields.include?(field_name.to_sym)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Clear all transient fields for this instance
|
169
|
+
#
|
170
|
+
# This method iterates through all defined transient fields and calls
|
171
|
+
# clear! on each RedactedString instance. Use this for cleanup when
|
172
|
+
# the object is no longer needed.
|
173
|
+
#
|
174
|
+
# @return [void]
|
175
|
+
#
|
176
|
+
# @example Clear all secrets when done
|
177
|
+
# client = ApiClient.new(token: 'secret', api_key: 'key123')
|
178
|
+
# # ... use client ...
|
179
|
+
# client.clear_transient_fields!
|
180
|
+
# client.token.cleared? # => true
|
181
|
+
#
|
182
|
+
def clear_transient_fields!
|
183
|
+
self.class.transient_fields.each do |field_name|
|
184
|
+
field_value = instance_variable_get("@#{field_name}")
|
185
|
+
if field_value.respond_to?(:clear!)
|
186
|
+
field_value.clear!
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Check if all transient fields have been cleared
|
192
|
+
#
|
193
|
+
# @return [Boolean] true if all transient fields are cleared, false otherwise
|
194
|
+
#
|
195
|
+
def transient_fields_cleared?
|
196
|
+
self.class.transient_fields.all? do |field_name|
|
197
|
+
field_value = instance_variable_get("@#{field_name}")
|
198
|
+
field_value.nil? || (field_value.respond_to?(:cleared?) && field_value.cleared?)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns a hash of transient field names and their redacted representations
|
203
|
+
#
|
204
|
+
# This method is useful for debugging and logging as it shows which transient
|
205
|
+
# fields are defined without exposing their actual values.
|
206
|
+
#
|
207
|
+
# @return [Hash] Hash with field names as keys and "[REDACTED]" as values
|
208
|
+
#
|
209
|
+
# @example Check transient field status
|
210
|
+
# client.transient_fields_summary
|
211
|
+
# # => { token: "[REDACTED]", api_key: "[REDACTED]" }
|
212
|
+
#
|
213
|
+
def transient_fields_summary
|
214
|
+
self.class.transient_fields.each_with_object({}) do |field_name, summary|
|
215
|
+
field_value = instance_variable_get("@#{field_name}")
|
216
|
+
if field_value.nil?
|
217
|
+
summary[field_name] = nil
|
218
|
+
elsif field_value.respond_to?(:cleared?) && field_value.cleared?
|
219
|
+
summary[field_name] = "[CLEARED]"
|
220
|
+
else
|
221
|
+
summary[field_name] = "[REDACTED]"
|
222
|
+
end
|
223
|
+
end
|
42
224
|
end
|
43
225
|
|
44
226
|
Familia::Base.add_feature self, :transient_fields, depends_on: nil
|
data/lib/familia/features.rb
CHANGED
@@ -60,13 +60,14 @@ module Familia
|
|
60
60
|
end
|
61
61
|
|
62
62
|
end
|
63
|
-
|
64
63
|
end
|
65
64
|
|
66
65
|
# Load all feature files from the features directory
|
67
66
|
features_dir = File.join(__dir__, 'features')
|
67
|
+
Familia.ld "[DEBUG] Loading features from #{features_dir}"
|
68
68
|
if Dir.exist?(features_dir)
|
69
69
|
Dir.glob(File.join(features_dir, '*.rb')).each do |feature_file|
|
70
|
+
Familia.ld "[DEBUG] Loading feature #{feature_file}"
|
70
71
|
require_relative feature_file
|
71
72
|
end
|
72
73
|
end
|
data/lib/familia/field_type.rb
CHANGED
@@ -27,7 +27,7 @@ module Familia
|
|
27
27
|
# end
|
28
28
|
#
|
29
29
|
class FieldType
|
30
|
-
attr_reader :name, :options, :method_name, :fast_method_name, :on_conflict
|
30
|
+
attr_reader :name, :options, :method_name, :fast_method_name, :on_conflict, :loggable
|
31
31
|
|
32
32
|
# Initialize a new field type
|
33
33
|
#
|
@@ -38,9 +38,11 @@ module Familia
|
|
38
38
|
# (defaults to "#{name}!"). If false, no fast method is created
|
39
39
|
# @param on_conflict [Symbol] Conflict resolution strategy when method
|
40
40
|
# already exists (:raise, :skip, :warn, :overwrite)
|
41
|
+
# @param loggable [Boolean] Whether this field should be included in
|
42
|
+
# serialization and logging operations (default: true)
|
41
43
|
# @param options [Hash] Additional options for the field type
|
42
44
|
#
|
43
|
-
def initialize(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, **options)
|
45
|
+
def initialize(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, loggable: true, **options)
|
44
46
|
@name = name.to_sym
|
45
47
|
@method_name = as == false ? nil : as.to_sym
|
46
48
|
@fast_method_name = fast_method == false ? nil : fast_method&.to_sym
|
@@ -51,6 +53,7 @@ module Familia
|
|
51
53
|
end
|
52
54
|
|
53
55
|
@on_conflict = on_conflict
|
56
|
+
@loggable = loggable
|
54
57
|
@options = options
|
55
58
|
end
|
56
59
|
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
class Horreum
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# Connection: Valkey connection management for Horreum instances
|
6
|
+
# Provides both instance and class-level connection methods
|
7
7
|
module Connection
|
8
8
|
attr_reader :uri
|
9
9
|
|
@@ -69,11 +69,5 @@ module Familia
|
|
69
69
|
block_result
|
70
70
|
end
|
71
71
|
end
|
72
|
-
|
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.
|
77
|
-
include Familia::Horreum::Connection
|
78
72
|
end
|
79
73
|
end
|
@@ -37,7 +37,7 @@ module Familia
|
|
37
37
|
# @note The default behavior maintains backward compatibility by treating empty hashes
|
38
38
|
# as non-existent. Use `check_size: false` for pure key existence checking.
|
39
39
|
def exists?(check_size: true)
|
40
|
-
key_exists = self.class.
|
40
|
+
key_exists = self.class.exists?(identifier)
|
41
41
|
return key_exists unless check_size
|
42
42
|
|
43
43
|
key_exists && !size.zero?
|
@@ -106,6 +106,19 @@ module Familia
|
|
106
106
|
dbclient.hset dbkey, field, value
|
107
107
|
end
|
108
108
|
|
109
|
+
# Sets field in the hash stored at key to value, only if field does not yet exist.
|
110
|
+
# If key does not exist, a new key holding a hash is created. If field already exists,
|
111
|
+
# this operation has no effect.
|
112
|
+
#
|
113
|
+
# @param field [String] The field to set in the hash
|
114
|
+
# @param value [String] The value to set for the field
|
115
|
+
# @return [Integer] 1 if the field is a new field in the hash and the value was set,
|
116
|
+
# 0 if the field already exists in the hash and no operation was performed
|
117
|
+
def hsetnx(field, value)
|
118
|
+
Familia.trace :HSETNX, dbclient, field, caller(1..1) if Familia.debug?
|
119
|
+
dbclient.hsetnx dbkey, field, value
|
120
|
+
end
|
121
|
+
|
109
122
|
def hmset(hsh = {})
|
110
123
|
hsh ||= to_h
|
111
124
|
Familia.trace :HMSET, dbclient, hsh, caller(1..1) if Familia.debug?
|
@@ -165,7 +178,5 @@ module Familia
|
|
165
178
|
end
|
166
179
|
alias clear delete!
|
167
180
|
end
|
168
|
-
|
169
|
-
include DatabaseCommands # these become Familia::Horreum instance methods
|
170
181
|
end
|
171
182
|
end
|