simple-feed 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +30 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +14 -0
- data/.yardopts +3 -0
- data/Gemfile +4 -0
- data/Guardfile +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +457 -0
- data/Rakefile +16 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/hash_provider_example.rb +24 -0
- data/examples/redis_provider_example.rb +28 -0
- data/examples/shared/provider_example.rb +66 -0
- data/lib/simple-feed.rb +1 -0
- data/lib/simple_feed.rb +1 -0
- data/lib/simplefeed.rb +61 -0
- data/lib/simplefeed/activity/base.rb +14 -0
- data/lib/simplefeed/activity/multi_user.rb +71 -0
- data/lib/simplefeed/activity/single_user.rb +70 -0
- data/lib/simplefeed/dsl.rb +38 -0
- data/lib/simplefeed/dsl/activities.rb +70 -0
- data/lib/simplefeed/dsl/formatter.rb +109 -0
- data/lib/simplefeed/event.rb +87 -0
- data/lib/simplefeed/feed.rb +78 -0
- data/lib/simplefeed/providers.rb +45 -0
- data/lib/simplefeed/providers/base/provider.rb +84 -0
- data/lib/simplefeed/providers/hash.rb +8 -0
- data/lib/simplefeed/providers/hash/paginator.rb +31 -0
- data/lib/simplefeed/providers/hash/provider.rb +169 -0
- data/lib/simplefeed/providers/proxy.rb +38 -0
- data/lib/simplefeed/providers/redis.rb +9 -0
- data/lib/simplefeed/providers/redis/boot_info.yml +99 -0
- data/lib/simplefeed/providers/redis/driver.rb +158 -0
- data/lib/simplefeed/providers/redis/provider.rb +255 -0
- data/lib/simplefeed/providers/redis/stats.rb +85 -0
- data/lib/simplefeed/providers/serialization/key.rb +82 -0
- data/lib/simplefeed/response.rb +77 -0
- data/lib/simplefeed/version.rb +3 -0
- data/man/running-the-example.png +0 -0
- data/man/sf-example.png +0 -0
- data/simple-feed.gemspec +44 -0
- metadata +333 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'simplefeed/dsl'
|
2
|
+
require 'simplefeed/activity/single_user'
|
3
|
+
require 'simplefeed/activity/multi_user'
|
4
|
+
module SimpleFeed
|
5
|
+
module DSL
|
6
|
+
module Formatter
|
7
|
+
include SimpleFeed::DSL
|
8
|
+
|
9
|
+
attr_accessor :activity, :feed
|
10
|
+
|
11
|
+
def color_dump(_activity = activity)
|
12
|
+
_activity = if _activity.is_a?(SimpleFeed::Activity::SingleUser)
|
13
|
+
_activity.feed.activity([_activity.user_id])
|
14
|
+
else
|
15
|
+
_activity
|
16
|
+
end
|
17
|
+
_puts
|
18
|
+
|
19
|
+
header do
|
20
|
+
field('Feed Name', feed.name, "\n")
|
21
|
+
field('Provider', feed.provider.provider.class, "\n")
|
22
|
+
field('Max Size', feed.max_size, "\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
with_activity(_activity) do
|
26
|
+
_activity.each do |user_id|
|
27
|
+
_last_event_at = nil
|
28
|
+
_last_read = (last_read[user_id] || 0.0).to_f
|
29
|
+
|
30
|
+
[['User ID', user_id, "\n"],
|
31
|
+
['Activities', sprintf('%d total, %d unread', total_count[user_id], unread_count[user_id]), "\n"],
|
32
|
+
['Last Read', _last_read ? Time.at(_last_read) : 'N/A'],
|
33
|
+
].each do |field, value, *args|
|
34
|
+
field(field, value, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
_puts; hr '¨'
|
38
|
+
|
39
|
+
_events = fetch[user_id]
|
40
|
+
_events_count = _events.size
|
41
|
+
_events.each_with_index do |_event, _index|
|
42
|
+
|
43
|
+
if _last_event_at.nil? && _event.at < _last_read
|
44
|
+
print_last_read_separator(_last_read)
|
45
|
+
elsif _last_event_at && _last_read < _last_event_at && _last_read > _event.at
|
46
|
+
print_last_read_separator(_last_read)
|
47
|
+
end
|
48
|
+
|
49
|
+
_last_event_at = _event.at # float
|
50
|
+
_print "[%2d] %16s %s\n", _index, _event.time.strftime(TIME_FORMAT).blue.bold, _event.value
|
51
|
+
if _index == _events_count - 1 && _last_read < _event.at
|
52
|
+
print_last_read_separator(_last_read)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def print_last_read_separator(lr)
|
60
|
+
_print ">>>> %16s <<<< last read\n", Time.at(lr).strftime(TIME_FORMAT).red.bold
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
@print_method = :printf
|
65
|
+
|
66
|
+
class << self
|
67
|
+
attr_accessor :print_method
|
68
|
+
end
|
69
|
+
|
70
|
+
def _print(*args, **opts, &block)
|
71
|
+
send(SimpleFeed::DSL.print_method, *args, **opts, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def _puts(*args)
|
75
|
+
send(SimpleFeed::DSL.print_method, "\n" + args.join)
|
76
|
+
end
|
77
|
+
|
78
|
+
def field_label(text)
|
79
|
+
sprintf ' %20s ', text
|
80
|
+
end
|
81
|
+
|
82
|
+
TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
|
83
|
+
|
84
|
+
def field_value(value)
|
85
|
+
case value
|
86
|
+
when Numeric
|
87
|
+
sprintf '%-20d', value
|
88
|
+
when Time
|
89
|
+
sprintf '%-30s', value.strftime(TIME_FORMAT)
|
90
|
+
else
|
91
|
+
sprintf '%-20s', value.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def field(label, value, sep = '')
|
96
|
+
_print field_label(label).italic + field_value(value).cyan.bold + sep
|
97
|
+
end
|
98
|
+
|
99
|
+
def hr(char = '—')
|
100
|
+
_print (char * 75 + "\n").magenta
|
101
|
+
end
|
102
|
+
|
103
|
+
def header(message = nil)
|
104
|
+
hr
|
105
|
+
block_given? ? yield : _print(message.capitalize.magenta.bold + "\n")
|
106
|
+
hr
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module SimpleFeed
|
4
|
+
class Event
|
5
|
+
attr_accessor :value, :at
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def initialize(*args, value: nil, at: Time.now)
|
9
|
+
if args && args.size > 0
|
10
|
+
self.value = args[0]
|
11
|
+
self.at = args[1] || at
|
12
|
+
else
|
13
|
+
self.value = value
|
14
|
+
self.at = at
|
15
|
+
end
|
16
|
+
|
17
|
+
self.at = self.at.to_f
|
18
|
+
|
19
|
+
validate!
|
20
|
+
end
|
21
|
+
|
22
|
+
def time
|
23
|
+
Time.at(at)
|
24
|
+
end
|
25
|
+
|
26
|
+
def <=>(other)
|
27
|
+
-self.at <=> -other.at
|
28
|
+
end
|
29
|
+
|
30
|
+
def eql?(other)
|
31
|
+
self == other
|
32
|
+
end
|
33
|
+
|
34
|
+
def ==(other)
|
35
|
+
other.is_a?(SimpleFeed::Event) &&
|
36
|
+
self.value == other.value
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_h
|
40
|
+
{ value: value, at: at, time: time }
|
41
|
+
end
|
42
|
+
|
43
|
+
def hash
|
44
|
+
self.value.hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json
|
48
|
+
to_h.to_json
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_yaml
|
52
|
+
YAML.dump(to_h)
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
"Event Value: [#{value}], epoch: [#{at}], time: [#{time}]"
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_color_s
|
60
|
+
counter = 0
|
61
|
+
to_s.gsub(/,/, '-').split(/[\[\]]/).map do |word|
|
62
|
+
counter += 1
|
63
|
+
counter.even? ? word.bold.yellow + "\n": word.blue
|
64
|
+
end.join('').gsub(/[\n] /, '')
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def validate!
|
75
|
+
unless self.value && self.at
|
76
|
+
raise ArgumentError, "Required arguments missing, value=[#{value}], at=[#{at}]"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def copy(&block)
|
81
|
+
copy = self.clone
|
82
|
+
copy.instance_eval(&block)
|
83
|
+
copy
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'providers'
|
2
|
+
require_relative 'activity/base'
|
3
|
+
module SimpleFeed
|
4
|
+
class Feed
|
5
|
+
|
6
|
+
attr_accessor :per_page, :max_size, :batch_size, :meta, :namespace
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
SimpleFeed::Providers.define_provider_methods(self) do |feed, method, opts, &block|
|
10
|
+
feed.provider.send(method, **opts, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
@name = name.underscore.to_sym unless name.is_a?(Symbol)
|
16
|
+
# set the defaults if not passed in
|
17
|
+
@meta = {}
|
18
|
+
@namespace = nil
|
19
|
+
@per_page ||= 50
|
20
|
+
@max_size ||= 1000
|
21
|
+
@batch_size||= 10
|
22
|
+
@proxy = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def provider=(definition)
|
26
|
+
@proxy = Providers::Proxy.from(definition)
|
27
|
+
@proxy.feed = self
|
28
|
+
@proxy
|
29
|
+
end
|
30
|
+
|
31
|
+
def provider
|
32
|
+
@proxy
|
33
|
+
end
|
34
|
+
|
35
|
+
def provider_type
|
36
|
+
SimpleFeed::Providers::Base::Provider.class_to_registry(@proxy.provider.class)
|
37
|
+
end
|
38
|
+
|
39
|
+
def user_activity(user_id)
|
40
|
+
Activity::SingleUser.new(user_id: user_id, feed: self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def users_activity(user_ids)
|
44
|
+
Activity::MultiUser.new(user_ids: user_ids, feed: self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Depending on the argument returns either +SingleUserActivity+ or +MultiUserActivity+
|
48
|
+
def activity(one_or_more_users)
|
49
|
+
one_or_more_users.is_a?(Array) ?
|
50
|
+
users_activity(one_or_more_users) :
|
51
|
+
user_activity(one_or_more_users)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @deprecated Please use {#activity} instead
|
55
|
+
def for(*args)
|
56
|
+
STDERR.puts 'WARNING: method #for is deprecated, please use #activity'
|
57
|
+
activity(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
def configure(hash = {})
|
61
|
+
SimpleFeed.symbolize!(hash)
|
62
|
+
class_attrs.each do |attr|
|
63
|
+
self.send("#{attr}=", hash[attr]) if hash.key?(attr)
|
64
|
+
end
|
65
|
+
yield self if block_given?
|
66
|
+
end
|
67
|
+
|
68
|
+
def eql?(other)
|
69
|
+
other.class == self.class &&
|
70
|
+
%i(per_page max_size name).all? { |m| self.send(m).equal?(other.send(m)) } &&
|
71
|
+
self.provider.provider.class == other.provider.provider.class
|
72
|
+
end
|
73
|
+
|
74
|
+
def class_attrs
|
75
|
+
SimpleFeed.class_attributes(self.class)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'providers/serialization/key'
|
2
|
+
require_relative 'providers/proxy'
|
3
|
+
|
4
|
+
module SimpleFeed
|
5
|
+
module Providers
|
6
|
+
@registry = {}
|
7
|
+
|
8
|
+
def self.registry
|
9
|
+
@registry
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register(provider_name, provider_class)
|
13
|
+
self.registry[provider_name] = provider_class
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.key(*args)
|
17
|
+
SimpleFeed::Providers::Serialization::Key.new(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
# These methods must be implemented by each Provider, and operation on a given
|
21
|
+
# set of users passed via the user_ids: parameter.
|
22
|
+
ACTIVITY_METHODS = %i(store delete delete_if wipe reset_last_read last_read paginate fetch total_count unread_count)
|
23
|
+
|
24
|
+
# These methods must be implemented in order to gather statistics about each provider's
|
25
|
+
# memory consumption and state.
|
26
|
+
FEED_METHODS = %i(total_memory_bytes total_users)
|
27
|
+
|
28
|
+
REQUIRED_METHODS = ACTIVITY_METHODS + FEED_METHODS
|
29
|
+
|
30
|
+
def self.define_provider_methods(klass, prefix = nil, &block)
|
31
|
+
# Methods on the class instance
|
32
|
+
klass.class_eval do
|
33
|
+
SimpleFeed::Providers::REQUIRED_METHODS.each do |m|
|
34
|
+
method_name = prefix ? "#{prefix}_#{m.to_s}".to_sym : m
|
35
|
+
define_method(method_name) do |*args, **opts, &b|
|
36
|
+
block.call(self, m, *args, **opts, &b)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require_relative 'providers/hash'
|
45
|
+
require_relative 'providers/redis'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'simplefeed/providers/serialization/key'
|
2
|
+
|
3
|
+
module SimpleFeed
|
4
|
+
module Providers
|
5
|
+
class NotImplementedError < ::StandardError
|
6
|
+
def initialize(klass, method)
|
7
|
+
super("Class #{klass.name} did not implement abstract method #{method}, but was called.")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Base
|
12
|
+
class Provider
|
13
|
+
attr_accessor :feed
|
14
|
+
|
15
|
+
def self.class_to_registry(klass)
|
16
|
+
klass.name.split('::').delete_if { |n| %w(SimpleFeed Providers Provider).include?(n) }.compact.last.downcase.to_sym
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.inherited(klass)
|
20
|
+
SimpleFeed::Providers.register(class_to_registry(klass), klass)
|
21
|
+
end
|
22
|
+
|
23
|
+
# SimpleFeed::Providers::REQUIRED_METHODS.each do |m|
|
24
|
+
# define_method(m) do |*|
|
25
|
+
# raise ::SimpleFeed::Providers::NotImplementedError.new(self, m)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def tap(value)
|
32
|
+
yield
|
33
|
+
value
|
34
|
+
end
|
35
|
+
|
36
|
+
def key(user_id)
|
37
|
+
::SimpleFeed::Providers.key(user_id, feed.namespace)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_array(user_ids)
|
41
|
+
user_ids.is_a?(Array) ? user_ids : [user_ids]
|
42
|
+
end
|
43
|
+
|
44
|
+
def batch_size
|
45
|
+
feed.batch_size
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_response_batched(user_ids, external_response = nil)
|
49
|
+
with_response(external_response) do |response|
|
50
|
+
batch(user_ids) do |key|
|
51
|
+
response.for(key.user_id) { yield(key, response) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def batch(user_ids)
|
57
|
+
to_array(user_ids).each_slice(batch_size) do |batch|
|
58
|
+
batch.each do |user_id|
|
59
|
+
yield(key(user_id))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def with_response(response = nil)
|
65
|
+
response ||= SimpleFeed::Response.new
|
66
|
+
yield(response)
|
67
|
+
if self.respond_to?(:transform_response)
|
68
|
+
response.transform do |user_id, result|
|
69
|
+
# calling into a subclass
|
70
|
+
transform_response(user_id, result)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
response
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_result
|
77
|
+
result = yield
|
78
|
+
result = transform_response(nil, result) if self.respond_to?(:transform_response)
|
79
|
+
result
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SimpleFeed
|
2
|
+
module Providers
|
3
|
+
module Hash
|
4
|
+
# Include this module into any provider etc that has access to the +feed.fetch+
|
5
|
+
# methods, and it will provide +paginate+ method based on all.
|
6
|
+
#
|
7
|
+
# Of course this is not very efficient, because it requires fetching all events for the user.
|
8
|
+
module Paginator
|
9
|
+
|
10
|
+
def paginate(user_ids:, page: nil, per_page: feed.per_page, &block)
|
11
|
+
response = feed.fetch(user_ids: user_ids)
|
12
|
+
response = SimpleFeed::Response.new(response.to_h)
|
13
|
+
response.transform do |*, events|
|
14
|
+
paginate_items(order_events(events, &block), page: page, per_page: per_page)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def paginate_items(items, page: nil, per_page: nil)
|
19
|
+
(page && page > 0) ? items[((page - 1) * per_page)...(page * per_page)] : items
|
20
|
+
end
|
21
|
+
|
22
|
+
def order_events(events, &block)
|
23
|
+
return nil unless events
|
24
|
+
events.sort do |a, b|
|
25
|
+
block ? yield(a, b) : b.at <=> a.at
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|