tally 1.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dcdb5d7466c778e0cff8dde7fd611b583964f98e873785a96c50d7a673fadd9
4
- data.tar.gz: 2694b8855c48dfe6186c738ccb0a6634d085b5e6c87e92c77ea3087027c58e0c
3
+ metadata.gz: a1c7ca6dc7f3a11bd24936a64edb11c8b18a21ef0788b09598a45a5eb746237d
4
+ data.tar.gz: 112af234512f74e6e284ed8f210084c586bc7eb3b36a7f1c1c12a9d21f373403
5
5
  SHA512:
6
- metadata.gz: 013706a817cd8a8baab90080ee92d509db1276d10fa29ca9ae06463646f87d58ccb723f99586e55506388c40f038b3818473daf5cda5be46e8b03eeb1725320e
7
- data.tar.gz: 0f1dff1e52bb51039ea6202a46717c67f81628fde77d98c135dbb1c450ee99d5211c410d0155c09456e403946204a690363abe21a3a164e374905b3de2a3992b
6
+ metadata.gz: 61eb9b0fc574bfcbc23585cdef758bde1b9f9e57afcff608b7b99131ce6b847dedd051d7d358fcc22287547a473da4f75ad3b1e4ece5951ad23dcbc58e253e7e
7
+ data.tar.gz: 455f9248a7f3427ef635209b806e7b69a17dace683821621b47bcd0608f3efa9d4257575c86fcb9bfad2517d768a7cd47b80c0f6312b25fe1c156f54088130f5
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Tally
2
2
 
