simple-feed 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/rubocop.yml +33 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.relaxed-rubocop-2.4.yml +174 -0
- data/.rspec +1 -1
- data/.rubocop.yml +42 -1149
- data/.rubocop_todo.yml +134 -0
- data/.travis.yml +15 -8
- data/Gemfile +2 -0
- data/Guardfile +3 -3
- data/{README.md → README.adoc} +205 -140
- data/Rakefile +6 -7
- data/bin/console +1 -0
- data/examples/shared/provider_example.rb +10 -3
- data/lib/simple-feed.rb +2 -0
- data/lib/simple_feed.rb +2 -0
- data/lib/simplefeed.rb +9 -7
- data/lib/simplefeed/activity/base.rb +2 -0
- data/lib/simplefeed/activity/multi_user.rb +8 -6
- data/lib/simplefeed/activity/single_user.rb +5 -4
- data/lib/simplefeed/dsl.rb +3 -1
- data/lib/simplefeed/dsl/activities.rb +4 -3
- data/lib/simplefeed/dsl/formatter.rb +12 -12
- data/lib/simplefeed/event.rb +4 -3
- data/lib/simplefeed/feed.rb +19 -22
- data/lib/simplefeed/key/template.rb +5 -9
- data/lib/simplefeed/key/type.rb +4 -2
- data/lib/simplefeed/providers.rb +24 -9
- data/lib/simplefeed/providers/base/provider.rb +6 -3
- data/lib/simplefeed/providers/hash.rb +2 -0
- data/lib/simplefeed/providers/hash/paginator.rb +4 -2
- data/lib/simplefeed/providers/hash/provider.rb +11 -21
- data/lib/simplefeed/providers/key.rb +20 -11
- data/lib/simplefeed/providers/proxy.rb +13 -12
- data/lib/simplefeed/providers/redis.rb +2 -0
- data/lib/simplefeed/providers/redis/driver.rb +23 -23
- data/lib/simplefeed/providers/redis/provider.rb +35 -33
- data/lib/simplefeed/providers/redis/stats.rb +12 -13
- data/lib/simplefeed/response.rb +4 -2
- data/lib/simplefeed/version.rb +3 -1
- data/simple-feed.gemspec +16 -14
- metadata +58 -41
data/lib/simplefeed/key/type.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'template'
|
2
4
|
|
3
5
|
module SimpleFeed
|
@@ -11,7 +13,8 @@ module SimpleFeed
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def render(opts = {})
|
14
|
-
|
16
|
+
template.render(opts.merge({ key_type: name,
|
17
|
+
key_marker: marker }))
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -21,6 +24,5 @@ module SimpleFeed
|
|
21
24
|
].map do |type|
|
22
25
|
Type.new(type[:name], type[:marker], DEFAULT_TEXT_TEMPLATE)
|
23
26
|
end
|
24
|
-
|
25
27
|
end
|
26
28
|
end
|
data/lib/simplefeed/providers.rb
CHANGED
@@ -1,33 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'providers/key'
|
2
4
|
require_relative 'providers/proxy'
|
3
5
|
|
4
6
|
module SimpleFeed
|
5
7
|
module Providers
|
6
8
|
@registry = {}
|
7
|
-
|
9
|
+
|
8
10
|
def self.registry
|
9
11
|
@registry
|
10
12
|
end
|
11
|
-
|
13
|
+
|
12
14
|
def self.register(provider_name, provider_class)
|
13
|
-
|
15
|
+
registry[provider_name] = provider_class
|
14
16
|
end
|
15
|
-
|
17
|
+
|
16
18
|
# These methods must be implemented by each Provider, and operation on a given
|
17
19
|
# set of users passed via the user_ids: parameter.
|
18
|
-
ACTIVITY_METHODS = %i
|
20
|
+
ACTIVITY_METHODS = %i[
|
21
|
+
store
|
22
|
+
delete
|
23
|
+
delete_if
|
24
|
+
wipe
|
25
|
+
reset_last_read
|
26
|
+
last_read paginate
|
27
|
+
fetch
|
28
|
+
total_count
|
29
|
+
unread_count
|
30
|
+
].freeze
|
19
31
|
|
20
|
-
# These methods must be implemented in order to gather statistics about each provider's
|
32
|
+
# These optional methods must be implemented in order to gather statistics about each provider's
|
21
33
|
# memory consumption and state.
|
22
|
-
FEED_METHODS = %i
|
34
|
+
FEED_METHODS = %i[
|
35
|
+
total_memory_bytes
|
36
|
+
total_users
|
37
|
+
].freeze
|
23
38
|
|
24
|
-
REQUIRED_METHODS = ACTIVITY_METHODS + FEED_METHODS
|
39
|
+
REQUIRED_METHODS = (ACTIVITY_METHODS + FEED_METHODS).freeze
|
25
40
|
|
26
41
|
def self.define_provider_methods(klass, prefix = nil, &block)
|
27
42
|
# Methods on the class instance
|
28
43
|
klass.class_eval do
|
29
44
|
SimpleFeed::Providers::REQUIRED_METHODS.each do |m|
|
30
|
-
method_name = prefix ? "#{prefix}_#{m
|
45
|
+
method_name = prefix ? "#{prefix}_#{m}".to_sym : m
|
31
46
|
define_method(method_name) do |*args, **opts, &b|
|
32
47
|
block.call(self, m, *args, **opts, &b)
|
33
48
|
end
|
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'simplefeed/providers/key'
|
4
|
+
require 'date'
|
5
|
+
require 'time'
|
2
6
|
|
3
7
|
module SimpleFeed
|
4
8
|
module Providers
|
@@ -27,7 +31,7 @@ module SimpleFeed
|
|
27
31
|
at = at.to_time if at.respond_to?(:to_time)
|
28
32
|
at = at.to_f if at.respond_to?(:to_f)
|
29
33
|
|
30
|
-
if
|
34
|
+
if respond_to?(:reset_last_read)
|
31
35
|
reset_last_read(user_ids: user_ids, at: at)
|
32
36
|
else
|
33
37
|
raise ArgumentError, "Class #{self.class} does not implement #reset_last_read method"
|
@@ -70,7 +74,7 @@ module SimpleFeed
|
|
70
74
|
def with_response(response = nil)
|
71
75
|
response ||= SimpleFeed::Response.new
|
72
76
|
yield(response)
|
73
|
-
if
|
77
|
+
if respond_to?(:transform_response)
|
74
78
|
response.transform do |user_id, result|
|
75
79
|
# calling into a subclass
|
76
80
|
transform_response(user_id, result)
|
@@ -78,7 +82,6 @@ module SimpleFeed
|
|
78
82
|
end
|
79
83
|
response
|
80
84
|
end
|
81
|
-
|
82
85
|
end
|
83
86
|
end
|
84
87
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SimpleFeed
|
2
4
|
module Providers
|
3
5
|
module Hash
|
@@ -6,7 +8,6 @@ module SimpleFeed
|
|
6
8
|
#
|
7
9
|
# Of course this is not very efficient, because it requires fetching all events for the user.
|
8
10
|
module Paginator
|
9
|
-
|
10
11
|
def paginate(user_ids:, page: nil, per_page: feed.per_page, &block)
|
11
12
|
response = feed.fetch(user_ids: user_ids)
|
12
13
|
response = SimpleFeed::Response.new(response.to_h)
|
@@ -16,11 +17,12 @@ module SimpleFeed
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def paginate_items(items, page: nil, per_page: nil)
|
19
|
-
|
20
|
+
page && page > 0 ? items[((page - 1) * per_page)...(page * per_page)] : items
|
20
21
|
end
|
21
22
|
|
22
23
|
def order_events(events, &block)
|
23
24
|
return nil unless events
|
25
|
+
|
24
26
|
events.sort do |a, b|
|
25
27
|
block ? yield(a, b) : b.at <=> a.at
|
26
28
|
end
|
@@ -1,11 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base62-rb'
|
2
4
|
require 'hashie'
|
3
5
|
require 'set'
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'knjrbfw'
|
7
|
-
rescue LoadError
|
8
|
-
end
|
6
|
+
require 'objspace'
|
9
7
|
|
10
8
|
require 'simplefeed/event'
|
11
9
|
require_relative 'paginator'
|
@@ -21,10 +19,10 @@ module SimpleFeed
|
|
21
19
|
include SimpleFeed::Providers::Hash::Paginator
|
22
20
|
|
23
21
|
def self.from_yaml(file)
|
24
|
-
|
22
|
+
new(YAML.parse(File.read(file)))
|
25
23
|
end
|
26
24
|
|
27
|
-
def initialize(
|
25
|
+
def initialize(opts)
|
28
26
|
self.h = {}
|
29
27
|
h.merge!(opts)
|
30
28
|
end
|
@@ -45,14 +43,12 @@ module SimpleFeed
|
|
45
43
|
end
|
46
44
|
end
|
47
45
|
|
48
|
-
def delete_if(user_ids
|
46
|
+
def delete_if(user_ids:)
|
49
47
|
with_response_batched(user_ids) do |key|
|
50
48
|
activity(key).map do |event|
|
51
49
|
if yield(event, key.user_id)
|
52
50
|
__delete(key, event)
|
53
51
|
event
|
54
|
-
else
|
55
|
-
nil
|
56
52
|
end
|
57
53
|
end.compact
|
58
54
|
end
|
@@ -60,7 +56,7 @@ module SimpleFeed
|
|
60
56
|
|
61
57
|
def wipe(user_ids:)
|
62
58
|
with_response_batched(user_ids) do |key|
|
63
|
-
deleted = activity(key).
|
59
|
+
deleted = !activity(key).empty?
|
64
60
|
wipe_user_record(key)
|
65
61
|
deleted
|
66
62
|
end
|
@@ -76,7 +72,7 @@ module SimpleFeed
|
|
76
72
|
|
77
73
|
with_response_batched(user_ids) do |key|
|
78
74
|
activity = activity(key)
|
79
|
-
result =
|
75
|
+
result = page && page > 0 ? activity[((page - 1) * per_page)...(page * per_page)] : activity
|
80
76
|
with_total ? { events: result, total_count: activity.length } : result
|
81
77
|
end
|
82
78
|
end
|
@@ -122,16 +118,11 @@ module SimpleFeed
|
|
122
118
|
end
|
123
119
|
|
124
120
|
def total_memory_bytes
|
125
|
-
|
126
|
-
analyzer = Knj::Memory_analyzer::Object_size_counter.new(self.h)
|
127
|
-
analyzer.calculate_size
|
128
|
-
else
|
129
|
-
raise LoadError, 'Please run "gem install knjrbfw" to get accurate hash size'
|
130
|
-
end
|
121
|
+
ObjectSpace.memsize_of(h)
|
131
122
|
end
|
132
123
|
|
133
124
|
def total_users
|
134
|
-
|
125
|
+
h.size
|
135
126
|
end
|
136
127
|
|
137
128
|
private
|
@@ -140,7 +131,6 @@ module SimpleFeed
|
|
140
131
|
# Methods below operate on a single user only
|
141
132
|
#
|
142
133
|
|
143
|
-
|
144
134
|
def changed_activity_size?(key)
|
145
135
|
ua = activity(key)
|
146
136
|
size_before = ua.size
|
@@ -181,7 +171,7 @@ module SimpleFeed
|
|
181
171
|
end
|
182
172
|
end
|
183
173
|
|
184
|
-
def __last_read(key,
|
174
|
+
def __last_read(key, _value = nil)
|
185
175
|
user_record(key)[:last_read]
|
186
176
|
end
|
187
177
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base62-rb'
|
2
4
|
require 'hashie/mash'
|
3
5
|
require 'simplefeed/key/template'
|
@@ -22,36 +24,40 @@ module SimpleFeed
|
|
22
24
|
def_delegators :@key_template, :key_names, :key_types
|
23
25
|
|
24
26
|
def initialize(user_id, key_template)
|
25
|
-
self.user_id
|
27
|
+
self.user_id = user_id
|
26
28
|
self.key_template = key_template
|
27
29
|
|
28
30
|
define_key_methods
|
29
31
|
end
|
30
32
|
|
33
|
+
# Defines #data and #meta methods.
|
31
34
|
def define_key_methods
|
32
35
|
key_template.key_types.each do |type|
|
33
36
|
key_name = type.name
|
34
|
-
|
35
|
-
self.class.send(:define_method, key_name) do
|
36
|
-
instance_variable_get("@#{key_name}") ||
|
37
|
-
instance_variable_set("@#{key_name}", type.render(render_options))
|
38
|
-
end
|
39
|
-
end
|
37
|
+
next if respond_to?(key_name)
|
40
38
|
|
39
|
+
self.class.send(:define_method, key_name) do
|
40
|
+
instance_variable_get("@#{key_name}") ||
|
41
|
+
instance_variable_set("@#{key_name}", type.render(render_options))
|
42
|
+
end
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
46
|
def base62_user_id
|
45
|
-
@base62_user_id ||=
|
47
|
+
@base62_user_id ||= if user_id.is_a?(Numeric)
|
48
|
+
::Base62.encode(user_id)
|
49
|
+
else
|
50
|
+
rot13(user_id.to_s)
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
def keys
|
49
|
-
key_names.map { |name|
|
55
|
+
key_names.map { |name| send(name) }
|
50
56
|
end
|
51
57
|
|
52
58
|
def render_options
|
53
59
|
key_template.render_options.merge!({
|
54
|
-
'user_id'
|
60
|
+
'user_id' => user_id,
|
55
61
|
'base62_user_id' => base62_user_id
|
56
62
|
})
|
57
63
|
end
|
@@ -66,7 +72,10 @@ module SimpleFeed
|
|
66
72
|
|
67
73
|
private
|
68
74
|
|
75
|
+
def rot13(value)
|
76
|
+
value.tr('abcdefghijklmnopqrstuvwxyz',
|
77
|
+
'nopqrstuvwxyzabcdefghijklm')
|
78
|
+
end
|
69
79
|
end
|
70
80
|
end
|
71
81
|
end
|
72
|
-
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SimpleFeed
|
2
4
|
module Providers
|
3
5
|
class Proxy
|
@@ -6,19 +8,18 @@ module SimpleFeed
|
|
6
8
|
def self.from(definition)
|
7
9
|
if definition.is_a?(::Hash)
|
8
10
|
::SimpleFeed.symbolize!(definition)
|
9
|
-
|
11
|
+
new(definition[:klass], *definition[:args], **definition[:opts])
|
10
12
|
else
|
11
|
-
|
13
|
+
new(definition)
|
12
14
|
end
|
13
|
-
|
14
15
|
end
|
15
16
|
|
16
17
|
def initialize(provider_or_klass, *args, **options)
|
17
|
-
if provider_or_klass.is_a?(::String)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
self.provider = if provider_or_klass.is_a?(::String)
|
19
|
+
::Object.const_get(provider_or_klass).new(*args, **options)
|
20
|
+
else
|
21
|
+
provider_or_klass
|
22
|
+
end
|
22
23
|
|
23
24
|
SimpleFeed::Providers::REQUIRED_METHODS.each do |m|
|
24
25
|
raise ArgumentError, "Invalid provider #{provider.class}\nMethod '#{m}' is required." unless provider.respond_to?(m)
|
@@ -26,11 +27,11 @@ module SimpleFeed
|
|
26
27
|
end
|
27
28
|
|
28
29
|
# Forward all other method calls to Provider
|
29
|
-
def method_missing(name, *args, &block)
|
30
|
-
if
|
31
|
-
|
30
|
+
def method_missing(name, *args, **opts, &block)
|
31
|
+
if provider&.respond_to?(name)
|
32
|
+
provider.send(name, *args, **opts, &block)
|
32
33
|
else
|
33
|
-
super(name, *args, &block)
|
34
|
+
super(name, *args, **opts, &block)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'redis'
|
2
4
|
require 'redis/connection/hiredis'
|
3
5
|
require 'connection_pool'
|
@@ -12,10 +14,10 @@ module SimpleFeed
|
|
12
14
|
@debug = ENV['REDIS_DEBUG']
|
13
15
|
|
14
16
|
def self.debug?
|
15
|
-
|
17
|
+
debug
|
16
18
|
end
|
17
19
|
|
18
|
-
def self.with_debug
|
20
|
+
def self.with_debug
|
19
21
|
previous_value = SimpleFeed::Providers::Redis.debug
|
20
22
|
SimpleFeed::Providers::Redis.debug = true
|
21
23
|
result = yield if block_given?
|
@@ -48,10 +50,11 @@ module SimpleFeed
|
|
48
50
|
colors = [:blue, nil, :blue, :blue, :yellow, :cyan, nil, :blue]
|
49
51
|
components = [
|
50
52
|
Time.now.strftime('%H:%M:%S.%L'), ' rtt=',
|
51
|
-
(sprintf '%.5f', delta*1000), ' ms ',
|
53
|
+
(sprintf '%.5f', delta * 1000), ' ms ',
|
52
54
|
(sprintf '%15s ', m.to_s.upcase),
|
53
55
|
(sprintf '%-40s', args.inspect.gsub(/[",\[\]]/, '')), ' ⇒ ',
|
54
|
-
(result.is_a?(::Redis::Future) ? '' : result.to_s)
|
56
|
+
(result.is_a?(::Redis::Future) ? '' : result.to_s)
|
57
|
+
]
|
55
58
|
components.each_with_index do |component, index|
|
56
59
|
color = self.class.disable_color ? nil : colors[index]
|
57
60
|
component = component.send(color) if color
|
@@ -71,18 +74,16 @@ module SimpleFeed
|
|
71
74
|
|
72
75
|
attr_accessor :pool
|
73
76
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
SimpleFeed::Redis::Driver.new(
|
79
|
-
SimpleFeed::Redis::Driver.new(redis:
|
80
|
-
SimpleFeed::Redis::Driver.new(redis:
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
def initialize(**opts)
|
85
|
-
if opts[:pool] && opts[:pool].respond_to?(:with)
|
77
|
+
#
|
78
|
+
# Various ways of defining a new Redis driver:
|
79
|
+
#
|
80
|
+
# SimpleFeed::Redis::Driver.new(pool: ConnectionPool.new(size: 2) { Redis.new })
|
81
|
+
# SimpleFeed::Redis::Driver.new(redis: -> { Redis.new }, pool_size: 2)
|
82
|
+
# SimpleFeed::Redis::Driver.new(redis: Redis.new)
|
83
|
+
# SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, timeout: 0.2 }, pool_size: 1)
|
84
|
+
#
|
85
|
+
def initialize(opts)
|
86
|
+
if opts[:pool]&.respond_to?(:with)
|
86
87
|
self.pool = opts[:pool]
|
87
88
|
|
88
89
|
elsif opts[:redis]
|
@@ -104,7 +105,7 @@ SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, tim
|
|
104
105
|
end
|
105
106
|
end
|
106
107
|
|
107
|
-
raise ArgumentError, "Unable to construct Redis connection from arguments: #{opts.inspect}" unless
|
108
|
+
raise ArgumentError, "Unable to construct Redis connection from arguments: #{opts.inspect}" unless pool&.respond_to?(:with)
|
108
109
|
end
|
109
110
|
|
110
111
|
%i(set get incr decr setex expire del setnx exists zadd zrange).each do |method|
|
@@ -117,7 +118,7 @@ SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, tim
|
|
117
118
|
alias_method :rm, :del
|
118
119
|
alias_method :exists?, :exists
|
119
120
|
|
120
|
-
def exec(redis_method, *args, **
|
121
|
+
def exec(redis_method, *args, **_opts, &block)
|
121
122
|
send_proc = redis_method if redis_method.respond_to?(:call)
|
122
123
|
send_proc ||= ->(redis) { redis.send(redis_method, *args, &block) }
|
123
124
|
|
@@ -125,7 +126,7 @@ SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, tim
|
|
125
126
|
end
|
126
127
|
|
127
128
|
class MockRedis
|
128
|
-
def method_missing(name, *args, &block)
|
129
|
+
def method_missing(name, *args, **_opts, &block)
|
129
130
|
puts "calling redis.#{name}(#{args.to_s.gsub(/[\[\]]/, '')}) { #{block ? block.call : nil} }"
|
130
131
|
end
|
131
132
|
end
|
@@ -133,7 +134,7 @@ SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, tim
|
|
133
134
|
def with_redis
|
134
135
|
with_retries do
|
135
136
|
pool.with do |redis|
|
136
|
-
yield(
|
137
|
+
yield(debug? ? LoggingRedis.new(redis) : redis)
|
137
138
|
end
|
138
139
|
end
|
139
140
|
end
|
@@ -170,13 +171,12 @@ SimpleFeed::Redis::Driver.new(redis: { host: 'localhost', port: 6379, db: 1, tim
|
|
170
171
|
on_error e
|
171
172
|
end
|
172
173
|
rescue ::Redis::CommandError => e
|
173
|
-
|
174
|
+
e.message =~ /loading/i || e.message =~ /connection/i ? on_error(e) : raise(e)
|
174
175
|
end
|
175
176
|
|
176
177
|
def on_error(e)
|
177
|
-
raise Error
|
178
|
+
raise Error, e
|
178
179
|
end
|
179
|
-
|
180
180
|
end
|
181
181
|
end
|
182
182
|
end
|