class TokenBucket < ApplicationRecord self.primary_key = "user_id" belongs_to :user def self.prune! where("last_touched_at < ?", 1.day.ago).delete_all end def self.create_default(user) TokenBucket.create(user_id: user.id, token_count: user.api_burst_limit, last_touched_at: Time.now) end def accept? token_count >= 1 end def add! now = Time.now TokenBucket.where(user_id: user_id).update_all(["token_count = least(token_count + (? * extract(epoch from ? - last_touched_at)), ?), last_touched_at = ?", user.api_regen_multiplier, now, user.api_burst_limit, now]) # estimate the token count to avoid reloading self.token_count += user.api_regen_multiplier * (now - last_touched_at) self.token_count = user.api_burst_limit if token_count > user.api_burst_limit end def consume! TokenBucket.where(user_id: user_id).update_all("token_count = greatest(0, token_count - 1)") self.token_count -= 1 end def throttled? add! if accept? consume! return false else return true end end end