limited 0.1.0 → 0.2.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: e0c628b05e6dd3db011e8ae8aa7299da4ab0664a
4
- data.tar.gz: c5185402f1a9f258c641276f36e640994b17c10e
3
+ metadata.gz: 4a28b517efc2c9c053d8fd054c5ee44620c42507
4
+ data.tar.gz: f6ec63d47da226112bb915aa4c881dd3c8205288
5
5
  SHA512:
6
- metadata.gz: 8a5dce139e3e678c083855773ac616ee638124fdce8d07dfa3c00ba1f5c2b634adfaefa43e36ca99f646f83d24addea085a1095a08904c6449cc2eeca6dbc2b4
7
- data.tar.gz: bdcee5091e679f9cdd3f12bb05f9bd3df953d8f929b0921405091b8538e35921cf897eb720473a36492d2e1fe1321e1c0e32c67016e9fcf63a7b692078339851
6
+ metadata.gz: 030ad2dcffe79f7294d4536c6dcc578e95aa6f38088acfae4b9ae05827935d428d0348e253a35962e2eb4eb07944c4ec93904f91ccc2086705d8986814c93ad1
7
+ data.tar.gz: 85033f466bf1893b485d82d3161b770e4bf126b1bba265f1a2fa9739706e73fd7c9386d23ba22db0d816208b2536f3b59f4b03c6cb6b176ab6a940f048619cf8
data/README.md CHANGED
@@ -24,13 +24,19 @@ You need to define your actions somewhere in your application like this:
24
24
 
25
25
  Limited.configure do
26
26
  # this action should only be 1337 times at most
27
- action :name_of_action, 1337
27
+ action :name_of_action, amount: 1337
28
28
 
29
29
  # only 1 login all 10 seconds
30
- action :login, 1, 10
30
+ action :login, amount: 1, every: 10
31
31
 
32
32
  # at maximum 123 contact emails a day
33
- action :sending_contact_email, 123, :day
33
+ action :sending_contact_email, amount: 123, every: :day
34
+
35
+ # you can also define identifier, which can be used to distinguish counters
36
+ identifier :category, [:category_id]
37
+
38
+ # no more than 50 posts a day are allowed per category
39
+ action :post, amount: 50, every: :day, per: :category
34
40
  end
35
41
 
36
42
  The second parameter given to action defines how many times the action
@@ -42,6 +48,7 @@ module to access your Action like this:
42
48
  Limited.name_of_action
43
49
  Limited.login
44
50
  Limited.sending_contact_email
51
+ Limited.post
45
52
 
46
53
  Here are a few commonly used methods which every Action provides:
47
54
 
@@ -56,6 +63,27 @@ Here are a few commonly used methods which every Action provides:
56
63
  </tr>
57
64
  </table>
58
65
 
66
+ For example:
67
+
68
+ unless Limited.sending_contact_email.limit_reached
69
+
70
+ # code to send contact email
71
+
72
+ Limited.sending_contact_email.executed
73
+ end
74
+
75
+ If the action uses an identifier (you used the `:per` option in the config file) you can pass
76
+ a hash containing the values used to distingish counters.
77
+
78
+ For example:
79
+
80
+ unless Limited.post.limit_reached({category_id: category.id})
81
+
82
+ # code to insert post into the category ...
83
+
84
+ Limited.post.executed({category_id: category.id})
85
+ end
86
+
59
87
  ## Contributing
60
88
 
61
89
  1. Fork it
@@ -8,6 +8,11 @@ module Limited
8
8
  attr_reader :name
9
9
  # the amount of times the action can be executed
10
10
  attr_reader :limit
11
+ # an object used to distinguish users
12
+ attr_reader :identifier
13
+ # users which have already executed this action
14
+ # they are differenciated by the identifier object
15
+ attr_reader :actors
11
16
 
12
17
  ##
13
18
  # :call-seq:
@@ -18,7 +23,7 @@ module Limited
18
23
  # === Example
