Module: Statisfy::Counter::ClassMethods

Defined in:
lib/statisfy/counter.rb

Instance Method Summary collapse

Instance Method Details

#aggregate_counter?Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/statisfy/counter.rb', line 69

def aggregate_counter?
  const_get(:COUNTER_TYPE) == :aggregate
end

#all_keys(scope: nil, month: nil) ⇒ Object

Returns the list of all the keys of this counter for a given scope (optional) and a given month (optional)



144
145
146
147
148
149
150
151
152
153
# File 'lib/statisfy/counter.rb', line 144

def all_keys(scope: nil, month: nil)
  redis_client.keys("*\"counter\":\"#{name.demodulize.underscore}\"*").filter do |json|
    key = JSON.parse(json)

    scope_matches = scope.nil? || (key["scope_type"] == scope.class.name && key["scope_id"] == scope.id)
    month_matches = month.nil? || key["month"] == month

    scope_matches && month_matches
  end
end

#apply_default_counter_options(args) ⇒ Object

This method serves as a syntactic sugar The below methods could be written directly in the class definition but the ‘count` DSL defines them automatically based on the options provided



41
42
43
44
45
46
47
48
# File 'lib/statisfy/counter.rb', line 41

def apply_default_counter_options(args)
  define_method(:identifier, args[:value] || 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 })
  define_method(:should_run?, args[:if] || -> { true })
  define_method(:decrement_on_destroy?, -> { args[:decrement_on_destroy] != false })
end

#average(scope: nil, month: nil) ⇒ Object

Returns the average of the elements in the set Example: append(value: 1) append(value: 2) average

> 1.5



103
104
105
106
107
108
# File 'lib/statisfy/counter.rb', line 103

def average(scope: nil, month: nil)
  stored_values = elements_in(scope:, month:)
  return 0 if stored_values.empty?

  stored_values.map(&:to_i).reduce(:+) / stored_values.length.to_f
end

#count(args = {}) ⇒ Object

This is a DSL method that helps you define a counter It will create a method that will be called when the event is triggered It will also create a method that will be called when you want to get the value of the counter

Parameters:

  • every:

    the event(s) that will trigger the counter

  • type:

    by default it increments, but you can also use :average

  • if:

    a block that returns a condition that must be met for the counter to be incremented (optional)

  • if_async:

    same as if option but runs async to avoid slowing down inserts and updates (optional)

  • uniq_by:

    a block to get the identifier of the element to be counted (optional)

  • scopes:

    a block to get the list of scopes for which the counter must be incremented (optional)

Raises:

  • (ArgumentError)


27
28
29
30
31
32
33
34
# File 'lib/statisfy/counter.rb', line 27

def count(args = {})
  raise ArgumentError, "You must provide at least one event" if args[:every].blank?

  catch_events(*args[:every])
  apply_default_counter_options(args)
  const_set(:COUNTER_TYPE, args[:type] || :increment)
  class_eval(&Statisfy.configuration.append_to_counters) if Statisfy.configuration.append_to_counters.present?
end

#elements_in(scope: nil, month: nil) ⇒ Object

Returns the list of elements in the set (in case you use .append and not .increment)



84
85
86
# File 'lib/statisfy/counter.rb', line 84

def elements_in(scope: nil, month: nil)
  redis_client.lrange(key_for(scope:, month:), 0, -1)
end

#key_for(scope:, month: nil, key_value: nil) ⇒ Object

This is the name of the Redis key that will be used to store the counter



113
114
115
116
117
118
119
120
121
# File 'lib/statisfy/counter.rb', line 113

def key_for(scope:, month: nil, key_value: nil)
  {
    counter: name.demodulize.underscore,
    month:,
    scope_type: scope&.class&.name,
    scope_id: scope&.id,
    key_value:
  }.to_json
end

#members(scope: nil, month: nil) ⇒ Object



77
78
79
# File 'lib/statisfy/counter.rb', line 77

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

#redis_clientObject



123
124
125
# File 'lib/statisfy/counter.rb', line 123

def redis_client
  Statisfy.configuration.redis_client
end

#reset(scope: nil, month: nil) ⇒ Object

This allows to reset all the counters for a given scope (optional) and a given month (optional)



160
161
162
163
164
165
166
# File 'lib/statisfy/counter.rb', line 160

def reset(scope: nil, month: nil)
  all_keys(scope:, month:).each do |key|
    redis_client.del(key)
  end

  true
end

#size(scope: nil, month: nil) ⇒ Object



73
74
75
# File 'lib/statisfy/counter.rb', line 73

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

#sum(scope: nil, month: nil) ⇒ Object



88
89
90
91
92
93
# File 'lib/statisfy/counter.rb', line 88

def sum(scope: nil, month: nil)
  stored_values = elements_in(scope:, month:)
  return 0 if stored_values.empty?

  stored_values.map(&:to_i).reduce(:+)
end

#trigger_with(resource, options = {}) ⇒ Object

This allows to run a counter increment manually It is useful when you want to backfill counters



131
132
133
134
135
136
137
138
# File 'lib/statisfy/counter.rb', line 131

def trigger_with(resource, options = {})
  counter = new
  counter.params = resource

  return unless options[:skip_validation] || counter.should_run?

  counter.perform(resource)
end

#value(scope: nil, month: nil) ⇒ Object

This is the method that is called when you want to get the value of a counter.

By default it returns the number of elements in the set. You can override it if the counter requires more complex logic see RateOfAutonomousUsers for example

Parameters:

  • scope: (defaults to: nil)

    the scope of the counter (an Organisation or a Department)

  • month: (defaults to: nil)

    the month for which you want the value of the counter (optional)



60
61
62
63
64
65
66
67
# File 'lib/statisfy/counter.rb', line 60

def value(scope: nil, month: nil)
  month = month&.strftime("%Y-%m") if month.present?
  if const_get(:COUNTER_TYPE) == :aggregate
    average(scope:, month:)
  else
    size(scope:, month:)
  end
end