familia 1.0.0.pre.rc1 → 1.0.0.pre.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +65 -12
- data/VERSION.yml +1 -1
- data/lib/familia/features/safe_dump.rb +21 -2
- data/lib/familia/horreum/class_methods.rb +129 -24
- data/lib/familia/horreum/commands.rb +84 -5
- data/lib/familia/horreum/serialization.rb +236 -53
- data/lib/familia/horreum/utils.rb +2 -1
- data/lib/familia/horreum.rb +58 -8
- data/lib/familia/logging.rb +4 -21
- data/lib/familia/redistype.rb +10 -10
- data/lib/familia/refinements.rb +88 -0
- data/lib/familia/types/hashkey.rb +6 -6
- data/lib/familia/types/list.rb +2 -0
- data/lib/familia/utils.rb +2 -6
- data/lib/familia.rb +1 -0
- data/try/27_redis_horreum_try.rb +53 -0
- data/try/test_helpers.rb +9 -1
- metadata +4 -3
@@ -1,24 +1,75 @@
|
|
1
1
|
# rubocop:disable all
|
2
2
|
#
|
3
3
|
module Familia
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
# instance-level functionality for Redis operations and object management.
|
4
|
+
|
5
|
+
|
6
|
+
# Familia::Horreum
|
8
7
|
#
|
9
8
|
class Horreum
|
10
|
-
|
11
|
-
#
|
9
|
+
# Serialization: Where Objects Go to Become Strings (and Vice Versa)!
|
10
|
+
#
|
11
|
+
# This module is chock-full of methods that'll make your head spin (in a
|
12
|
+
# good way)! We've got loaders, dumpers, and refreshers galore. It's like
|
13
|
+
# a laundromat for your data, but instead of quarters, it runs on Redis commands.
|
14
|
+
#
|
15
|
+
# A Note on Our Refreshing Refreshers:
|
16
|
+
# In the wild world of Ruby, '!' usually means "Watch out! I'm dangerous!"
|
17
|
+
# But here in Familia-land, we march to the beat of a different drummer.
|
18
|
+
# Our refresh! method is the real deal, doing all the heavy lifting.
|
19
|
+
# The non-bang refresh? Oh, it's just as rowdy, but it plays nice with
|
20
|
+
# method chaining. It's like the polite twin who still knows how to party.
|
21
|
+
#
|
22
|
+
# Remember: In Familia, refreshing isn't just a chore, it's a chance to
|
23
|
+
# dance with data! Whether you bang(!) or not, you're still invited to
|
24
|
+
# the Redis disco.
|
25
|
+
#
|
26
|
+
# (P.S. If you're reading these docs, lol sorry. I asked Claude 3.5 to
|
27
|
+
# write in the style of _why the lucky stiff today and got this uncanny
|
28
|
+
# valley response. I hope you enjoy reading it as much as I did writing
|
29
|
+
# the prompt for it. - @delano).
|
30
|
+
#
|
31
|
+
# (Ahem! What I meant to say was that if you're reading this, congratulations!
|
32
|
+
# You've stumbled upon the secret garden of documentation. Feel free to smell
|
33
|
+
# the Ruby roses, but watch out for the Redis thorns!)
|
12
34
|
#
|
13
35
|
module Serialization
|
14
|
-
#include Familia::RedisType::Serialization
|
15
36
|
|
16
37
|
attr_writer :redis
|
17
38
|
|
39
|
+
# Summon the mystical Redis connection from the depths of instance or class.
|
40
|
+
#
|
41
|
+
# This method is like a magical divining rod, always pointing to the nearest
|
42
|
+
# source of Redis goodness. It first checks if we have a personal Redis
|
43
|
+
# connection (@redis), and if not, it borrows the class's connection.
|
44
|
+
#
|
45
|
+
# @return [Redis] A shimmering Redis connection, ready for your bidding.
|
46
|
+
#
|
47
|
+
# @example Finding your Redis way
|
48
|
+
# puts object.redis
|
49
|
+
# # => #<Redis client v4.5.1 for redis://localhost:6379/0>
|
50
|
+
#
|
18
51
|
def redis
|
19
52
|
@redis || self.class.redis
|
20
53
|
end
|
21
54
|
|
55
|
+
# Perform a sacred Redis transaction ritual.
|
56
|
+
#
|
57
|
+
# This method creates a protective circle around your Redis operations,
|
58
|
+
# ensuring they all succeed or fail together. It's like a group hug for your
|
59
|
+
# data operations, but with more ACID properties.
|
60
|
+
#
|
61
|
+
# @yield [conn] A block where you can perform your Redis incantations.
|
62
|
+
# @yieldparam conn [Redis] A Redis connection in multi mode.
|
63
|
+
#
|
64
|
+
# @example Performing a Redis rain dance
|
65
|
+
# transaction do |conn|
|
66
|
+
# conn.set("weather", "rainy")
|
67
|
+
# conn.set("mood", "melancholic")
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @note This method temporarily replaces your Redis connection with a multi
|
71
|
+
# connection. Don't worry, it puts everything back where it found it when it's done.
|
72
|
+
#
|
22
73
|
def transaction
|
23
74
|
original_redis = self.redis
|
24
75
|
|
@@ -32,25 +83,74 @@ module Familia
|
|
32
83
|
end
|
33
84
|
end
|
34
85
|
|
35
|
-
#
|
36
|
-
#
|
86
|
+
# Save our precious data to Redis, with a sprinkle of timestamp magic!
|
87
|
+
#
|
88
|
+
# This method is like a conscientious historian, not only recording your
|
89
|
+
# object's current state but also meticulously timestamping when it was
|
90
|
+
# created and last updated. It's the record keeper of your data's life story!
|
91
|
+
#
|
92
|
+
# @return [Boolean] true if the save was successful, false if Redis was grumpy.
|
93
|
+
#
|
94
|
+
# @example Preserving your pet rock for posterity
|
95
|
+
# rocky = PetRock.new(name: "Dwayne")
|
96
|
+
# rocky.save
|
97
|
+
# # => true (Dwayne is now immortalized in Redis)
|
98
|
+
#
|
99
|
+
# @note This method will leave breadcrumbs (traces) if you're in debug mode.
|
100
|
+
# It's like Hansel and Gretel, but for data operations!
|
101
|
+
#
|
37
102
|
def save
|
38
103
|
Familia.trace :SAVE, redis, redisuri, caller(1..1) if Familia.debug?
|
39
104
|
|
40
|
-
# Update
|
105
|
+
# Update our object's life story
|
106
|
+
self.key ||= self.identifier
|
41
107
|
self.updated = Familia.now.to_i
|
42
|
-
self.created
|
108
|
+
self.created ||= Familia.now.to_i
|
43
109
|
|
44
|
-
#
|
110
|
+
# Commit our tale to the Redis chronicles
|
45
111
|
ret = commit_fields # e.g. ["OK"]
|
46
112
|
|
47
113
|
Familia.ld "[save] #{self.class} #{rediskey} #{ret}"
|
48
114
|
|
49
|
-
#
|
50
|
-
ret.all? { |value| value == "OK" }
|
115
|
+
# Did Redis accept our offering?
|
116
|
+
ret.uniq.all? { |value| value == "OK" }
|
51
117
|
end
|
52
118
|
|
53
|
-
#
|
119
|
+
# Apply a smattering of fields to this object like fairy dust.
|
120
|
+
#
|
121
|
+
# @param fields [Hash] A magical bag of named attributes to sprinkle onto this instance.
|
122
|
+
# Each key-value pair is like a tiny spell, ready to enchant our object's properties.
|
123
|
+
#
|
124
|
+
# @return [self] Returns the newly bejeweled instance, now sparkling with fresh attributes.
|
125
|
+
#
|
126
|
+
# @example Giving your object a makeover
|
127
|
+
# dragon.apply_fields(name: "Puff", breathes: "fire", loves: "little boys named Jackie")
|
128
|
+
# # => #<Dragon:0x007f8a1c8b0a28 @name="Puff", @breathes="fire", @loves="little boys named Jackie">
|
129
|
+
#
|
130
|
+
def apply_fields(**fields)
|
131
|
+
fields.each do |field, value|
|
132
|
+
# Whisper the new value into the object's ear (if it's listening)
|
133
|
+
send("#{field}=", value) if respond_to?("#{field}=")
|
134
|
+
end
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
# Commit our precious fields to Redis.
|
139
|
+
#
|
140
|
+
# This method performs a sacred ritual, sending our cherished attributes
|
141
|
+
# on a journey through the ethernet to find their resting place in Redis.
|
142
|
+
#
|
143
|
+
# @return [Array<String>] A mystical array of strings, cryptic messages from the Redis gods.
|
144
|
+
#
|
145
|
+
# @note Be warned, young programmer! This method dabbles in the arcane art of transactions.
|
146
|
+
# Side effects may include data persistence and a slight tingling sensation.
|
147
|
+
#
|
148
|
+
# @example Offering your changes to the Redis deities
|
149
|
+
# unicorn.name = "Charlie"
|
150
|
+
# unicorn.horn_length = "magnificent"
|
151
|
+
# unicorn.commit_fields
|
152
|
+
# # => ["OK", "OK"] (The Redis gods are pleased with your offering)
|
153
|
+
#
|
54
154
|
def commit_fields
|
55
155
|
Familia.ld "[commit_fields] #{self.class} #{rediskey} #{to_h}"
|
56
156
|
transaction do |conn|
|
@@ -59,33 +159,107 @@ module Familia
|
|
59
159
|
end
|
60
160
|
end
|
61
161
|
|
162
|
+
# Dramatically vanquish this object from the face of Redis! (ed: delete it)
|
163
|
+
#
|
164
|
+
# This method is the doomsday device of our little data world. It will
|
165
|
+
# mercilessly eradicate all traces of our object from Redis, leaving naught
|
166
|
+
# but digital dust in its wake. Use with caution, lest you accidentally
|
167
|
+
# destroy the wrong data-verse!
|
168
|
+
#
|
169
|
+
# @return [void] Returns nothing, for nothing remains after destruction.
|
170
|
+
#
|
171
|
+
# @example Bidding a fond farewell to your pet rock
|
172
|
+
# rocky = PetRock.new(name: "Dwayne")
|
173
|
+
# rocky.destroy!
|
174
|
+
# # => *poof* Rocky is no more. A moment of silence, please.
|
175
|
+
#
|
176
|
+
# @note If debugging is enabled, this method will leave a trace of its
|
177
|
+
# destructive path, like breadcrumbs for future data archaeologists.
|
178
|
+
#
|
179
|
+
# @see #delete! The actual hitman carrying out the deed.
|
180
|
+
#
|
62
181
|
def destroy!
|
63
182
|
Familia.trace :DESTROY, redis, redisuri, caller(1..1) if Familia.debug?
|
64
183
|
delete!
|
65
184
|
end
|
66
185
|
|
186
|
+
|
187
|
+
# Refreshes the object's state by querying Redis and overwriting the
|
188
|
+
# current field values. This method performs a destructive update on the
|
189
|
+
# object, regardless of unsaved changes.
|
190
|
+
#
|
191
|
+
# @note This is a destructive operation that will overwrite any unsaved
|
192
|
+
# changes.
|
193
|
+
# @return The list of field names that were updated.
|
194
|
+
def refresh!
|
195
|
+
Familia.trace :REFRESH, redis, redisuri, caller(1..1) if Familia.debug?
|
196
|
+
fields = hgetall
|
197
|
+
Familia.ld "[refresh!] #{self.class} #{rediskey} #{fields.keys}"
|
198
|
+
optimistic_refresh(**fields)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Refreshes the object's state and returns self to allow method chaining.
|
202
|
+
# This method calls refresh! internally, performing the actual Redis
|
203
|
+
# query and state update.
|
204
|
+
#
|
205
|
+
# @note While this method allows chaining, it still performs a
|
206
|
+
# destructive update like refresh!.
|
207
|
+
# @return [self] Returns the object itself after refreshing, allowing
|
208
|
+
# method chaining.
|
209
|
+
def refresh
|
210
|
+
refresh!
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
# Transform this object into a magical hash of wonders!
|
215
|
+
#
|
216
|
+
# This method performs an alchemical transmutation, turning our noble object
|
217
|
+
# into a more plebeian hash. But fear not, for in this form, it can slip through
|
218
|
+
# the cracks of the universe (or at least, into Redis) with ease.
|
219
|
+
#
|
220
|
+
# @return [Hash] A glittering hash, each key a field name, each value a Redis-ready treasure.
|
221
|
+
#
|
222
|
+
# @example Turning your dragon into a hash
|
223
|
+
# dragon.to_h
|
224
|
+
# # => {"name"=>"Puff", "breathes"=>"fire", "age"=>1000}
|
225
|
+
#
|
226
|
+
# @note Watch in awe as each field is lovingly prepared for its Redis adventure!
|
227
|
+
#
|
67
228
|
def to_h
|
68
|
-
# Use self.class.fields to efficiently generate a hash
|
69
|
-
# of all the fields for this object
|
70
229
|
self.class.fields.inject({}) do |hsh, field|
|
71
230
|
val = send(field)
|
72
|
-
prepared = val
|
231
|
+
prepared = to_redis(val)
|
73
232
|
Familia.ld " [to_h] field: #{field} val: #{val.class} prepared: #{prepared.class}"
|
74
233
|
hsh[field] = prepared
|
75
234
|
hsh
|
76
235
|
end
|
77
236
|
end
|
78
237
|
|
238
|
+
# Line up all our attributes in a neat little array parade!
|
239
|
+
#
|
240
|
+
# This method marshals all our object's attributes into an orderly procession,
|
241
|
+
# ready to march into Redis in perfect formation. It's like a little data army,
|
242
|
+
# but friendlier and less prone to conquering neighboring databases.
|
243
|
+
#
|
244
|
+
# @return [Array] A splendid array of Redis-ready values, in the order of our fields.
|
245
|
+
#
|
246
|
+
# @example Arranging your unicorn's attributes in a line
|
247
|
+
# unicorn.to_a
|
248
|
+
# # => ["Charlie", "magnificent", 5]
|
249
|
+
#
|
250
|
+
# @note Each value is carefully disguised in its Redis costume before joining the parade.
|
251
|
+
#
|
79
252
|
def to_a
|
80
253
|
self.class.fields.map do |field|
|
81
254
|
val = send(field)
|
82
|
-
|
83
|
-
|
255
|
+
prepared = to_redis(val)
|
256
|
+
Familia.ld " [to_a] field: #{field} val: #{val.class} prepared: #{prepared.class}"
|
257
|
+
prepared
|
84
258
|
end
|
85
259
|
end
|
86
260
|
|
87
|
-
# The to_redis method in Familia::Redistype and Familia::Horreum serve
|
88
|
-
# but have some key differences in their implementation:
|
261
|
+
# The to_redis method in Familia::Redistype and Familia::Horreum serve
|
262
|
+
# similar purposes but have some key differences in their implementation:
|
89
263
|
#
|
90
264
|
# Similarities:
|
91
265
|
# - Both methods aim to serialize various data types for Redis storage
|
@@ -97,16 +271,19 @@ module Familia
|
|
97
271
|
# - Familia::Horreum had more explicit type checking and conversion
|
98
272
|
# - Familia::Redistype includes more extensive debug tracing
|
99
273
|
#
|
100
|
-
# The centralized Familia.distinguisher method accommodates both approaches
|
101
|
-
#
|
274
|
+
# The centralized Familia.distinguisher method accommodates both approaches
|
275
|
+
# by:
|
276
|
+
# 1. Handling a wide range of data types, including those from both
|
277
|
+
# implementations
|
102
278
|
# 2. Providing a 'strict_values' option for flexible type handling
|
103
279
|
# 3. Supporting custom serialization through a dump_method
|
104
280
|
# 4. Including debug tracing similar to Familia::Redistype
|
105
281
|
#
|
106
|
-
# By using Familia.distinguisher, we achieve more consistent behavior
|
107
|
-
# different parts of the library while maintaining the flexibility
|
108
|
-
# various data types and custom serialization needs. This
|
109
|
-
# also makes it easier to extend or modify serialization
|
282
|
+
# By using Familia.distinguisher, we achieve more consistent behavior
|
283
|
+
# across different parts of the library while maintaining the flexibility
|
284
|
+
# to handle various data types and custom serialization needs. This
|
285
|
+
# centralization also makes it easier to extend or modify serialization
|
286
|
+
# behavior in the future.
|
110
287
|
#
|
111
288
|
def to_redis(val)
|
112
289
|
prepared = Familia.distinguisher(val, false)
|
@@ -115,40 +292,46 @@ module Familia
|
|
115
292
|
prepared = val.send(dump_method)
|
116
293
|
end
|
117
294
|
|
118
|
-
|
295
|
+
if prepared.nil?
|
296
|
+
Familia.ld "[#{self.class}#to_redis] nil returned for #{self.class}##{name}"
|
297
|
+
end
|
298
|
+
|
119
299
|
prepared
|
120
300
|
end
|
121
301
|
|
302
|
+
# Set an expiration date for our data, like a "best before" sticker for Redis!
|
303
|
+
#
|
304
|
+
# This method gives our data a lifespan in Redis. It's like telling Redis,
|
305
|
+
# "Hey, this data is fresh now, but it might get stale after a while!"
|
306
|
+
#
|
307
|
+
# @param ttl [Integer, nil] The Time To Live in seconds. If nil, we'll check
|
308
|
+
# our options for a default expiration time.
|
309
|
+
#
|
310
|
+
# @return [Boolean] true if the expiration was set successfully, false otherwise.
|
311
|
+
# It's like asking Redis, "Did you stick that expiration label on properly?"
|
312
|
+
#
|
313
|
+
# @example Making your pet rock data mortal
|
314
|
+
# rocky.update_expiration(86400) # Dwayne will live in Redis for one day
|
315
|
+
#
|
316
|
+
# @note If the TTL is zero, we assume our data wants to live forever.
|
317
|
+
# Immortality in Redis! Who wouldn't want that?
|
318
|
+
#
|
122
319
|
def update_expiration(ttl = nil)
|
123
320
|
ttl ||= opts[:ttl]
|
124
|
-
|
321
|
+
ttl = ttl.to_i
|
322
|
+
|
323
|
+
return if ttl.zero?
|
125
324
|
|
126
|
-
Familia.ld "#{rediskey} to #{ttl}"
|
127
|
-
|
325
|
+
Familia.ld "Setting expiration for #{rediskey} to #{ttl} seconds"
|
326
|
+
|
327
|
+
# EXPIRE command returns 1 if the timeout was set, 0 if key does not
|
328
|
+
# exist or the timeout could not be set.
|
329
|
+
expire(ttl).positive?
|
128
330
|
end
|
331
|
+
|
129
332
|
end
|
333
|
+
# End of Serialization module
|
130
334
|
|
131
335
|
include Serialization # these become Horreum instance methods
|
132
336
|
end
|
133
337
|
end
|
134
|
-
|
135
|
-
__END__
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# From RedisHash
|
140
|
-
def save
|
141
|
-
hsh = { :key => identifier }
|
142
|
-
ret = commit_fields hsh
|
143
|
-
ret == "OK"
|
144
|
-
end
|
145
|
-
|
146
|
-
def update_fields hsh={}
|
147
|
-
check_identifier!
|
148
|
-
hsh[:updated] = OT.now.to_i
|
149
|
-
hsh[:created] = OT.now.to_i unless has_key?(:created)
|
150
|
-
ret = update hsh # update is defined in HashKey
|
151
|
-
## NOTE: caching here like this only works if hsh has all keys
|
152
|
-
#self.cache.replace hsh
|
153
|
-
ret
|
154
|
-
end
|
@@ -27,9 +27,10 @@ module Familia
|
|
27
27
|
# Whether this is a Horreum or RedisType object, the value is taken
|
28
28
|
# from the `identifier` method).
|
29
29
|
#
|
30
|
-
def rediskey(suffix =
|
30
|
+
def rediskey(suffix = nil, ignored = nil)
|
31
31
|
Familia.ld "[#rediskey] #{identifier} for #{self.class}"
|
32
32
|
raise Familia::NoIdentifier, "No identifier for #{self.class}" if identifier.to_s.empty?
|
33
|
+
suffix ||= self.suffix # use the instance method to get the default suffix
|
33
34
|
self.class.rediskey identifier, suffix
|
34
35
|
end
|
35
36
|
|
data/lib/familia/horreum.rb
CHANGED
@@ -94,6 +94,15 @@ module Familia
|
|
94
94
|
# end
|
95
95
|
end
|
96
96
|
|
97
|
+
# Automatically add a 'key' field if it's not already defined
|
98
|
+
# This ensures that every object has a unique identifier
|
99
|
+
unless self.class.fields.include?(:key)
|
100
|
+
# Define the 'key' field for this class
|
101
|
+
# This approach allows flexibility in how identifiers are generated
|
102
|
+
# while ensuring each object has a consistent way to be referenced
|
103
|
+
self.class.field :key # , default: -> { identifier }
|
104
|
+
end
|
105
|
+
|
97
106
|
# Implementing classes can define an init method to do any
|
98
107
|
# additional initialization. Notice that this is called
|
99
108
|
# after the fields are set.
|
@@ -145,23 +154,64 @@ module Familia
|
|
145
154
|
end
|
146
155
|
end
|
147
156
|
|
157
|
+
# Initializes the object with positional arguments.
|
158
|
+
# Maps each argument to a corresponding field in the order they are defined.
|
159
|
+
#
|
160
|
+
# @param args [Array] List of values to be assigned to fields
|
161
|
+
# @return [Array<Symbol>] List of field names that were successfully updated
|
162
|
+
# (i.e., had non-nil values assigned)
|
163
|
+
# @private
|
148
164
|
def initialize_with_positional_args(*args)
|
149
|
-
|
150
|
-
|
165
|
+
Familia.trace :INITIALIZE_ARGS, redis, args, caller(1..1) if Familia.debug?
|
166
|
+
self.class.fields.zip(args).filter_map do |field, value|
|
167
|
+
if value
|
168
|
+
send(:"#{field}=", value)
|
169
|
+
field.to_sym
|
170
|
+
end
|
151
171
|
end
|
152
172
|
end
|
153
173
|
private :initialize_with_positional_args
|
154
174
|
|
155
|
-
|
156
|
-
|
175
|
+
# Initializes the object with keyword arguments.
|
176
|
+
# Assigns values to fields based on the provided hash of field names and values.
|
177
|
+
# Handles both symbol and string keys to accommodate different sources of data.
|
178
|
+
#
|
179
|
+
# @param fields [Hash] Hash of field names (as symbols or strings) and their values
|
180
|
+
# @return [Array<Symbol>] List of field names that were successfully updated
|
181
|
+
# (i.e., had non-nil values assigned)
|
182
|
+
# @private
|
183
|
+
def initialize_with_keyword_args(**fields)
|
184
|
+
Familia.trace :INITIALIZE_KWARGS, redis, fields.keys, caller(1..1) if Familia.debug?
|
185
|
+
self.class.fields.filter_map do |field|
|
157
186
|
# Redis will give us field names as strings back, but internally
|
158
|
-
# we use symbols. So we
|
159
|
-
value =
|
160
|
-
|
187
|
+
# we use symbols. So we check for both.
|
188
|
+
value = fields[field.to_sym] || fields[field.to_s]
|
189
|
+
if value
|
190
|
+
send(:"#{field}=", value)
|
191
|
+
field.to_sym
|
192
|
+
end
|
161
193
|
end
|
162
194
|
end
|
163
195
|
private :initialize_with_keyword_args
|
164
196
|
|
197
|
+
# A thin wrapper around the private initialize method that accepts a field
|
198
|
+
# hash and refreshes the existing object.
|
199
|
+
#
|
200
|
+
# This method is part of horreum.rb rather than serialization.rb because it
|
201
|
+
# operates solely on the provided values and doesn't query Redis or other
|
202
|
+
# external sources. That's why it's called "optimistic" refresh: it assumes
|
203
|
+
# the provided values are correct and updates the object accordingly.
|
204
|
+
#
|
205
|
+
# @see #refresh!
|
206
|
+
#
|
207
|
+
# @param fields [Hash] A hash of field names and their new values to update
|
208
|
+
# the object with.
|
209
|
+
# @return [Array] The list of field names that were updated.
|
210
|
+
def optimistic_refresh(**fields)
|
211
|
+
Familia.ld "[optimistic_refresh] #{self.class} #{rediskey} #{fields.keys}"
|
212
|
+
initialize_with_keyword_args(**fields)
|
213
|
+
end
|
214
|
+
|
165
215
|
# Determines the unique identifier for the instance
|
166
216
|
# This method is used to generate Redis keys for the object
|
167
217
|
def identifier
|
@@ -183,7 +233,7 @@ module Familia
|
|
183
233
|
end
|
184
234
|
|
185
235
|
# If the unique_id is nil, raise an error
|
186
|
-
raise Problem, "Identifier is nil for #{self}" if unique_id.nil?
|
236
|
+
raise Problem, "Identifier is nil for #{self.class}" if unique_id.nil?
|
187
237
|
raise Problem, 'Identifier is empty' if unique_id.empty?
|
188
238
|
|
189
239
|
unique_id
|
data/lib/familia/logging.rb
CHANGED
@@ -3,23 +3,6 @@
|
|
3
3
|
require 'pathname'
|
4
4
|
require 'logger'
|
5
5
|
|
6
|
-
module LoggerTraceRefinement
|
7
|
-
# Set to same value as Logger::DEBUG since 0 is the floor
|
8
|
-
# without either more invasive changes to the Logger class
|
9
|
-
# or a CustomLogger class that inherits from Logger.
|
10
|
-
TRACE = 2 unless defined?(TRACE)
|
11
|
-
refine Logger do
|
12
|
-
|
13
|
-
def trace(progname = nil, &block)
|
14
|
-
Thread.current[:severity_letter] = 'T'
|
15
|
-
add(LoggerTraceRefinement::TRACE, nil, progname, &block)
|
16
|
-
ensure
|
17
|
-
Thread.current[:severity_letter] = nil
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
6
|
module Familia
|
24
7
|
@logger = Logger.new($stdout)
|
25
8
|
@logger.progname = name
|
@@ -38,7 +21,7 @@ module Familia
|
|
38
21
|
# variable `severity_letter` is arbitrary and could be anything.
|
39
22
|
severity_letter = Thread.current[:severity_letter] || severity_letter
|
40
23
|
|
41
|
-
"#{severity_letter}, #{utc_datetime} #{pid} #{thread_id}: #{msg}
|
24
|
+
"#{severity_letter}, #{utc_datetime} #{pid} #{thread_id}: #{msg} [#{relative_path}:#{line}]\n"
|
42
25
|
end
|
43
26
|
|
44
27
|
# The Logging module provides a set of methods and constants for logging messages
|
@@ -120,7 +103,7 @@ module Familia
|
|
120
103
|
attr_reader :logger
|
121
104
|
|
122
105
|
# Gives our logger the ability to use our trace method.
|
123
|
-
|
106
|
+
using LoggerTraceRefinement if LoggerTraceRefinement::ENABLED
|
124
107
|
|
125
108
|
def info(*msg)
|
126
109
|
@logger.info(*msg)
|
@@ -156,14 +139,14 @@ module Familia
|
|
156
139
|
# @return [nil]
|
157
140
|
#
|
158
141
|
def trace(label, redis_instance, ident, context = nil)
|
159
|
-
return unless
|
142
|
+
return unless LoggerTraceRefinement::ENABLED
|
160
143
|
instance_id = redis_instance&.id
|
161
144
|
codeline = if context
|
162
145
|
context = [context].flatten
|
163
146
|
context.reject! { |line| line =~ %r{lib/familia} }
|
164
147
|
context.first
|
165
148
|
end
|
166
|
-
@logger.
|
149
|
+
@logger.trace format('[%s] %s -> %s <- at %s', label, instance_id, ident, codeline)
|
167
150
|
end
|
168
151
|
|
169
152
|
end
|
data/lib/familia/redistype.rb
CHANGED
@@ -61,10 +61,10 @@ module Familia
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
attr_reader :
|
64
|
+
attr_reader :keystring, :parent, :opts
|
65
65
|
attr_writer :dump_method, :load_method
|
66
66
|
|
67
|
-
# +
|
67
|
+
# +keystring+: If parent is set, this will be used as the suffix
|
68
68
|
# for rediskey. Otherwise this becomes the value of the key.
|
69
69
|
# If this is an Array, the elements will be joined.
|
70
70
|
#
|
@@ -92,10 +92,10 @@ module Familia
|
|
92
92
|
#
|
93
93
|
# Uses the redis connection of the parent or the value of
|
94
94
|
# opts[:redis] or Familia.redis (in that order).
|
95
|
-
def initialize(
|
95
|
+
def initialize(keystring, opts = {})
|
96
96
|
#Familia.ld " [initializing] #{self.class} #{opts}"
|
97
|
-
@
|
98
|
-
@
|
97
|
+
@keystring = keystring
|
98
|
+
@keystring = @keystring.join(Familia.delim) if @keystring.is_a?(Array)
|
99
99
|
|
100
100
|
# Remove all keys from the opts that are not in the allowed list
|
101
101
|
@opts = opts || {}
|
@@ -112,7 +112,7 @@ module Familia
|
|
112
112
|
|
113
113
|
# Produces the full redis key for this object.
|
114
114
|
def rediskey
|
115
|
-
Familia.ld "[rediskey] #{
|
115
|
+
Familia.ld "[rediskey] #{keystring} for #{self.class} (#{opts})"
|
116
116
|
|
117
117
|
# Return the hardcoded key if it's set. This is useful for
|
118
118
|
# support legacy keys that aren't derived in the same way.
|
@@ -121,15 +121,15 @@ module Familia
|
|
121
121
|
if parent_instance?
|
122
122
|
# This is an instance-level redistype object so the parent instance's
|
123
123
|
# rediskey method is defined in Familia::Horreum::InstanceMethods.
|
124
|
-
parent.rediskey(
|
124
|
+
parent.rediskey(keystring)
|
125
125
|
elsif parent_class?
|
126
126
|
# This is a class-level redistype object so the parent class' rediskey
|
127
127
|
# method is defined in Familia::Horreum::ClassMethods.
|
128
|
-
parent.rediskey(
|
128
|
+
parent.rediskey(keystring, nil)
|
129
129
|
else
|
130
|
-
# This is a standalone RedisType object where it's
|
130
|
+
# This is a standalone RedisType object where it's keystring
|
131
131
|
# is the full key.
|
132
|
-
|
132
|
+
keystring
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|