19
24
  #
20
25
  # Limited::Action.new :do_stuff, 1000
21
- def initialize(name, limit, interval = nil)
26
+ def initialize(name, limit, interval = nil, identifier = [])
22
27
  raise ArgumentError.new("Limited::Action::name needs to be a Symbol") unless name.is_a?(Symbol)
23
28
  raise ArgumentError.new("Limited::Action::limit needs to be an Integer") unless limit.is_a?(Integer)
24
29
  raise ArgumentError.new("Limited::Action::limit needs to be positive") if limit < 0
@@ -27,6 +32,9 @@ module Limited
27
32
  @limit = limit
28
33
  @num_executed = 0
29
34
  @interval = Interval.new(interval.nil? ? :endless : interval)
35
+ @identifier = identifier.is_a?(Limited::Actor::Identifier) ? identifier : Limited::Actor::Identifier.new(*identifier)
36
+
37
+ @actors = []
30
38
  check_new_interval
31
39
  end
32
40
 
@@ -40,34 +48,58 @@ module Limited
40
48
  ##
41
49
  # returns the amount of times the action already
42
50
  # has been executed
43
- def num_executed
51
+ def num_executed(actor_attributes = nil)
44
52
  check_new_interval
45
- @num_executed
53
+ return @num_executed unless actor_attributes.is_a?(Hash)
54
+ actor_num_executed(actor_attributes)
46
55
  end
47
56
 
48
57
  ##
49
58
  # returns how many times the action can be executed
50
59
  # until the given limit is reached
51
- def num_left
60
+ def num_left(actor_attributes = nil)
52
61
  check_new_interval
53
- @limit - @num_executed
62
+ return @limit - @num_executed unless actor_attributes.is_a?(Hash)
63
+ actor_num_left(actor_attributes)
54
64
  end
55
65
 
56
66
  ##
57
67
  # should be called everytime
58
68
  # the action gets executed
59
69
  # so the internal counter is always up-to-date
60
- def executed
70
+ def executed(actor_attributes = nil)
61
71
  check_new_interval
62
- @num_executed += 1
72
+ if actor_attributes.is_a?(Hash)
73
+ actor_executed(actor_attributes)
74
+ else
75
+ @num_executed += 1
76
+ end
63
77
  end
64
78
 
65
79
  ##
66
80
  # wheter the limit of executions
67
81
  # has been exceeded
68
- def limit_reached
82
+ def limit_reached(actor_attributes = nil)
69
83
  check_new_interval
70
- @limit <= @num_executed
84
+ return @limit <= @num_executed unless actor_attributes.is_a?(Hash)
85
+ actor_limit_reached(actor_attributes)
86
+ end
87
+
88
+ ##
89
+ # get a user which can execute this
90
+ # action by the attributes
91
+ #
92
+ # if the actor doesn't exist yet it is created
93
+ def actor(attributes)
94
+ actor = nil
95
+ @actors.each do |current_actor|
96
+ if current_actor.attributes == attributes
97
+ actor = current_actor
98
+ end
99
+ end
100
+
101
+ @actors << actor = Limited::Actor.new(@identifier, attributes) if actor.nil?
102
+ actor
71
103
  end
72
104
 
73
105
  private
@@ -81,7 +113,27 @@ module Limited
81
113
  if @interval.passed?
82
114
  @interval.reset_start
83
115
  @num_executed = 0
116
+ @actors.each do |current_actor|
117
+ current_actor.num_executed = 0
118
+ end
84
119
  end
85
120
  end
121
+
122
+ def actor_num_executed(attributes)
123
+ actor(attributes).num_executed + @num_executed
124
+ end
125
+
126
+ def actor_num_left(attributes)
127
+ @limit - actor_num_executed(attributes)
128
+ end
129
+
130
+ def actor_executed(attributes)
131
+ actor(attributes).execute
132
+ end
133
+
134
+ def actor_limit_reached(attributes)
135
+ @limit <= actor_num_executed(attributes)
136
+ end
137
+
86
138
  end
