Skip to content

Commit

Permalink
Better strategy for handling decrement
Browse files Browse the repository at this point in the history
  • Loading branch information
Michaelvilleneuve committed Dec 10, 2023
1 parent 280d689 commit 290b142
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
statisfy (0.0.3)
statisfy (0.0.4)

GEM
remote: https://rubygems.org/
Expand Down
58 changes: 50 additions & 8 deletions lib/statisfy/counter.rb
Expand Up @@ -39,7 +39,7 @@ def count(args = {})
# but the `count` DSL defines them automatically based on the options provided
#
def apply_default_counter_options(args)
define_method(:identifier, args[:uniq_by] || -> { params["id"] })
define_method(:identifier, args[:uniq_by] || -> { nil })
define_method(:scopes, args[:scopes] || Statisfy.configuration.default_scopes || -> { [] })
define_method(:if_async, args[:if_async] || -> { true })
define_method(:decrement?, args[:decrement_if] || -> { false })
Expand All @@ -66,10 +66,18 @@ def value(scope: nil, month: nil)
if const_get(:COUNTER_TYPE) == :aggregate
average(scope:, month:)
else
elements_in(scope:, month:).uniq.size
size(scope:, month:)
end
end

def size(scope: nil, month: nil)
redis_client.scard(key_for(scope:, month:))
end

def members(scope: nil, month: nil)
redis_client.smembers(key_for(scope:, month:))
end

#
# Returns the list of elements in the set (in case you use .append and not .increment)
#
Expand Down Expand Up @@ -102,12 +110,13 @@ def average(scope: nil, month: nil)
#
# This is the name of the Redis key that will be used to store the counter
#
def key_for(scope:, month: nil)
def key_for(scope:, month: nil, key_value: nil)
{
counter: name.demodulize.underscore,
month:,
scope_type: scope&.class&.name,
scope_id: scope&.id
scope_id: scope&.id,
key_value:
}.to_json
end

Expand Down Expand Up @@ -171,11 +180,15 @@ def process_event
return if destroy_event_handled?
return unless if_async

decrement? ? decrement : increment
if value.present?
append(value:)
else
decrement? ? decrement : increment
end
end

def destroy_event_handled?
return false unless params[:statisfy_trigger] == :destroy
return false unless params[:statisfy_trigger] == :destroy && value.blank?

if decrement_on_destroy?
decrement
Expand All @@ -188,6 +201,14 @@ def destroy_event_handled?
false
end

def key_value
value || identifier || params["id"]
end

def custom_key_value?
identifier.present? || value.present?
end

#
# This allows to iterate over all the counters that need to be updated
# (in general the Department(s) and Organisation(s) for both the current month and the global counter)
Expand All @@ -202,15 +223,36 @@ def all_counters

def increment
all_counters do |key|
self.class.redis_client.rpush(key, value || identifier)
self.class.redis_client.sadd?(key, key_value)
self.class.redis_client.sadd?(uniq_by_ids(key), params["id"]) if custom_key_value?
end
end

#
# To be used to store a list of values instead of a basic counter
#
def append(value:)
all_counters do |key|
self.class.redis_client.rpush(key, value)
end
end

# rubocop:disable Metrics/AbcSize
def decrement
all_counters do |key|
self.class.redis_client.lrem(key, 1, value || identifier)
if custom_key_value?
self.class.redis_client.srem?(uniq_by_ids(key), params["id"])
self.class.redis_client.srem?(key, key_value) if self.class.redis_client.scard(uniq_by_ids(key)).zero?
else
self.class.redis_client.srem?(key, key_value)
end
end
end
# rubocop:enable Metrics/AbcSize

def uniq_by_ids(key)
"#{key};#{key_value}"
end
end
end

Expand Down
Binary file added statisfy-0.0.4.gem
Binary file not shown.
2 changes: 1 addition & 1 deletion statisfy.gemspec
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |s|
s.name = "statisfy"
s.version = "0.0.3"
s.version = "0.0.4"
s.required_ruby_version = ">= 3.2.0"
s.date = "2023-12-07"
s.summary = "A performant and flexible counter solution"
Expand Down
18 changes: 18 additions & 0 deletions test/statisfy_test.rb
Expand Up @@ -229,4 +229,22 @@ class UserCounter
User.last.destroy
assert_equal UserCounter.value, 0
end

test "decrement_on_destroy works with custom uniq uniq_by" do
class OrganisationsWithUsersCounter
include Statisfy::Counter

count every: :user_created, decrement_on_destroy: true, uniq_by: -> { user.organisation_id }
end

User.create!(organisation_id: 8)
User.create!(organisation_id: 8)
User.create!(organisation_id: 2)

assert_equal OrganisationsWithUsersCounter.value, 2
User.where(organisation_id: 8).first.destroy
assert_equal OrganisationsWithUsersCounter.value, 2
User.where(organisation_id: 8).last.destroy
assert_equal OrganisationsWithUsersCounter.value, 1
end
end

0 comments on commit 290b142

Please sign in to comment.