Loading repository data…
Loading repository data…
Kasvit / repository
A flexible and robust rate limiting solution for Ruby on Rails applications. The gem implements a sliding window log algorithm.
A flexible and robust rate limiting solution for Ruby on Rails applications. The gem implements a sliding window log algorithm, which means it tracks the exact timestamp of each request and calculates the count within a sliding time window. This provides more accurate rate limiting compared to fixed window approaches.
For example, if you set a limit of 100 requests per hour, and a user makes 100 requests at 2:30 PM, they won't be able to make another request until some of those requests "expire" after 2:30 PM the next hour. This prevents the common issue with fixed windows where users could potentially make 200 requests around the window boundary.
The gem supports rate limiting for both HTTP requests (in controllers) and instance method calls (in any Ruby class), with multiple storage backends (Redis, Memcached, Memory).
Add this line to your application's Gemfile:
gem 'rails_rate_limit'
And then execute:
$ bundle install
Generate the initializer:
$ rails generate rails_rate_limit:install
This will create a configuration file at config/initializers/rails_rate_limit.rb with all available options commented out.
The generated initializer (config/initializers/rails_rate_limit.rb) includes all available configuration options with default values commented out. You can uncomment and modify the options you want to customize:
RailsRateLimit.configure do |config|
# Choose your storage backend (default: :memory)
# Available options: :redis, :memcached, :memory
# config.default_store = :memory
# Configure Redis connection (required if using Redis store)
# config.redis_connection = Redis.new(
# url: ENV['REDIS_URL'],
# timeout: 1,
# reconnect_attempts: 2
# )
# Configure Memcached connection (required if using Memcached store)
# config.memcached_connection = Dalli::Client.new(
# ENV['MEMCACHED_URL'],
# { expires_in: 1.day, compress: true }
# )
# Configure logging (set to nil to disable logging)
# config.logger = Rails.logger
# Configure default handler for controllers (HTTP requests)
# config.handle_controller_exceeded = -> {
# render json: {
# error: "Too many requests",
# retry_after: response.headers["Retry-After"]
# }, status: :too_many_requests
# }
# Configure default handler for methods
# By default, it raises RailsRateLimit::RateLimitExceeded
# config.handle_klass_exceeded = -> {
# raise RailsRateLimit::RateLimitExceeded, "Rate limit exceeded"
# }
end
Include the module and set rate limits for your controllers:
class UsersController < ApplicationController
include RailsRateLimit::Controller # include this module
# Basic usage - limit all actions
set_rate_limit limit: 100, # Maximum requests allowed
period: 1.minute # Time window for the limit
# Advanced usage - limit specific actions with all options
set_rate_limit only: [:create, :update], # Only these actions (optional)
except: [:index, :show], # Exclude these actions (optional)
limit: 50, # Maximum requests allowed
period: 1.hour, # Time window for the limit
by: -> { current_user&.id || request.remote_ip }, # Request identifier
store: :redis, # Override default store
on_exceeded: -> { # Custom error handler
render json: {
error: 'Custom error message',
plan_limit: current_user.plan.limit,
upgrade_url: pricing_url
}, status: :too_many_requests
},
as: :custom_rate_limit # Custom name for rate limit (optional)
end
You can set multiple rate limits for a single controller or his ancestors. Each rate limit can have its own configuration:
class ApiController < ApplicationController
include RailsRateLimit::Controller
# Global rate limit for all actions
set_rate_limit limit: 1000,
period: 1.hour,
as: :global_rate_limit # Custom name for rate limit (optional)
# Stricter limit for write operations
set_rate_limit only: [:create, :update, :destroy],
limit: 100,
period: 1.hour,
as: :write_operations_limit # Custom name for rate limit (optional)
# Custom limit for specific action
set_rate_limit only: [:expensive_operation],
limit: 10,
period: 1.day,
as: :expensive_operation_limit # Custom name for rate limit (optional)
end
You can give your rate limits custom names using the as option. This is useful for:
class ApplicationController < ActionController::API
include RailsRateLimit::Controller
# Global rate limit that applies to all inherited controllers
set_rate_limit limit: 1000,
period: 1.hour,
as: :global_rate_limit
end
class ApiController < ApplicationController
# Additional limit for API endpoints
set_rate_limit limit: 100,
period: 1.minute,
as: :api_rate_limit
end
You can skip specific rate limits for certain actions using skip_before_action:
class PaymentsController < ApiController
# Skip global rate limit for webhook endpoint
skip_before_action :global_rate_limit, only: [:webhook]
# Skip API rate limit for status check
skip_before_action :api_rate_limit, only: [:status]
def webhook
# This action will ignore global rate limit (custom name)
end
def status
# This action will ignore API rate limit (custom name)
end
end
You can limit both instance and class methods in your classes:
class ApiClient
include RailsRateLimit::Klass
# Instance method
def make_request
# Your API call logic here
end
# Class method
def self.bulk_request
# Your API call logic here
end
# Rate limit for instance method
set_rate_limit :make_request,
limit: 100,
period: 1.minute
# Rate limit for class method
set_rate_limit :bulk_request,
limit: 10,
period: 1.hour
# Advanced usage with all options (instance method)
set_rate_limit :another_method,
limit: 10, # Maximum calls allowed
period: 1.hour, # Time window for the limit
by: -> { "client:#{id}" }, # Method call identifier
store: :memcached, # Override default store
on_exceeded: -> { # Custom error handler
# You can handle the error here and return any value (including nil)
notify_admin
log_exceeded_event
nil # Method will return nil
}
# Advanced usage with all options (class method)
set_rate_limit :another_class_method,
limit: 5, # Maximum calls allowed
period: 1.day, # Time window for the limit
by: -> { "global:#{name}" }, # Method call identifier
store: :redis, # Override default store
on_exceeded: -> { # Custom error handler
log_exceeded_event
"Rate limit exceeded" # Return custom message
}
# Direct rate limit setting
# You can also set rate limits directly if you have both instance and class methods with the same name
set_instance_rate_limit :process, # For instance method
limit: 10,
period: 1.hour
set_class_rate_limit :process, # For class method
limit: 5,
period: 1.hour
end
For both controllers and methods:
limit: (Required) Maximum number of requests/calls allowedperiod: (Required) Time period for the limit (in seconds or ActiveSupport::Duration)by: (Optional) Lambda/Proc to generate unique identifier
"#{controller.class.name}:#{controller.request.remote_ip}""#{self.class.name}##{method_name}:#{respond_to?(:id) ? 'id='+id.to_s : 'object_id='+object_id.to_s}""#{class.name}.#{method_name}"store: (Optional) Override default storage backend (:redis, :memcached, :memory)on_exceeded: (Optional) Custom handler for rate limit exceededAdditional options for controllers:
as: (Optional) Custom name for rate limitonly: (Optional) Array of action names to limitexcept: (Optional) Array of action names to excludeThe gem provides different default behaviors for controllers and methods:
For controllers (HTTP requests):
on_exceeded handler (or default handler) is calledFor methods:
on_exceeded handler (if provided) is called firstRailsRateLimit::RateLimitExceeded exception is raisedBy default, the gem logs the error message to the logger together with your custom on_exceeded message.
@logger.warn(
"Rate limit exceeded for #{key}. " \
"Limit: #{limit} requests per #{period} seconds"
)
# where key for klass is `by` or default unique identifier
# Rate limit exceeded for ReportGenerator#generate:object_id=218520. Limit: 2 requests per 10 seconds
# Rate limit exceeded for Notification#deliver:id=1. Limit: 3 requests per 60 seconds
# Rate limit exceeded for ReportGenerator.generate. Limit: 2 requests per 10 seconds
# where key for controller is `by` or default unique identifier
# Rate limit exceeded for HomeController:127.0.0.1. Limit: 100 requests per 1 minute
You can remove it by setting config.logger = nil or specify by options.
For controller rate limiting, the following headers are automatically added:
X-RateLimit-Limit: Maximum requests allowedX-RateLimit-Remaining: Remaining requests in current periodX-RateLimit-Reset: Time when the current period will reset (Unix timestamp)redis-rails gem