87
139
  end
@@ -0,0 +1,36 @@
1
+ module Limited
2
+ class Actor
3
+ class Identifier
4
+
5
+ attr_reader :keys
6
+ attr_reader :hash_keys
7
+
8
+ def initialize(*symbols)
9
+ raise ArgumentError.new("The symbols passed need to be unique") unless symbols.uniq.size == symbols.uniq.size
10
+ @keys = symbols.sort
11
+ @hash_keys = {}
12
+ @keys.each do |k|
13
+ raise ArgumentError.new("You need to pass a list of symbols") unless k.is_a?(Symbol)
14
+ @hash_keys[k] = nil
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ attr_reader :attributes
21
+ attr_reader :num_executed
22
+
23
+ def initialize(identifier, values, num_executed = 0)
24
+ raise ArgumentError.new("first parameter needs to be an identifier") unless identifier.is_a?(Identifier)
25
+ raise ArgumentError.new("second parameter needs to be a hash of values") unless values.is_a?(Hash)
26
+ raise ArgumentError.new("the values given in the second parameter needs to match with the keys of the identifier") unless identifier.keys.sort == values.keys.sort
27
+
28
+ @attributes = values
29
+ @num_executed = num_executed
30
+ end
31
+
32
+ def execute
33
+ @num_executed += 1
34
+ end
35
+ end
36
+ end
@@ -11,11 +11,27 @@ 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, interval_length = nil)
15
- action = Limited::Action.new(name, limit, interval_length)
14
+ def self.action(name, options = {})
15
+ raise ArgumentError.new("the options parameter needs to be a hash") unless options.is_a?(Hash)
16
+ limit = options[:amount]
17
+ interval = options[:every] || :endless
18
+ identifier = options[:per].is_a?(Symbol) ? Limited::identifiers[options[:per]] : options[:per]
19
+ action = Limited::Action.new(name, limit, interval, identifier)
16
20
  raise ArgumentError.new("action with name :#{name.to_s} has already been added") if Limited.actions.has_key?(name)
17
21
  Limited.actions[name] = action
18
22
  end
23
+
24
+ ##
25
+ # adds a new identifier to the list of identifiers
26
+ # to be used by actions.
27
+ #
28
+ # raises an ArgumentError if the the name of an
29
+ # identifier has already been taken
30
+ def self.identifier(name, symbols)
31
+ identifier = Limited::Actor::Identifier.new *symbols
32
+ raise ArgumentError.new("identifier with name :#{name.to_s} has already been added") if Limited.identifiers.has_key?(name)
33
+ Limited.identifiers[name] = identifier
34
+ end
19
35
  end
20
36
 
21
37
  ##
@@ -1,3 +1,3 @@
1
1
  module Limited
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/limited.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "limited/version"
2
2
  require "limited/action"
3
3
  require "limited/interval"
4
+ require "limited/actor"
4
5
 
5
6
  module Limited
6
7
  # a hash containing all the
@@ -15,6 +16,12 @@ module Limited
15
16
  @actions
16
17
  end
17
18
 
19
+ @identifiers = {}
20
+
21
+ def self.identifiers
22
+ @identifiers
23
+ end
24
+
18
25
 
19
26
  ##
20
27
  # :call-seq:
@@ -4,7 +4,7 @@ require 'limited'
4
4
  require 'limited/action'
5
5
 
6
6
  describe Limited::Action do
7
- before { @action = Limited::Action.new :do_some_stuff, 1337 }
7
+ before { @action = Limited::Action.new :do_some_stuff, 1337, :endless, [:id] }
8
8
  subject { @action }
9
9
 
10
10
  it { should respond_to(:name) }
@@ -12,6 +12,7 @@ describe Limited::Action do
12
12
  it { should respond_to(:interval) }
