Skip to content
This repository has been archived by the owner on Jan 20, 2020. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
Document with YARD and bump version to 1.0.0.
  • Loading branch information
Jim Posen committed May 5, 2015
1 parent 4895266 commit ec62b76
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 77 deletions.
4 changes: 2 additions & 2 deletions Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
traffic_jam (0.1.0)
traffic_jam (1.0.0)
redis (~> 3.0)

GEM
Expand All @@ -19,7 +19,7 @@ GEM
multi_json (1.10.1)
netrc (0.10.3)
rake (10.4.2)
redis (3.2.0)
redis (3.2.1)
rest-client (1.7.3)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
Expand Down
75 changes: 1 addition & 74 deletions README.md
Expand Up @@ -25,6 +25,7 @@ limit = TrafficJam::Limit.new(
limit.increment # => true
limit.increment(2) # => true
limit.increment # => false
limit.increment! # => raises TrafficJam::LimitExceededError

sleep 1

Expand All @@ -36,80 +37,6 @@ limit.used # => 2
limit.remaining # => 1
```

### TrafficJam::Limit

**initialize(action, value, max: *cap*, period: *period in seconds*)**

Constructor for `TrafficJam::Limit` takes an action name as a symbol, an integer maximum cap, and the period of limit in seconds. `max` and `period` are required keyword arguments. The value should be a string or convertible to a distinct string when `to_s` is called. If you would like to use objects that can be converted to a unique string, like a database-mapped object, you can implement `to_rate_limit_value` on the object, which returns a deterministic string unique to that object.

**increment(amount = 1)**

Increment the amount used by the given number. Returns true if increment succeded and false if incrementing would exceed the limit.

**decrement(amount = 1)**

Decrement the amount used by the given number. Will never decrement below 0. Always returns true.

**increment!(amount = 1)**

Increment the amount used by the given number. Raises `TrafficJam::LimitExceededError` if incrementing would exceed the limit.

**exceeded?(amount = 1)**

Return whether incrementing by the given amount would exceed limit. Does not change amount used.

**reset**

Sets amount used to 0.

**used**

Return current amount used.

**remaining**

Return current amount remaining.

**TrafficJam.reset_all(action: nil)**

Reset all limits associated with the given action. If action is omitted or nil, this will reset all limits. *Warning: Not to be used in production.*

### TrafficJam::LimitGroup

A limit group is a way of enforcing a cap over a set of limits with the guarantee that either all limits will be incremented or none. This is useful if you must check multiple limits before allowing an action to be taken.

**initialize(\*limits)**

Constructor for `TrafficJam::Limit` takes either an array or splat of limits or other limit groups.

**increment(amount = 1)**

Increment the limits by the given number. Returns true if all increments are successful and false if incrementing would exceed any rate limit.

**decrement(amount = 1)**

Decrement the limits by the given number. Will never decrement below 0. Always returns true.

**increment!(amount = 1)**

Increment the limits by the given number. Raises `TrafficJam::LimitExceededError` if incrementing would exceed the limit. Will not increment if error is raised.

**exceeded?(amount = 1)**

Return whether incrementing by the given amount would exceed any limit. Does not change amount used.

**limit_exceeded(amount = 1)**

Return the first `TrafficJam::Limit` that would be exceeded by this amount or `nil` if incrementing would be safe.

**reset**

Sets limits to 0.

**remaining**

Return minimum amount remaining of all limits.

## Configuration

TrafficJam configuration object can be accessed with `TrafficJam.config` or in a block like `TrafficJam.configure { |config| ... }`. Configuration options are:
Expand Down
15 changes: 15 additions & 0 deletions lib/traffic_jam.rb
Expand Up @@ -17,15 +17,29 @@ module TrafficJam
class << self
attr_reader :config

# Configure library in a block.
#
# @yield [TrafficJam::Configuration]
def configure
yield config
end

# Create limit with registed max/period.
#
# @param action [Symbol] registered action name
# @param value [String] limit target value
# @return [TrafficJam::Limit]
def limit(action, value)
limits = config.limits(action.to_sym)
TrafficJam::Limit.new(action, value, **limits)
end

# Reset all limits associated with the given action. If action is omitted or
# nil, this will reset all limits.
#
# @note Not recommended for use in production.
# @param action [Symbol] action to reset limits for
# @return [nil]
def reset_all(action: nil)
prefix =
if action.nil?
Expand All @@ -36,6 +50,7 @@ def reset_all(action: nil)
config.redis.keys(prefix).each do |key|
config.redis.del(key)
end
nil
end

%w( exceeded? increment increment! decrement reset used remaining )
Expand Down
31 changes: 31 additions & 0 deletions lib/traffic_jam/configuration.rb
@@ -1,6 +1,17 @@
module TrafficJam
# Configuration for TrafficJam library.
#
# @see TrafficJam#configure
class Configuration
OPTIONS = %i( key_prefix hash_length redis )

# @!attribute redis
# @return [Redis] the connected Redis client the library uses
# @!attribute key_prefix
# @return [String] the prefix of all limit keys in Redis
# @!attribute hash_length
# @return [String] the number of characters to use from the Base64 encoded
# hashes of the limit values
attr_accessor *OPTIONS

def initialize(options = {})
Expand All @@ -9,19 +20,39 @@ def initialize(options = {})
end
end

# Register a default cap and period with an action name. For use with
# {TrafficJam.limit}.
#
# @param action [Symbol] action name
# @param max [Integer] limit cap
# @param period [Fixnum] limit period in seconds
def register(action, max, period)
@limits ||= {}
@limits[action.to_sym] = { max: max, period: period }
end

# Get the limit cap registered to an action.
#
# @see #register
# @return [Integer] limit cap
def max(action)
limits(action)[:max]
end

# Get the limit period registered to an action.
#
# @see #register
# @return [Integer] limit period in seconds
def period(action)
limits(action)[:period]
end

# Get registered limit parameters for an action.
#
# @see #register
# @param action [Symbol] action name
# @return [Hash] max and period parameters in a hash
# @raise [TrafficJam::LimitNotFound] if action is not registered
def limits(action)
@limits ||= {}
limits = @limits[action.to_sym]
Expand Down
74 changes: 74 additions & 0 deletions lib/traffic_jam/limit.rb
@@ -1,23 +1,69 @@
require_relative 'scripts'

module TrafficJam
# This class represents a rate limit on an action, value pair. For example, if
# rate limiting the number of requests per IP address, the action could be
# +:requests+ and the value would be the IP address. The class exposes atomic
# increment operations and allows querying of the current amount used and
# amount remaining.
class Limit
# @!attribute [r] action
# @return [Symbol] the name of the action being rate limited.
# @!attribute [r] value
# @return [String] the target of the limit. The value should be a string
# or convertible to a distinct string when +to_s+ is called. If you
# would like to use objects that can be converted to a unique string,
# like a database-mapped object with an ID, you can implement
# +to_rate_limit_value+ on the object, which returns a deterministic
# string unique to that object.
# @!attribute [r] max
# @return [Integer] the integral cap of the limit amount.
# @!attribute [r] period
# @return [Integer] the duration of the limit in seconds. Regardless of
# the current amount used, after the period passes, the amount used will
# be 0.
attr_reader :action, :max, :period, :value

# 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
def initialize(action, value, max: nil, period: nil)
raise ArgumentError('Max is required') if max.nil?
raise ArgumentError('Period is required') if period.nil?
@action, @value, @max, @period = action, value, max, period
end

# Return whether incrementing by the given amount would exceed limit. Does
# not change amount used.
#
# @param amount [Integer]
# @return [Boolean]
def exceeded?(amount = 1)
used + amount > max
end

# Return itself if incrementing by the given amount would exceed limit,
# otherwise nil. Does not change amount used.
#
# @return [TrafficJam::Limit, nil]
def limit_exceeded(amount = 1)
self if exceeded?(amount)
end

# Increment the amount used by the given number. Does not perform increment
# if the operation would exceed the limit. Returns whether the operation was
# successful. Time of increment can be specified optionally with a keyword
# argument, which is useful for rolling back with a decrement.
#
# @param amount [Integer] amount to increment by
# @param time [Time] time when increment occurs
# @return [Boolean] true if increment succeded and false if incrementing
# would exceed the limit
def increment(amount = 1, time: Time.now)
return amount <= 0 if max.zero?

Expand All @@ -39,20 +85,45 @@ def increment(amount = 1, time: Time.now)
!!result
end

# Increment the amount used by the given number. Does not perform increment
# if the operation would exceed the limit. Raises an exception if the
# operation is unsuccessful. Time of# increment can be specified optionally
# with a keyword argument, which is useful for rolling back with a
# decrement.
#
# @param amount [Integer] amount to increment by
# @param time [Time] time when increment occurs
# @return [nil]
# @raise [TrafficJam::LimitExceededError] if incrementing would exceed the
# limit
def increment!(amount = 1, time: Time.now)
if !increment(amount, time: time)
raise TrafficJam::LimitExceededError.new(self)
end
end

# Decrement the amount used by the given number. Time of decrement can be
# specified optionally with a keyword argument, which is useful for rolling
# back an increment operation at a certain time.
#
# @param amount [Integer] amount to increment by
# @param time [Time] time when increment occurs
# @return [true]
def decrement(amount = 1, time: Time.now)
increment(-amount, time: time)
end

# Reset amount used to 0.
#
# @return [nil]
def reset
redis.del(key)
nil
end

# Return amount of limit used, taking time drift into account.
#
# @return [Integer] amount used
def used
return 0 if max.zero?

Expand All @@ -69,6 +140,9 @@ def used
end
end

# Return amount of limit remaining, taking time drift into account.
#
# @return [Integer] amount remaining
def remaining
max - used
end
Expand Down

0 comments on commit ec62b76

Please sign in to comment.