familia 1.0.0.pre.rc6 → 1.1.0.pre.rc1
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/Gemfile +3 -1
- data/Gemfile.lock +3 -1
- data/README.md +5 -5
- data/VERSION.yml +2 -2
- data/familia.gemspec +1 -2
- data/lib/familia/base.rb +11 -11
- data/lib/familia/connection.rb +18 -2
- data/lib/familia/errors.rb +14 -0
- data/lib/familia/features/expiration.rb +13 -2
- data/lib/familia/features/quantization.rb +1 -1
- data/lib/familia/horreum/class_methods.rb +99 -41
- data/lib/familia/horreum/commands.rb +53 -14
- data/lib/familia/horreum/relations_management.rb +9 -2
- data/lib/familia/horreum/serialization.rb +32 -23
- data/lib/familia/horreum.rb +8 -2
- data/lib/familia/redistype/commands.rb +5 -2
- data/lib/familia/redistype/serialization.rb +17 -16
- data/lib/familia/redistype/types/hashkey.rb +166 -0
- data/lib/familia/{types → redistype/types}/list.rb +19 -14
- data/lib/familia/{types → redistype/types}/sorted_set.rb +23 -19
- data/lib/familia/{types → redistype/types}/string.rb +8 -6
- data/lib/familia/{types → redistype/types}/unsorted_set.rb +16 -12
- data/lib/familia/redistype.rb +19 -9
- data/lib/familia.rb +5 -1
- data/try/10_familia_try.rb +1 -1
- data/try/20_redis_type_try.rb +1 -1
- data/try/21_redis_type_zset_try.rb +1 -1
- data/try/22_redis_type_set_try.rb +1 -1
- data/try/23_redis_type_list_try.rb +2 -2
- data/try/24_redis_type_string_try.rb +3 -3
- data/try/25_redis_type_hash_try.rb +1 -1
- data/try/26_redis_bool_try.rb +2 -2
- data/try/27_redis_horreum_try.rb +4 -4
- data/try/30_familia_object_try.rb +8 -5
- data/try/40_customer_try.rb +6 -6
- metadata +15 -15
- data/lib/familia/types/hashkey.rb +0 -108
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7e5fe48e629eab30af342be4ad81f6afe5854b33b678b926b9c3470c0515627
|
4
|
+
data.tar.gz: 1d938baae35feb17f3d9855055152fdb0ba441317315fd3b2de6932e3c47e0cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 707e9aba376653fa439ab1ffc85c4b794f1f52444dbed86a093f1d9c93a1952d8f84fe465cdcf8798bf0f72e5a4099586f1895cad5225f6d81899deeedcbf3a5
|
7
|
+
data.tar.gz: a9323482330bef4a632d92fd6828da32f0be7aa4f107d659e4440a2e3a09ad3dd0c2011d90a5e1a3380ca85fdf5afe47fb4634e9cf21951986d92115523bfbba
|
data/Gemfile
CHANGED
@@ -5,7 +5,9 @@ source 'https://rubygems.org'
|
|
5
5
|
gemspec
|
6
6
|
|
7
7
|
group :development, :test do
|
8
|
-
|
8
|
+
# byebug only works with MRI
|
9
|
+
gem 'byebug', '~> 11.0', require: false if RUBY_ENGINE == 'ruby'
|
10
|
+
gem 'pry-byebug', '~> 3.10.1', require: false if RUBY_ENGINE == 'ruby'
|
9
11
|
gem 'rubocop', require: false
|
10
12
|
gem 'rubocop-performance', require: false
|
11
13
|
gem 'rubocop-thread_safety', require: false
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
familia (1.
|
4
|
+
familia (1.1.0.pre.rc1)
|
5
5
|
redis (>= 4.8.1, < 6.0)
|
6
|
+
stringio (~> 3.1.1)
|
6
7
|
uri-redis (~> 1.3)
|
7
8
|
|
8
9
|
GEM
|
@@ -55,6 +56,7 @@ GEM
|
|
55
56
|
rubocop (>= 0.90.0)
|
56
57
|
ruby-progressbar (1.13.0)
|
57
58
|
storable (0.10.0)
|
59
|
+
stringio (3.1.1)
|
58
60
|
strscan (3.1.0)
|
59
61
|
sysinfo (0.10.0)
|
60
62
|
drydock (< 1.0)
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Familia - 1.
|
1
|
+
# Familia - 1.1.0-rc1 (November 2024)
|
2
2
|
|
3
3
|
**Organize and store Ruby objects in Redis. A powerful Ruby ORM (of sorts) for Redis.**
|
4
4
|
|
@@ -9,7 +9,7 @@ Familia provides a flexible and feature-rich way to interact with Redis using Ru
|
|
9
9
|
|
10
10
|
Get it in one of the following ways:
|
11
11
|
|
12
|
-
* In your Gemfile: `gem 'familia', '>= 1.
|
12
|
+
* In your Gemfile: `gem 'familia', '>= 1.1.0-rc1'`
|
13
13
|
* Install it by hand: `gem install familia --pre`
|
14
14
|
* Or for development: `git clone git@github.com:delano/familia.git`
|
15
15
|
|
@@ -152,11 +152,11 @@ end
|
|
152
152
|
|
153
153
|
```ruby
|
154
154
|
class ComplexObject < Familia::Horreum
|
155
|
-
def
|
155
|
+
def serialize_value
|
156
156
|
custom_serialization_method
|
157
157
|
end
|
158
158
|
|
159
|
-
def self.
|
159
|
+
def self.deserialize_value(data)
|
160
160
|
custom_deserialization_method(data)
|
161
161
|
end
|
162
162
|
end
|
@@ -188,7 +188,7 @@ flower.save
|
|
188
188
|
### Retrieving and Updating Objects
|
189
189
|
|
190
190
|
```ruby
|
191
|
-
rose = Flower.
|
191
|
+
rose = Flower.find_by_id("rrose")
|
192
192
|
rose.name = "Pink Rose"
|
193
193
|
rose.save
|
194
194
|
```
|
data/VERSION.yml
CHANGED
data/familia.gemspec
CHANGED
@@ -20,9 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.8')
|
21
21
|
|
22
22
|
spec.add_dependency 'redis', '>= 4.8.1', '< 6.0'
|
23
|
+
spec.add_dependency 'stringio', '~> 3.1.1'
|
23
24
|
spec.add_dependency 'uri-redis', '~> 1.3'
|
24
25
|
|
25
|
-
# byebug only works with MRI
|
26
|
-
spec.add_development_dependency 'byebug', '~> 11.0' if RUBY_ENGINE == 'ruby'
|
27
26
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
28
27
|
end
|
data/lib/familia/base.rb
CHANGED
@@ -30,21 +30,21 @@ module Familia
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
#
|
34
|
-
#
|
33
|
+
# Base implementation of update_expiration that maintains API compatibility
|
34
|
+
# with the :expiration feature's implementation.
|
35
35
|
#
|
36
|
-
#
|
37
|
-
#
|
36
|
+
# This is a no-op implementation that gets overridden by features like
|
37
|
+
# :expiration. It accepts an optional ttl parameter to maintain interface
|
38
|
+
# compatibility with the overriding implementations.
|
38
39
|
#
|
39
|
-
# @
|
40
|
+
# @param ttl [Integer, nil] Time To Live in seconds (ignored in base implementation)
|
41
|
+
# @return [nil] Always returns nil
|
40
42
|
#
|
41
|
-
# @
|
42
|
-
#
|
43
|
+
# @note This is a no-op implementation. Classes that need expiration
|
44
|
+
# functionality should include the :expiration feature.
|
43
45
|
#
|
44
|
-
|
45
|
-
|
46
|
-
def update_expiration(*)
|
47
|
-
Familia.info "[update_expiration] Skipped for #{rediskey}. #{self.class} data is immortal!"
|
46
|
+
def update_expiration(ttl: nil)
|
47
|
+
Familia.ld "[update_expiration] Feature not enabled for #{self.class}. Key: #{rediskey} (caller: #{caller(1..1)})"
|
48
48
|
nil
|
49
49
|
end
|
50
50
|
|
data/lib/familia/connection.rb
CHANGED
@@ -6,6 +6,7 @@ require_relative '../../lib/redis_middleware'
|
|
6
6
|
module Familia
|
7
7
|
@uri = URI.parse 'redis://127.0.0.1'
|
8
8
|
@redis_clients = {}
|
9
|
+
@redis_uri_by_class = {}
|
9
10
|
|
10
11
|
# The Connection module provides Redis connection management for Familia.
|
11
12
|
# It allows easy setup and access to Redis clients across different URIs.
|
@@ -25,17 +26,20 @@ module Familia
|
|
25
26
|
# Establishes a connection to a Redis server.
|
26
27
|
#
|
27
28
|
# @param uri [String, URI, nil] The URI of the Redis server to connect to.
|
28
|
-
# If nil, uses the default URI.
|
29
|
-
# @return [Redis] The connected Redis client
|
29
|
+
# If nil, uses the default URI from `@redis_uri_by_class` or `Familia.uri`.
|
30
|
+
# @return [Redis] The connected Redis client.
|
31
|
+
# @raise [ArgumentError] If no URI is specified.
|
30
32
|
# @example
|
31
33
|
# Familia.connect('redis://localhost:6379')
|
32
34
|
def connect(uri = nil)
|
33
35
|
uri = URI.parse(uri) if uri.is_a?(String)
|
36
|
+
uri ||= @redis_uri_by_class[self]
|
34
37
|
uri ||= Familia.uri
|
35
38
|
|
36
39
|
raise ArgumentError, 'No URI specified' unless uri
|
37
40
|
|
38
41
|
conf = uri.conf
|
42
|
+
@redis_uri_by_class[self] = uri.serverid
|
39
43
|
|
40
44
|
if Familia.enable_redis_logging
|
41
45
|
RedisLogger.logger = Familia.logger
|
@@ -49,6 +53,9 @@ module Familia
|
|
49
53
|
end
|
50
54
|
|
51
55
|
redis = Redis.new(conf)
|
56
|
+
|
57
|
+
# Close the existing connection if it exists
|
58
|
+
@redis_clients[uri.serverid].close if @redis_clients[uri.serverid]
|
52
59
|
@redis_clients[uri.serverid] = redis
|
53
60
|
end
|
54
61
|
|
@@ -72,6 +79,15 @@ module Familia
|
|
72
79
|
@redis_clients[uri.serverid]
|
73
80
|
end
|
74
81
|
|
82
|
+
# Retrieves the Redis client associated with the given class.
|
83
|
+
#
|
84
|
+
# @param klass [Class] The class for which to retrieve the Redis client.
|
85
|
+
# @return [Redis] The Redis client associated with the given class.
|
86
|
+
def redis_uri_by_class(klass)
|
87
|
+
uri = @redis_uri_by_class[klass]
|
88
|
+
connect(uri)
|
89
|
+
end
|
90
|
+
|
75
91
|
# Sets the default URI for Redis connections.
|
76
92
|
#
|
77
93
|
# @param v [String, URI] The new default URI
|
data/lib/familia/errors.rb
CHANGED
@@ -30,4 +30,18 @@ module Familia
|
|
30
30
|
"No client for #{uri.serverid}"
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
# Raised when attempting to refresh an object whose key doesn't exist in Redis
|
35
|
+
class KeyNotFoundError < Problem
|
36
|
+
attr_reader :key
|
37
|
+
|
38
|
+
def initialize(key)
|
39
|
+
@key = key
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def message
|
44
|
+
"Key not found in Redis: #{key}"
|
45
|
+
end
|
46
|
+
end
|
33
47
|
end
|
@@ -49,7 +49,7 @@ module Familia::Features
|
|
49
49
|
# false otherwise.
|
50
50
|
#
|
51
51
|
# @example Setting an expiration of one day
|
52
|
-
# object.update_expiration(86400)
|
52
|
+
# object.update_expiration(ttl: 86400)
|
53
53
|
#
|
54
54
|
# @note If TTL is set to zero, the expiration will be removed, making the
|
55
55
|
# data persist indefinitely.
|
@@ -57,8 +57,19 @@ module Familia::Features
|
|
57
57
|
# @raise [Familia::Problem] Raises an error if the TTL is not a non-negative
|
58
58
|
# integer.
|
59
59
|
#
|
60
|
-
|
60
|
+
def update_expiration(ttl: nil)
|
61
61
|
ttl ||= self.ttl
|
62
|
+
|
63
|
+
if self.class.has_relations?
|
64
|
+
Familia.ld "[update_expiration] #{self.class} has relations: #{self.class.redis_types.keys}"
|
65
|
+
self.class.redis_types.each do |name, definition|
|
66
|
+
next if definition.opts[:ttl].nil?
|
67
|
+
obj = send(name)
|
68
|
+
Familia.ld "[update_expiration] Updating expiration for #{name} (#{obj.rediskey}) to #{ttl}"
|
69
|
+
obj.update_expiration(ttl: ttl)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
62
73
|
# It's important to raise exceptions here and not just log warnings. We
|
63
74
|
# don't want to silently fail at setting expirations and cause data
|
64
75
|
# retention issues (e.g. not removed in a timely fashion).
|
@@ -46,7 +46,7 @@ module Familia::Features
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def qstamp(quantum = nil, pattern: nil, time: nil)
|
49
|
-
self.class.qstamp(quantum || ttl, pattern: pattern, time: time)
|
49
|
+
self.class.qstamp(quantum || self.class.ttl, pattern: pattern, time: time)
|
50
50
|
end
|
51
51
|
|
52
52
|
extend ClassMethods
|
@@ -33,9 +33,6 @@ module Familia
|
|
33
33
|
include Familia::Settings
|
34
34
|
include Familia::Horreum::RelationsManagement
|
35
35
|
|
36
|
-
attr_accessor :parent
|
37
|
-
attr_writer :redis, :dump_method, :load_method
|
38
|
-
|
39
36
|
# Returns the Redis connection for the class.
|
40
37
|
#
|
41
38
|
# This method retrieves the Redis connection instance for the class. If no
|
@@ -74,42 +71,90 @@ module Familia
|
|
74
71
|
fields << name
|
75
72
|
attr_accessor name
|
76
73
|
|
77
|
-
# Every field gets a fast
|
78
|
-
|
74
|
+
# Every field gets a fast attribute method for immediately persisting
|
75
|
+
fast_attribute! name
|
79
76
|
end
|
80
77
|
|
81
|
-
# Defines a fast
|
82
|
-
# attribute name. Fast
|
83
|
-
# attribute values to Redis. Calling a fast
|
84
|
-
# effect on any of the object's other attributes and does not
|
85
|
-
# a
|
78
|
+
# Defines a fast attribute method with a bang (!) suffix for a given
|
79
|
+
# attribute name. Fast attribute methods are used to immediately read or
|
80
|
+
# write attribute values from/to Redis. Calling a fast attribute method
|
81
|
+
# has no effect on any of the object's other attributes and does not
|
82
|
+
# trigger a call to update the object's expiration time.
|
86
83
|
#
|
87
84
|
# The dynamically defined method performs the following:
|
88
|
-
# -
|
85
|
+
# - Acts as both a reader and a writer method.
|
86
|
+
# - When called without arguments, retrieves the current value from Redis.
|
87
|
+
# - When called with an argument, persists the value to Redis immediately.
|
88
|
+
# - Checks if the correct number of arguments is provided (zero or one).
|
89
89
|
# - Converts the provided value to a format suitable for Redis storage.
|
90
|
-
# - Uses the existing accessor method to set the attribute value
|
91
|
-
#
|
92
|
-
# -
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
|
90
|
+
# - Uses the existing accessor method to set the attribute value when
|
91
|
+
# writing.
|
92
|
+
# - Persists the value to Redis immediately using the hset command when
|
93
|
+
# writing.
|
94
|
+
# - Includes custom error handling to raise an ArgumentError if the wrong
|
95
|
+
# number of arguments is given.
|
96
|
+
# - Raises a custom error message if an exception occurs during the
|
97
|
+
# execution of the method.
|
98
|
+
#
|
99
|
+
# @param [Symbol, String] name the name of the attribute for which the
|
100
|
+
# fast method is defined.
|
101
|
+
# @return [Object] the current value of the attribute when called without
|
102
|
+
# arguments.
|
103
|
+
# @raise [ArgumentError] if more than one argument is provided.
|
104
|
+
# @raise [RuntimeError] if an exception occurs during the execution of the
|
105
|
+
# method.
|
106
|
+
#
|
107
|
+
def fast_attribute!(name = nil)
|
108
|
+
# Fast attribute accessor method for the '#{name}' attribute.
|
109
|
+
# This method provides immediate read and write access to the attribute
|
110
|
+
# in Redis.
|
111
|
+
#
|
112
|
+
# When called without arguments, it retrieves the current value of the
|
113
|
+
# attribute from Redis.
|
114
|
+
# When called with an argument, it immediately persists the new value to
|
115
|
+
# Redis.
|
116
|
+
#
|
117
|
+
# @overload #{name}!
|
118
|
+
# Retrieves the current value of the attribute from Redis.
|
119
|
+
# @return [Object] the current value of the attribute.
|
120
|
+
#
|
121
|
+
# @overload #{name}!(value)
|
122
|
+
# Sets and immediately persists the new value of the attribute to
|
123
|
+
# Redis.
|
124
|
+
# @param value [Object] the new value to set for the attribute.
|
125
|
+
# @return [Object] the newly set value.
|
126
|
+
#
|
127
|
+
# @raise [ArgumentError] if more than one argument is provided.
|
128
|
+
# @raise [RuntimeError] if an exception occurs during the execution of
|
129
|
+
# the method.
|
130
|
+
#
|
131
|
+
# @note This method bypasses any object-level caching and interacts
|
132
|
+
# directly with Redis. It does not trigger updates to other attributes
|
133
|
+
# or the object's expiration time.
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
#
|
137
|
+
# def #{name}!(*args)
|
138
|
+
# # Method implementation
|
139
|
+
# end
|
140
|
+
#
|
100
141
|
define_method :"#{name}!" do |*args|
|
101
142
|
# Check if the correct number of arguments is provided (exactly one).
|
102
|
-
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" if args.size
|
143
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0 or 1)" if args.size > 1
|
103
144
|
|
104
145
|
val = args.first
|
105
146
|
|
147
|
+
# If no value is provided to this fast attribute method, make a call
|
148
|
+
# to redis to return the current stored value of the hash field.
|
149
|
+
return hget name if val.nil?
|
150
|
+
|
106
151
|
begin
|
107
152
|
# Trace the operation if debugging is enabled.
|
108
153
|
Familia.trace :FAST_WRITER, redis, "#{name}: #{val.inspect}", caller(1..1) if Familia.debug?
|
109
154
|
|
110
155
|
# Convert the provided value to a format suitable for Redis storage.
|
111
|
-
prepared =
|
112
|
-
Familia.ld "[.
|
156
|
+
prepared = serialize_value(val)
|
157
|
+
Familia.ld "[.fast_attribute!] #{name} val: #{val.class} prepared: #{prepared.class}"
|
113
158
|
|
114
159
|
# Use the existing accessor method to set the attribute value.
|
115
160
|
send :"#{name}=", val
|
@@ -148,6 +193,10 @@ module Familia
|
|
148
193
|
@redis_types
|
149
194
|
end
|
150
195
|
|
196
|
+
def has_relations?
|
197
|
+
@has_relations ||= false
|
198
|
+
end
|
199
|
+
|
151
200
|
def db(v = nil)
|
152
201
|
@db = v unless v.nil?
|
153
202
|
@db || parent&.db
|
@@ -161,16 +210,20 @@ module Familia
|
|
161
210
|
def all(suffix = nil)
|
162
211
|
suffix ||= self.suffix
|
163
212
|
# objects that could not be parsed will be nil
|
164
|
-
keys(suffix).filter_map { |k|
|
213
|
+
keys(suffix).filter_map { |k| find_by_key(k) }
|
165
214
|
end
|
166
215
|
|
167
216
|
def any?(filter = '*')
|
168
|
-
|
217
|
+
matching_keys_count(filter) > 0
|
169
218
|
end
|
170
219
|
|
171
|
-
|
220
|
+
# Returns the number of Redis keys matching the given filter pattern
|
221
|
+
# @param filter [String] Redis key pattern to match (default: '*')
|
222
|
+
# @return [Integer] Number of matching keys
|
223
|
+
def matching_keys_count(filter = '*')
|
172
224
|
redis.keys(rediskey(filter)).compact.size
|
173
225
|
end
|
226
|
+
alias size matching_keys_count # For backwards compatibility
|
174
227
|
|
175
228
|
def suffix(a = nil, &blk)
|
176
229
|
@suffix = a || blk if a || !blk.nil?
|
@@ -264,17 +317,17 @@ module Familia
|
|
264
317
|
# debugging.
|
265
318
|
#
|
266
319
|
# @example
|
267
|
-
# User.
|
320
|
+
# User.find_by_key("user:123") # Returns a User instance if it exists,
|
268
321
|
# nil otherwise
|
269
322
|
#
|
270
|
-
def
|
323
|
+
def find_by_key(objkey)
|
271
324
|
raise ArgumentError, 'Empty key' if objkey.to_s.empty?
|
272
325
|
|
273
326
|
# We use a lower-level method here b/c we're working with the
|
274
327
|
# full key and not just the identifier.
|
275
328
|
does_exist = redis.exists(objkey).positive?
|
276
329
|
|
277
|
-
Familia.ld "[.
|
330
|
+
Familia.ld "[.find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
|
278
331
|
Familia.trace :FROM_KEY, redis, objkey, caller(1..1) if Familia.debug?
|
279
332
|
|
280
333
|
# This is the reason for calling exists first. We want to definitively
|
@@ -289,6 +342,7 @@ module Familia
|
|
289
342
|
|
290
343
|
new(**obj)
|
291
344
|
end
|
345
|
+
alias from_rediskey find_by_key # deprecated
|
292
346
|
|
293
347
|
# Retrieves and instantiates an object from Redis using its identifier.
|
294
348
|
#
|
@@ -299,7 +353,7 @@ module Familia
|
|
299
353
|
# @return [Object, nil] An instance of the class if found, nil otherwise.
|
300
354
|
#
|
301
355
|
# This method constructs the full Redis key using the provided identifier
|
302
|
-
# and suffix, then delegates to `
|
356
|
+
# and suffix, then delegates to `find_by_key` for the actual retrieval and
|
303
357
|
# instantiation.
|
304
358
|
#
|
305
359
|
# It's a higher-level method that abstracts away the key construction,
|
@@ -307,19 +361,21 @@ module Familia
|
|
307
361
|
# identifier.
|
308
362
|
#
|
309
363
|
# @example
|
310
|
-
# User.
|
364
|
+
# User.find_by_id(123) # Equivalent to User.find_by_key("user:123:object")
|
311
365
|
#
|
312
|
-
def
|
366
|
+
def find_by_id(identifier, suffix = nil)
|
313
367
|
suffix ||= self.suffix
|
314
368
|
return nil if identifier.to_s.empty?
|
315
369
|
|
316
370
|
objkey = rediskey(identifier, suffix)
|
317
371
|
|
318
|
-
Familia.ld "[.
|
319
|
-
Familia.trace :
|
320
|
-
|
372
|
+
Familia.ld "[.find_by_id] #{self} from key #{objkey})"
|
373
|
+
Familia.trace :FIND_BY_ID, Familia.redis(uri), objkey, caller(1..1).first if Familia.debug?
|
374
|
+
find_by_key objkey
|
321
375
|
end
|
322
|
-
alias
|
376
|
+
alias find find_by_id
|
377
|
+
alias load find_by_id # deprecated
|
378
|
+
alias from_identifier find_by_id # deprecated
|
323
379
|
|
324
380
|
# Checks if an object with the given identifier exists in Redis.
|
325
381
|
#
|
@@ -351,8 +407,10 @@ module Familia
|
|
351
407
|
# @param suffix [Symbol, nil] The suffix to use in the Redis key (default: class suffix).
|
352
408
|
# @return [Boolean] true if the object was successfully destroyed, false otherwise.
|
353
409
|
#
|
354
|
-
# This method
|
355
|
-
#
|
410
|
+
# This method is part of Familia's high-level object lifecycle management. While `delete!`
|
411
|
+
# operates directly on Redis keys, `destroy!` operates at the object level and is used for
|
412
|
+
# ORM-style operations. Use `destroy!` when removing complete objects from the system, and
|
413
|
+
# `delete!` when working directly with Redis keys.
|
356
414
|
#
|
357
415
|
# @example
|
358
416
|
# User.destroy!(123) # Removes user:123:object from Redis
|
@@ -364,7 +422,7 @@ module Familia
|
|
364
422
|
objkey = rediskey identifier, suffix
|
365
423
|
|
366
424
|
ret = redis.del objkey
|
367
|
-
Familia.trace :
|
425
|
+
Familia.trace :DESTROY!, redis, "#{objkey} #{ret.inspect}", caller(1..1) if Familia.debug?
|
368
426
|
ret.positive?
|
369
427
|
end
|
370
428
|
|
@@ -380,7 +438,7 @@ module Familia
|
|
380
438
|
# User.find # Returns all keys matching user:*:object
|
381
439
|
# User.find('active') # Returns all keys matching user:*:active
|
382
440
|
#
|
383
|
-
def
|
441
|
+
def find_keys(suffix = '*')
|
384
442
|
redis.keys(rediskey('*', suffix)) || []
|
385
443
|
end
|
386
444
|
|
@@ -18,11 +18,35 @@ module Familia
|
|
18
18
|
#
|
19
19
|
module Commands
|
20
20
|
|
21
|
+
def move(db)
|
22
|
+
redis.move rediskey, db
|
23
|
+
end
|
24
|
+
|
25
|
+
# Checks if the calling object's key exists in Redis and has a non-zero size.
|
26
|
+
#
|
27
|
+
# This method retrieves the Redis URI associated with the calling object's class
|
28
|
+
# using `Familia.redis_uri_by_class`. It then checks if the specified key exists
|
29
|
+
# in Redis and that its size is not zero. If debugging is enabled, it logs the
|
30
|
+
# existence check using `Familia.trace`.
|
31
|
+
#
|
32
|
+
# @return [Boolean] Returns `true` if the key exists in Redis and its size is not zero, otherwise `false`.
|
33
|
+
# @example
|
34
|
+
# if some_object.exists?
|
35
|
+
# # perform action
|
36
|
+
# end
|
21
37
|
def exists?
|
22
|
-
|
23
|
-
|
38
|
+
true_or_false = self.class.redis.exists?(rediskey) && !size.zero?
|
39
|
+
Familia.trace :EXISTS, redis, "#{key} #{true_or_false.inspect}", caller(1..1) if Familia.debug?
|
40
|
+
true_or_false
|
24
41
|
end
|
25
42
|
|
43
|
+
# Returns the number of fields in the main object hash
|
44
|
+
# @return [Integer] number of fields
|
45
|
+
def field_count
|
46
|
+
redis.hlen rediskey
|
47
|
+
end
|
48
|
+
alias size field_count
|
49
|
+
|
26
50
|
# Sets a timeout on key. After the timeout has expired, the key will
|
27
51
|
# automatically be deleted. Returns 1 if the timeout was set, 0 if key
|
28
52
|
# does not exist or the timeout could not be set.
|
@@ -33,20 +57,27 @@ module Familia
|
|
33
57
|
redis.expire rediskey, ttl.to_i
|
34
58
|
end
|
35
59
|
|
60
|
+
# Retrieves the remaining time to live (TTL) for the object's Redis key.
|
61
|
+
#
|
62
|
+
# This method accesses the ovjects Redis client to obtain the TTL of `rediskey`.
|
63
|
+
# If debugging is enabled, it logs the TTL retrieval operation using `Familia.trace`.
|
64
|
+
#
|
65
|
+
# @return [Integer] The TTL of the key in seconds. Returns -1 if the key does not exist
|
66
|
+
# or has no associated expire time.
|
36
67
|
def realttl
|
37
68
|
Familia.trace :REALTTL, redis, redisuri, caller(1..1) if Familia.debug?
|
38
69
|
redis.ttl rediskey
|
39
70
|
end
|
40
71
|
|
41
|
-
#
|
72
|
+
# Removes a field from the hash stored at the Redis key.
|
42
73
|
#
|
43
|
-
# @param field [String] The field to
|
74
|
+
# @param field [String] The field to remove from the hash.
|
44
75
|
# @return [Integer] The number of fields that were removed from the hash (0 or 1).
|
45
|
-
|
46
|
-
def hdel!(field)
|
76
|
+
def remove_field(field)
|
47
77
|
Familia.trace :HDEL, redis, field, caller(1..1) if Familia.debug?
|
48
78
|
redis.hdel rediskey, field
|
49
79
|
end
|
80
|
+
alias remove remove_field # deprecated
|
50
81
|
|
51
82
|
def redistype
|
52
83
|
Familia.trace :REDISTYPE, redis, redisuri, caller(1..1) if Familia.debug?
|
@@ -59,6 +90,15 @@ module Familia
|
|
59
90
|
redis.rename rediskey, newkey
|
60
91
|
end
|
61
92
|
|
93
|
+
# Retrieves the prefix for the current instance by delegating to its class.
|
94
|
+
#
|
95
|
+
# @return [String] The prefix associated with the class of the current instance.
|
96
|
+
# @example
|
97
|
+
# instance.prefix
|
98
|
+
def prefix
|
99
|
+
self.class.prefix
|
100
|
+
end
|
101
|
+
|
62
102
|
# For parity with RedisType#hgetall
|
63
103
|
def hgetall
|
64
104
|
Familia.trace :HGETALL, redis, redisuri, caller(1..1) if Familia.debug?
|
@@ -78,8 +118,10 @@ module Familia
|
|
78
118
|
redis.hset rediskey, field, value
|
79
119
|
end
|
80
120
|
|
81
|
-
def hmset
|
82
|
-
|
121
|
+
def hmset(hsh={})
|
122
|
+
hsh ||= self.to_h
|
123
|
+
Familia.trace :HMSET, redis, hsh, caller(1..1) if Familia.debug?
|
124
|
+
redis.hmset rediskey(suffix), hsh
|
83
125
|
end
|
84
126
|
|
85
127
|
def hkeys
|
@@ -116,11 +158,6 @@ module Familia
|
|
116
158
|
end
|
117
159
|
alias decrement decr
|
118
160
|
|
119
|
-
def hlen
|
120
|
-
redis.hlen rediskey(suffix)
|
121
|
-
end
|
122
|
-
alias hlength hlen
|
123
|
-
|
124
161
|
def hstrlen(field)
|
125
162
|
redis.hstrlen rediskey(suffix), field
|
126
163
|
end
|
@@ -131,12 +168,14 @@ module Familia
|
|
131
168
|
end
|
132
169
|
alias has_key? key?
|
133
170
|
|
171
|
+
# Deletes the entire Redis key
|
172
|
+
# @return [Boolean] true if the key was deleted, false otherwise
|
134
173
|
def delete!
|
135
174
|
Familia.trace :DELETE!, redis, redisuri, caller(1..1) if Familia.debug?
|
136
175
|
ret = redis.del rediskey
|
137
176
|
ret.positive?
|
138
177
|
end
|
139
|
-
|
178
|
+
alias clear delete!
|
140
179
|
|
141
180
|
end
|
142
181
|
|
@@ -16,6 +16,10 @@ module Familia
|
|
16
16
|
# Call setup_relations_accessors to initialize the feature
|
17
17
|
#
|
18
18
|
module RelationsManagement
|
19
|
+
# A practical flag to indicate that a Horreum member has relations,
|
20
|
+
# not just theoretically but actually at least one list/haskey/etc.
|
21
|
+
@has_relations = nil
|
22
|
+
|
19
23
|
def self.included(base)
|
20
24
|
base.extend(ClassMethods)
|
21
25
|
base.setup_relations_accessors
|
@@ -31,14 +35,17 @@ module Familia
|
|
31
35
|
|
32
36
|
# Dynamically define instance-level relation methods
|
33
37
|
#
|
34
|
-
# Once defined, these methods can be used at the
|
38
|
+
# Once defined, these methods can be used at the instance-level of a
|
35
39
|
# Familia member to define *instance-level* relations to any of the
|
36
40
|
# RedisType types (e.g. set, list, hash, etc).
|
37
41
|
#
|
38
42
|
define_method :"#{kind}" do |*args|
|
39
43
|
name, opts = *args
|
44
|
+
|
45
|
+
# As log as we have at least one relation, we can set this flag.
|
46
|
+
@has_relations = true
|
47
|
+
|
40
48
|
attach_instance_redis_object_relation name, klass, opts
|
41
|
-
redis_types[name.to_s.to_sym]
|
42
49
|
end
|
43
50
|
define_method :"#{kind}?" do |name|
|
44
51
|
obj = redis_types[name.to_s.to_sym]
|