3 # Copyright (C) 2012-2013 Gitorious AS
4 # Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 module RecordThrottling
21 class LimitReachedError < StandardError; end
32 !@@enabled || !RecordThrottling::default_behavior
35 def self.reset_to_default
36 @@enabled = RecordThrottling::default_behavior
39 def self.default_behavior
40 Gitorious::Configuration.get("enable_record_throttling", true)
43 @@enabled = RecordThrottling::default_behavior
45 def self.included(base)
47 include RecordThrottlingInstanceMethods
49 # Thottles record creation/update.
50 # Raises RecordThrottling::RecordThrottleLimitReachedError if limit is
54 # +:limit+ the amount of records allowed within (eg. 5)
55 # +:timeframe+ the timeframe the limit should be within (eg. 5.minutes)
56 # +:counter+ A proc returning the value to compare +:limit+ against
57 # +:conditions+ A proc of the counts the last created_at query should use
58 # Both the +:counter+ and +:conditions+ procs will receive the record
62 # throttle_records :create, :limit => 5,
63 # :counter => proc{|record|
64 # record.user.projects.where("created_at > ?", 5.minutes.ago).count
66 # :conditions => proc{|record| {:user_id => record.user.id} },
67 # :timeframe => 5.minutes
68 def self.throttle_records(create_or_update, options)
69 options.assert_valid_keys(:limit, :counter, :conditions, :timeframe, :actor)
70 class_attribute :creation_throttle_options
71 self.creation_throttle_options = options
72 send("before_#{create_or_update}", :check_throttle_limits)
77 module RecordThrottlingInstanceMethods
78 def check_throttle_limits
79 options = self.class.creation_throttle_options
80 scope = options[:scope] || self.class
81 actor = options[:actor].call(self)
82 raise LimitReachedError if !RecordThrottling.allowed?(scope, actor, options)
86 def self.allowed?(scope, actor, options = {})
87 return true if RecordThrottling.disabled?
88 return true if options[:counter].call(actor) < options[:limit]
89 cond = options[:conditions].call(actor)
90 last_create = scope.maximum(:created_at, :conditions => cond)
91 return true if !last_create || last_create < options[:timeframe].ago