Class: Limit::BaseRateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/limit.rb

Overview

Base Class, implementing:

  • Method to connect with redis

  • Signature for limit_calculator

Constant Summary collapse

REQUIRED_KEYS =
i[max_requests window_seconds].freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(identifier_prefix:, limit_calculator:, host: nil, port: nil, password: nil) ⇒ BaseRateLimiter

Returns a new instance of BaseRateLimiter.

Raises:

  • (ArgumentError)


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/limit.rb', line 54

def initialize(identifier_prefix:, limit_calculator:, host: nil, port: nil, password: nil)

  # @param identifier_prefix: [String] A namespace prefix for redis keys for this limiter instance
  # @param limit_calculator: Lambda that takes a key(String) and returns hash: {max_requests: Integer, window_seconds: Integer}
  # But diff implementation can update the hash struct

  unless identifier_prefix.is_a?(String) && !identifier_prefix.empty?
    raise ArgumentError, 'identifier_prefix must be a non-empty String'
  end

  raise ArgumentError, 'limit_calculator must be a Lambda' unless limit_calculator.lambda?

  # Will be using the same connection across all instances unless wanted to connect to diff instance of redis

  @redis = if host && port && password
             self.class.send(:create_connection, host: host, port: port, password: password)
           else
             self.class.connection
           end

  @identifier_prefix = identifier_prefix
  @limit_calculator = limit_calculator
  @logger = self.class.logger

  begin
    @redis.ping
    @logger.info("Successfully connected to Redis @ #{@redis.connection[:host]}:#{@redis.connection[:port]}")
  rescue Redis::BaseError => e
    @logger.error("Error connecting to Redis: #{e.message}")
    raise e
  end

  self.class.load_script
end

Class Attribute Details

.script_shaObject (readonly)

Returns the value of attribute script_sha.



39
40
41
# File 'lib/limit.rb', line 39

def script_sha
  @script_sha
end

Instance Attribute Details

#identifier_prefixObject (readonly)

Returns the value of attribute identifier_prefix.



13
14
15
# File 'lib/limit.rb', line 13

def identifier_prefix
  @identifier_prefix
end

#limit_calculatorObject (readonly)

Returns the value of attribute limit_calculator.



13
14
15
# File 'lib/limit.rb', line 13

def limit_calculator
  @limit_calculator
end

Class Method Details

.connectionObject



22
23
24
25
26
27
# File 'lib/limit.rb', line 22

def connection
  host = ENV['REDIS_HOST']
  port = ENV['REDIS_PORT']
  password = ENV['REDIS_PASSWORD']
  @redis ||= create_connection(host: host, port: port, password: password)
end

.load_scriptObject



33
34
35
36
37
# File 'lib/limit.rb', line 33

def load_script
  script_name = name.split('::')[1].chomp('RateLimiter')
  script_path = File.expand_path("scripts/#{script_name}.lua", __dir__)
  @script_sha = @redis.script(:load, File.read(script_path))
end

.loggerObject



29
30
31
# File 'lib/limit.rb', line 29

def logger
  @logger ||= Logger.new($stdout)
end

Instance Method Details

#allowed?(key) ⇒ Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/limit.rb', line 89

def allowed?(key)
  raise NotImplementedError "#{self.class.name} must implement the allowed? method"
end

#get_key(key) ⇒ Object



93
94
95
# File 'lib/limit.rb', line 93

def get_key(key)
  "#{@identifier_prefix}:#{key}"
end