class TrafficJam::RollingLimit

This class represents a rolling limit on an action, value pair. For example, if limiting the amount of money a user can transfer in a week, the action could be :transfers and the value would be the user ID. The class exposes atomic increment operations and allows querying of the current amount used and amount remaining.

This class also handles 0 for period, where 0 is no period (each request is compared to the max).

This class departs from the design of Limit by tracking a sum of the actions in a second, in a hash keyed by the timestamp. Therefore, this limit can put a lot of data size pressure on the Redis storage, so use it wisely.

Public Class Methods

new(action, value, max: nil, period: nil) click to toggle source

Constructor takes an action name as a symbol, a maximum cap, and the period of limit. max and period are required keyword arguments.

@param action [Symbol] action name @param value [String] limit target value @param max [Integer] required limit maximum @param period [Integer] required limit period in seconds @raise [ArgumentError] if max or period is nil

Calls superclass method TrafficJam::Limit::new
# File lib/traffic_jam/rolling_limit.rb, line 25
def initialize(action, value, max: nil, period: nil)
  super(action, value, max: max, period: period)
end

Public Instance Methods

increment(amount = 1, time: Time.now) click to toggle source

Increment the amount used by the given number. Rolls back the increment if the operation exceeds the limit. Returns whether the operation was successful. Time of increment can be specified optionally with a keyword argument, which is not really useful since it be undone by used.

@param amount [Integer] amount to increment by @param time [Time] time when increment occurs (ignored) @return [Boolean] true if increment succeded and false if incrementing

would exceed the limit
# File lib/traffic_jam/rolling_limit.rb, line 38
def increment(amount = 1, time: Time.now)
  raise ArgumentError, 'Amount must be an integer' if amount != amount.to_i
  return amount <= 0 if max.zero?
  return amount <= max if period.zero?
  return true if amount.zero?
  return false if amount > max

  !run_incr([time.to_i, amount.to_i, max, period]).nil?
end
used() click to toggle source

Return amount of limit used

@return [Integer] amount used

# File lib/traffic_jam/rolling_limit.rb, line 51
def used
  return 0 if max.zero? || period.zero?
  [sum, max].min
end

Private Instance Methods

clear_before() click to toggle source
# File lib/traffic_jam/rolling_limit.rb, line 62
def clear_before
  Time.now.to_i - period
end
run_incr(argv) click to toggle source
# File lib/traffic_jam/rolling_limit.rb, line 73
def run_incr(argv)
  redis.evalsha(
    Scripts::INCREMENT_ROLLING_HASH, keys: [key], argv: argv
  )
rescue Redis::CommandError => error
  raise error if /ERR Error running script/ =~ error.message
  redis.eval(Scripts::INCREMENT_ROLLING, keys: [key], argv: argv)
end
run_sum(argv) click to toggle source
# File lib/traffic_jam/rolling_limit.rb, line 66
def run_sum(argv)
  redis.evalsha(Scripts::SUM_ROLLING_HASH, keys: [key], argv: argv)
rescue Redis::CommandError => error
  raise error if /ERR Error running script/ =~ error.message
  redis.eval(Scripts::SUM_ROLLING, keys: [key], argv: argv)
end
sum() click to toggle source
# File lib/traffic_jam/rolling_limit.rb, line 58
def sum
  run_sum([Time.now.to_i, period])
end