limited 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9760e85bf1a3b2c89385bdcbd8076ff30e0cbd4
4
- data.tar.gz: f1bec62f986e779c66f70a01c06e6e8136de4861
3
+ metadata.gz: e0c628b05e6dd3db011e8ae8aa7299da4ab0664a
4
+ data.tar.gz: c5185402f1a9f258c641276f36e640994b17c10e
5
5
  SHA512:
6
- metadata.gz: 7c0936a592fd48b030c5bbaac2851fc058c0ba8e4242de3efe0a710704f37e28c7a8dfe86384800095834e9c425eee87453e43972176ca59508b527d719a79ce
7
- data.tar.gz: 1e39cf4620ba29bd06a3bf471fb43e52ccfd5592ba2016028b47fbdd2dd6bb6f17c3d4936abcf2edd0d9c8e783a09f97e2dda37a83ab2eb21cead18dd9cc8a27
6
+ metadata.gz: 8a5dce139e3e678c083855773ac616ee638124fdce8d07dfa3c00ba1f5c2b634adfaefa43e36ca99f646f83d24addea085a1095a08904c6449cc2eeca6dbc2b4
7
+ data.tar.gz: bdcee5091e679f9cdd3f12bb05f9bd3df953d8f929b0921405091b8538e35921cf897eb720473a36492d2e1fe1321e1c0e32c67016e9fcf63a7b692078339851
data/README.md CHANGED
@@ -23,9 +23,14 @@ Or install it yourself as:
23
23
  You need to define your actions somewhere in your application like this:
24
24
 
25
25
  Limited.configure do
26
+ # this action should only be 1337 times at most
26
27
  action :name_of_action, 1337
27
- action :login, 20
28
- action :sending_contact_email, 123
28
+
29
+ # only 1 login all 10 seconds
30
+ action :login, 1, 10
31
+
32
+ # at maximum 123 contact emails a day
33
+ action :sending_contact_email, 123, :day
29
34
  end
30
35
 
31
36
  The second parameter given to action defines how many times the action
@@ -42,11 +47,11 @@ Here are a few commonly used methods which every Action provides:
42
47
 
43
48
  <table>
44
49
  <tr>
45
- <td>`executed`</td>
50
+ <td>executed</td>
46
51
  <td>Call this method everytime the specified action is being executed</td>
47
52
  </tr>
48
53
  <tr>
49
- <td>`limit_reached`</td>
54
+ <td>limit_reached</td>
50
55
  <td>Wheter the limit has been reached or not</td>
51
56
  </tr>
52
57
  </table>
@@ -8,8 +8,6 @@ module Limited
8
8
  attr_reader :name
9
9
  # the amount of times the action can be executed
10
10
  attr_reader :limit
11
- # the amount of times the action already has been executed
12
- attr_reader :num_executed
13
11
 
14
12
  ##
15
13
  # :call-seq:
@@ -20,7 +18,7 @@ module Limited
20
18
  # === Example
21
19
  #
22
20
  # Limited::Action.new :do_stuff, 1000
23
- def initialize(name, limit)
21
+ def initialize(name, limit, interval = nil)
24
22
  raise ArgumentError.new("Limited::Action::name needs to be a Symbol") unless name.is_a?(Symbol)
25
23
  raise ArgumentError.new("Limited::Action::limit needs to be an Integer") unless limit.is_a?(Integer)
26
24
  raise ArgumentError.new("Limited::Action::limit needs to be positive") if limit < 0
@@ -28,12 +26,30 @@ module Limited
28
26
  @name = name
29
27
  @limit = limit
30
28
  @num_executed = 0
29
+ @interval = Interval.new(interval.nil? ? :endless : interval)
30
+ check_new_interval
31
+ end
32
+
33
+ ##
34
+ # returns the interval for which the limit counts
35
+ def interval
36
+ check_new_interval
37
+ @interval
38
+ end
39
+
40
+ ##
41
+ # returns the amount of times the action already
42
+ # has been executed
43
+ def num_executed
44
+ check_new_interval
45
+ @num_executed
31
46
  end
32
47
 
33
48
  ##
34
49
  # returns how many times the action can be executed
35
50
  # until the given limit is reached
36
51
  def num_left
52
+ check_new_interval
37
53
  @limit - @num_executed
38
54
  end
39
55
 
@@ -42,6 +58,7 @@ module Limited
42
58
  # the action gets executed
43
59
  # so the internal counter is always up-to-date
44
60
  def executed
61
+ check_new_interval
45
62
  @num_executed += 1
46
63
  end
47
64
 
@@ -49,7 +66,22 @@ module Limited
49
66
  # wheter the limit of executions
50
67
  # has been exceeded
51
68
  def limit_reached
69
+ check_new_interval
52
70
  @limit <= @num_executed
53
71
  end
72
+
73
+ private
74
+
75
+ ##
76
+ # checks if the interval
77
+ # passes and resets the number
78
+ # of executions so
79
+ # the limit can be reached again
80
+ def check_new_interval
81
+ if @interval.passed?
82
+ @interval.reset_start
83
+ @num_executed = 0
84
+ end
85
+ end
54
86
  end
