Class: SpreeMenuChat::RateLimiter
- Inherits:
-
Object
- Object
- SpreeMenuChat::RateLimiter
- Defined in:
- app/services/spree_menu_chat/rate_limiter.rb
Overview
Fixed-window rate limiter — SpreeMenuChat::Config.rate_limit_per_hour requests per (store, identifier) per rolling hour, Postgres-backed (no Redis anywhere in this stack, same rationale as Solid Queue running inside Postgres rather than a separate broker).
Called from SpreeMenuChat::ChatController with request.remote_ip as the identifier — the widget is anonymous (no session/customer model yet), so IP is what's actually available before any answer is generated.
Defined Under Namespace
Classes: LimitExceededError
Class Method Summary collapse
Instance Method Summary collapse
-
#check! ⇒ Object
Raises LimitExceededError once the identifier has made rate_limit_per_hour requests within the current window; otherwise increments the counter and returns normally.
-
#initialize(identifier, store:) ⇒ RateLimiter
constructor
A new instance of RateLimiter.
Constructor Details
#initialize(identifier, store:) ⇒ RateLimiter
Returns a new instance of RateLimiter.
17 18 19 20 |
# File 'app/services/spree_menu_chat/rate_limiter.rb', line 17 def initialize(identifier, store:) @identifier = identifier @store = store end |
Class Method Details
.check!(identifier, store: Spree::Store.default) ⇒ Object
13 14 15 |
# File 'app/services/spree_menu_chat/rate_limiter.rb', line 13 def self.check!(identifier, store: Spree::Store.default) new(identifier, store: store).check! end |
Instance Method Details
#check! ⇒ Object
Raises LimitExceededError once the identifier has made rate_limit_per_hour requests within the current window; otherwise increments the counter and returns normally. Fixed window (resets a full hour after the first request in it), not a true sliding window — simpler, and standard practice for a limit this size.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'app/services/spree_menu_chat/rate_limiter.rb', line 27 def check! ActiveRecord::Base.transaction do record = SpreeMenuChat::RateLimit.lock.find_or_initialize_by(store: @store, identifier: @identifier) reset_if_window_expired(record) raise LimitExceededError if record.count >= SpreeMenuChat::Config.rate_limit_per_hour record.count += 1 record.save! end rescue ActiveRecord::RecordNotUnique # Lost a race with a concurrent first-request from the same # identifier — the row exists now, retry against it. Same pattern as # SpreeSquare::WebhooksController#find_or_log_event. retry end |