Module: Familia::Horreum::ClassMethods
- Includes:
- RelationsManagement, Settings
- Defined in:
- lib/familia/horreum/class_methods.rb
Overview
ClassMethods: Provides class-level functionality for Horreum
This module is extended into classes that include Familia::Horreum, providing methods for Redis operations and object management.
Key features:
-
Includes RelationsManagement for Redis-type field handling
-
Defines methods for managing fields, identifiers, and Redis keys
-
Provides utility methods for working with Redis objects
Instance Attribute Summary collapse
- #dump_method ⇒ Object
- #load_method ⇒ Object
-
#parent ⇒ Object
Returns the value of attribute parent.
-
#redis ⇒ Redis
Returns the Redis connection for the class.
Attributes included from Settings
Instance Method Summary collapse
- #all(suffix = :object) ⇒ Object
- #any?(filter = '*') ⇒ Boolean
- #class_redis_types ⇒ Object
- #class_redis_types?(name) ⇒ Boolean
- #create(*args) ⇒ Object
- #db(v = nil) ⇒ Object
- #destroy!(identifier, suffix = :object) ⇒ Object
- #exists?(identifier, suffix = :object) ⇒ Boolean
-
#fast_writer!(name) ⇒ Object
Defines a writer method with a bang (!) suffix for a given attribute name.
-
#field(name) ⇒ Object
Defines a field for the class and creates accessor methods.
-
#fields ⇒ Object
Returns the list of field names defined for the class in the order that they were defined.
- #find(suffix = '*') ⇒ Object
-
#from_key(objkey) ⇒ Object?
Retrieves and instantiates an object from Redis using the full object key.
-
#from_redis(identifier, suffix = :object) ⇒ Object?
Retrieves and instantiates an object from Redis using its identifier.
-
#identifier(val = nil) ⇒ Object
Sets or retrieves the unique identifier for the class.
- #multiget(*ids) ⇒ Object
- #prefix(a = nil) ⇒ Object
- #qstamp(quantum = nil, pattern = nil, now = Familia.now) ⇒ Object
- #rawmultiget(*ids) ⇒ Object
- #redis_object?(name) ⇒ Boolean
- #redis_types ⇒ Object
-
#rediskey(identifier, suffix = self.suffix) ⇒ Object
identifiercan be a value or an Array of values used to create the index. - #size(filter = '*') ⇒ Object
- #suffix(a = nil, &blk) ⇒ Object
- #ttl(v = nil) ⇒ Object
- #uri(v = nil) ⇒ Object
Methods included from RelationsManagement
#attach_class_redis_object_relation, #attach_instance_redis_object_relation, included
Methods included from Settings
Instance Attribute Details
#dump_method ⇒ Object
335 336 337 |
# File 'lib/familia/horreum/class_methods.rb', line 335 def dump_method @dump_method || :to_json # Familia.dump_method end |
#load_method ⇒ Object
339 340 341 |
# File 'lib/familia/horreum/class_methods.rb', line 339 def load_method @load_method || :from_json # Familia.load_method end |
#parent ⇒ Object
Returns the value of attribute parent.
36 37 38 |
# File 'lib/familia/horreum/class_methods.rb', line 36 def parent @parent end |
#redis ⇒ Redis
Returns the Redis connection for the class.
This method retrieves the Redis connection instance for the class. If no connection is set, it initializes a new connection using the provided URI or database configuration.
47 48 49 |
# File 'lib/familia/horreum/class_methods.rb', line 47 def redis @redis || Familia.redis(uri || db) end |
Instance Method Details
#all(suffix = :object) ⇒ Object
164 165 166 167 |
# File 'lib/familia/horreum/class_methods.rb', line 164 def all(suffix = :object) # objects that could not be parsed will be nil keys(suffix).filter_map { |k| from_key(k) } end |
#any?(filter = '*') ⇒ Boolean
169 170 171 |
# File 'lib/familia/horreum/class_methods.rb', line 169 def any?(filter = '*') size(filter) > 0 end |
#class_redis_types ⇒ Object
131 132 133 134 |
# File 'lib/familia/horreum/class_methods.rb', line 131 def class_redis_types @class_redis_types ||= {} @class_redis_types end |
#class_redis_types?(name) ⇒ Boolean
136 137 138 |
# File 'lib/familia/horreum/class_methods.rb', line 136 def class_redis_types?(name) class_redis_types.key? name.to_s.to_sym end |
#create(*args) ⇒ Object
187 188 189 190 191 192 193 |
# File 'lib/familia/horreum/class_methods.rb', line 187 def create *args me = from_array(*args) raise "#{self} exists: #{me.rediskey}" if me.exists? me.save me end |
#db(v = nil) ⇒ Object
154 155 156 157 |
# File 'lib/familia/horreum/class_methods.rb', line 154 def db(v = nil) @db = v unless v.nil? @db || parent&.db end |
#destroy!(identifier, suffix = :object) ⇒ Object
297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/familia/horreum/class_methods.rb', line 297 def destroy!(identifier, suffix = :object) return false if identifier.to_s.empty? objkey = rediskey identifier, suffix ret = redis.del objkey if Familia.debug? Familia.trace :DELETED, redis, "#{objkey}: #{ret.inspect}", caller end ret.positive? end |
#exists?(identifier, suffix = :object) ⇒ Boolean
287 288 289 290 291 292 293 294 295 |
# File 'lib/familia/horreum/class_methods.rb', line 287 def exists?(identifier, suffix = :object) return false if identifier.to_s.empty? objkey = rediskey identifier, suffix ret = redis.exists objkey Familia.trace :EXISTS, redis, "#{objkey} #{ret.inspect}", caller if Familia.debug? ret.positive? end |
#fast_writer!(name) ⇒ Object
Defines a writer method with a bang (!) suffix for a given attribute name.
The dynamically defined method performs the following:
-
Checks if the correct number of arguments is provided (exactly one).
-
Converts the provided value to a format suitable for Redis storage.
-
Uses the existing accessor method to set the attribute value.
-
Persists the value to Redis immediately using the hset command.
-
Includes custom error handling to raise an ArgumentError if the wrong number of arguments is given.
-
Raises a custom error message if an exception occurs during the execution of the method.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/familia/horreum/class_methods.rb', line 95 def fast_writer!(name) define_method :"#{name}!" do |*args| # Check if the correct number of arguments is provided (exactly one). if args.size != 1 raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" end value = args.first begin # Trace the operation if debugging is enabled. Familia.trace :FAST_WRITER, redis, "#{name}: #{value.inspect}", caller if Familia.debug? # Convert the provided value to a format suitable for Redis storage. prepared = to_redis(value) Familia.ld "[.fast_writer!] #{name} val: #{value.class} prepared: #{prepared.class}" # Use the existing accessor method to set the attribute value. send :"#{name}=", value # Persist the value to Redis immediately using the hset command. hset name, prepared rescue Familia::Problem => e # Raise a custom error message if an exception occurs during the execution of the method. raise "#{name}! method failed: #{e.}", e.backtrace end end end |
#field(name) ⇒ Object
Defines a field for the class and creates accessor methods.
This method defines a new field for the class, creating getter and setter instance methods similar to ‘attr_accessor`. It also generates a fast writer method for immediate persistence to Redis.
73 74 75 76 77 78 79 |
# File 'lib/familia/horreum/class_methods.rb', line 73 def field(name) fields << name attr_accessor name # Every field gets a fast writer method for immediately persisting fast_writer! name end |
#fields ⇒ Object
Returns the list of field names defined for the class in the order that they were defined. i.e. ‘field :a; field :b; fields => [:a, :b]`.
126 127 128 129 |
# File 'lib/familia/horreum/class_methods.rb', line 126 def fields @fields ||= [] @fields end |
#find(suffix = '*') ⇒ Object
310 311 312 |
# File 'lib/familia/horreum/class_methods.rb', line 310 def find(suffix = '*') redis.keys(rediskey('*', suffix)) || [] end |
#from_key(objkey) ⇒ Object?
Retrieves and instantiates an object from Redis using the full object key.
This method performs a two-step process to safely retrieve and instantiate objects:
-
It first checks if the key exists in Redis. This is crucial because:
-
It provides a definitive answer about the object’s existence.
-
It prevents ambiguity that could arise from ‘hgetall` returning an empty hash for non-existent keys, which could lead to the creation of “empty” objects.
-
-
If the key exists, it retrieves the object’s data and instantiates it.
This approach ensures that we only attempt to instantiate objects that actually exist in Redis, improving reliability and simplifying debugging.
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/familia/horreum/class_methods.rb', line 236 def from_key(objkey) raise ArgumentError, 'Empty key' if objkey.to_s.empty? # We use a lower-level method here b/c we're working with the # full key and not just the identifier. does_exist = redis.exists(objkey).positive? Familia.ld "[.from_key] #{self} from key #{objkey} (exists: #{does_exist})" Familia.trace :FROM_KEY, redis, objkey, caller if Familia.debug? # This is the reason for calling exists first. We want to definitively # and without any ambiguity know if the object exists in Redis. If it # doesn't, we return nil. If it does, we proceed to load the object. # Otherwise, hgetall will return an empty hash, which will be passed to # the constructor, which will then be annoying to debug. return unless does_exist obj = redis.hgetall(objkey) # horreum objects are persisted as redis hashes Familia.trace :FROM_KEY2, redis, "#{objkey}: #{obj.inspect}", caller if Familia.debug? new(**obj) end |
#from_redis(identifier, suffix = :object) ⇒ Object?
Retrieves and instantiates an object from Redis using its identifier.
This method constructs the full Redis key using the provided identifier and suffix, then delegates to ‘from_key` for the actual retrieval and instantiation.
It’s a higher-level method that abstracts away the key construction, making it easier to retrieve objects when you only have their identifier.
278 279 280 281 282 283 284 285 |
# File 'lib/familia/horreum/class_methods.rb', line 278 def from_redis(identifier, suffix = :object) return nil if identifier.to_s.empty? objkey = rediskey(identifier, suffix) Familia.ld "[.from_redis] #{self} from key #{objkey})" Familia.trace :FROM_REDIS, Familia.redis(uri), objkey, caller(1..1).first if Familia.debug? from_key objkey end |
#identifier(val = nil) ⇒ Object
Sets or retrieves the unique identifier for the class.
This method defines or returns the unique identifier used to generate the Redis key for the object. If a value is provided, it sets the identifier; otherwise, it returns the current identifier.
60 61 62 63 |
# File 'lib/familia/horreum/class_methods.rb', line 60 def identifier(val = nil) @identifier = val if val @identifier end |
#multiget(*ids) ⇒ Object
195 196 197 198 |
# File 'lib/familia/horreum/class_methods.rb', line 195 def multiget(*ids) ids = rawmultiget(*ids) ids.filter_map { |json| from_json(json) } end |
#prefix(a = nil) ⇒ Object
182 183 184 185 |
# File 'lib/familia/horreum/class_methods.rb', line 182 def prefix(a = nil) @prefix = a if a @prefix || name.downcase.gsub('::', Familia.delim).to_sym end |
#qstamp(quantum = nil, pattern = nil, now = Familia.now) ⇒ Object
314 315 316 317 318 319 |
# File 'lib/familia/horreum/class_methods.rb', line 314 def qstamp(quantum = nil, pattern = nil, now = Familia.now) quantum ||= ttl || 10.minutes pattern ||= '%H%M' rounded = now - (now % quantum) Time.at(rounded).utc.strftime(pattern) end |
#rawmultiget(*ids) ⇒ Object
200 201 202 203 204 205 206 |
# File 'lib/familia/horreum/class_methods.rb', line 200 def rawmultiget(*ids) ids.collect! { |objid| rediskey(objid) } return [] if ids.compact.empty? Familia.trace :MULTIGET, redis, "#{ids.size}: #{ids}", caller if Familia.debug? redis.mget(*ids) end |
#redis_object?(name) ⇒ Boolean
140 141 142 |
# File 'lib/familia/horreum/class_methods.rb', line 140 def redis_object?(name) redis_types.key? name.to_s.to_sym end |
#redis_types ⇒ Object
144 145 146 147 |
# File 'lib/familia/horreum/class_methods.rb', line 144 def redis_types @redis_types ||= {} @redis_types end |
#rediskey(identifier, suffix = self.suffix) ⇒ Object
identifier can be a value or an Array of values used to create the index. We don’t enforce a default suffix; that’s left up to the instance. The suffix is used to differentiate between different types of objects.
A nil suffix will not be included in the key.
327 328 329 330 331 332 333 |
# File 'lib/familia/horreum/class_methods.rb', line 327 def rediskey(identifier, suffix = self.suffix) Familia.ld "[.rediskey] #{identifier} for #{self} (suffix:#{suffix})" raise NoIdentifier, self if identifier.to_s.empty? identifier &&= identifier.to_s Familia.rediskey(prefix, identifier, suffix) end |
#size(filter = '*') ⇒ Object
173 174 175 |
# File 'lib/familia/horreum/class_methods.rb', line 173 def size(filter = '*') redis.keys(rediskey(filter)).compact.size end |
#suffix(a = nil, &blk) ⇒ Object
177 178 179 180 |
# File 'lib/familia/horreum/class_methods.rb', line 177 def suffix(a = nil, &blk) @suffix = a || blk if a || !blk.nil? @suffix || Familia.default_suffix end |
#ttl(v = nil) ⇒ Object
149 150 151 152 |
# File 'lib/familia/horreum/class_methods.rb', line 149 def ttl(v = nil) @ttl = v unless v.nil? @ttl || parent&.ttl end |
#uri(v = nil) ⇒ Object
159 160 161 162 |
# File 'lib/familia/horreum/class_methods.rb', line 159 def uri(v = nil) @uri = v unless v.nil? @uri || parent&.uri end |