familia 1.0.0.pre.rc2 → 1.0.0.pre.rc3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4f3c02cad65603669b086ec26f31512004e939da510fec97fcc3803f132e5f5
4
- data.tar.gz: cdd710ea75ff3c6ce3ccb88caef97226ee9fb549435fce605433621a8583f06d
3
+ metadata.gz: c5cdd5bc9ccadb7e69c324e7a2716b378fa5e5fe9938c4ed44a61b23eef99c6a
4
+ data.tar.gz: de5bb8d9e3b6b09906e777f9a5586eff32a5b1d3ec7c5e51a3a26dac2bd85415
5
5
  SHA512:
6
- metadata.gz: '058e49695798298238523d571a4d21b47234874b47af018fdb085d22a430fa8db2ca725cb3103e0c8eee309b000eea7598cc4e9aed00373191d393af8d4ab27d'
7
- data.tar.gz: 9ff11c6c341d540e69ddb9d0518b67c9463b2248899b6f74a94a92b609140ed064e303e3559a455847727d5f7581aa41fccce5fdaac5effb5d74a30202e75721
6
+ metadata.gz: 76ffa691585dde5c45aaa17e1d02171bacb3fad0267f638b02d7c2cf0a65eaed4d0062656be2496c27ff9bd9788839a71d8475f3e14577ef607684cd179209c1
7
+ data.tar.gz: 96d52e17f1c3f3092d4ec39ad0fbd1455ef54a902a2f2aaa65c5531d11cdd8b53ee50ef1fa0bd967bd4fa49fd4b0693d89031bcb6decb6cc2def5cb4e1d85c9a
data/VERSION.yml CHANGED
@@ -2,4 +2,4 @@
2
2
  :MAJOR: 1
3
3
  :MINOR: 0
4
4
  :PATCH: 0
5
- :PRE: rc2
5
+ :PRE: rc3
@@ -36,20 +36,40 @@ module Familia
36
36
  attr_accessor :parent
37
37
  attr_writer :redis, :dump_method, :load_method
38
38
 
39
+ # Returns the Redis connection for the class.
40
+ #
41
+ # This method retrieves the Redis connection instance for the class. If no
42
+ # connection is set, it initializes a new connection using the provided URI
43
+ # or database configuration.
44
+ #
45
+ # @return [Redis] the Redis connection instance.
46
+ #
39
47
  def redis
40
48
  @redis || Familia.redis(uri || db)
41
49
  end
42
50
 
43
- # The object field or instance method to call to get the unique identifier
44
- # for that instance. The value returned by this method will be used to
45
- # generate the key for the object in Redis.
51
+ # Sets or retrieves the unique identifier for the class.
52
+ #
53
+ # This method defines or returns the unique identifier used to generate the
54
+ # Redis key for the object. If a value is provided, it sets the identifier;
55
+ # otherwise, it returns the current identifier.
56
+ #
57
+ # @param [Object] val the value to set as the identifier (optional).
58
+ # @return [Object] the current identifier.
59
+ #
46
60
  def identifier(val = nil)
47
61
  @identifier = val if val
48
62
  @identifier
49
63
  end
50
64
 
51
- # Define a field for the class. This will create getter and setter
52
- # instance methods just like any "attr_accessor" methods.
65
+ # Defines a field for the class and creates accessor methods.
66
+ #
67
+ # This method defines a new field for the class, creating getter and setter
68
+ # instance methods similar to `attr_accessor`. It also generates a fast
69
+ # writer method for immediate persistence to Redis.
70
+ #
71
+ # @param [Symbol, String] name the name of the field to define.
72
+ #
53
73
  def field(name)
54
74
  fields << name
55
75
  attr_accessor name
@@ -58,13 +78,46 @@ module Familia
58
78
  fast_writer! name
59
79
  end
60
80
 
61
- # @return The return value from redis client for hset command
81
+ # Defines a writer method with a bang (!) suffix for a given attribute name.
82
+ #
83
+ # The dynamically defined method performs the following:
84
+ # - Checks if the correct number of arguments is provided (exactly one).
85
+ # - Converts the provided value to a format suitable for Redis storage.
86
+ # - Uses the existing accessor method to set the attribute value.
87
+ # - Persists the value to Redis immediately using the hset command.
88
+ # - Includes custom error handling to raise an ArgumentError if the wrong number of arguments is given.
89
+ # - Raises a custom error message if an exception occurs during the execution of the method.
90
+ #
91
+ # @param [Symbol, String] name the name of the attribute for which the writer method is defined.
92
+ # @raise [ArgumentError] if the wrong number of arguments is provided.
93
+ # @raise [RuntimeError] if an exception occurs during the execution of the method.
94
+ #
62
95
  def fast_writer!(name)
