simple-feed 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +30 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +14 -0
  7. data/.yardopts +3 -0
  8. data/Gemfile +4 -0
  9. data/Guardfile +18 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +457 -0
  12. data/Rakefile +16 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/examples/hash_provider_example.rb +24 -0
  16. data/examples/redis_provider_example.rb +28 -0
  17. data/examples/shared/provider_example.rb +66 -0
  18. data/lib/simple-feed.rb +1 -0
  19. data/lib/simple_feed.rb +1 -0
  20. data/lib/simplefeed.rb +61 -0
  21. data/lib/simplefeed/activity/base.rb +14 -0
  22. data/lib/simplefeed/activity/multi_user.rb +71 -0
  23. data/lib/simplefeed/activity/single_user.rb +70 -0
  24. data/lib/simplefeed/dsl.rb +38 -0
  25. data/lib/simplefeed/dsl/activities.rb +70 -0
  26. data/lib/simplefeed/dsl/formatter.rb +109 -0
  27. data/lib/simplefeed/event.rb +87 -0
  28. data/lib/simplefeed/feed.rb +78 -0
  29. data/lib/simplefeed/providers.rb +45 -0
  30. data/lib/simplefeed/providers/base/provider.rb +84 -0
  31. data/lib/simplefeed/providers/hash.rb +8 -0
  32. data/lib/simplefeed/providers/hash/paginator.rb +31 -0
  33. data/lib/simplefeed/providers/hash/provider.rb +169 -0
  34. data/lib/simplefeed/providers/proxy.rb +38 -0
  35. data/lib/simplefeed/providers/redis.rb +9 -0
  36. data/lib/simplefeed/providers/redis/boot_info.yml +99 -0
  37. data/lib/simplefeed/providers/redis/driver.rb +158 -0
  38. data/lib/simplefeed/providers/redis/provider.rb +255 -0
  39. data/lib/simplefeed/providers/redis/stats.rb +85 -0
  40. data/lib/simplefeed/providers/serialization/key.rb +82 -0
  41. data/lib/simplefeed/response.rb +77 -0
  42. data/lib/simplefeed/version.rb +3 -0
  43. data/man/running-the-example.png +0 -0
  44. data/man/sf-example.png +0 -0
  45. data/simple-feed.gemspec +44 -0
  46. 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,8 @@
1
+ module SimpleFeed
2
+ module Providers
3
+ module Hash
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative 'hash/provider'
@@ -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