familia 0.10.2 → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.pre-commit-config.yaml +1 -1
  4. data/.rubocop.yml +75 -0
  5. data/.rubocop_todo.yml +63 -0
  6. data/Gemfile +6 -1
  7. data/Gemfile.lock +47 -15
  8. data/README.md +11 -12
  9. data/VERSION.yml +4 -3
  10. data/familia.gemspec +18 -13
  11. data/lib/familia/base.rb +33 -0
  12. data/lib/familia/connection.rb +87 -0
  13. data/lib/familia/core_ext.rb +119 -124
  14. data/lib/familia/errors.rb +33 -0
  15. data/lib/familia/features/api_version.rb +19 -0
  16. data/lib/familia/features/atomic_saves.rb +8 -0
  17. data/lib/familia/features/quantizer.rb +35 -0
  18. data/lib/familia/features/safe_dump.rb +175 -0
  19. data/lib/familia/features.rb +51 -0
  20. data/lib/familia/horreum/class_methods.rb +240 -0
  21. data/lib/familia/horreum/commands.rb +59 -0
  22. data/lib/familia/horreum/relations_management.rb +141 -0
  23. data/lib/familia/horreum/serialization.rb +154 -0
  24. data/lib/familia/horreum/settings.rb +63 -0
  25. data/lib/familia/horreum/utils.rb +43 -0
  26. data/lib/familia/horreum.rb +198 -0
  27. data/lib/familia/logging.rb +249 -0
  28. data/lib/familia/redistype/commands.rb +56 -0
  29. data/lib/familia/redistype/serialization.rb +110 -0
  30. data/lib/familia/redistype.rb +185 -0
  31. data/lib/familia/settings.rb +38 -0
  32. data/lib/familia/types/hashkey.rb +108 -0
  33. data/lib/familia/types/list.rb +155 -0
  34. data/lib/familia/types/sorted_set.rb +234 -0
  35. data/lib/familia/types/string.rb +115 -0
  36. data/lib/familia/types/unsorted_set.rb +123 -0
  37. data/lib/familia/utils.rb +129 -0
  38. data/lib/familia/version.rb +25 -0
  39. data/lib/familia.rb +56 -161
  40. data/lib/redis_middleware.rb +109 -0
  41. data/try/00_familia_try.rb +5 -4
  42. data/try/10_familia_try.rb +21 -17
  43. data/try/20_redis_type_try.rb +67 -0
  44. data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
  45. data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
  46. data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
  47. data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
  48. data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
  49. data/try/26_redis_bool_try.rb +10 -6
  50. data/try/27_redis_horreum_try.rb +40 -0
  51. data/try/30_familia_object_try.rb +21 -20
  52. data/try/35_feature_safedump_try.rb +83 -0
  53. data/try/40_customer_try.rb +140 -0
  54. data/try/41_customer_safedump_try.rb +86 -0
  55. data/try/test_helpers.rb +186 -0
  56. metadata +50 -47
  57. data/lib/familia/helpers.rb +0 -70
  58. data/lib/familia/object.rb +0 -533
  59. data/lib/familia/redisobject.rb +0 -1017
  60. data/lib/familia/test_helpers.rb +0 -40
  61. data/lib/familia/tools.rb +0 -67
  62. data/try/20_redis_object_try.rb +0 -44
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'relations_management'
4
+
5
+ module Familia
6
+ class Horreum
7
+ # Class-level instance variables
8
+ # These are set up as nil initially and populated later
9
+ @redis = nil
10
+ @identifier = nil
11
+ @fields = nil # []
12
+ @ttl = nil
13
+ @db = nil
14
+ @uri = nil
15
+ @suffix = nil
16
+ @prefix = nil
17
+ @class_redis_types = nil # {}
18
+ @redis_types = nil # {}
19
+ @defined_fields = nil # {}
20
+ @dump_method = nil
21
+ @load_method = nil
22
+
23
+ # ClassMethods: Provides class-level functionality for Horreum
24
+ #
25
+ # This module is extended into classes that include Familia::Horreum,
26
+ # providing methods for Redis operations and object management.
27
+ #
28
+ # Key features:
29
+ # * Includes RelationsManagement for Redis-type field handling
30
+ # * Defines methods for managing fields, identifiers, and Redis keys
31
+ # * Provides utility methods for working with Redis objects
32
+ #
33
+ module ClassMethods
34
+ include Familia::Settings
35
+ include Familia::Horreum::RelationsManagement
36
+
37
+ attr_accessor :parent
38
+ attr_writer :redis, :dump_method, :load_method
39
+
40
+ def redis
41
+ @redis || Familia.redis(uri || db)
42
+ end
43
+
44
+ # The object field or instance method to call to get the unique identifier
45
+ # for that instance. The value returned by this method will be used to
46
+ # generate the key for the object in Redis.
47
+ def identifier(val = nil)
48
+ @identifier = val if val
49
+ @identifier
50
+ end
51
+
52
+ # Define a field for the class. This will create getter and setter
53
+ # instance methods just like any "attr_accessor" methods.
54
+ def field(name)
55
+ fields << name
56
+ attr_accessor name
57
+ end
58
+
59
+ # Returns the list of field names defined for the class in the order
60
+ # that they were defined. i.e. `field :a; field :b; fields => [:a, :b]`.
61
+ def fields
62
+ @fields ||= []
63
+ @fields
64
+ end
65
+
66
+ def class_redis_types
67
+ @class_redis_types ||= {}
68
+ @class_redis_types
69
+ end
70
+
71
+ def class_redis_types?(name)
72
+ class_redis_types.key? name.to_s.to_sym
73
+ end
74
+
75
+ def redis_object?(name)
76
+ redis_types.key? name.to_s.to_sym
77
+ end
78
+
79
+ def redis_types
80
+ @redis_types ||= {}
81
+ @redis_types
82
+ end
83
+
84
+ def defined_fields
85
+ @defined_fields ||= {}
86
+ @defined_fields
87
+ end
88
+
89
+ def ttl(v = nil)
90
+ @ttl = v unless v.nil?
91
+ @ttl || parent&.ttl
92
+ end
93
+
94
+ def db(v = nil)
95
+ @db = v unless v.nil?
96
+ @db || parent&.db
97
+ end
98
+
99
+ def uri(v = nil)
100
+ @uri = v unless v.nil?
101
+ @uri || parent&.uri
102
+ end
103
+
104
+ def all(suffix = :object)
105
+ # objects that could not be parsed will be nil
106
+ keys(suffix).filter_map { |k| from_key(k) }
107
+ end
108
+
109
+ def any?(filter = '*')
110
+ size(filter) > 0
111
+ end
112
+
113
+ def size(filter = '*')
114
+ redis.keys(rediskey(filter)).compact.size
115
+ end
116
+
117
+ def suffix(a = nil, &blk)
118
+ @suffix = a || blk if a || !blk.nil?
119
+ @suffix || Familia.default_suffix
120
+ end
121
+
122
+ def prefix(a = nil)
123
+ @prefix = a if a
124
+ @prefix || name.downcase.gsub('::', Familia.delim).to_sym
125
+ end
126
+
127
+ def create *args
128
+ me = from_array(*args)
129
+ raise "#{self} exists: #{me.rediskey}" if me.exists?
130
+
131
+ me.save
132
+ me
133
+ end
134
+
135
+ def multiget(*ids)
136
+ ids = rawmultiget(*ids)
137
+ ids.filter_map { |json| from_json(json) }
138
+ end
139
+
140
+ def rawmultiget(*ids)
141
+ ids.collect! { |objid| rediskey(objid) }
142
+ return [] if ids.compact.empty?
143
+
144
+ Familia.trace :MULTIGET, redis, "#{ids.size}: #{ids}", caller if Familia.debug?
145
+ redis.mget(*ids)
146
+ end
147
+
148
+ def from_key(objkey)
149
+ raise ArgumentError, 'Empty key' if objkey.to_s.empty?
150
+
151
+ # We use a lower-level method here b/c we're working with the
152
+ # full key and not just the identifier.
153
+ does_exist = redis.exists(objkey).positive?
154
+
155
+ Familia.ld "[.from_key] #{self} from key #{objkey} (exists: #{does_exist})"
156
+ Familia.trace :LOAD, redis, objkey, caller if Familia.debug?
157
+
158
+ # This is reason for calling exists first. We want to definitively and without any
159
+ # ambiguity know if the object exists in Redis. If it doesn't, we return nil. If
160
+ # it does, we proceed to load the object. Otherwise, hgetall will return an empty
161
+ # hash, which will be passed to the constructor, which will then be annoying to
162
+ # debug.
163
+ return unless does_exist
164
+
165
+ obj = redis.hgetall(objkey) # horreum objects are persisted as redis hashes
166
+ Familia.trace :HGETALL, redis, "#{objkey}: #{obj.inspect}", caller if Familia.debug?
167
+
168
+ new(**obj)
169
+ end
170
+
171
+ def from_redis(identifier, suffix = :object)
172
+ return nil if identifier.to_s.empty?
173
+
174
+ objkey = rediskey(identifier, suffix)
175
+ Familia.ld "[.from_redis] #{self} from key #{objkey})"
176
+ Familia.trace :FROMREDIS, Familia.redis(uri), objkey, caller(1..1).first if Familia.debug?
177
+ from_key objkey
178
+ end
179
+
180
+ def exists?(identifier, suffix = :object)
181
+ return false if identifier.to_s.empty?
182
+
183
+ objkey = rediskey identifier, suffix
184
+
185
+ ret = redis.exists objkey
186
+ if Familia.debug?
187
+ Familia.trace :EXISTS, redis, "#{objkey} #{ret.inspect}",
188
+ caller
189
+ end
190
+ ret.positive?
191
+ end
192
+
193
+ def destroy!(identifier, suffix = :object)
194
+ return false if identifier.to_s.empty?
195
+
196
+ objkey = rediskey identifier, suffix
197
+
198
+ ret = redis.del objkey
199
+ if Familia.debug?
200
+ Familia.trace :DELETED, redis, "#{objkey}: #{ret.inspect}",
201
+ caller
202
+ end
203
+ ret.positive?
204
+ end
205
+
206
+ def find(suffix = '*')
207
+ redis.keys(rediskey('*', suffix)) || []
208
+ end
209
+
210
+ def qstamp(quantum = nil, pattern = nil, now = Familia.now)
211
+ quantum ||= ttl || 10.minutes
212
+ pattern ||= '%H%M'
213
+ rounded = now - (now % quantum)
214
+ Time.at(rounded).utc.strftime(pattern)
215
+ end
216
+
217
+ # +identifier+ can be a value or an Array of values used to create the index.
218
+ # We don't enforce a default suffix; that's left up to the instance.
219
+ # The suffix is used to differentiate between different types of objects.
220
+ #
221
+ #
222
+ # A nil +suffix+ will not be included in the key.
223
+ def rediskey(identifier, suffix = self.suffix)
224
+ Familia.ld "[.rediskey] #{identifier} for #{self} (suffix:#{suffix})"
225
+ raise NoIdentifier, self if identifier.to_s.empty?
226
+
227
+ identifier &&= identifier.to_s
228
+ Familia.rediskey(prefix, identifier, suffix)
229
+ end
230
+
231
+ def dump_method
232
+ @dump_method || :to_json # Familia.dump_method
233
+ end
234
+
235
+ def load_method
236
+ @load_method || :from_json # Familia.load_method
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,59 @@
1
+ # rubocop:disable all
2
+ #
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.
8
+ #
9
+ class Horreum
10
+
11
+ # Methods that call Redis commands (InstanceMethods)
12
+ #
13
+ # NOTE: There is no hgetall for Horreum. This is because Horreum
14
+ # is a single hash in Redis that we aren't meant to have be working
15
+ # on in memory for more than, making changes -> committing. To
16
+ # emphasize this, instead of "refreshing" the object with hgetall,
17
+ # just load the object again.
18
+ #
19
+ module Commands
20
+
21
+ def exists?
22
+ ret = redis.exists rediskey
23
+ ret.positive?
24
+ end
25
+
26
+ def expire(ttl = nil)
27
+ ttl ||= self.class.ttl
28
+ redis.expire rediskey, ttl.to_i
29
+ end
30
+
31
+ def realttl
32
+ redis.ttl rediskey
33
+ end
34
+
35
+ def hdel!(field)
36
+ redis.hdel rediskey, field
37
+ end
38
+
39
+ def redistype(suffix = nil)
40
+ redis.type rediskey(suffix)
41
+ end
42
+
43
+ def hmset(suffix = nil)
44
+ suffix ||= self.class.suffix
45
+ redis.hmset rediskey(suffix), to_h
46
+ end
47
+
48
+ def delete!
49
+ Familia.trace :DELETE!, redis, redisuri, caller(1..1) if Familia.debug?
50
+ ret = redis.del rediskey
51
+ ret.positive?
52
+ end
53
+ protected :delete!
54
+
55
+ end
56
+
57
+ include Commands # these become Familia::Horreum instance methods
58
+ end
59
+ end
@@ -0,0 +1,141 @@
1
+ module Familia
2
+ class Horreum
3
+ #
4
+ # RelationsManagement: Manages Redis-type fields and relations
5
+ #
6
+ # This module uses metaprogramming to dynamically create methods
7
+ # for managing different types of Redis objects (e.g., sets, lists, hashes).
8
+ #
9
+ # Key metaprogramming features:
10
+ # * Dynamically defines methods for each Redis type (e.g., set, list, hashkey)
11
+ # * Creates both instance-level and class-level relation methods
12
+ # * Provides query methods for checking relation types
13
+ #
14
+ # Usage:
15
+ # Include this module in classes that need Redis-type management
16
+ # Call setup_relations_accessors to initialize the feature
17
+ #
18
+ module RelationsManagement
19
+ def self.included(base)
20
+ base.extend(ClassMethods)
21
+ base.setup_relations_accessors
22
+ end
23
+
24
+ module ClassMethods
25
+ # Sets up all Redis-type related methods
26
+ # This method is the core of the metaprogramming logic
27
+ #
28
+ def setup_relations_accessors
29
+ Familia::RedisType.registered_types.each_pair do |kind, klass|
30
+ Familia.ld "[registered_types] #{kind} => #{klass}"
31
+
32
+ # Dynamically define instance-level relation methods
33
+ #
34
+ # Once defined, these methods can be used at the class-level of a
35
+ # Familia member to define *instance-level* relations to any of the
36
+ # RedisType types (e.g. set, list, hash, etc).
37
+ #
38
+ define_method :"#{kind}" do |*args|
39
+ name, opts = *args
40
+ attach_instance_redis_object_relation name, klass, opts
41
+ redis_types[name.to_s.to_sym]
42
+ end
43
+ define_method :"#{kind}?" do |name|
44
+ obj = redis_types[name.to_s.to_sym]
45
+ !obj.nil? && klass == obj.klass
46
+ end
47
+ define_method :"#{kind}s" do
48
+ names = redis_types.keys.select { |name| send(:"#{kind}?", name) }
49
+ names.collect! { |name| redis_types[name] }
50
+ names
51
+ end
52
+
53
+ # Dynamically define class-level relation methods
54
+ #
55
+ # Once defined, these methods can be used at the class-level of a
56
+ # Familia member to define *class-level relations* to any of the
57
+ # RedisType types (e.g. class_set, class_list, class_hash, etc).
58
+ #
59
+ define_method :"class_#{kind}" do |*args|
60
+ name, opts = *args
61
+ attach_class_redis_object_relation name, klass, opts
62
+ end
63
+ define_method :"class_#{kind}?" do |name|
64
+ obj = class_redis_types[name.to_s.to_sym]
65
+ !obj.nil? && klass == obj.klass
66
+ end
67
+ define_method :"class_#{kind}s" do
68
+ names = class_redis_types.keys.select { |name| send(:"class_#{kind}?", name) }
69
+ # TODO: This returns instances of the RedisType class which
70
+ # also contain the options. This is different from the instance
71
+ # RedisTypes defined above which returns the Struct of name, klass, and opts.
72
+ # names.collect! { |name| self.send name }
73
+ # OR NOT:
74
+ names.collect! { |name| class_redis_types[name] }
75
+ names
76
+ end
77
+ end
78
+ end
79
+ end
80
+ # End of ClassMethods module
81
+
82
+ # Creates an instance-level relation
83
+ def attach_instance_redis_object_relation(name, klass, opts)
84
+ Familia.ld "[Attaching instance-level #{name}] #{klass} => (#{self}) #{opts}"
85
+ raise ArgumentError, "Name is blank (#{klass})" if name.to_s.empty?
86
+
87
+ name = name.to_s.to_sym
88
+ opts ||= {}
89
+
90
+ redis_types[name] = Struct.new(:name, :klass, :opts).new
91
+ redis_types[name].name = name
92
+ redis_types[name].klass = klass
93
+ redis_types[name].opts = opts
94
+
95
+ attr_reader name
96
+
97
+ define_method :"#{name}=" do |val|
98
+ send(name).replace val
99
+ end
100
+ define_method :"#{name}?" do
101
+ !send(name).empty?
102
+ end
103
+
104
+ redis_types[name]
105
+ end
106
+
107
+ # Creates a class-level relation
108
+ def attach_class_redis_object_relation(name, klass, opts)
109
+ Familia.ld "[#{self}] Attaching class-level #{name} #{klass} => #{opts}"
110
+ raise ArgumentError, 'Name is blank (klass)' if name.to_s.empty?
111
+
112
+ name = name.to_s.to_sym
113
+ opts = opts.nil? ? {} : opts.clone
114
+ opts[:parent] = self unless opts.key?(:parent)
115
+
116
+ class_redis_types[name] = Struct.new(:name, :klass, :opts).new
117
+ class_redis_types[name].name = name
118
+ class_redis_types[name].klass = klass
119
+ class_redis_types[name].opts = opts
120
+
121
+ # An accessor method created in the metaclass will
122
+ # access the instance variables for this class.
123
+ singleton_class.attr_reader name
124
+
125
+ define_singleton_method :"#{name}=" do |v|
126
+ send(name).replace v
127
+ end
128
+ define_singleton_method :"#{name}?" do
129
+ !send(name).empty?
130
+ end
131
+
132
+ redis_object = klass.new name, opts
133
+ redis_object.freeze
134
+ instance_variable_set(:"@#{name}", redis_object)
135
+
136
+ class_redis_types[name]
137
+ end
138
+ end
139
+ # End of RelationsManagement module
140
+ end
141
+ end
@@ -0,0 +1,154 @@
1
+ # rubocop:disable all
2
+ #
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.
8
+ #
9
+ class Horreum
10
+
11
+ # Methods that call load and dump (InstanceMethods)
12
+ #
13
+ module Serialization
14
+ #include Familia::RedisType::Serialization
15
+
16
+ attr_writer :redis
17
+
18
+ def redis
19
+ @redis || self.class.redis
20
+ end
21
+
22
+ def transaction
23
+ original_redis = self.redis
24
+
25
+ begin
26
+ redis.multi do |conn|
27
+ self.instance_variable_set(:@redis, conn)
28
+ yield(conn)
29
+ end
30
+ ensure
31
+ self.redis = original_redis
32
+ end
33
+ end
34
+
35
+ # A thin wrapper around `commit_fields` that updates the timestamps and
36
+ # returns a boolean.
37
+ def save
38
+ Familia.trace :SAVE, redis, redisuri, caller(1..1) if Familia.debug?
39
+
40
+ # Update timestamp fields
41
+ self.updated = Familia.now.to_i
42
+ self.created = Familia.now.to_i unless self.created
43
+
44
+ # Thr return value of commit_fields is an array of strings: ["OK"].
45
+ ret = commit_fields # e.g. ["OK"]
46
+
47
+ Familia.ld "[save] #{self.class} #{rediskey} #{ret}"
48
+
49
+ # Convert the return value to a boolean
50
+ ret.all? { |value| value == "OK" }
51
+ end
52
+
53
+ # +return: [Array<String>] The return value of the Redis multi command
54
+ def commit_fields
55
+ Familia.ld "[commit_fields] #{self.class} #{rediskey} #{to_h}"
56
+ transaction do |conn|
57
+ hmset
58
+ update_expiration
59
+ end
60
+ end
61
+
62
+ def destroy!
63
+ Familia.trace :DESTROY, redis, redisuri, caller(1..1) if Familia.debug?
64
+ delete!
65
+ end
66
+
67
+ def to_h
68
+ # Use self.class.fields to efficiently generate a hash
69
+ # of all the fields for this object
70
+ self.class.fields.inject({}) do |hsh, field|
71
+ val = send(field)
72
+ prepared = val.to_s
73
+ Familia.ld " [to_h] field: #{field} val: #{val.class} prepared: #{prepared.class}"
74
+ hsh[field] = prepared
75
+ hsh
76
+ end
77
+ end
78
+
79
+ def to_a
80
+ self.class.fields.map do |field|
81
+ val = send(field)
82
+ Familia.ld " [to_a] field: #{field} val: #{val}"
83
+ to_redis(val)
84
+ end
85
+ end
86
+
87
+ # The to_redis method in Familia::Redistype and Familia::Horreum serve similar purposes
88
+ # but have some key differences in their implementation:
89
+ #
90
+ # Similarities:
91
+ # - Both methods aim to serialize various data types for Redis storage
92
+ # - Both handle basic data types like String, Symbol, and Numeric
93
+ # - Both have provisions for custom serialization methods
94
+ #
95
+ # Differences:
96
+ # - Familia::Redistype uses the opts[:class] for type hints
97
+ # - Familia::Horreum had more explicit type checking and conversion
98
+ # - Familia::Redistype includes more extensive debug tracing
99
+ #
100
+ # The centralized Familia.distinguisher method accommodates both approaches by:
101
+ # 1. Handling a wide range of data types, including those from both implementations
102
+ # 2. Providing a 'strict_values' option for flexible type handling
103
+ # 3. Supporting custom serialization through a dump_method
104
+ # 4. Including debug tracing similar to Familia::Redistype
105
+ #
106
+ # By using Familia.distinguisher, we achieve more consistent behavior across
107
+ # different parts of the library while maintaining the flexibility to handle
108
+ # various data types and custom serialization needs. This centralization
109
+ # also makes it easier to extend or modify serialization behavior in the future.
110
+ #
111
+ def to_redis(val)
112
+ prepared = Familia.distinguisher(val, false)
113
+
114
+ if prepared.nil? && val.respond_to?(dump_method)
115
+ prepared = val.send(dump_method)
116
+ end
117
+
118
+ Familia.ld "[#{self.class}#to_redis] nil returned for #{self.class}##{name}" if prepared.nil?
119
+ prepared
120
+ end
121
+
122
+ def update_expiration(ttl = nil)
123
+ ttl ||= opts[:ttl]
124
+ return if ttl.to_i.zero? # nil will be zero
125
+
126
+ Familia.ld "#{rediskey} to #{ttl}"
127
+ expire ttl.to_i
128
+ end
129
+ end
130
+
131
+ include Serialization # these become Horreum instance methods
132
+ end
133
+ 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
@@ -0,0 +1,63 @@
1
+ # rubocop:disable all
2
+ #
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.
8
+ #
9
+ class Horreum
10
+
11
+ # Settings - Module containing settings for Familia::Horreum (InstanceMethods)
12
+ #
13
+ module Settings
14
+ attr_writer :dump_method, :load_method, :suffix
15
+
16
+ def opts
17
+ @opts ||= {}
18
+ @opts
19
+ end
20
+
21
+ def redisdetails
22
+ {
23
+ uri: self.class.uri,
24
+ db: self.class.db,
25
+ key: rediskey,
26
+ type: redistype,
27
+ ttl: ttl,
28
+ realttl: realttl
29
+ }
30
+ end
31
+
32
+ def ttl=(v)
33
+ @ttl = v.to_i
34
+ end
35
+
36
+ def ttl
37
+ @ttl || self.class.ttl
38
+ end
39
+
40
+ def db=(v)
41
+ @db = v.to_i
42
+ end
43
+
44
+ def db
45
+ @db || self.class.db
46
+ end
47
+
48
+ def suffix
49
+ @suffix || self.class.suffix
50
+ end
51
+
52
+ def dump_method
53
+ @dump_method || self.class.dump_method
54
+ end
55
+
56
+ def load_method
57
+ @load_method || self.class.load_method
58
+ end
59
+ end
60
+
61
+ include Settings # these become Horreum instance methods
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ # rubocop:disable all
2
+ #
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.
8
+ #
9
+ class Horreum
10
+
11
+ # Utils - Module containing utility methods for Familia::Horreum (InstanceMethods)
12
+ #
13
+ module Utils
14
+
15
+ def redisuri(suffix = nil)
16
+ u = Familia.redisuri(self.class.uri) # returns URI::Redis
17
+ u.db ||= self.class.db.to_s # TODO: revisit logic (should the horrerum instance know its uri?)
18
+ u.key = rediskey(suffix)
19
+ u
20
+ end
21
+
22
+ # +suffix+ is the value to be used at the end of the redis key
23
+ # (e.g. `customer:customer_id:scores` would have `scores` as the suffix
24
+ # and `customer_id` would have been the identifier in that case).
25
+ #
26
+ # identifier is the value that distinguishes this object from others.
27
+ # Whether this is a Horreum or RedisType object, the value is taken
28
+ # from the `identifier` method).
29
+ #
30
+ def rediskey(suffix = self.suffix, ignored = nil)
31
+ Familia.ld "[#rediskey] #{identifier} for #{self.class}"
32
+ raise Familia::NoIdentifier, "No identifier for #{self.class}" if identifier.to_s.empty?
33
+ self.class.rediskey identifier, suffix
34
+ end
35
+
36
+ def join(*args)
37
+ Familia.join(args.map { |field| send(field) })
38
+ end
39
+ end
40
+
41
+ include Utils # these become Horreum instance methods
42
+ end
43
+ end