glebprop 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Glebprop
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/glebprop.rb CHANGED
@@ -1,8 +1,117 @@
1
- puts 'loading glebprop gem'
2
-
3
1
  require "glebprop/version"
4
- require 'prop'
2
+ require 'digest/md5'
5
3
 
6
- module Glebprop
7
- # Your code goes here...
4
+ class Object
5
+ def define_prop_class_method(name, &blk)
6
+ (class << self; self; end).instance_eval { define_method(name, &blk) }
7
+ end
8
8
  end
9
+
10
+ class Glebprop
11
+ class RateLimitExceededError < RuntimeError
12
+ attr_accessor :handle, :retry_after
13
+
14
+ def self.create(handle, key, threshold)
15
+ error = new("#{handle} threshold of #{threshold} exceeded for key '#{key}'")
16
+ error.handle = handle
17
+ error.retry_after = threshold - Time.now.to_i % threshold if threshold > 0
18
+ raise error
19
+ end
20
+ end
21
+
22
+ class << self
23
+ attr_accessor :handles, :reader, :writer
24
+
25
+ def read(&blk)
26
+ self.reader = blk
27
+ end
28
+
29
+ def write(&blk)
30
+ self.writer = blk
31
+ end
32
+
33
+ def increment(&blk)
34
+ self.incrementer = blk
35
+ end
36
+
37
+ def incrementer=(value)
38
+ @incrementer = value
39
+ end
40
+
41
+ def incrementer
42
+ @incrementer ? Proc.new { |key, inc| @incrementer.call(key, inc) } :
43
+ Proc.new { |key, inc| self.writer.call(key, (self.reader.call(key) || 0).to_i + inc) }
44
+ end
45
+
46
+ def defaults(handle, defaults)
47
+ raise RuntimeError.new("Invalid threshold setting") unless defaults[:threshold].to_i > 0
48
+ raise RuntimeError.new("Invalid interval setting") unless defaults[:interval].to_i > 0
49
+
50
+ self.handles ||= {}
51
+ self.handles[handle] = defaults
52
+ end
53
+
54
+ def throttle!(handle, key = nil, options = {})
55
+ options = sanitized_prop_options(handle, key, options)
56
+ cache_key = sanitized_prop_key(key, options[:interval])
57
+ counter = reader.call(cache_key).to_i
58
+
59
+ incrementer.call(cache_key, [ 1, options[:increment].to_i ].max)
60
+
61
+ if counter >= options[:threshold]
62
+ raise Glebprop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold])
63
+ end
64
+ end
65
+
66
+ def throttled?(handle, key = nil, options = {})
67
+ options = sanitized_prop_options(handle, key, options)
68
+ cache_key = sanitized_prop_key(key, options[:interval])
69
+
70
+ reader.call(cache_key).to_i >= options[:threshold]
71
+ end
72
+
73
+ def reset(handle, key = nil, options = {})
74
+ options = sanitized_prop_options(handle, key, options)
75
+ cache_key = sanitized_prop_key(key, options[:interval])
76
+
77
+ writer.call(cache_key, 0)
78
+ end
79
+
80
+ def query(handle, key = nil, options = {})
81
+ options = sanitized_prop_options(handle, key, options)
82
+ cache_key = sanitized_prop_key(key, options[:interval])
83
+
84
+ reader.call(cache_key).to_i
85
+ end
86
+
87
+ private
88
+
89
+ # Builds the expiring cache key
90
+ def sanitized_prop_key(key, interval)
91
+ window = (Time.now.to_i / interval)
92
+ cache_key = "#{normalize_cache_key(key)}/#{ window }"
93
+ "prop/#{Digest::MD5.hexdigest(cache_key)}"
94
+ end
95
+
96
+ # Sanitizes the option set and sets defaults
97
+ def sanitized_prop_options(handle, key, options)
98
+ defaults = (handles || {})[handle] || {}
99
+ return {
100
+ :key => normalize_cache_key(key),
101
+ :increment => defaults[:increment],
102
+ :threshold => defaults[:threshold].to_i,
103
+ :interval => defaults[:interval].to_i
104
+ }.merge(options)
105
+ end
106
+
107
+ # Simple key expansion only supports arrays and primitives
108
+ def normalize_cache_key(key)
109
+ if key.is_a?(Array)
110
+ key.map { |part| normalize_cache_key(part) }.join('/')
111
+ else
112
+ key.to_s
113
+ end
114
+ end
115
+
116
+ end
117
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glebprop
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gleb Pomykalov
@@ -34,7 +34,6 @@ files:
34
34
  - glebprop.gemspec
