redis-objects-daily-counter 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +204 -0
- data/.gem_comet.yml +24 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +39 -0
- data/.rubocop_todo.yml +32 -0
- data/CHANGELOG.md +21 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Dockerfile +7 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +95 -0
- data/LICENSE.txt +21 -0
- data/README.md +122 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +17 -0
- data/lib/redis/annual_counter.rb +18 -0
- data/lib/redis/base_counter_object.rb +54 -0
- data/lib/redis/daily_counter.rb +18 -0
- data/lib/redis/hourly_counter.rb +26 -0
- data/lib/redis/minutely_counter.rb +26 -0
- data/lib/redis/monthly_counter.rb +18 -0
- data/lib/redis/objects/annual_counters.rb +41 -0
- data/lib/redis/objects/daily-counter/version.rb +11 -0
- data/lib/redis/objects/daily_counters.rb +41 -0
- data/lib/redis/objects/hourly_counters.rb +41 -0
- data/lib/redis/objects/minutely_counters.rb +41 -0
- data/lib/redis/objects/monthly_counters.rb +41 -0
- data/lib/redis/objects/weekly_counters.rb +41 -0
- data/lib/redis/weekly_counter.rb +18 -0
- data/lib/redis-objects-daily-counter.rb +37 -0
- data/redis-objects-daily-counter.gemspec +35 -0
- metadata +93 -0
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
[![CircleCI](https://circleci.com/gh/ryz310/redis-objects-daily-counter.svg?style=svg)](https://circleci.com/gh/ryz310/redis-objects-daily-counter) [![Gem Version](https://badge.fury.io/rb/redis-objects-daily-counter.svg)](https://badge.fury.io/rb/redis-objects-daily-counter) [![Maintainability](https://api.codeclimate.com/v1/badges/3639d1776e23031b1b31/maintainability)](https://codeclimate.com/github/ryz310/redis-objects-daily-counter/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/3639d1776e23031b1b31/test_coverage)](https://codeclimate.com/github/ryz310/redis-objects-daily-counter/test_coverage) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=ryz310/redis-objects-daily-counter)](https://dependabot.com)
|
2
|
+
|
3
|
+
# Redis::Objects::Daily::Counter
|
4
|
+
|
5
|
+
This is a gem which extends [Redis::Objects](https://github.com/nateware/redis-objects) gem. Once install this gem, you can use the daily counter, etc. in addition to the standard features of Redis::Objects. These counters are useful for measuring conversions, implementing API rate limiting, and more.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'redis-objects-daily-counter'
|
13
|
+
```
|
14
|
+
|
15
|
+
If you want to know about installation and standard usage, please see Redis::Objects' GitHub page.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
`daily_counter` automatically creates keys that are unique to each object, in the format:
|
20
|
+
|
21
|
+
```
|
22
|
+
model_name:id:field_name:yyyy-mm-dd
|
23
|
+
```
|
24
|
+
|
25
|
+
For illustration purposes, consider this stub class:
|
26
|
+
|
27
|
+
```rb
|
28
|
+
class Homepage
|
29
|
+
include Redis::Objects
|
30
|
+
|
31
|
+
daily_counter :pv, expireat: -> { Time.now + 2_678_400 } # about a month
|
32
|
+
|
33
|
+
def id
|
34
|
+
1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# 2021-04-01
|
39
|
+
homepage = Homepage.new
|
40
|
+
homepage.id # 1
|
41
|
+
|
42
|
+
homepage.pv.increment
|
43
|
+
homepage.pv.increment
|
44
|
+
homepage.pv.increment
|
45
|
+
puts homepage.pv.value # 3
|
46
|
+
|
47
|
+
# 2021-04-02 (next day)
|
48
|
+
puts homepage.pv.value # 0
|
49
|
+
homepage.pv.increment
|
50
|
+
homepage.pv.increment
|
51
|
+
puts homepage.pv.value # 2
|
52
|
+
|
53
|
+
start_date = Date.new(2021, 4, 1)
|
54
|
+
end_date = Date.new(2021, 4, 2)
|
55
|
+
homepage.pv.range(start_date, end_date) # [3, 2]
|
56
|
+
```
|
57
|
+
|
58
|
+
The daily counter automatically switches the save destination when the date changes.
|
59
|
+
You can access past dates counted values like Ruby arrays:
|
60
|
+
|
61
|
+
```rb
|
62
|
+
# 2021-04-01
|
63
|
+
homepage.pv.increment(3)
|
64
|
+
|
65
|
+
# 2021-04-02 (next day)
|
66
|
+
homepage.pv.increment(2)
|
67
|
+
|
68
|
+
# 2021-04-03 (next day)
|
69
|
+
homepage.pv.increment(5)
|
70
|
+
|
71
|
+
homepage.pv[Date.new(2021, 4, 1)] # => 3
|
72
|
+
homepage.pv[Date.new(2021, 4, 1), 3] # => [3, 2, 5]
|
73
|
+
homepage.pv[Date.new(2021, 4, 1)..Date.new(2021, 4, 2)] # => [3, 2]
|
74
|
+
|
75
|
+
homepage.pv.delete(Date.new(2021, 4, 1))
|
76
|
+
homepage.pv.range(Date.new(2021, 4, 1), Date.new(2021, 4, 3)) # => [0, 2, 5]
|
77
|
+
homepage.pv.at(Date.new(2021, 4, 2)) # => 2
|
78
|
+
```
|
79
|
+
|
80
|
+
### Counters
|
81
|
+
|
82
|
+
I recommend using with `expireat` option.
|
83
|
+
|
84
|
+
* `annual_counter`
|
85
|
+
* Key format: `model_name:id:field_name:yyyy`
|
86
|
+
* Redis is a highly volatile key-value store, so I don't recommend using it.
|
87
|
+
* `monthly_counter`
|
88
|
+
* Key format: `model_name:id:field_name:yyyy-mm`
|
89
|
+
* `weekly_counter`
|
90
|
+
* Key format: `model_name:id:field_name:yyyyWw`
|
91
|
+
* `daily_counter`
|
92
|
+
* Key format: `model_name:id:field_name:yyyy-mm-dd`
|
93
|
+
* `hourly_counter`
|
94
|
+
* Key format: `model_name:id:field_name:yyyy-mm-ddThh`
|
95
|
+
* `minutely_counter`
|
96
|
+
* Key format: `model_name:id:field_name:yyyy-mm-ddThh:mi`
|
97
|
+
|
98
|
+
### Timezone
|
99
|
+
|
100
|
+
This gem follows Ruby process' time zone, but if you extends Time class by ActiveSupport (e.g. `Time.current`), follows Rails process' timezone.
|
101
|
+
|
102
|
+
## Development
|
103
|
+
|
104
|
+
The development environment for this gem is configured with docker-compose.
|
105
|
+
Please use the following command:
|
106
|
+
|
107
|
+
$ docker-compose up -d
|
108
|
+
$ docker-compose run --rm ruby bundle
|
109
|
+
$ docker-compose run --rm ruby rspec .
|
110
|
+
$ docker-compose run --rm ruby rubocop -a
|
111
|
+
|
112
|
+
## Contributing
|
113
|
+
|
114
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/redis-objects-daily-counter. 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]/redis-objects-daily-counter/blob/master/CODE_OF_CONDUCT.md).
|
115
|
+
|
116
|
+
## License
|
117
|
+
|
118
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
119
|
+
|
120
|
+
## Code of Conduct
|
121
|
+
|
122
|
+
Everyone interacting in the Redis::Objects::Daily::Counter project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/redis-objects-daily-counter/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'redis-objects-daily-counter'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/base_counter_object"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class AnnualCounter < BaseCounterObject
|
7
|
+
private
|
8
|
+
|
9
|
+
def redis_daily_field_key(date_or_time)
|
10
|
+
date_key = date_or_time.strftime('%Y')
|
11
|
+
[original_key, date_key].flatten.join(':')
|
12
|
+
end
|
13
|
+
|
14
|
+
def next_key(date, length)
|
15
|
+
date.next_year(length - 1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class BaseCounterObject < Counter
|
5
|
+
def initialize(key, *args)
|
6
|
+
@original_key = key
|
7
|
+
super(redis_daily_field_key(current_time), *args)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :original_key
|
11
|
+
|
12
|
+
def [](date_or_time, length = nil)
|
13
|
+
if date_or_time.is_a? Range
|
14
|
+
range(date_or_time.first, date_or_time.max)
|
15
|
+
elsif length
|
16
|
+
case length <=> 0
|
17
|
+
when 1 then range(date_or_time, next_key(date_or_time, length))
|
18
|
+
when 0 then []
|
19
|
+
when -1 then nil # Ruby does this (a bit weird)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
at(date_or_time)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
alias slice []
|
26
|
+
|
27
|
+
def delete(date_or_time)
|
28
|
+
redis.del(redis_daily_field_key(date_or_time))
|
29
|
+
end
|
30
|
+
|
31
|
+
def range(start_date, end_date)
|
32
|
+
keys = (start_date..end_date).map { |date| redis_daily_field_key(date) }.uniq
|
33
|
+
redis.mget(*keys).map(&:to_i)
|
34
|
+
end
|
35
|
+
|
36
|
+
def at(date_or_time)
|
37
|
+
redis.get(redis_daily_field_key(date_or_time)).to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
def current_time
|
41
|
+
@current_time ||= Time.respond_to?(:current) ? Time.current : Time.now
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def redis_daily_field_key(_date_or_time)
|
47
|
+
raise 'not implemented'
|
48
|
+
end
|
49
|
+
|
50
|
+
def next_key(_date, _length)
|
51
|
+
raise 'not implemented'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/base_counter_object"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class DailyCounter < BaseCounterObject
|
7
|
+
private
|
8
|
+
|
9
|
+
def redis_daily_field_key(date_or_time)
|
10
|
+
date_key = date_or_time.strftime('%Y-%m-%d')
|
11
|
+
[original_key, date_key].flatten.join(':')
|
12
|
+
end
|
13
|
+
|
14
|
+
def next_key(date, length)
|
15
|
+
date + length - 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/base_counter_object"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class HourlyCounter < BaseCounterObject
|
7
|
+
def range(start_time, end_time)
|
8
|
+
keys =
|
9
|
+
(start_time.to_i..end_time.to_i)
|
10
|
+
.step(3600)
|
11
|
+
.map { |integer| redis_daily_field_key(Time.at(integer)) }
|
12
|
+
redis.mget(*keys).map(&:to_i)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def redis_daily_field_key(time)
|
18
|
+
time_key = time.strftime('%Y-%m-%dT%H')
|
19
|
+
[original_key, time_key].flatten.join(':')
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_key(time, length)
|
23
|
+
time + 3600 * (length - 1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/base_counter_object"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class MinutelyCounter < BaseCounterObject
|
7
|
+
def range(start_time, end_time)
|
8
|
+
keys =
|
9
|
+
(start_time.to_i..end_time.to_i)
|
10
|
+
.step(60)
|
11
|
+
.map { |integer| redis_daily_field_key(Time.at(integer)) }
|
12
|
+
redis.mget(*keys).map(&:to_i)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def redis_daily_field_key(time)
|
18
|
+
time_key = time.strftime('%Y-%m-%dT%H:%M')
|
19
|
+
[original_key, time_key].flatten.join(':')
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_key(time, length)
|
23
|
+
time + 60 * (length - 1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/base_counter_object"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class MonthlyCounter < BaseCounterObject
|
7
|
+
private
|
8
|
+
|
9
|
+
def redis_daily_field_key(date_or_time)
|
10
|
+
date_key = date_or_time.strftime('%Y-%m')
|
11
|
+
[original_key, date_key].flatten.join(':')
|
12
|
+
end
|
13
|
+
|
14
|
+
def next_key(date, length)
|
15
|
+
date.next_month(length - 1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/annual_counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module AnnualCounters
|
7
|
+
class << self
|
8
|
+
def included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def annual_counter(name, options = {}) # rubocop:disable Metrics/MethodLength
|
15
|
+
options[:start] ||= 0
|
16
|
+
options[:type] ||= (options[:start]).zero? ? :increment : :decrement
|
17
|
+
redis_objects[name.to_sym] = options.merge(type: :counter)
|
18
|
+
|
19
|
+
mod = Module.new do
|
20
|
+
define_method(name) do
|
21
|
+
Redis::AnnualCounter.new(
|
22
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:global]
|
28
|
+
extend mod
|
29
|
+
|
30
|
+
# dispatch to class methods
|
31
|
+
define_method(name) do
|
32
|
+
self.class.public_send(name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
include mod
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/daily_counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module DailyCounters
|
7
|
+
class << self
|
8
|
+
def included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def daily_counter(name, options = {}) # rubocop:disable Metrics/MethodLength
|
15
|
+
options[:start] ||= 0
|
16
|
+
options[:type] ||= (options[:start]).zero? ? :increment : :decrement
|
17
|
+
redis_objects[name.to_sym] = options.merge(type: :counter)
|
18
|
+
|
19
|
+
mod = Module.new do
|
20
|
+
define_method(name) do
|
21
|
+
Redis::DailyCounter.new(
|
22
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:global]
|
28
|
+
extend mod
|
29
|
+
|
30
|
+
# dispatch to class methods
|
31
|
+
define_method(name) do
|
32
|
+
self.class.public_send(name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
include mod
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/hourly_counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module HourlyCounters
|
7
|
+
class << self
|
8
|
+
def included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def hourly_counter(name, options = {}) # rubocop:disable Metrics/MethodLength
|
15
|
+
options[:start] ||= 0
|
16
|
+
options[:type] ||= (options[:start]).zero? ? :increment : :decrement
|
17
|
+
redis_objects[name.to_sym] = options.merge(type: :counter)
|
18
|
+
|
19
|
+
mod = Module.new do
|
20
|
+
define_method(name) do
|
21
|
+
Redis::HourlyCounter.new(
|
22
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:global]
|
28
|
+
extend mod
|
29
|
+
|
30
|
+
# dispatch to class methods
|
31
|
+
define_method(name) do
|
32
|
+
self.class.public_send(name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
include mod
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/minutely_counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module MinutelyCounters
|
7
|
+
class << self
|
8
|
+
def included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def minutely_counter(name, options = {}) # rubocop:disable Metrics/MethodLength
|
15
|
+
options[:start] ||= 0
|
16
|
+
options[:type] ||= (options[:start]).zero? ? :increment : :decrement
|
17
|
+
redis_objects[name.to_sym] = options.merge(type: :counter)
|
18
|
+
|
19
|
+
mod = Module.new do
|
20
|
+
define_method(name) do
|
21
|
+
Redis::MinutelyCounter.new(
|
22
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:global]
|
28
|
+
extend mod
|
29
|
+
|
30
|
+
# dispatch to class methods
|
31
|
+
define_method(name) do
|
32
|
+
self.class.public_send(name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
include mod
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/daily_counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module MonthlyCounters
|
7
|
+
class << self
|
8
|
+
def included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def monthly_counter(name, options = {}) # rubocop:disable Metrics/MethodLength
|
15
|
+
options[:start] ||= 0
|
16
|
+
options[:type] ||= (options[:start]).zero? ? :increment : :decrement
|
17
|
+
redis_objects[name.to_sym] = options.merge(type: :counter)
|
18
|
+
|
19
|
+
mod = Module.new do
|
20
|
+
define_method(name) do
|
21
|
+
Redis::MonthlyCounter.new(
|
22
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:global]
|
28
|
+
extend mod
|
29
|
+
|
30
|
+
# dispatch to class methods
|
31
|
+
define_method(name) do
|
32
|
+
self.class.public_send(name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
include mod
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis/weekly_counter'
|
4
|
+
class Redis
|
5
|
+
module Objects
|
6
|
+
module WeeklyCounters
|
7
|
+
class << self
|
8
|
+
def included(klass)
|
9
|
+
klass.extend ClassMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def weekly_counter(name, options = {}) # rubocop:disable Metrics/MethodLength
|
15
|
+
options[:start] ||= 0
|
16
|
+
options[:type] ||= (options[:start]).zero? ? :increment : :decrement
|
17
|
+
redis_objects[name.to_sym] = options.merge(type: :counter)
|
18
|
+
|
19
|
+
mod = Module.new do
|
20
|
+
define_method(name) do
|
21
|
+
Redis::WeeklyCounter.new(
|
22
|
+
redis_field_key(name), redis_field_redis(name), redis_options(name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:global]
|
28
|
+
extend mod
|
29
|
+
|
30
|
+
# dispatch to class methods
|
31
|
+
define_method(name) do
|
32
|
+
self.class.public_send(name)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
include mod
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "#{File.dirname(__FILE__)}/base_counter_object"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class WeeklyCounter < BaseCounterObject
|
7
|
+
private
|
8
|
+
|
9
|
+
def redis_daily_field_key(date_or_time)
|
10
|
+
date_key = date_or_time.strftime('%YW%W')
|
11
|
+
[original_key, date_key].flatten.join(':')
|
12
|
+
end
|
13
|
+
|
14
|
+
def next_key(date, length)
|
15
|
+
date + 7 * (length - 1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis-objects'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
autoload :DailyCounter, 'redis/daily_counter'
|
7
|
+
autoload :WeeklyCounter, 'redis/weekly_counter'
|
8
|
+
autoload :MonthlyCounter, 'redis/monthly_counter'
|
9
|
+
autoload :AnnualCounter, 'redis/annual_counter'
|
10
|
+
autoload :HourlyCounter, 'redis/hourly_counter'
|
11
|
+
autoload :MinutelyCounter, 'redis/minutely_counter'
|
12
|
+
|
13
|
+
module Objects
|
14
|
+
autoload :DailyCounters, 'redis/objects/daily_counters'
|
15
|
+
autoload :WeeklyCounters, 'redis/objects/weekly_counters'
|
16
|
+
autoload :MonthlyCounters, 'redis/objects/monthly_counters'
|
17
|
+
autoload :AnnualCounters, 'redis/objects/annual_counters'
|
18
|
+
autoload :HourlyCounters, 'redis/objects/hourly_counters'
|
19
|
+
autoload :MinutelyCounters, 'redis/objects/minutely_counters'
|
20
|
+
|
21
|
+
class << self
|
22
|
+
alias original_included included
|
23
|
+
|
24
|
+
def included(klass)
|
25
|
+
original_included(klass)
|
26
|
+
|
27
|
+
# Pull in each object type
|
28
|
+
klass.send :include, Redis::Objects::DailyCounters
|
29
|
+
klass.send :include, Redis::Objects::WeeklyCounters
|
30
|
+
klass.send :include, Redis::Objects::MonthlyCounters
|
31
|
+
klass.send :include, Redis::Objects::AnnualCounters
|
32
|
+
klass.send :include, Redis::Objects::HourlyCounters
|
33
|
+
klass.send :include, Redis::Objects::MinutelyCounters
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|