55
87
  end
@@ -11,8 +11,8 @@ module Limited
11
11
  #
12
12
  # raises an ArgumentError if the same name
13
13
  # for an action is not unique
14
- def self.action(name, limit)
15
- action = Limited::Action.new(name, limit)
14
+ def self.action(name, limit, interval_length = nil)
15
+ action = Limited::Action.new(name, limit, interval_length)
16
16
  raise ArgumentError.new("action with name :#{name.to_s} has already been added") if Limited.actions.has_key?(name)
17
17
  Limited.actions[name] = action
18
18
  end
@@ -0,0 +1,60 @@
1
+ module Limited
2
+ ##
3
+ # This represents a timespan within a limit counts
4
+ # after this timespan ends the limit
5
+ # should be reset
6
+ class Interval
7
+ # the length of the interval in seconds
8
+ attr_reader :length
9
+ # a timestamp when the last interval started
10
+ attr_reader :last_start
11
+
12
+ # common lengths of the intervals
13
+ # and shortcuts for them
14
+ @@interval_lengths = {
15
+ second: 1,
16
+ minute: 60,
17
+ hour: 60 * 60,
18
+ day: 60 * 60 * 24,
19
+ endless: 1.0/0.0
20
+ }
21
+
22
+ ##
23
+ # initializes an interval
24
+ # by either supplying the length of the
25
+ # interval in seconds as an Numeric
26
+ # or by supplying one of the symbols
27
+ # :second, :minute, :hour or :day
28
+ def initialize(length)
29
+ is_sym = length.is_a?(Symbol) and @@interval_lengths.has_key?(length)
30
+ raise ArgumentError.new("Limited::Interval.length needs to be a Numeric or one of the symbols :second, :minute, :hour or :day") unless length.is_a?(Numeric) or is_sym
31
+ @length = is_sym ? @@interval_lengths[length] : length
32
+ reset_start
33
+ end
34
+
35
+ ##
36
+ # start a new interval and
37
+ # reset the last_start variable
38
+ def reset_start
39
+ now = Time.now
40
+ @last_start = (now.to_f / @length).to_i * @length
41
+ @last_start = 0 if @last_start.is_a?(Float) and @last_start.nan?
42
+ end
43
+
44
+ ##
45
+ # calculate the amount of seconds remaining
46
+ # in seconds for the interval to end
47
+ #
48
+ # when the interval ended 0 is returned so
49
+ # this method never returns negative numbers
50
+ def time_left
51
+ [0, (last_start.to_f + length - Time.now.to_f)].max
52
+ end
53
+
54
+ ##
55
+ # wheter the interval has finished
56
+ def passed?
57
+ time_left == 0
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module Limited
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/limited.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "limited/version"
2
2
  require "limited/action"
3
+ require "limited/interval"
3
4
 
4
5
  module Limited
5
6
  # a hash containing all the
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  require 'limited'
2
4
  require 'limited/action'
3
5
 
@@ -7,6 +9,7 @@ describe Limited::Action do
7
9
 
8
10
  it { should respond_to(:name) }
9
11
  it { should respond_to(:limit) }
12
+ it { should respond_to(:interval) }
10
13
  it { should respond_to(:num_executed) }
11
14
  it { should respond_to(:num_left) }
12
15
 
@@ -72,4 +75,26 @@ describe Limited::Action do
72
75
  @action.limit_reached.should be_true
73
76
  end
74
77
  end
78
+
79
+ describe "with an ending interval" do
80
+ before { @interval_action = Limited::Action.new :ending_interval, 5, :minute }
81
+ it "should be able to reach the limit before the interval ends" do
82
+ @interval_action.limit_reached.should be_false
83
+ @interval_action.executed
84
+ @interval_action.limit_reached.should be_false
85
+ end
86
+
87
+ describe "after the limit has been reached" do
88
+ before { 5.times { @interval_action.executed } }
89
+ it { @interval_action.limit_reached.should be_true }
90
+
91
+ describe "after the interval passed" do
92
+ before { Time.testing_offset += @interval_action.interval.time_left }
93
+ it "should reset num_executed" do
94
+ @interval_action.num_executed.should be 0
95
+ @interval_action.limit_reached.should be_false
96
+ end
97
+ end
98
+ end
99
+ end
75
100
  end
@@ -7,10 +7,32 @@ describe Limited::Config do
7
7
 
8
8
  it "should add an Limited::Action to Limited::actions when calling Limited::Config::action" do
9
9
  expect do
10
- Limited::Config.action :new, 123
10
+ Limited::Config.action :new, 123, :day
11
11
  end.to change(Limited, :actions)
12
12
  end
13
13
 