13
13
  it { should respond_to(:num_executed) }
14
14
  it { should respond_to(:num_left) }
15
+ it { should respond_to(:identifier) }
15
16
 
16
17
  describe "constructor" do
17
18
  it "should take a name" do
@@ -23,6 +24,16 @@ describe Limited::Action do
23
24
  limit = Limited::Action.new(:do_stuff, 15).limit
24
25
  limit.should eq 15
25
26
  end
27
+
28
+ it "should take an interval" do
29
+ interval = Limited::Action.new(:do_stuff, 15, :minute).interval
30
+ interval.length.should eq 60
31
+ end
32
+
33
+ it "should take an identifier" do
34
+ identifier = Limited::Action.new(:do_stuff, 15, :minute, [:asd]).identifier
35
+ identifier.keys.should eq [:asd]
36
+ end
26
37
  end
27
38
 
28
39
  describe "name" do
@@ -50,13 +61,31 @@ describe Limited::Action do
50
61
  describe "num_executed" do
51
62
  it "should be initialized to 0" do
52
63
  @action.num_executed.should eq 0
64
+ @action.num_executed({id: 21304843}).should eq 0
53
65
  end
54
66
  end
55
67
 
56
68
  describe "executed" do
57
- before { @action.executed }
58
- it "should increase the num_executed counter" do
59
- @action.num_executed.should eq 1
69
+ describe "without actors" do
70
+ before { @action.executed }
71
+ it "should increase the num_executed counter" do
72
+ @action.num_executed.should eq 1
73
+ end
74
+ end
75
+
76
+ describe "with actors" do
77
+ before do
78
+ @actor1 = {id: 3561}
79
+ @actor2 = {id: 5631}
80
+ end
81
+
82
+ it "should increase seperately" do
83
+ @action.executed(@actor1)
84
+ @action.executed(@actor1)
85
+ @action.executed(@actor2)
86
+ @action.num_executed(@actor1).should eq 2
87
+ @action.num_executed(@actor2).should eq 1
88
+ end
60
89
  end
61
90
  end
62
91
 
@@ -74,6 +103,30 @@ describe Limited::Action do
74
103
  @action.executed
75
104
  @action.limit_reached.should be_true
76
105
  end
106
+
107
+ describe "with actors" do
108
+ before do
109
+ @actor1 = {id: 356}
110
+ @actor2 = {id: 563}
111
+ end
112
+
113
+ it "should count seperately" do
114
+ @action.limit_reached(@actor1).should be_false
115
+ @action.limit_reached(@actor2).should be_false
116
+ 1336.times { @action.executed(@actor1) }
117
+ @action.limit_reached(@actor1).should be_false
118
+ @action.limit_reached(@actor2).should be_false
119
+ 1336.times { @action.executed(@actor2) }
120
+ @action.limit_reached(@actor1).should be_false
121
+ @action.limit_reached(@actor2).should be_false
122
+ @action.executed(@actor1)
123
+ @action.limit_reached(@actor1).should be_true
124
+ @action.limit_reached(@actor2).should be_false
125
+ @action.executed(@actor2)
126
+ @action.limit_reached(@actor1).should be_true
127
+ @action.limit_reached(@actor2).should be_true
128
+ end
129
+ end
77
130
  end
78
131
 
79
132
  describe "with an ending interval" do
@@ -97,4 +150,18 @@ describe Limited::Action do
97
150
  end
98
151
  end
99
152
  end
153
+
154
+ describe "actor" do
155
+ before do
156
+ @action.actor(id: 15)
157
+ end
158
+
159
+ it "should create an actor if it doesn't exist yet" do
160
+ expect { @action.actor(id: 1337) }.to change{@action.actors.count}.by(1)
161
+ end
162
+
163
+ it "should always return an Actor object" do
164
+ @action.actor(id: 15).attributes.should eq({id: 15})
165
+ end
166
+ end
100
167
  end
