familia 0.10.2 → 1.0.0.pre.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.pre-commit-config.yaml +1 -1
- data/.rubocop.yml +75 -0
- data/.rubocop_todo.yml +63 -0
- data/Gemfile +6 -1
- data/Gemfile.lock +47 -15
- data/README.md +65 -13
- data/VERSION.yml +4 -3
- data/familia.gemspec +18 -13
- data/lib/familia/base.rb +33 -0
- data/lib/familia/connection.rb +87 -0
- data/lib/familia/core_ext.rb +119 -124
- data/lib/familia/errors.rb +33 -0
- data/lib/familia/features/api_version.rb +19 -0
- data/lib/familia/features/atomic_saves.rb +8 -0
- data/lib/familia/features/quantizer.rb +35 -0
- data/lib/familia/features/safe_dump.rb +194 -0
- data/lib/familia/features.rb +51 -0
- data/lib/familia/horreum/class_methods.rb +292 -0
- data/lib/familia/horreum/commands.rb +106 -0
- data/lib/familia/horreum/relations_management.rb +141 -0
- data/lib/familia/horreum/serialization.rb +193 -0
- data/lib/familia/horreum/settings.rb +63 -0
- data/lib/familia/horreum/utils.rb +44 -0
- data/lib/familia/horreum.rb +248 -0
- data/lib/familia/logging.rb +232 -0
- data/lib/familia/redistype/commands.rb +56 -0
- data/lib/familia/redistype/serialization.rb +110 -0
- data/lib/familia/redistype.rb +185 -0
- data/lib/familia/refinements.rb +88 -0
- data/lib/familia/settings.rb +38 -0
- data/lib/familia/types/hashkey.rb +107 -0
- data/lib/familia/types/list.rb +155 -0
- data/lib/familia/types/sorted_set.rb +234 -0
- data/lib/familia/types/string.rb +115 -0
- data/lib/familia/types/unsorted_set.rb +123 -0
- data/lib/familia/utils.rb +125 -0
- data/lib/familia/version.rb +25 -0
- data/lib/familia.rb +57 -161
- data/lib/redis_middleware.rb +109 -0
- data/try/00_familia_try.rb +5 -4
- data/try/10_familia_try.rb +21 -17
- data/try/20_redis_type_try.rb +67 -0
- data/try/{21_redis_object_zset_try.rb → 21_redis_type_zset_try.rb} +2 -2
- data/try/{22_redis_object_set_try.rb → 22_redis_type_set_try.rb} +2 -2
- data/try/{23_redis_object_list_try.rb → 23_redis_type_list_try.rb} +2 -2
- data/try/{24_redis_object_string_try.rb → 24_redis_type_string_try.rb} +6 -6
- data/try/{25_redis_object_hash_try.rb → 25_redis_type_hash_try.rb} +3 -3
- data/try/26_redis_bool_try.rb +10 -6
- data/try/27_redis_horreum_try.rb +93 -0
- data/try/30_familia_object_try.rb +21 -20
- data/try/35_feature_safedump_try.rb +83 -0
- data/try/40_customer_try.rb +140 -0
- data/try/41_customer_safedump_try.rb +86 -0
- data/try/test_helpers.rb +194 -0
- metadata +51 -47
- data/lib/familia/helpers.rb +0 -70
- data/lib/familia/object.rb +0 -533
- data/lib/familia/redisobject.rb +0 -1017
- data/lib/familia/test_helpers.rb +0 -40
- data/lib/familia/tools.rb +0 -67
- data/try/20_redis_object_try.rb +0 -44
@@ -0,0 +1,292 @@
|
|
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
|
+
@ttl = nil
|
12
|
+
@db = nil
|
13
|
+
@uri = nil
|
14
|
+
@suffix = nil
|
15
|
+
@prefix = nil
|
16
|
+
@fields = nil # []
|
17
|
+
@class_redis_types = nil # {}
|
18
|
+
@redis_types = nil # {}
|
19
|
+
@dump_method = nil
|
20
|
+
@load_method = nil
|
21
|
+
|
22
|
+
# ClassMethods: Provides class-level functionality for Horreum
|
23
|
+
#
|
24
|
+
# This module is extended into classes that include Familia::Horreum,
|
25
|
+
# providing methods for Redis operations and object management.
|
26
|
+
#
|
27
|
+
# Key features:
|
28
|
+
# * Includes RelationsManagement for Redis-type field handling
|
29
|
+
# * Defines methods for managing fields, identifiers, and Redis keys
|
30
|
+
# * Provides utility methods for working with Redis objects
|
31
|
+
#
|
32
|
+
module ClassMethods
|
33
|
+
include Familia::Settings
|
34
|
+
include Familia::Horreum::RelationsManagement
|
35
|
+
|
36
|
+
attr_accessor :parent
|
37
|
+
attr_writer :redis, :dump_method, :load_method
|
38
|
+
|
39
|
+
def redis
|
40
|
+
@redis || Familia.redis(uri || db)
|
41
|
+
end
|
42
|
+
|
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.
|
46
|
+
def identifier(val = nil)
|
47
|
+
@identifier = val if val
|
48
|
+
@identifier
|
49
|
+
end
|
50
|
+
|
51
|
+
# Define a field for the class. This will create getter and setter
|
52
|
+
# instance methods just like any "attr_accessor" methods.
|
53
|
+
def field(name)
|
54
|
+
fields << name
|
55
|
+
attr_accessor name
|
56
|
+
|
57
|
+
# Every field gets a fast writer method for immediately persisting
|
58
|
+
fast_writer! name
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return The return value from redis client for hset command
|
62
|
+
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
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the list of field names defined for the class in the order
|
72
|
+
# that they were defined. i.e. `field :a; field :b; fields => [:a, :b]`.
|
73
|
+
def fields
|
74
|
+
@fields ||= []
|
75
|
+
@fields
|
76
|
+
end
|
77
|
+
|
78
|
+
def class_redis_types
|
79
|
+
@class_redis_types ||= {}
|
80
|
+
@class_redis_types
|
81
|
+
end
|
82
|
+
|
83
|
+
def class_redis_types?(name)
|
84
|
+
class_redis_types.key? name.to_s.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
def redis_object?(name)
|
88
|
+
redis_types.key? name.to_s.to_sym
|
89
|
+
end
|
90
|
+
|
91
|
+
def redis_types
|
92
|
+
@redis_types ||= {}
|
93
|
+
@redis_types
|
94
|
+
end
|
95
|
+
|
96
|
+
def ttl(v = nil)
|
97
|
+
@ttl = v unless v.nil?
|
98
|
+
@ttl || parent&.ttl
|
99
|
+
end
|
100
|
+
|
101
|
+
def db(v = nil)
|
102
|
+
@db = v unless v.nil?
|
103
|
+
@db || parent&.db
|
104
|
+
end
|
105
|
+
|
106
|
+
def uri(v = nil)
|
107
|
+
@uri = v unless v.nil?
|
108
|
+
@uri || parent&.uri
|
109
|
+
end
|
110
|
+
|
111
|
+
def all(suffix = :object)
|
112
|
+
# objects that could not be parsed will be nil
|
113
|
+
keys(suffix).filter_map { |k| from_key(k) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def any?(filter = '*')
|
117
|
+
size(filter) > 0
|
118
|
+
end
|
119
|
+
|
120
|
+
def size(filter = '*')
|
121
|
+
redis.keys(rediskey(filter)).compact.size
|
122
|
+
end
|
123
|
+
|
124
|
+
def suffix(a = nil, &blk)
|
125
|
+
@suffix = a || blk if a || !blk.nil?
|
126
|
+
@suffix || Familia.default_suffix
|
127
|
+
end
|
128
|
+
|
129
|
+
def prefix(a = nil)
|
130
|
+
@prefix = a if a
|
131
|
+
@prefix || name.downcase.gsub('::', Familia.delim).to_sym
|
132
|
+
end
|
133
|
+
|
134
|
+
def create *args
|
135
|
+
me = from_array(*args)
|
136
|
+
raise "#{self} exists: #{me.rediskey}" if me.exists?
|
137
|
+
|
138
|
+
me.save
|
139
|
+
me
|
140
|
+
end
|
141
|
+
|
142
|
+
def multiget(*ids)
|
143
|
+
ids = rawmultiget(*ids)
|
144
|
+
ids.filter_map { |json| from_json(json) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def rawmultiget(*ids)
|
148
|
+
ids.collect! { |objid| rediskey(objid) }
|
149
|
+
return [] if ids.compact.empty?
|
150
|
+
|
151
|
+
Familia.trace :MULTIGET, redis, "#{ids.size}: #{ids}", caller if Familia.debug?
|
152
|
+
redis.mget(*ids)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Retrieves and instantiates an object from Redis using the full object
|
156
|
+
# key.
|
157
|
+
#
|
158
|
+
# @param objkey [String] The full Redis key for the object.
|
159
|
+
# @return [Object, nil] An instance of the class if the key exists, nil
|
160
|
+
# otherwise.
|
161
|
+
# @raise [ArgumentError] If the provided key is empty.
|
162
|
+
#
|
163
|
+
# This method performs a two-step process to safely retrieve and
|
164
|
+
# instantiate objects:
|
165
|
+
#
|
166
|
+
# 1. It first checks if the key exists in Redis. This is crucial because:
|
167
|
+
# - It provides a definitive answer about the object's existence.
|
168
|
+
# - It prevents ambiguity that could arise from `hgetall` returning an
|
169
|
+
# empty hash for non-existent keys, which could lead to the creation
|
170
|
+
# of "empty" objects.
|
171
|
+
#
|
172
|
+
# 2. If the key exists, it retrieves the object's data and instantiates
|
173
|
+
# it.
|
174
|
+
#
|
175
|
+
# This approach ensures that we only attempt to instantiate objects that
|
176
|
+
# actually exist in Redis, improving reliability and simplifying
|
177
|
+
# debugging.
|
178
|
+
#
|
179
|
+
# @example
|
180
|
+
# User.from_key("user:123") # Returns a User instance if it exists,
|
181
|
+
# nil otherwise
|
182
|
+
#
|
183
|
+
def from_key(objkey)
|
184
|
+
raise ArgumentError, 'Empty key' if objkey.to_s.empty?
|
185
|
+
|
186
|
+
# We use a lower-level method here b/c we're working with the
|
187
|
+
# full key and not just the identifier.
|
188
|
+
does_exist = redis.exists(objkey).positive?
|
189
|
+
|
190
|
+
Familia.ld "[.from_key] #{self} from key #{objkey} (exists: #{does_exist})"
|
191
|
+
Familia.trace :FROM_KEY, redis, objkey, caller if Familia.debug?
|
192
|
+
|
193
|
+
# This is the reason for calling exists first. We want to definitively
|
194
|
+
# and without any ambiguity know if the object exists in Redis. If it
|
195
|
+
# doesn't, we return nil. If it does, we proceed to load the object.
|
196
|
+
# Otherwise, hgetall will return an empty hash, which will be passed to
|
197
|
+
# the constructor, which will then be annoying to debug.
|
198
|
+
return unless does_exist
|
199
|
+
|
200
|
+
obj = redis.hgetall(objkey) # horreum objects are persisted as redis hashes
|
201
|
+
Familia.trace :FROM_KEY2, redis, "#{objkey}: #{obj.inspect}", caller if Familia.debug?
|
202
|
+
|
203
|
+
new(**obj)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Retrieves and instantiates an object from Redis using its identifier.
|
207
|
+
#
|
208
|
+
# @param identifier [String, Integer] The unique identifier for the
|
209
|
+
# object.
|
210
|
+
# @param suffix [Symbol] The suffix to use in the Redis key (default:
|
211
|
+
# :object).
|
212
|
+
# @return [Object, nil] An instance of the class if found, nil otherwise.
|
213
|
+
#
|
214
|
+
# This method constructs the full Redis key using the provided identifier
|
215
|
+
# and suffix, then delegates to `from_key` for the actual retrieval and
|
216
|
+
# instantiation.
|
217
|
+
#
|
218
|
+
# It's a higher-level method that abstracts away the key construction,
|
219
|
+
# making it easier to retrieve objects when you only have their
|
220
|
+
# identifier.
|
221
|
+
#
|
222
|
+
# @example
|
223
|
+
# User.from_redis(123) # Equivalent to User.from_key("user:123:object")
|
224
|
+
#
|
225
|
+
def from_redis(identifier, suffix = :object)
|
226
|
+
return nil if identifier.to_s.empty?
|
227
|
+
|
228
|
+
objkey = rediskey(identifier, suffix)
|
229
|
+
Familia.ld "[.from_redis] #{self} from key #{objkey})"
|
230
|
+
Familia.trace :FROM_REDIS, Familia.redis(uri), objkey, caller(1..1).first if Familia.debug?
|
231
|
+
from_key objkey
|
232
|
+
end
|
233
|
+
|
234
|
+
def exists?(identifier, suffix = :object)
|
235
|
+
return false if identifier.to_s.empty?
|
236
|
+
|
237
|
+
objkey = rediskey identifier, suffix
|
238
|
+
|
239
|
+
ret = redis.exists objkey
|
240
|
+
Familia.trace :EXISTS, redis, "#{objkey} #{ret.inspect}", caller if Familia.debug?
|
241
|
+
ret.positive?
|
242
|
+
end
|
243
|
+
|
244
|
+
def destroy!(identifier, suffix = :object)
|
245
|
+
return false if identifier.to_s.empty?
|
246
|
+
|
247
|
+
objkey = rediskey identifier, suffix
|
248
|
+
|
249
|
+
ret = redis.del objkey
|
250
|
+
if Familia.debug?
|
251
|
+
Familia.trace :DELETED, redis, "#{objkey}: #{ret.inspect}",
|
252
|
+
caller
|
253
|
+
end
|
254
|
+
ret.positive?
|
255
|
+
end
|
256
|
+
|
257
|
+
def find(suffix = '*')
|
258
|
+
redis.keys(rediskey('*', suffix)) || []
|
259
|
+
end
|
260
|
+
|
261
|
+
def qstamp(quantum = nil, pattern = nil, now = Familia.now)
|
262
|
+
quantum ||= ttl || 10.minutes
|
263
|
+
pattern ||= '%H%M'
|
264
|
+
rounded = now - (now % quantum)
|
265
|
+
Time.at(rounded).utc.strftime(pattern)
|
266
|
+
end
|
267
|
+
|
268
|
+
# +identifier+ can be a value or an Array of values used to create the index.
|
269
|
+
# We don't enforce a default suffix; that's left up to the instance.
|
270
|
+
# The suffix is used to differentiate between different types of objects.
|
271
|
+
#
|
272
|
+
#
|
273
|
+
# A nil +suffix+ will not be included in the key.
|
274
|
+
def rediskey(identifier, suffix = self.suffix)
|
275
|
+
Familia.ld "[.rediskey] #{identifier} for #{self} (suffix:#{suffix})"
|
276
|
+
raise NoIdentifier, self if identifier.to_s.empty?
|
277
|
+
|
278
|
+
identifier &&= identifier.to_s
|
279
|
+
Familia.rediskey(prefix, identifier, suffix)
|
280
|
+
end
|
281
|
+
|
282
|
+
def dump_method
|
283
|
+
@dump_method || :to_json # Familia.dump_method
|
284
|
+
end
|
285
|
+
|
286
|
+
def load_method
|
287
|
+
@load_method || :from_json # Familia.load_method
|
288
|
+
end
|
289
|
+
end
|
290
|
+
# End of ClassMethods module
|
291
|
+
end
|
292
|
+
end
|
@@ -0,0 +1,106 @@
|
|
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
|
40
|
+
redis.type rediskey(suffix)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Parity with RedisType#rename
|
44
|
+
def rename(newkey)
|
45
|
+
redis.rename rediskey, newkey
|
46
|
+
end
|
47
|
+
|
48
|
+
# For parity with RedisType#hgetall
|
49
|
+
def hgetall
|
50
|
+
Familia.trace :HGETALL, redis, redisuri, caller(1..1) if Familia.debug?
|
51
|
+
redis.hgetall rediskey(suffix)
|
52
|
+
end
|
53
|
+
alias all hgetall
|
54
|
+
|
55
|
+
def hget(field)
|
56
|
+
redis.hget rediskey(suffix), field
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return The number of fields that were added to the hash. If the
|
60
|
+
# field already exists, this will return 0.
|
61
|
+
def hset(field, value)
|
62
|
+
Familia.trace :HSET, redis, redisuri, caller(1..1) if Familia.debug?
|
63
|
+
redis.hset rediskey, field, value
|
64
|
+
end
|
65
|
+
|
66
|
+
def hmset
|
67
|
+
redis.hmset rediskey(suffix), self.to_h
|
68
|
+
end
|
69
|
+
|
70
|
+
def hkeys
|
71
|
+
Familia.trace :HKEYS, redis, 'redisuri', caller(1..1) if Familia.debug?
|
72
|
+
redis.hkeys rediskey(suffix)
|
73
|
+
end
|
74
|
+
|
75
|
+
def hvals
|
76
|
+
redis.hvals rediskey(suffix)
|
77
|
+
end
|
78
|
+
|
79
|
+
def hincrby(field, increment)
|
80
|
+
redis.hincrby rediskey(suffix), field, increment
|
81
|
+
end
|
82
|
+
|
83
|
+
def hincrbyfloat(field, increment)
|
84
|
+
redis.hincrbyfloat rediskey(suffix), field, increment
|
85
|
+
end
|
86
|
+
|
87
|
+
def hlen
|
88
|
+
redis.hlen rediskey(suffix)
|
89
|
+
end
|
90
|
+
|
91
|
+
def hstrlen(field)
|
92
|
+
redis.hstrlen rediskey(suffix), field
|
93
|
+
end
|
94
|
+
|
95
|
+
def delete!
|
96
|
+
Familia.trace :DELETE!, redis, redisuri, caller(1..1) if Familia.debug?
|
97
|
+
ret = redis.del rediskey
|
98
|
+
ret.positive?
|
99
|
+
end
|
100
|
+
protected :delete!
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
include Commands # these become Familia::Horreum instance methods
|
105
|
+
end
|
106
|
+
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
|