sideroo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5553a7ee34ceb7c54c39d8d9d3fcf409515745f06a951a077fa3dade1ea8227b
4
+ data.tar.gz: 1bc91fa63750c7a9f97b904059bc94eecaea61ebaea85760d05483480baaea7e
5
+ SHA512:
6
+ metadata.gz: d1054a3f260db3e4cfefe54e55b365e447ba6c90263f1ab68c6a8408c2bed63c64caf6a5dc54a4550e7c41425fc397a0f3d1c54c88bf20316d381490e0637f46
7
+ data.tar.gz: acad185b19f07e88a74af249bf845f6564f750d2d56896f78b00a2f39090f573244697395ad98bc86f9c1f90f269efc8b9da918ad177be36ebf9275fa38eef0c
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.6
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considerooed inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at ntd251@users.noreply.github.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sideroo.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem "redis", "~> 3.3"
9
+ gem "redis-namespace", "~>1.6"
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sideroo (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (12.3.3)
11
+ redis (3.3.5)
12
+ redis-namespace (1.6.0)
13
+ redis (>= 3.0.4)
14
+ rspec (3.9.0)
15
+ rspec-core (~> 3.9.0)
16
+ rspec-expectations (~> 3.9.0)
17
+ rspec-mocks (~> 3.9.0)
18
+ rspec-core (3.9.2)
19
+ rspec-support (~> 3.9.3)
20
+ rspec-expectations (3.9.2)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.9.0)
23
+ rspec-mocks (3.9.1)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.9.0)
26
+ rspec-support (3.9.3)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ rake (~> 12.0)
33
+ redis (~> 3.3)
34
+ redis-namespace (~> 1.6)
35
+ rspec (~> 3.0)
36
+ sideroo!
37
+
38
+ BUNDLED WITH
39
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Duong Nguyen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,587 @@
1
+ # Sideroo
2
+
3
+ > Declarative and auditable object-oriented library for Redis
4
+
5
+ `Sideroo` is `Object Oriented Redis` (`ooredis`) spelled backward.
6
+
7
+ ## 1. Motivations
8
+
9
+ This gem is aimed to provide
10
+ - a **declarative** Redis key definition
11
+ ```rb
12
+ class TopStoriesCache < Sideroo::Set
13
+ key_pattern 'top_stories:{country}:{category}'
14
+ description 'Cache top stories by ID per country and category'
15
+ example 'top_stories:us:romance'
16
+ key_regex /^top_stories\:(\w{2})\:([^\:]+)$/ # OPTIONAL - read docs below
17
+ end
18
+ ```
19
+ - an **intuitive** Redis key initialization
20
+ ```rb
21
+ cache = TopStoriesCache.new(country: 'us', category: 'romance')
22
+ # instead of repeating key = "top_stories:#{country}:#{category}"
23
+ ```
24
+ - **object-oriented** methods for each Redis data type
25
+ ```rb
26
+ # Redis Set methods
27
+ cache.sadd(story_id) # instead of redis.sadd(key, story_id)
28
+ cache.smembers # instead of redis.smembers(key)
29
+ cache.sismember(member) # instead of redis.sismember(key, member)
30
+ ```
31
+ - an **auditable** Redis key management
32
+ ```rb
33
+ TopStoriesCache.count # key count - COMING SOON
34
+ TopStoriesCache.all.map(&:key) # list all keys
35
+ TopStoriesCache.flush # delete all keys of the same pattern - COMING SOON
36
+
37
+ # Support `where` for key searching
38
+ # `each`, `map` for enumerable
39
+ TopStoriesCache.where(category: 'romance').each do |set|
40
+ # ...
41
+ end
42
+ ```
43
+ - Potential **self-generated documentation** for Redis usage
44
+ ```rb
45
+ Sideroo.report # COMING SOON
46
+ ```
47
+
48
+ All of these are done while maintaining a **thin** abstraction on top of `redis` gem.
49
+
50
+ ## 2. Installation
51
+
52
+ Add this line to your application's Gemfile:
53
+
54
+ ```ruby
55
+ gem 'sideroo'
56
+ ```
57
+
58
+ And then execute:
59
+
60
+ $ bundle install
61
+
62
+ Or install it yourself as:
63
+
64
+ $ gem install sideroo
65
+
66
+
67
+ ## 3. Usage
68
+
69
+ ### 3.0. Configurations - REQUIRED
70
+
71
+ ```rb
72
+ Sideroo.configure do |c|
73
+ c.redis_client = Redis.new
74
+ end
75
+ ```
76
+
77
+ Sideroo provides a thin OOP abstraction on top of `redis-rb`. Therefore, it's recommended to use `redis-rb` (with / without `redis-namespace`). Any Redis clients with the same interfaces are fine too.
78
+
79
+ Most of the usage are just to abstract the `key` argument into the internal state of the obj.
80
+
81
+ #### Examples
82
+
83
+ For Redis `Set`
84
+
85
+ ```rb
86
+ # in Redis
87
+ key = "namespace:#{dimension_1}:#{dimension_2}"
88
+ redis_client.scard(key)
89
+ redis_client.sadd(key, member)
90
+
91
+ # in Sideroo
92
+ class MySet < Sideroo::Set
93
+ key_pattern 'namespace:{dimension_1}:{dimension_2}'
94
+ end
95
+
96
+ sideroo_set = MySet.new(
97
+ dimension_1: value_1,
98
+ dimension_2: value_2,
99
+ )
100
+ sideroo_set.scard
101
+ sideroo_set.sadd(member)
102
+ ```
103
+
104
+ ### 3.1. Define a Redis usage
105
+
106
+ Each Redis usages usually
107
+ - have a key pattern
108
+ - use a certain Redis data type
109
+
110
+ `Sideroo` provides what you need and more.
111
+
112
+ There are some configurations you can specify for each use-cases.
113
+
114
+ #### `key_pattern` - String - required
115
+
116
+ Pattern used by the use-case
117
+
118
+ ```rb
119
+ key_pattern 'top_stories:{country}:{category}'
120
+ ```
121
+
122
+ #### `key_regex` - Regexp - optional
123
+
124
+ Regex for better pattern matching for search. See more in Section 5.
125
+
126
+ Can be 100% optional if key namespacing is done well.
127
+
128
+ ```rb
129
+ key_regex /^top_stories\:(\w{2})\:([^\:]+)$/
130
+ ```
131
+
132
+ #### `description` - String - optional
133
+
134
+ Provide description to the use-cases.
135
+
136
+ ```rb
137
+ description 'Cache top stories per country and category'
138
+ ```
139
+
140
+ #### `example` - String - optional
141
+
142
+ Example of actual Redis keys would be used. If specified, you can utilize `example_valid?` check to validate `key_regex` in your specs.
143
+
144
+ ```rb
145
+ example 'top_stories:us:romance'
146
+ ```
147
+
148
+ #### Dynamic initalization
149
+
150
+ When there are dynamic components inside the key pattern, e.g. `top_stories:{country}:{category}`, the constructor would detect and require `country` and `category` during initialization.
151
+
152
+
153
+ ```rb
154
+ # Static cache key
155
+ class TopUserCache < Sideroo::List
156
+ key_pattern 'top_users' # REQUIRED
157
+ description 'Cache 50 top users worldwide'
158
+ end
159
+
160
+ # 1-dimension cache key
161
+ class CountryPolicyCache < Sideroo::String
162
+ key_pattern 'policy:{country}' # REQUIRED
163
+ key_regex /^page\:(\w{2})$/ # Optional. To resolve key conflicts with other usages if any.
164
+ description 'Cache Policy page per country'
165
+ end
166
+
167
+ CountryPolicyCache.new # MissingKeys: Missing country
168
+ CountryPolicyCache.new(country: 'us') # Good
169
+ CountryPolicyCache.new(gender: 'us') # UnexpectedKeys: Unexpected keys gender
170
+
171
+ # 2-dimension cache key
172
+ class TopStoriesCache < Sideroo::List
173
+ key_pattern 'top_stories:{country}:{category}'
174
+ description 'Cache top stories by ID per country and category'
175
+ end
176
+
177
+ TopStoriesCache.new # MissingKeys: Missing country, category
178
+ TopStoriesCache.new(country: 'us') # MissingKeys: Missing category
179
+ TopStoriesCache.new(country: 'us', category: 'romance') # GOOD
180
+ TopStoriesCache.new(country: 'us', cateogry: 'romance', random_key: 'random_value') # UnexpectedKeys: Unexpected keys random_key
181
+ ```
182
+
183
+ ### 3.2. Object-oriented methods for each data type
184
+
185
+ ```rb
186
+ class CountryPageCache < Sideroo::String
187
+ key_pattern 'page:{country}'
188
+ key_regex /^page\:(\w{2})$/
189
+ end
190
+
191
+ # The key-value params are auto detected
192
+ page_cache = CountryPageCache.new(country: country)
193
+ page_cache.get
194
+
195
+ class TopStoriesCache < Sideroo::List
196
+ key_pattern 'top_stories:{country}:{category}'
197
+ description 'Cache top stories by ID per country and category'
198
+ end
199
+
200
+ # The key-value params are auto detected
201
+ cache = TopStoryCache.new(country: 'sg', category: 10)
202
+ cache.lpush(story_id)
203
+ cache.set(story_id) # NoMethodError - since `set` is not a method of List type
204
+ ```
205
+
206
+ ### 3.3. Search and Enumerable
207
+
208
+ ```rb
209
+ class TopStoriesCache < Sideroo::Set
210
+ key_pattern 'top_stories:{country}:{category}'
211
+ description 'Cache top stories by ID per country and category'
212
+ end
213
+ ```
214
+
215
+ ```
216
+ top_stories:sg:10
217
+ top_stories:sg:20
218
+ top_stories:us:10
219
+ top_stories:us:12
220
+ ```
221
+
222
+ ```rb
223
+ TopStoriesCache.all # Not recommended for large db
224
+ TopStoriesCache.all.to_a
225
+
226
+ TopStoriesCache.where(country: 'sg').to_a
227
+
228
+ # Loop through `top_stories:sg:*`
229
+ TopStoriesCache.where(country: 'sg').each do |list|
230
+ list.key # top_stories:sg:10
231
+ list.smembers # return story ids
232
+ # ...
233
+ end
234
+
235
+ TopStoriesCache.where(country: 'sg').map do |list|
236
+ #...
237
+ end
238
+
239
+ TopStoriesCache.where(country: 'sg').count
240
+ ```
241
+
242
+ ### 3.4. Report & Generate documentation - COMING SOON
243
+
244
+ ```rb
245
+ Sideroo.report
246
+ ```
247
+
248
+ ```
249
+ TBD
250
+ ```
251
+
252
+ ### 3.5. Audit keys
253
+
254
+ ```rb
255
+ TopStoriesCache.count # Scan and count
256
+
257
+ TopStoriesCache.all.to_a # NOT RECOMMENDED if there are too many keys
258
+ ```
259
+
260
+ ### 3.6. Flush keys - COMING SOON
261
+
262
+ ```rb
263
+ TopStoriesCache.flush # Delete all keys of TopStoriesCache
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 4. Data Types
269
+
270
+ `Sideroo` provides support for 7 main Redis data types.
271
+
272
+ All `key`-related Redis methods are supported by all below types.
273
+
274
+ ### KEY-related methods
275
+
276
+ ```rb
277
+ class AnyRecord < Sideroo::Base
278
+ # ...
279
+ end
280
+
281
+ record = AnyRecord.new(...)
282
+
283
+ record.del
284
+ record.dump
285
+ record.exists
286
+ record.expire(duration_in_seconds)
287
+ record.expireat(time_in_seconds)
288
+ record.persist
289
+ record.pexpire(duration_in_ms)
290
+ record.pexpireat(time_in_ms)
291
+ record.pttl
292
+ record.rename(new_key)
293
+ record.renamenx(new_key)
294
+ record.restore(ttl, serialized_value, options)
295
+ record.touch
296
+ record.ttl
297
+ record.type
298
+ record.unlink
299
+ ```
300
+
301
+ ### 4.1. `Sideroo::String`
302
+
303
+ Support all KEY-related methods and its own methods.
304
+
305
+ ```rb
306
+ class MyStringCache < Sideroo::String
307
+ # ...
308
+ end
309
+
310
+ string = MyStringCache.new(...)
311
+
312
+ string.append(value)
313
+ string.decr
314
+ string.decrby(value) # number
315
+ string.get
316
+ string.getbit(offset)
317
+ string.getrange(start, stop)
318
+ string.getset(value)
319
+ string.incr
320
+ string.incrby(value)
321
+ string.incrbyfloat(value)
322
+ string.psetex(ttl, value)
323
+ string.set(value)
324
+ string.setbit(offset, value)
325
+ string.setex(ttl, value)
326
+ string.setnx(value)
327
+ string.setrange(offset, value)
328
+ string.strlen
329
+ ```
330
+
331
+ ### 4.2. `Sideroo::Hash`
332
+
333
+ Support all KEY-related methods and its own methods.
334
+
335
+ ```rb
336
+ class MyHash < Sideroo::Hash
337
+ # ...
338
+ end
339
+
340
+ hash = MyHash.new(...)
341
+
342
+ hash.hdel(*fields)
343
+ hash.hexists(field)
344
+ hash.hget(field)
345
+ hash.hgetall
346
+ hash.hincrby(field, increment)
347
+ hash.hincrbyfloat(field, increment)
348
+ hash.hkeys
349
+ hash.hlen
350
+ hash.hmget(*fields, &blk)
351
+ hash.hmset(*attrs)
352
+ hash.hscan(cursor, options = {})
353
+ hash.hscan_each(options = {}, &block)
354
+ hash.hset(field, value)
355
+ hash.hsetnx(field, value)
356
+ hash.hvals
357
+ hash.mapped_hmget(*field)
358
+ hash.mapped_hmset(hash)
359
+ ```
360
+
361
+ ### 4.3. `Sideroo::List`
362
+
363
+ Support all KEY-related methods and its own methods.
364
+
365
+ ```rb
366
+ class MyList < Sideroo::List
367
+ # ...
368
+ end
369
+
370
+ list = MyList.new(...)
371
+
372
+ list.blpop(timeout:)
373
+ list.brpop(timeout:)
374
+ list.brpoplpush(destination, options = {})
375
+ list.lindex(index) # => String
376
+ list.linsert(where, pivot, value) # => Fixnum
377
+ list.llen # => Fixnum
378
+ list.lpop # => String
379
+ list.lpush(value) # => Fixnum
380
+ list.lpushx(value) # => Fixnum
381
+ list.lrange(start, stop) # => Array<String>
382
+ list.lrem(count, value) # => Fixnum
383
+ list.lset(index, value) # => String
384
+ list.ltrim(start, stop) # => String
385
+ list.rpop # => String
386
+ list.rpoplpush(source, destination) # => nil, String
387
+ list.rpush(value) # => Fixnum
388
+ list.rpushx(value) # => Fixnum
389
+ ```
390
+
391
+ ### 4.4. `Sideroo::Set`
392
+
393
+ Support all KEY-related methods and its own methods.
394
+
395
+ ```rb
396
+ class SiteSet < Sideroo::Set
397
+ # ...
398
+ end
399
+
400
+ set = SiteSet.new(...)
401
+
402
+ set.sadd(member) # => Boolean, Fixnum
403
+ set.scard
404
+ set.sdiff(*other_keys)
405
+ set.sinter(*other_keys)
406
+ set.sismember(member)
407
+ set.smembers
408
+ set.smove(destination, member)
409
+ set.spop(count = nil)
410
+ set.srandmember(count = nil)
411
+ set.srem(member)
412
+ set.sscan(cursor, options = {}) # => String+
413
+ set.sscan_each(options = {}, &block) # => Enumerator
414
+ set.sunion(*other_keys)
415
+ set.sdiffstore(destination, *other_keys)
416
+ set.sdiffstore!(*other_keys)
417
+ set.sinterstore(destination, *other_keys)
418
+ set.sinterstore!(*other_keys)
419
+ set.sunionstore(destination, *other_keys)
420
+ set.sunionstore!(*other_keys)
421
+ ```
422
+
423
+ ### 4.5. `Sideroo::SortedSet`
424
+
425
+ Support all KEY-related methods and its own methods.
426
+
427
+ ```rb
428
+ class MySortedSet < Sideroo::SortedSet
429
+ # ...
430
+ end
431
+
432
+ sorted_set = MySortedSet.new(...)
433
+
434
+ sorted_set.zadd(*args) # => Boolean, ...
435
+ sorted_set.zcard # => Fixnum
436
+ sorted_set.zcount(min, max) # => Fixnum
437
+ sorted_set.zincrby(increment, member) # => Float
438
+ sorted_set.zlexcount(min, max) # => Fixnum
439
+ sorted_set.zpopmax(count = nil) # => Array<String, Float>+
440
+ sorted_set.zpopmin(count = nil) # => Array<String, Float>+
441
+ sorted_set.zrange(start, stop, options = {}) # => Array<String>, Arra
442
+ sorted_set.zrangebylex(min, max, options = {}) # => Array<String>, Arra
443
+ sorted_set.zrangebyscore(min, max, options = {}) # => Array<String>, Arra
444
+ sorted_set.zrank(member) # => Fixnum
445
+ sorted_set.zrem(member) # => Boolean, Fixnum
446
+ sorted_set.zremrangebyrank(start, stop) # => Fixnum
447
+ sorted_set.zremrangebyscore(min, max) # => Fixnum
448
+ sorted_set.zrevrange(start, stop, options = {}) # => Object
449
+ sorted_set.zrevrangebylex(max, min, options = {}) # => Object
450
+ sorted_set.zrevrangebyscore(max, min, options = {}) # => Object
451
+ sorted_set.zrevrank(member) # => Fixnum
452
+ sorted_set.zscan(cursor, options = {}) # => String, Arra
453
+ sorted_set.zscan_each(options = {}, &block) # => Enumerator
454
+ sorted_set.zscore(member) # => Float
455
+ sorted_set.zinterstore(destination, *other_keys)
456
+ sorted_set.zinterstore!(*other_keys)
457
+ sorted_set.zunionstore(destination, *other_keys)
458
+ sorted_set.zunionstore!(*other_keys)
459
+ ```
460
+
461
+ ### 4.6. `Sideroo::Bitmap`
462
+
463
+ Support all KEY-related methods and its own methods.
464
+
465
+ ```rb
466
+ class MyBitmap < Sideroo::Bitmap
467
+ # ...
468
+ end
469
+
470
+ bitmap = MyBitmap.new(...)
471
+
472
+ bitmap.getbit(offset)
473
+ bitmap.setbit(offset, value)
474
+ ```
475
+
476
+ ### 4.7. `Sideroo::HyperLogLog`
477
+
478
+ Support all KEY-related methods and its own methods.
479
+
480
+ ```rb
481
+ class MyHLL < Sideroo::HyperLogLog
482
+ # ...
483
+ end
484
+
485
+ hll = MyHLL.new(...)
486
+
487
+ hll.pfadd(member)
488
+ hll.pfcount
489
+ hll.pfmerge(destination, *other_keys)
490
+ hll.pfmerge!(*other_keys)
491
+ ```
492
+
493
+ ---
494
+
495
+ ## 5. Known issues
496
+
497
+ ### 5.1. Key conflicts on search
498
+
499
+ Redis search via `keys` and `scan` methods only support `glob`-style patterns.
500
+
501
+ - `h?llo` matches `hello`, `hallo` and `hxllo`
502
+ - `h*llo` matches `hllo` and `heeeello`
503
+ - `h[ae]llo` matches `hello` and `hallo`, but not `hillo`
504
+ - `h[^e]llo` matches `hallo`, `hbllo`, ... but not `hello`
505
+ - `h[a-b]llo` matches `hallo` and `hbllo`
506
+
507
+ `glob`-style patterns are not as comprehensive as Regexp. This introduces conflicts for similar key patterns.
508
+
509
+ For examples,
510
+
511
+ - `users:{country}:{gender}` would use search pattern `users:*:*`
512
+ - `users:{age}` would use search pattern `users:*`
513
+
514
+ The second pattern does cover the data set of the first pattern. This could be avoid by having better namespacing in your applications.
515
+
516
+ e.g.`ucg:{country}:{gender}` vs. `u:{user_id}`.
517
+
518
+ `Sideroo` also provides an additional matching options called `key_regex` for each `class`. This would allow deeper key selection.
519
+
520
+ ```rb
521
+ class TopCountryGenderUsersCache < Sideroo::Set
522
+ key_pattern 'users:{country}:{gender}'
523
+ key_regex /^users\:([a-z]{2})\:([mf])$/
524
+ example 'users:sg:m'
525
+ description 'Top users per country per gender'
526
+ end
527
+
528
+ class UserStoriesCache < Sideroo::Set
529
+ key_pattern 'users:{user_id}'
530
+ key_regex /^users\:\d+$/
531
+ example 'users:12345'
532
+ description 'Top stories per users'
533
+ end
534
+ ```
535
+
536
+ ## 6. Redis Clients
537
+
538
+ Redis clients can be customized at 3 levels
539
+ - Global
540
+ - Class
541
+ - Instance
542
+
543
+ The lower level would inherit the config from parent level if a custom Redis client is not specified.
544
+
545
+ ### 6.1. Global `Sideroo` config
546
+
547
+ ```rb
548
+ Sideroo.configure do |c|
549
+ c.redis_client = global_redis_client
550
+ end
551
+ ```
552
+
553
+ ### 6.2. Class level config
554
+
555
+ ```rb
556
+ class UserStoriesCache < Sideroo::Set
557
+ # ...
558
+ redis_client class_redis_client
559
+ end
560
+ ```
561
+
562
+ ### 6.3. Instance level config
563
+
564
+ ```rb
565
+ cache = UserStoriesCache.new(...)
566
+ cache.use_client(instance_redis_client)
567
+ ```
568
+
569
+
570
+ ## 7. Development
571
+
572
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
573
+
574
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
575
+
576
+ ## 8. Contributing
577
+
578
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sideroo. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/sideroo/blob/master/CODE_OF_CONDUCT.md).
579
+
580
+
581
+ ## 9. License
582
+
583
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
584
+
585
+ ## 10. Code of Conduct
586
+
587
+ Everyone interacting in the Sideroo project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/sideroo/blob/master/CODE_OF_CONDUCT.md).