63
- define_method :"#{name}!" do |value|
64
- prepared = to_redis(value)
65
- Familia.ld "[.fast_writer!] #{name} val: #{value.class} prepared: #{prepared.class}"
66
- send :"#{name}=", value # use the existing accessor
67
- hset name, prepared # persist to Redis without delay
96
+ define_method :"#{name}!" do |*args|
97
+ # Check if the correct number of arguments is provided (exactly one).
98
+ if args.size != 1
99
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)"
100
+ end
101
+
102
+ value = args.first
103
+
104
+ begin
105
+ # Trace the operation if debugging is enabled.
106
+ Familia.trace :FAST_WRITER, redis, "#{name}: #{value.inspect}", caller if Familia.debug?
107
+
108
+ # Convert the provided value to a format suitable for Redis storage.
109
+ prepared = to_redis(value)
110
+ Familia.ld "[.fast_writer!] #{name} val: #{value.class} prepared: #{prepared.class}"
111
+
112
+ # Use the existing accessor method to set the attribute value.
113
+ send :"#{name}=", value
114
+
115
+ # Persist the value to Redis immediately using the hset command.
116
+ hset name, prepared
117
+ rescue Familia::Problem => e
118
+ # Raise a custom error message if an exception occurs during the execution of the method.
119
+ raise "#{name}! method failed: #{e.message}", e.backtrace
120
+ end
68
121
  end
69
122
  end
70
123
 
@@ -20,9 +20,12 @@ module Familia
20
20
 
21
21
  def exists?
22
22
  ret = redis.exists rediskey
23
- ret.positive?
23
+ ret.positive? # differs from redis API but I think it's okay bc `exists?` is a predicate method.
24
24
  end
25
25
 
26
+ # Sets a timeout on key. After the timeout has expired, the key will automatically be deleted.
27
+ # Returns 1 if the timeout was set, 0 if key does not exist or the timeout could not be set.
28
+ #
26
29
  def expire(ttl = nil)
27
30
  ttl ||= self.class.ttl
28
31
  redis.expire rediskey, ttl.to_i
@@ -32,6 +35,11 @@ module Familia
32
35
  redis.ttl rediskey
33
36
  end
34
37
 
38
+ # Deletes a field from the hash stored at the Redis key.
39
+ #
40
+ # @param field [String] The field to delete from the hash.
41
+ # @return [Integer] The number of fields that were removed from the hash (0 or 1).
42
+ # @note This method is destructive, as indicated by the bang (!).
35
43
  def hdel!(field)
36
44
  redis.hdel rediskey, field
37
45
  end
@@ -76,21 +84,45 @@ module Familia
76
84
  redis.hvals rediskey(suffix)
77
85
  end
78
86
 
79
- def hincrby(field, increment)
87
+ def incr(field)
88
+ redis.hincrby rediskey(suffix), field, 1
89
+ end
90
+ alias increment incr
91
+
92
+ def incrby(field, increment)
80
93
  redis.hincrby rediskey(suffix), field, increment
81
94
  end
95
+ alias incrementby incrby
82
96
 
83
- def hincrbyfloat(field, increment)
97
+ def incrbyfloat(field, increment)
84
98
  redis.hincrbyfloat rediskey(suffix), field, increment
85
99
  end
100
+ alias incrementbyfloat incrbyfloat
101
+
102
+ def decrby(field, decrement)
103
+ redis.decrby rediskey(suffix), field, decrement
104
+ end
105
+ alias decrementby decrby
106
+
107
+ def decr(field)
108
+ redis.hdecr field
109
+ end
110
+ alias decrement decr
86
111
 
87
112
  def hlen
88
113
  redis.hlen rediskey(suffix)
89
114
  end
115
+ alias hlength hlen
90
116
 
91
117
  def hstrlen(field)
92
118
  redis.hstrlen rediskey(suffix), field
93
119
  end
120
+ alias hstrlength hstrlen
121
+
122
+ def key?(field)
123
+ redis.hexists rediskey(suffix), field
124
+ end
125
+ alias has_key? key?
94
126
 
95
127
  def delete!
96
128
  Familia.trace :DELETE!, redis, redisuri, caller(1..1) if Familia.debug?
@@ -1,30 +1,75 @@
1
1
  # rubocop:disable all
2
2
  #
3
3
  module Familia
4
- # InstanceMethods - Module containing instance-level methods for Familia
5
- #
6
- # This module is included in classes that include Familia, providing
7
- # instance-level functionality for Redis operations and object management.
4
+
5
+
6
+ # Familia::Horreum
8
7
  #
9
8
  class Horreum
10
-
11
- # Methods that call load and dump (InstanceMethods)
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
- # Note on refresh methods:
14
- # In this class, refresh! is the primary method that performs the Redis
15
- # query and state update. The non-bang refresh method is provided as a
16
- # convenience for method chaining, but still performs the same destructive
17
- # update as refresh!. This deviates from common Ruby conventions to better
18
- # fit the specific needs of this system.
19
35
  module Serialization
20
- #include Familia::RedisType::Serialization
21
36
 
22
37
  attr_writer :redis