14
+ describe "the Action objects in the Limited::actions array" do
15
+ before(:all) do
16
+ Limited::Config.action :value_check, 123, :day
17
+ Limited::Config.action :value_check1, 3
18
+ end
19
+
20
+ it "should have the correct name" do
21
+ Limited.actions[:value_check].name.should eq :value_check
22
+ Limited.actions[:value_check1].name.should eq :value_check1
23
+ end
24
+
25
+ it "should have the correct limit" do
26
+ Limited.actions[:value_check].limit.should eq 123
27
+ Limited.actions[:value_check1].limit.should eq 3
28
+ end
29
+
30
+ it "should have the correct interval" do
31
+ Limited.actions[:value_check].interval.length.should eq 24 * 60 * 60
32
+ Limited.actions[:value_check1].interval.length.should eq 1.0/0.0
33
+ end
34
+ end
35
+
14
36
  it "should not be possible to add an action with the same name twice" do
15
37
  expect do
16
38
  Limited::Config.action :same_name, 123
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+ require 'limited'
3
+
4
+ describe Limited::Interval do
5
+ let(:infinity) { 1.0/0.0 }
6
+ let(:endless) { Limited::Interval.new(:endless) }
7
+
8
+ subject { Limited::Interval.new 234 }
9
+
10
+ it { should respond_to(:length) }
11
+ it { should respond_to(:last_start) }
12
+ it { should respond_to(:time_left) }
13
+ it { should respond_to(:reset_start) }
14
+ it { should respond_to(:passed?) }
15
+
16
+
17
+ describe "length" do
18
+ it "should be set by the constructor" do
19
+ i = Limited::Interval.new(123)
20
+ i.length.should eq 123
21
+ end
22
+
23
+ describe "can also be set via symbols :second, :minute, :hour, :day" do
24
+ it { Limited::Interval.new(:second ).length.should eq 1 }
25
+ it { Limited::Interval.new(:minute ).length.should eq 60 }
26
+ it { Limited::Interval.new(:hour ).length.should eq 60 * 60 }
27
+ it { Limited::Interval.new(:day ).length.should eq 60 * 60 * 24 }
28
+ it { endless.length.should eq infinity }
29
+ end
30
+
31
+ it "should raise error when no integer or symbol given" do
32
+ expect { Limited::Interval.new(nil) }.to raise_error(ArgumentError)
33
+ end
34
+ end
35
+
36
+ describe "time_left" do
37
+ it "should return a value equal or lower than length" do
38
+ Limited::Interval.new(541).time_left.should be <= 541
39
+ end
40
+
41
+ it "should be infinite if interval has the length Infinify" do
42
+ endless.time_left.should eq infinity
43
+ end
44
+ end
45
+
46
+ describe "reset_start" do
47
+ it "should be calculated to a time near Time.now which difference is shorter than length" do
48
+ c = Limited::Interval.new(100)
49
+ (Time.now.to_f - c.last_start.to_f).should be <= 100
50
+ end
51
+
52
+ it "should set last_start to 0 when interval is endless" do
53
+ endless.last_start.should eq 0
54
+ end
55
+ end
56
+
57
+ describe "passed" do
58
+ let(:i) { Limited::Interval.new(541) }
59
+ it "should return false if length seconds have not yet passed" do
60
+ i.passed?.should be_false
61
+ Time.testing_offset += i.time_left - 1
62
+ i.passed?.should be_false
63
+ end
64
+
65
+ it "should return true when enough time passed" do
66
+ Time.testing_offset += i.time_left
67
+ i.time_left.should eq 0
68
+ i.passed?.should be_true
69
+ end
70
+
71
+ it "should always return false when interval is endless" do
72
+ endless.passed?.should be_false
73
+ Time.testing_offset += i.time_left
74
+ endless.passed?.should be_false
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ class Time #:nodoc:
2
+ class <<self
3
+ attr_accessor :testing_offset
4
+ alias_method :real_now, :now
5
+ def now
6
+ real_now + testing_offset
7
+ end
8
+ alias_method :new, :now
9
+ end
10
+ end
11
+
12
+ Time.testing_offset = 0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: limited
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moritz Küttel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-06-09 00:00:00.000000000 Z
11
+ date: 2013-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -81,11 +81,14 @@ files:
81
81
  - lib/limited.rb
82
82
  - lib/limited/action.rb
83
83
  - lib/limited/config.rb
84
+ - lib/limited/interval.rb
84
85
  - lib/limited/version.rb
85
86
  - limited.gemspec
86
87
  - spec/limited_action_spec.rb
87
88
  - spec/limited_config_spec.rb
89
+ - spec/limited_interval_spec.rb
88
90
  - spec/limited_spec.rb
91
+ - spec/spec_helper.rb
89
92
  homepage: https://github.com/mkuettel/limited
90
93
  licenses:
91
94
  - MIT
@@ -115,4 +118,6 @@ summary: Some utility functions to help you limit some actions in your applicati
115
118
  test_files:
116
119
  - spec/limited_action_spec.rb
117
120
  - spec/limited_config_spec.rb
121
+ - spec/limited_interval_spec.rb
118
122
  - spec/limited_spec.rb
123
+ - spec/spec_helper.rb