simple-feed 1.0.2
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 +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
|