{"id":24399899,"url":"https://github.com/kasvit/rails_rate_limit","last_synced_at":"2025-10-07T04:27:34.064Z","repository":{"id":272816695,"uuid":"917739599","full_name":"Kasvit/rails_rate_limit","owner":"Kasvit","description":"A flexible and robust rate limiting solution for Ruby on Rails applications. The gem implements a sliding window log algorithm.","archived":false,"fork":false,"pushed_at":"2025-01-23T15:07:52.000Z","size":76,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-17T14:25:29.849Z","etag":null,"topics":["memcached","memory-cache","rails","redis"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Kasvit.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-16T15:01:53.000Z","updated_at":"2025-03-30T09:28:41.000Z","dependencies_parsed_at":"2025-01-23T22:23:30.552Z","dependency_job_id":null,"html_url":"https://github.com/Kasvit/rails_rate_limit","commit_stats":null,"previous_names":["kasvit/rails_rate_limit"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Kasvit/rails_rate_limit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kasvit%2Frails_rate_limit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kasvit%2Frails_rate_limit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kasvit%2Frails_rate_limit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kasvit%2Frails_rate_limit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kasvit","download_url":"https://codeload.github.com/Kasvit/rails_rate_limit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kasvit%2Frails_rate_limit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271730185,"owners_count":24811053,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["memcached","memory-cache","rails","redis"],"created_at":"2025-01-19T23:55:27.335Z","updated_at":"2025-10-07T04:27:29.003Z","avatar_url":"https://github.com/Kasvit.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **Rails Rate Limit**\n\n[![Gem Version](https://badge.fury.io/rb/rails_rate_limit.svg)](https://badge.fury.io/rb/rails_rate_limit)\n[![Build Status](https://github.com/kasvit/rails_rate_limit/workflows/Ruby/badge.svg)](https://github.com/kasvit/rails_rate_limit/actions)\n\nA 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.\n\nFor 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.\n\nThe 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).\n\n## Features\n\n- Multiple storage backends (Redis, Memcached, Memory)\n- Sliding window algorithm for accurate rate limiting\n- Support for both controllers and Ruby classes\n- Multiple rate limits for controllers\n- Custom rate limit names and skipping for controllers\n- Flexible configuration options\n- Automatic HTTP headers\n- Custom error handlers\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'rails_rate_limit'\n```\n\nAnd then execute:\n\n```bash\n$ bundle install\n```\n\nGenerate the initializer:\n\n```bash\n$ rails generate rails_rate_limit:install\n```\n\nThis will create a configuration file at `config/initializers/rails_rate_limit.rb` with all available options commented out.\n\n## Configuration\n\nThe 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:\n\n```ruby\nRailsRateLimit.configure do |config|\n  # Choose your storage backend (default: :memory)\n  # Available options: :redis, :memcached, :memory\n  # config.default_store = :memory\n\n  # Configure Redis connection (required if using Redis store)\n  # config.redis_connection = Redis.new(\n  #   url: ENV['REDIS_URL'],\n  #   timeout: 1,\n  #   reconnect_attempts: 2\n  # )\n\n  # Configure Memcached connection (required if using Memcached store)\n  # config.memcached_connection = Dalli::Client.new(\n  #   ENV['MEMCACHED_URL'],\n  #   { expires_in: 1.day, compress: true }\n  # )\n\n  # Configure logging (set to nil to disable logging)\n  # config.logger = Rails.logger\n\n  # Configure default handler for controllers (HTTP requests)\n  # config.handle_controller_exceeded = -\u003e {\n  #   render json: {\n  #     error: \"Too many requests\",\n  #     retry_after: response.headers[\"Retry-After\"]\n  #   }, status: :too_many_requests\n  # }\n\n  # Configure default handler for methods\n  # By default, it raises RailsRateLimit::RateLimitExceeded\n  # config.handle_klass_exceeded = -\u003e {\n  #   raise RailsRateLimit::RateLimitExceeded, \"Rate limit exceeded\"\n  # }\nend\n```\n\n## Usage\n\n### Rate Limiting Controllers\n\nInclude the module and set rate limits for your controllers:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  include RailsRateLimit::Controller    # include this module\n\n  # Basic usage - limit all actions\n  set_rate_limit limit: 100,            # Maximum requests allowed\n                period: 1.minute        # Time window for the limit\n\n  # Advanced usage - limit specific actions with all options\n  set_rate_limit only: [:create, :update],       # Only these actions (optional)\n                except: [:index, :show],         # Exclude these actions (optional)\n                limit: 50,                       # Maximum requests allowed\n                period: 1.hour,                  # Time window for the limit\n                by: -\u003e { current_user\u0026.id || request.remote_ip }, # Request identifier\n                store: :redis,                   # Override default store\n                on_exceeded: -\u003e {                # Custom error handler\n                  render json: {\n                    error: 'Custom error message',\n                    plan_limit: current_user.plan.limit,\n                    upgrade_url: pricing_url\n                  }, status: :too_many_requests\n                },\n                as: :custom_rate_limit           # Custom name for rate limit (optional)\nend\n```\n\n### Multiple Rate Limits\n\nYou can set multiple rate limits for a single controller or his ancestors. Each rate limit can have its own configuration:\n\n```ruby\nclass ApiController \u003c ApplicationController\n  include RailsRateLimit::Controller\n\n  # Global rate limit for all actions\n  set_rate_limit limit: 1000,\n                period: 1.hour,\n                as: :global_rate_limit         # Custom name for rate limit (optional)\n\n  # Stricter limit for write operations\n  set_rate_limit only: [:create, :update, :destroy],\n                limit: 100,\n                period: 1.hour,\n                as: :write_operations_limit    # Custom name for rate limit (optional)\n\n  # Custom limit for specific action\n  set_rate_limit only: [:expensive_operation],\n                limit: 10,\n                period: 1.day,\n                as: :expensive_operation_limit # Custom name for rate limit (optional)\nend\n```\n\n### Custom Rate Limit Names\n\nYou can give your rate limits custom names using the `as` option. This is useful for:\n- Better logging and debugging\n- Skipping specific rate limits\n- Better organization of multiple limits\n\n```ruby\nclass ApplicationController \u003c ActionController::API\n  include RailsRateLimit::Controller\n\n  # Global rate limit that applies to all inherited controllers\n  set_rate_limit limit: 1000,\n                period: 1.hour,\n                as: :global_rate_limit\nend\n\nclass ApiController \u003c ApplicationController\n  # Additional limit for API endpoints\n  set_rate_limit limit: 100,\n                period: 1.minute,\n                as: :api_rate_limit\nend\n```\n\n### Skipping Rate Limits\n\nYou can skip specific rate limits for certain actions using `skip_before_action`:\n\n```ruby\nclass PaymentsController \u003c ApiController\n  # Skip global rate limit for webhook endpoint\n  skip_before_action :global_rate_limit, only: [:webhook]\n\n  # Skip API rate limit for status check\n  skip_before_action :api_rate_limit, only: [:status]\n\n  def webhook\n    # This action will ignore global rate limit (custom name)\n  end\n\n  def status\n    # This action will ignore API rate limit (custom name)\n  end\nend\n```\n\n### Rate Limiting Methods\n\nYou can limit both instance and class methods in your classes:\n\n```ruby\nclass ApiClient\n  include RailsRateLimit::Klass\n\n  # Instance method\n  def make_request\n    # Your API call logic here\n  end\n\n  # Class method\n  def self.bulk_request\n    # Your API call logic here\n  end\n\n  # Rate limit for instance method\n  set_rate_limit :make_request,\n                limit: 100,\n                period: 1.minute\n\n  # Rate limit for class method\n  set_rate_limit :bulk_request,\n                limit: 10,\n                period: 1.hour\n\n  # Advanced usage with all options (instance method)\n  set_rate_limit :another_method,\n                limit: 10,                       # Maximum calls allowed\n                period: 1.hour,                  # Time window for the limit\n                by: -\u003e { \"client:#{id}\" },       # Method call identifier\n                store: :memcached,               # Override default store\n                on_exceeded: -\u003e {                # Custom error handler\n                  # You can handle the error here and return any value (including nil)\n                  notify_admin\n                  log_exceeded_event\n                  nil # Method will return nil\n                }\n\n  # Advanced usage with all options (class method)\n  set_rate_limit :another_class_method,\n                limit: 5,                        # Maximum calls allowed\n                period: 1.day,                   # Time window for the limit\n                by: -\u003e { \"global:#{name}\" },     # Method call identifier\n                store: :redis,                   # Override default store\n                on_exceeded: -\u003e {                # Custom error handler\n                  log_exceeded_event\n                  \"Rate limit exceeded\"          # Return custom message\n                }\n\n  # Direct rate limit setting\n  # You can also set rate limits directly if you have both instance and class methods with the same name\n  set_instance_rate_limit :process,             # For instance method\n                         limit: 10,\n                         period: 1.hour\n\n  set_class_rate_limit :process,                # For class method\n                      limit: 5,\n                      period: 1.hour\nend\n```\n\n### Available Options\n\nFor both controllers and methods:\n- `limit`: (Required) Maximum number of requests/calls allowed\n- `period`: (Required) Time period for the limit (in seconds or ActiveSupport::Duration)\n- `by`: (Optional) Lambda/Proc to generate unique identifier\n  - Default for controllers: `\"#{controller.class.name}:#{controller.request.remote_ip}\"`\n  - Default for instance methods: `\"#{self.class.name}##{method_name}:#{respond_to?(:id) ? 'id='+id.to_s : 'object_id='+object_id.to_s}\"`\n  - Default for class methods: `\"#{class.name}.#{method_name}\"`\n- `store`: (Optional) Override default storage backend (`:redis`, `:memcached`, `:memory`)\n- `on_exceeded`: (Optional) Custom handler for rate limit exceeded\n\nAdditional options for controllers:\n- `as`: (Optional) Custom name for rate limit\n- `only`: (Optional) Array of action names to limit\n- `except`: (Optional) Array of action names to exclude\n\n### Rate Limit Exceeded Handling\n\nThe gem provides different default behaviors for controllers and methods:\n\n1. For controllers (HTTP requests):\n   - The `on_exceeded` handler (or default handler) is called\n   - By default, returns HTTP 429 with a JSON error message\n   - Headers are automatically added with limit information\n   - The handler's return value is used (usually render/redirect)\n\n2. For methods:\n   - The `on_exceeded` handler (if provided) is called first\n   - Then `RailsRateLimit::RateLimitExceeded` exception is raised\n   - The event is logged if a logger is configured\n   - You should catch the exception to handle the error\n\n### Default error messages\n\nBy default, the gem logs the error message to the logger together with your custom `on_exceeded` message.\n```ruby\n@logger.warn(\n  \"Rate limit exceeded for #{key}. \" \\\n  \"Limit: #{limit} requests per #{period} seconds\"\n)\n# where key for klass is `by` or default unique identifier\n# Rate limit exceeded for ReportGenerator#generate:object_id=218520. Limit: 2 requests per 10 seconds\n# Rate limit exceeded for Notification#deliver:id=1. Limit: 3 requests per 60 seconds\n# Rate limit exceeded for ReportGenerator.generate. Limit: 2 requests per 10 seconds\n\n# where key for controller is `by` or default unique identifier\n# Rate limit exceeded for HomeController:127.0.0.1. Limit: 100 requests per 1 minute\n```\n\nYou can remove it by setting `config.logger = nil` or specify `by` options.\n\n### HTTP Headers\n\nFor controller rate limiting, the following headers are automatically added:\n- `X-RateLimit-Limit`: Maximum requests allowed\n- `X-RateLimit-Remaining`: Remaining requests in current period\n- `X-RateLimit-Reset`: Time when the current period will reset (Unix timestamp)\n\n## Storage Backends\n\n### Memory (Default)\n- No additional dependencies\n- Perfect for development or single-server setups\n- Data is lost on server restart\n- Not suitable for distributed systems\n- Thread-safe implementation\n\n### Redis\n- Requires the `redis-rails` gem\n- Best for distributed systems\n- Automatic cleanup of expired data\n- Atomic operations ensure accuracy\n- Recommended for production use\n\n### Memcached\n- Requires the `dalli` gem\n- Good balance of performance and features\n- Automatic cleanup via TTL\n- Works well in distributed environments\n- Good option if you're already using Memcached\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/kasvit/rails_rate_limit. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](LICENSE.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkasvit%2Frails_rate_limit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkasvit%2Frails_rate_limit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkasvit%2Frails_rate_limit/lists"}