35
35
  - lib/glebprop.rb
36
36
  - lib/glebprop/version.rb
37
- - lib/prop.rb
38
37
  homepage: ""
39
38
  licenses: []
40
39
 
data/lib/prop.rb DELETED
@@ -1,118 +0,0 @@
1
- puts 'loading prop'
2
-
3
- require 'digest/md5'
4
-
5
- class Object
6
- def define_prop_class_method(name, &blk)
7
- (class << self; self; end).instance_eval { define_method(name, &blk) }
8
- end
9
- end
10
-
11
- class Prop
12
- class RateLimitExceededError < RuntimeError
13
- attr_accessor :handle, :retry_after
14
-
15
- def self.create(handle, key, threshold)
16
- error = new("#{handle} threshold of #{threshold} exceeded for key '#{key}'")
17
- error.handle = handle
18
- error.retry_after = threshold - Time.now.to_i % threshold if threshold > 0
19
- raise error
20
- end
21
- end
22
-
23
- class << self
24
- attr_accessor :handles, :reader, :writer
25
-
26
- def read(&blk)
27
- self.reader = blk
28
- end
29
-
30
- def write(&blk)
31
- self.writer = blk
32
- end
33
-
34
- def increment(&blk)
35
- self.incrementer = blk
36
- end
37
-
38
- def incrementer=(value)
39
- @incrementer = value
40
- end
41
-
42
- def incrementer
43
- @incrementer ? Proc.new { |key, inc| @incrementer.call(key, inc) } :
44
- Proc.new { |key, inc| self.writer.call(key, (self.reader.call(key) || 0).to_i + inc) }
45
- end
46
-
47
- def defaults(handle, defaults)
48
- raise RuntimeError.new("Invalid threshold setting") unless defaults[:threshold].to_i > 0
49
- raise RuntimeError.new("Invalid interval setting") unless defaults[:interval].to_i > 0
50
-
51
- self.handles ||= {}
52
- self.handles[handle] = defaults
53
- end
54
-
55
- def throttle!(handle, key = nil, options = {})
56
- options = sanitized_prop_options(handle, key, options)
57
- cache_key = sanitized_prop_key(key, options[:interval])
58
- counter = reader.call(cache_key).to_i
59
-
60
- incrementer.call(cache_key, [ 1, options[:increment].to_i ].max)
61
-
62
- if counter >= options[:threshold]
63
- raise Prop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold])
64
- end
65
- end
66
-
67
- def throttled?(handle, key = nil, options = {})
68
- options = sanitized_prop_options(handle, key, options)
69
- cache_key = sanitized_prop_key(key, options[:interval])
70
-
71
- reader.call(cache_key).to_i >= options[:threshold]
72
- end
73
-
74
- def reset(handle, key = nil, options = {})
75
- options = sanitized_prop_options(handle, key, options)
76
- cache_key = sanitized_prop_key(key, options[:interval])
77
-
78
- writer.call(cache_key, 0)
79
- end
80
-
81
- def query(handle, key = nil, options = {})
82
- options = sanitized_prop_options(handle, key, options)
83
- cache_key = sanitized_prop_key(key, options[:interval])
84
-
85
- reader.call(cache_key).to_i
86
- end
87
-
88
- private
89
-
90
- # Builds the expiring cache key
91
- def sanitized_prop_key(key, interval)
92
- window = (Time.now.to_i / interval)
93
- cache_key = "#{normalize_cache_key(key)}/#{ window }"
94
- "prop/#{Digest::MD5.hexdigest(cache_key)}"
95
- end
96
-
97
- # Sanitizes the option set and sets defaults
98
- def sanitized_prop_options(handle, key, options)
99
- defaults = (handles || {})[handle] || {}
100
- return {
101
- :key => normalize_cache_key(key),
102
- :increment => defaults[:increment],
103
- :threshold => defaults[:threshold].to_i,
104
- :interval => defaults[:interval].to_i
105
- }.merge(options)
106
- end
107
-
108
- # Simple key expansion only supports arrays and primitives
109
- def normalize_cache_key(key)
110
- if key.is_a?(Array)
111
- key.map { |part| normalize_cache_key(part) }.join('/')
112
- else
113
- key.to_s
114
- end
115
- end
116
-
117
- end
118
- end