limited 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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