tally 1.0.2 → 2.1.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.
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: []