@@ -0,0 +1,36 @@
1
+ describe Limited::Actor::Identifier do
2
+ before(:all) do
3
+ @identifier = Limited::Actor::Identifier.new
4
+ end
5
+ describe "constructor" do
6
+ it "should take a series of symbols used as keys for identification of the actor" do
7
+ expect do
8
+ Limited::Actor::Identifier.new :id
9
+ Limited::Actor::Identifier.new :id, :ip
10
+ Limited::Actor::Identifier.new :id, :ip, :page
11
+ end.not_to raise_error
12
+ end
13
+
14
+ it "should not allow two symbols with the same name" do
15
+ expect { Limited::Actor.new :id, :id }.to raise_error(ArgumentError)
16
+ end
17
+ end
18
+
19
+ describe "keys" do
20
+ it { @identifier.should respond_to(:keys) }
21
+ it "should return all the symbols given in the connstructor as an array" do
22
+ Limited::Actor::Identifier.new(:id).keys.should eq [:id]
23
+ Limited::Actor::Identifier.new(:id, :ip).keys.should eq [:id, :ip]
24
+ Limited::Actor::Identifier.new(:id, :ip, :page).keys.should eq [:id, :ip, :page]
25
+ end
26
+ end
27
+
28
+ describe "empty_hash" do
29
+ it { @identifier.should respond_to(:hash_keys) }
30
+ it "should return all the symbols given in the connstructor used as key in a hash" do
31
+ Limited::Actor::Identifier.new(:id).hash_keys.should eq({ id: nil })
32
+ Limited::Actor::Identifier.new(:id, :ip).hash_keys.should eq({ id: nil, ip: nil })
33
+ Limited::Actor::Identifier.new(:id, :ip, :page).hash_keys.should eq({ id: nil, ip: nil, page: nil })
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ describe Limited::Actor do
2
+ before do
3
+ @identifier = Limited::Actor::Identifier.new :ip, :user_agent
4
+ @actor = Limited::Actor.new @identifier, { ip: '1.3.3.7', user_agent: 'void browser' }
5
+ end
6
+
7
+ describe "constructor" do
8
+ it "should take an identifier, the the values for identifying the actor" do
9
+ expect do
10
+ Limited::Actor.new @identifier, { ip: '1.3.3.7', user_agent: 'void browser' }
11
+ Limited::Actor.new @identifier, { ip: '1.3.3.7', user_agent: '1337 browser' }
12
+ end.not_to raise_error
13
+ end
14
+
15
+ it "should throw an execption if the values for identifying the actor don't match" do
16
+ expect do
17
+ Limited::Actor.new @identifier, { ip: '1.3.3.7' }
18
+ end.to raise_error(ArgumentError)
19
+
20
+ expect do
21
+ Limited::Actor.new @identifier, { ip: '1.3.3.7', user_agent: '123123', stuff: 'asdasd' }
22
+ end.to raise_error(ArgumentError)
23
+ end
24
+ end
25
+
26
+ describe "attributes" do
27
+ it { @actor.should respond_to(:attributes) }
28
+
29
+ it "should return a hash containing the data to identify this actor" do
30
+ @actor.attributes.should eq({ ip: '1.3.3.7', user_agent: 'void browser' })
31
+ end
32
+ end
33
+
34
+ describe "num_executed" do
35
+ before do
36
+ @actor_with_num_executed = Limited::Actor.new @identifier, { ip: '', user_agent: '' }, 15
37
+ end
38
+
39
+ it "should initialize it with 0" do
40
+ @actor.num_executed.should eq 0
41
+ end
42
+
43
+ it "should be settable using the constructor" do
44
+ @actor_with_num_executed .num_executed.should eq 15
45
+ end
46
+ end
47
+
48
+ describe "execute" do
49
+ it "should increase num_executed by 1" do
50
+ expect { @actor.execute }.to change(@actor, :num_executed).by(1)
51
+ end
52
+ end
53
+ end
@@ -7,14 +7,15 @@ 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, :day
10
+ Limited::Config.action :new, amount: 123, every: :day
11
11
  end.to change(Limited, :actions)