3
- [![CircleCI](https://circleci.com/gh/jdtornow/tally.svg?style=svg)](https://circleci.com/gh/jdtornow/tally)
3
+ [![Tests](https://github.com/jdtornow/tally/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/jdtornow/tally/actions/workflows/tests.yml)
4
4
 
5
5
  Tally is a simple Rails engine for capturing counts of various activities around an app. These counts are quickly captured in Redis then are archived periodically within the app's default relational database.
6
6
 
@@ -12,8 +12,8 @@ _[Read more about Tally in my blog post introducing it ...](https://johntornow.c
12
12
 
13
13
  ## Requirements
14
14
 
15
- * Ruby 2.2+
16
- * Rails 5.2.x+
15
+ * Ruby 3.2+
16
+ * Rails 7+
17
17
  * Redis 4+
18
18
 
19
19
  ## Installation
@@ -204,10 +204,31 @@ The endpoints can be used to display JSON-formatted data from `Tally::Record`. T
204
204
 
205
205
  Tally works _really_ well with [Sidekiq](https://github.com/mperham/sidekiq/), but it isn't required. If Sidekiq is installed in your app, Tally will use its connection pooling for Redis connections. If Sidekiq isn't in use, the `Redis.current` connection is used to store stats. If you'd like to override the specific connection used for Tally's redis store, you can do so by setting `Tally.redis_connection` to another instance of `Redis`. This can be useful to use an alternate Redis store for just stats, for example.
206
206
 
207
+ As of version 2.0.0 this connection is automatically used within a [ConnectionPool](https://github.com/mperham/connection_pool).
208
+
207
209
  ```ruby
208
210
  # use an alternate Redis connection (for non-sidekiq integrations)
209
211
  Tally.redis_connection = Redis.new(...)
210
212
  ```
213
+
214
+ Alternatively, you can just set the config for Redis and `Redis.new` will be called for you:
215
+
216
+ ```ruby
217
+ # provide Redis config
218
+ Tally.config.redis_config = {
219
+ driver: :ruby,
220
+ url: "redis://127.0.0.1:6379/10",
221
+ ssl_params: {
222
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
223
+ }
224
+ }
225
+
226
+ # then Tally uses the connection within a pool:
227
+ Tally.redis do |connection|
228
+ connection.incr("test")
229
+ end
230
+ ```
231
+
211
232
  ## Issues
212
233
 
213
234
  If you have any issues or find bugs running Tally, please [report them on Github](https://github.com/jdtornow/tally/issues).
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tally
4
+ class ArchiverJob < ApplicationJob
5
+
6
+ def perform(day = "today")
7
+ case day.to_s
8
+ when /^\d{4}-\d{2}-\d{2}$/
9
+ Tally::Archiver.archive! day: Date.parse(day)
10
+ when "yesterday"
11
+ Tally::Archiver.archive! day: 1.day.ago.to_date
12
+ else
13
+ Tally::Archiver.archive!
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tally
4
+ class SweeperJob < ApplicationJob
5
+
6
+ def perform
7
+ Tally::Sweeper.sweep!
8
+ end
9
+
10
+ end
11
+ end
data/lib/tally/daily.rb CHANGED
@@ -10,26 +10,6 @@ module Tally
10
10
 
11
11
  private
12
12
 
13
- def all_keys
14
- @keys ||= build_keys_from_redis
15
- end
16
-
17
- def build_keys_from_redis
18
- result = []
19
- cursor = ""
20
-
21
- scan = scan_from_redis
22
-
23
- while cursor != "0"
24
- result << scan.last
25
- cursor = scan.first
26
-
27
- scan = scan_from_redis(cursor: cursor)
28
- end
29
-
30
- result.flatten
31
- end
32
-
33
13
  def day_key
34
14
  @day_key ||= day.strftime(Tally.config.date_format)
35
15
  end
@@ -39,8 +19,20 @@ module Tally
39
19
  end
40
20
 
41
21
  def scan_from_redis(cursor: "0")
42
- Tally.redis do |conn|
43
- conn.sscan(daily_key, cursor, match: scan_key, count: 25)
22
+ klass = Tally.redis { |conn| conn.class.to_s }
23
+
24
+ # if we're using sidekiq / RedisClient, scan needs a block, and doesn't worry about the cursor
25
+ if klass == "Sidekiq::RedisClientAdapter::CompatClient"
26
+ Tally.redis do |conn|
27
+ [
28
+ "0", # fake cursor to match redis-rb output
29
+ conn.sscan(daily_key, "MATCH", scan_key, "COUNT", 25).to_a
30
+ ]
31
+ end
32
+ else
33
+ Tally.redis do |conn|
34
+ conn.sscan(daily_key, cursor, match: scan_key, count: 25)
35
+ end
44
36
  end
45
37
  end
46
38
 
@@ -7,10 +7,10 @@ module Tally
7
7
  Tally.redis do |conn|
8
8
  conn.multi do |pipeline|
9
9
  pipeline.incrby(redis_key, by)
10
- pipeline.expire(redis_key, Tally.config.ttl) if Tally.config.ttl.present?
10
+ pipeline.expire(redis_key, Tally.config.ttl.to_i) if Tally.config.ttl.present?
11
11
 
12
12
  pipeline.sadd(daily_key, simple_key)
13
- pipeline.expire(daily_key, Tally.config.ttl) if Tally.config.ttl.present?
13
+ pipeline.expire(daily_key, Tally.config.ttl.to_i) if Tally.config.ttl.present?
14
14
  end
15
15
  end
16
16
  end
@@ -73,18 +73,16 @@ module Tally
73
73
 
74
74
  def build_keys_from_redis
75
75
  result = []
76
- cursor = ""
77
-
78
- scan = scan_from_redis
76
+ cursor = nil
79
77
 
80
78
  while cursor != "0"
79
+ scan = scan_from_redis(cursor: cursor.presence || "0")
80
+
81
81
  result << scan.last
82
82
  cursor = scan.first
83
-
84
- scan = scan_from_redis(cursor: cursor)
85
83
  end
86
84
 
87
- result.flatten
85
+ result.flatten.uniq
88
86
  end
89
87
 
90
88
  def day_key
@@ -100,7 +98,19 @@ module Tally
100
98
  end
101
99
 
102
100
  def scan_from_redis(cursor: "0")
103
- Tally.redis { |conn| conn.scan(cursor, match: scan_key) }
101
+ klass = Tally.redis { |conn| conn.class.to_s }
102
+
103
+ # if we're using sidekiq / RedisClient, scan needs a block, and doesn't worry about the cursor
104
+ if klass == "Sidekiq::RedisClientAdapter::CompatClient"
105
+ Tally.redis do |conn|
106
+ [
107
+ "0", # fake cursor to match redis-rb output
108
+ conn.scan("MATCH", scan_key).to_a
109
+ ]
110
+ end
111
+ else
112
+ Tally.redis { |conn| conn.scan(cursor, match: scan_key) }
113
+ end
104
114
  end
105
115
 
106
116
  def scan_key
data/lib/tally/sweeper.rb CHANGED
@@ -22,7 +22,7 @@ module Tally
22
22
 
23
23
  def sweep!
24
24
  Tally.redis do |conn|
25
- purgeable_keys.in_groups_of(25, fill_with = nil).each do |group|
25
+ purgeable_keys.in_groups_of(25, fill_with = false).each do |group|
26
26
  conn.pipelined do |pipeline|
27
27
  group.each do |key|
28
28
  pipeline.del(key)
data/lib/tally/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Tally
2
2
 
3
- VERSION = "1.0.2"
3
+ VERSION = "2.1.0"
4
4
 
5
5
  end
data/lib/tally.rb CHANGED
@@ -46,6 +46,12 @@ module Tally
46
46
  # Archivers get queued into the background with ActiveJob by default
47
47
  # Set to :now to run inline
48
48
  config.perform_calculators = :later
49
+
50
+ # override to set default redis configuration for Tally when Sidekiq is not present
51
+ config.redis_config = {}
52
+
53
+ # override to set connection pool details for Redis
54
+ config.redis_pool_config = {}
49
55
  end
50
56
 
51
57
  # If sidekiq is available, piggyback on its pooling
@@ -57,18 +63,28 @@ module Tally
57
63
  if defined?(Sidekiq)
58
64
  Sidekiq.redis(&block)
59
65
  else
60
- block.call(redis_connection)
66
+ redis_pool.with(&block)
61
67
  end
62
68
  end
63
69
 
64
70
  def self.redis_connection
65
- @redis_connection ||= Redis.current
71
+ @redis_connection ||= Redis.new(Tally.config.redis_config)
66
72
  end
67
73
 
68
74
  def self.redis_connection=(connection)
69
75
  @redis_connection = connection
70
76
  end
71
77
 
78
+ def self.redis_pool
79
+ @redis_pool ||= ConnectionPool.new(Tally.config.redis_pool_config) do
80
+ redis_connection
81
+ end
82
+ end
83
+
84
+ def self.redis_pool=(pool)
85
+ @redis_pool = pool
86
+ end
87
+
72
88
  def self.increment(*args)
73
89
  Increment.public_send(:increment, *args)
74
90
  end
@@ -1,21 +1,21 @@
1
1
  namespace :tally do
2
2
  desc "Sweep all outdated keys from the data store"
3
- task :sweep => :environment do
4
- Tally::Sweeper.sweep!
3
+ task sweep: :environment do
4
+ Tally::SweeperJob.perform_now
5
5
 
6
6
  Rake::Task["tally:wait_for_async_queue"].execute
7
7
  end
8
8
 
9
9
  desc "Archive today's temporary keys into record entries in the database"
10
- task :archive => :environment do
11
- Tally::Archiver.archive!
10
+ task archive: :environment do
11
+ Tally::ArchiverJob.perform_now
12
12
 
13
13
  Rake::Task["tally:wait_for_async_queue"].execute
14
14
  end
15
15
 
16
16
  desc "Archive yesterday's temporary keys into record entries in the database"
17
- task "archive:yesterday" => :environment do
18
- Tally::Archiver.archive! day: 1.day.ago
17
+ task "archive:yesterday": :environment do
18
+ Tally::ArchiverJob.perform_now("yesterday")
19
19
 
20
20
  Rake::Task["tally:wait_for_async_queue"].execute
21
21
  end
@@ -24,7 +24,7 @@ namespace :tally do
24
24
  #
25
25
  # This is not needed for other adapters besides async, but since it is the
26
26
  # Rails default, we're accounting for it here.
27
- task :wait_for_async_queue => :environment do
27
+ task wait_for_async_queue: :environment do
28
28
  if Rails.application.config.active_job.queue_adapter == :async
29
29
  ActiveJob::Base.queue_adapter.shutdown(wait: true)
30
30
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tally
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John D. Tornow
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-04-12 00:00:00.000000000 Z
10
+ date: 2025-01-21 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -19,7 +18,7 @@ dependencies:
19
18
  version: 5.2.0
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
- version: '8'
21
+ version: '9'
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +28,7 @@ dependencies:
29
28
  version: 5.2.0
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
- version: '8'
31
+ version: '9'
33
32
  - !ruby/object:Gem::Dependency
34
33
  name: redis
35
34
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +43,20 @@ dependencies:
44
43
  - - ">="
45
44
  - !ruby/object:Gem::Version
46
45
  version: '4.1'
46
+ - !ruby/object:Gem::Dependency
47
+ name: connection_pool
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '2.0'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
47
60
  - !ruby/object:Gem::Dependency
48
61
  name: kaminari-activerecord
49
62
  requirement: !ruby/object:Gem::Requirement
@@ -78,14 +91,14 @@ dependencies:
78
91
  requirements:
79
92
  - - "~>"
80
93
  - !ruby/object:Gem::Version
81
- version: '5.1'
94
+ version: '6'
82
95
  type: :development
83
96
  prerelease: false
84
97
  version_requirements: !ruby/object:Gem::Requirement
85
98
  requirements:
86
99
  - - "~>"
87
100
  - !ruby/object:Gem::Version
88
- version: '5.1'
101
+ version: '6'
89
102
  - !ruby/object:Gem::Dependency
90
103
  name: factory_bot_rails
91
104
  requirement: !ruby/object:Gem::Requirement
@@ -114,20 +127,6 @@ dependencies:
114
127
  - - "~>"
115
128
  - !ruby/object:Gem::Version
116
129
  version: '5.1'
117
- - !ruby/object:Gem::Dependency
118
- name: simplecov
119
- requirement: !ruby/object:Gem::Requirement
120
- requirements:
121
- - - "~>"
122
- - !ruby/object:Gem::Version
123
- version: '0.11'
124
- type: :development
125
- prerelease: false
126
- version_requirements: !ruby/object:Gem::Requirement
127
- requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '0.11'
131
130
  - !ruby/object:Gem::Dependency
132
131
  name: rspec_junit_formatter
133
132
  requirement: !ruby/object:Gem::Requirement
@@ -191,7 +190,9 @@ files:
191
190
  - app/helpers/tally/application_helper.rb
192
191
  - app/helpers/tally/increment_helper.rb
193
192
  - app/jobs/tally/application_job.rb
193
+ - app/jobs/tally/archiver_job.rb
194
194
  - app/jobs/tally/calculator_runner_job.rb
195
+ - app/jobs/tally/sweeper_job.rb
195
196
  - app/models/tally/application_record.rb
196
197
  - app/models/tally/record.rb
197
198
  - app/presenters/tally/record_presenter.rb
@@ -222,7 +223,6 @@ metadata:
222
223
  homepage_uri: https://github.com/jdtornow/tally
223
224
  source_code_uri: https://github.com/jdtornow/tally
224
225
  wiki_uri: https://github.com/jdtornow/tally/wiki
225
- post_install_message:
226
226
  rdoc_options: []
227
227
  require_paths:
228
228
  - lib
@@ -230,15 +230,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
230
230
  requirements:
231
231
  - - ">="
232
232
  - !ruby/object:Gem::Version
233
- version: 2.5.7
233
+ version: 3.0.3
234
234
  required_rubygems_version: !ruby/object:Gem::Requirement
235
235
  requirements:
236
236
  - - ">="
237
237
  - !ruby/object:Gem::Version
238
238
  version: 1.8.11
239
239
  requirements: []
240
- rubygems_version: 3.2.32
241
- signing_key:
240
+ rubygems_version: 3.6.3
242
241
  specification_version: 4
243
242
  summary: Stats collection and reporting
244
243
  test_files: []