glebprop 0.0.2 → 0.0.3

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.
@@ -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