23
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
+ #
24
51
  def redis
25
52
  @redis || self.class.redis
26
53
  end
27
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
+ #
28
73
  def transaction
29
74
  original_redis = self.redis
30
75
 
@@ -38,26 +83,74 @@ module Familia
38
83
  end
39
84
  end
40
85
 
41
- # A thin wrapper around `commit_fields` that updates the timestamps and
42
- # returns a boolean.
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
+ #
43
102
  def save
44
103
  Familia.trace :SAVE, redis, redisuri, caller(1..1) if Familia.debug?
45
104
 
46
- # Update timestamp fields
105
+ # Update our object's life story
47
106
  self.key ||= self.identifier
48
107
  self.updated = Familia.now.to_i
49
108
  self.created ||= Familia.now.to_i
50
109
 
51
- # Thr return value of commit_fields is an array of strings: ["OK"].
110
+ # Commit our tale to the Redis chronicles
52
111
  ret = commit_fields # e.g. ["OK"]
53
112
 
54
113
  Familia.ld "[save] #{self.class} #{rediskey} #{ret}"
55
114
 
56
- # Convert the return value to a boolean
57
- ret.all? { |value| value == "OK" }
115
+ # Did Redis accept our offering?
116
+ ret.uniq.all? { |value| value == "OK" }
58
117
  end
59
118
 
60
- # +return: [Array<String>] The return value of the Redis multi command
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
+ #
61
154
  def commit_fields
62
155
  Familia.ld "[commit_fields] #{self.class} #{rediskey} #{to_h}"
63
156
  transaction do |conn|
@@ -66,11 +159,31 @@ module Familia
66
159
  end
67
160
  end
68
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
+ #
69
181
  def destroy!
70
182
  Familia.trace :DESTROY, redis, redisuri, caller(1..1) if Familia.debug?
71
183
  delete!
72
184
  end
73
185
 
186
+
74
187
  # Refreshes the object's state by querying Redis and overwriting the
75
188
  # current field values. This method performs a destructive update on the
76
189
  # object, regardless of unsaved changes.
@@ -98,9 +211,21 @@ module Familia
98
211
  self
99
212
  end
100
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
+ #
101
228
  def to_h
102
- # Use self.class.fields to efficiently generate a hash
103
- # of all the fields for this object
104
229
  self.class.fields.inject({}) do |hsh, field|
105
230
  val = send(field)
106
231
  prepared = to_redis(val)
@@ -110,6 +235,20 @@ module Familia
110
235
  end
111
236
  end
112
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
+ #
113
252
  def to_a
114
253
  self.class.fields.map do |field|
115
254
  val = send(field)
@@ -160,34 +299,39 @@ module Familia
160
299
  prepared
161
300
  end
162
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
+ #
163
319
  def update_expiration(ttl = nil)
164
320
  ttl ||= opts[:ttl]
165
- return if ttl.to_i.zero? # nil will be zero
321
+ ttl = ttl.to_i
166
322
 
167
- Familia.ld "#{rediskey} to #{ttl}"
168
- expire ttl.to_i
323
+ return if ttl.zero?
324
+
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?
169
330
  end
331
+
170
332
  end
171
333
  # End of Serialization module
172
334
 
173
335
  include Serialization # these become Horreum instance methods
174
336
  end
175
337
  end
176
-
177
- __END__
178
-
179
- # Consider adding a retry mechanism for the refresh operation
180
- # if it fails to fetch the expected data:
181
- def refresh_with_retry(max_attempts = 3)
182
- attempts = 0
183
- base = 2
184
- while attempts < max_attempts
185
- refresh!
186
- return if name == "Jane Doe" # Or whatever condition indicates a successful refresh
187
- attempts += 1
188
-
189
- sleep_time = 0.1 * (base ** attempts)
190
- sleep(sleep_time) # Exponential backoff
191
- end
192
- raise "Failed to refresh after #{max_attempts} attempts"
193
- end
@@ -60,11 +60,12 @@ module Familia
60
60
  end
61
61
  alias all hgetall
62
62
 
63
- def has_key?(field)
63
+ def key?(field)
64
64
  redis.hexists rediskey, field
65
65
  end
66
- alias include? has_key?
67
- alias member? has_key?
66
+ alias has_key? key?
67
+ alias include? key?
68
+ alias member? key?
68
69
 
69
70
  def delete(field)
70
71
  redis.hdel rediskey, field
@@ -18,6 +18,7 @@ module Familia
18
18
  update_expiration
19
19
  self
20
20
  end
21
+ alias append push
21
22
 
22
23
  def <<(val)
23
24
  push val
@@ -31,6 +32,7 @@ module Familia
31
32
  update_expiration
32
33
  self
33
34
  end
35
+ alias prepend unshift
34
36
 
35
37
  def pop
36
38
  from_redis redis.rpop(rediskey)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: familia
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.rc2
4
+ version: 1.0.0.pre.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum