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

Attributes included from Settings

#delim

Instance Method Summary collapse

Methods included from RelationsManagement

#attach_class_redis_object_relation, #attach_instance_redis_object_relation, included

Methods included from Settings

#default_suffix

Instance Attribute Details

#dump_methodObject



282
283
284
# File 'lib/familia/horreum/class_methods.rb', line 282

def dump_method
  @dump_method || :to_json # Familia.dump_method
end

#load_methodObject



286
287
288
# File 'lib/familia/horreum/class_methods.rb', line 286

def load_method
  @load_method || :from_json # Familia.load_method
end

#parentObject

Returns the value of attribute parent.



36
37
38
# File 'lib/familia/horreum/class_methods.rb', line 36

def parent
  @parent
end

#redisObject



39
40
41
# File 'lib/familia/horreum/class_methods.rb', line 39

def redis
  @redis || Familia.redis(uri || db)
end

Instance Method Details

#all(suffix = :object) ⇒ Object



111
112
113
114
# File 'lib/familia/horreum/class_methods.rb', line 111

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

Returns:

  • (Boolean)


116
117
118
# File 'lib/familia/horreum/class_methods.rb', line 116

def any?(filter = '*')
  size(filter) > 0
end

#class_redis_typesObject



78
79
80
81
# File 'lib/familia/horreum/class_methods.rb', line 78

def class_redis_types
  @class_redis_types ||= {}
  @class_redis_types
end

#class_redis_types?(name) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/familia/horreum/class_methods.rb', line 83

def class_redis_types?(name)
  class_redis_types.key? name.to_s.to_sym
end

#create(*args) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/familia/horreum/class_methods.rb', line 134

def create *args
  me = from_array(*args)
  raise "#{self} exists: #{me.rediskey}" if me.exists?

  me.save
  me
end

#db(v = nil) ⇒ Object



101
102
103
104
# File 'lib/familia/horreum/class_methods.rb', line 101

def db(v = nil)
  @db = v unless v.nil?
  @db || parent&.db
end

#destroy!(identifier, suffix = :object) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/familia/horreum/class_methods.rb', line 244

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

Returns:

  • (Boolean)


234
235
236
237
238
239
240
241
242
# File 'lib/familia/horreum/class_methods.rb', line 234

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

Returns The return value from redis client for hset command.

Returns:

  • The return value from redis client for hset command



62
63
64
65
66
67
68
69
# File 'lib/familia/horreum/class_methods.rb', line 62

def fast_writer!(name)
  define_method :"#{name}!" do |value|
    prepared = to_redis(value)
    Familia.ld "[.fast_writer!] #{name} val: #{value.class} prepared: #{prepared.class}"
    send :"#{name}=", value # use the existing accessor
    hset name, prepared # persist to Redis without delay
  end
end

#field(name) ⇒ Object

Define a field for the class. This will create getter and setter instance methods just like any “attr_accessor” methods.



53
54
55
56
57
58
59
# File 'lib/familia/horreum/class_methods.rb', line 53

def field(name)
  fields << name
  attr_accessor name

  # Every field gets a fast writer method for immediately persisting
  fast_writer! name
end

#fieldsObject

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]`.



73
74
75
76
# File 'lib/familia/horreum/class_methods.rb', line 73

def fields
  @fields ||= []
  @fields
end

#find(suffix = '*') ⇒ Object



257
258
259
# File 'lib/familia/horreum/class_methods.rb', line 257

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:

  1. 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.

  2. 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.

Examples:

User.from_key("user:123")  # Returns a User instance if it exists,
nil otherwise

Parameters:

  • objkey (String)

    The full Redis key for the object.

Returns:

  • (Object, nil)

    An instance of the class if the key exists, nil otherwise.

Raises:

  • (ArgumentError)

    If the provided key is empty.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/familia/horreum/class_methods.rb', line 183

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.

Examples:

User.from_redis(123)  # Equivalent to User.from_key("user:123:object")

Parameters:

  • identifier (String, Integer)

    The unique identifier for the object.

  • suffix (Symbol) (defaults to: :object)

    The suffix to use in the Redis key (default: :object).

Returns:

  • (Object, nil)

    An instance of the class if found, nil otherwise.



225
226
227
228
229
230
231
232
# File 'lib/familia/horreum/class_methods.rb', line 225

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

The object field or instance method to call to get the unique identifier for that instance. The value returned by this method will be used to generate the key for the object in Redis.



46
47
48
49
# File 'lib/familia/horreum/class_methods.rb', line 46

def identifier(val = nil)
  @identifier = val if val
  @identifier
end

#multiget(*ids) ⇒ Object



142
143
144
145
# File 'lib/familia/horreum/class_methods.rb', line 142

def multiget(*ids)
  ids = rawmultiget(*ids)
  ids.filter_map { |json| from_json(json) }
end

#prefix(a = nil) ⇒ Object



129
130
131
132
# File 'lib/familia/horreum/class_methods.rb', line 129

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



261
262
263
264
265
266
# File 'lib/familia/horreum/class_methods.rb', line 261

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



147
148
149
150
151
152
153
# File 'lib/familia/horreum/class_methods.rb', line 147

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

Returns:

  • (Boolean)


87
88
89
# File 'lib/familia/horreum/class_methods.rb', line 87

def redis_object?(name)
  redis_types.key? name.to_s.to_sym
end

#redis_typesObject



91
92
93
94
# File 'lib/familia/horreum/class_methods.rb', line 91

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.

Raises:



274
275
276
277
278
279
280
# File 'lib/familia/horreum/class_methods.rb', line 274

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



120
121
122
# File 'lib/familia/horreum/class_methods.rb', line 120

def size(filter = '*')
  redis.keys(rediskey(filter)).compact.size
end

#suffix(a = nil, &blk) ⇒ Object



124
125
126
127
# File 'lib/familia/horreum/class_methods.rb', line 124

def suffix(a = nil, &blk)
  @suffix = a || blk if a || !blk.nil?
  @suffix || Familia.default_suffix
end

#ttl(v = nil) ⇒ Object



96
97
98
99
# File 'lib/familia/horreum/class_methods.rb', line 96

def ttl(v = nil)
  @ttl = v unless v.nil?
  @ttl || parent&.ttl
end

#uri(v = nil) ⇒ Object



106
107
108
109
# File 'lib/familia/horreum/class_methods.rb', line 106

def uri(v = nil)
  @uri = v unless v.nil?
  @uri || parent&.uri
end