12
12
  end
13
13
 
14
14
  describe "the Action objects in the Limited::actions array" do
15
15
  before(:all) do
16
- Limited::Config.action :value_check, 123, :day
17
- Limited::Config.action :value_check1, 3
16
+ Limited::Config.identifier :category, [:id]
17
+ Limited::Config.action :value_check, amount: 123, every: :day
18
+ Limited::Config.action :value_check1, amount: 3, per: :category
18
19
  end
19
20
 
20
21
  it "should have the correct name" do
@@ -31,6 +32,29 @@ describe Limited::Config do
31
32
  Limited.actions[:value_check].interval.length.should eq 24 * 60 * 60
32
33
  Limited.actions[:value_check1].interval.length.should eq 1.0/0.0
33
34
  end
35
+
36
+ it "should have the correct identifier" do
37
+ Limited.actions[:value_check].identifier.keys.should eq []
38
+ Limited.actions[:value_check1].identifier.keys.should eq [:id]
39
+ end
40
+ end
41
+
42
+ describe "the identifier objects in the Limited::identifiers array" do
43
+ before(:all) do
44
+ Limited.configure do
45
+ identifier :section, [:id]
46
+ action :do_some_task, amount: 2, per: :section
47
+ end
48
+ end
49
+
50
+ it "should use the name to store the object" do
51
+ Limited.identifiers[:section].should be_a(Limited::Actor::Identifier)
52
+ end
53
+
54
+
55
+ it "should contain the symbols passed with the Limited::Config.identifier method" do
56
+ Limited.identifiers[:section].keys.should eq [:id]
57
+ end
34
58
  end
35
59
 
36
60
  it "should not be possible to add an action with the same name twice" do
@@ -44,9 +68,17 @@ describe Limited::Config do
44
68
  it "should be possible to add actions via the Limited::configure method" do
45
69
  expect do
46
70
  Limited.configure do
47
- action :some_action, 123
71
+ action :some_action, amount: 123
48
72
  end
49
73
  end.to change(Limited, :actions)
50
74
  end
75
+
76
+ it "should be possible to add identifiers via the Limited::configure method" do
77
+ expect do
78
+ Limited.configure do
79
+ identifier :user, [:user_id]
80
+ end
81
+ end.to change(Limited, :identifiers)
82
+ end
51
83
  end
52
84
  end
data/spec/limited_spec.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Limited.configure do
2
- action :missing_method_test, 13
2
+ action :missing_method_test, amount: 13
3
3
  end
4
4
 
5
5
  describe Limited do
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.1.0
4
+ version: 0.2.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-10 00:00:00.000000000 Z
11
+ date: 2013-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,11 +80,14 @@ files:
80
80
  - Rakefile
81
81
  - lib/limited.rb
82
82
  - lib/limited/action.rb
83
+ - lib/limited/actor.rb
83
84
  - lib/limited/config.rb
84
85
  - lib/limited/interval.rb
85
86
  - lib/limited/version.rb
86
87
  - limited.gemspec
87
88
  - spec/limited_action_spec.rb
89
+ - spec/limited_actor_identifier_spec.rb
90
+ - spec/limited_actor_spec.rb
88
91
  - spec/limited_config_spec.rb
89
92
  - spec/limited_interval_spec.rb
90
93
  - spec/limited_spec.rb
@@ -117,6 +120,8 @@ summary: Some utility functions to help you limit some actions in your applicati
117
120
  your services.
118
121
  test_files:
119
122
  - spec/limited_action_spec.rb
123
+ - spec/limited_actor_identifier_spec.rb
124
+ - spec/limited_actor_spec.rb
120
125
  - spec/limited_config_spec.rb
121
126
  - spec/limited_interval_spec.rb
122
127
  - spec/limited_spec.rb