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.
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
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ require 'yard'
5
+
6
+
7
+ YARD::Rake::YardocTask.new(:doc) do |t|
8
+ t.files = %w(lib/**/*.rb exe/*.rb - README.md LICENSE.txt)
9
+ t.options.unshift('--title', '"SimpleFeed — Fast and Scalable "write-time" Simple Feed for Social Networks, with a Redis-based default backend implementation."')
10
+ t.after = ->() { exec('open doc/index.html') } if RUBY_PLATFORM =~ /darwin/
11
+ end
12
+
13
+
14
+ RSpec::Core::RakeTask.new(:spec)
15
+
16
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "activity/feed"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This is an executable example of working with a Redis feed, and storing
4
+ # events for various users.
5
+ #
6
+ # DEPENDENCIES:
7
+ # gem install colored2
8
+ # gem install awesome_print
9
+ #
10
+ # RUNNING
11
+ # ruby redis-feed.rb
12
+ #
13
+
14
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
15
+
16
+ require 'simplefeed'
17
+
18
+ @feed = SimpleFeed.define(:news) do |f|
19
+ f.provider = SimpleFeed.provider(:hash)
20
+ f.max_size = 1000
21
+ end
22
+
23
+ require_relative 'shared/provider_example'
24
+
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This is an executable example of working with a Redis feed, and storing
4
+ # events for various users.
5
+ #
6
+ # DEPENDENCIES:
7
+ # gem install colored2
8
+ # gem install awesome_print
9
+ #
10
+ # RUNNING
11
+ # ruby examples/redis-feed.rb [ number_of_users ]
12
+ #
13
+ # TO SEE ALL REDIS COMMANDS:
14
+ # export REDIS_DEBUG=1
15
+ # ruby examples/redis-feed.rb
16
+
17
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
18
+
19
+ require 'simplefeed'
20
+
21
+ @feed = SimpleFeed.define(:news) do |f|
22
+ f.provider = SimpleFeed.provider(:redis,
23
+ redis: -> { Redis.new },
24
+ pool_size: 10,
25
+ batch_size: 10)
26
+ end
27
+
28
+ require_relative 'shared/provider_example'
@@ -0,0 +1,66 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
+
3
+ # Please set @feed in the enclosing context
4
+
5
+ raise ArgumentError, 'No @feed defined in the enclosing example' unless defined?(@feed)
6
+
7
+ require 'simplefeed'
8
+
9
+ @number_of_users = ARGV[0] ? ARGV[0].to_i : 1
10
+ @users = Array.new(@number_of_users) { rand(200_000_000...800_000_000) }
11
+ @activity = @feed.activity(@users)
12
+ @uid = @users.first
13
+
14
+ include SimpleFeed::DSL
15
+
16
+ class Object
17
+ def _v
18
+ self.to_s.bold.red
19
+ end
20
+ end
21
+
22
+ def p(*args)
23
+ printf "%-40s %s\n", args[0].blue, args[1].bold.red
24
+ end
25
+
26
+ with_activity(@activity) do
27
+ header "#{@activity.feed.provider_type.to_s} provider example"
28
+ wipe { puts 'wiping feed...' }
29
+
30
+ store('value one') { p 'storing', 'value one' }
31
+ store('value two') { p 'storing', 'value two' }
32
+ store('value three') { p 'storing', 'value three' }
33
+ hr
34
+
35
+ total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
36
+ unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
37
+
38
+ header 'FIRST PAGE (PER-PAGE: 2) #to_json'
39
+ paginate(page: 1, per_page: 2) { |r| puts r[@uid].map(&:to_json) }
40
+ header 'SECOND PAGE (PER-PAGE: 2) #to_s'
41
+ paginate(page: 2, per_page: 2) { |r| puts r[@uid].map(&:to_s) }
42
+ header 'LAST PAGE (PER-PAGE: 1) #to_color_s'
43
+ paginate(page: 3, per_page: 1) { |r| puts r[@uid].map(&:to_color_s) }
44
+
45
+ hr
46
+ total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
47
+ unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
48
+
49
+ hr
50
+ store('value four') { p 'storing', 'value four' }
51
+ total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
52
+ unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
53
+
54
+ color_dump
55
+
56
+ hr
57
+ delete('value three') { p 'deleting', 'value three' }
58
+ total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
59
+ unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
60
+ hr
61
+ delete('value four') { p 'deleting', 'value four' }
62
+ total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
63
+ unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
64
+
65
+ end
66
+
@@ -0,0 +1 @@
1
+ require 'simplefeed'
@@ -0,0 +1 @@
1
+ require 'simplefeed'
data/lib/simplefeed.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'hashie/extensions/symbolize_keys'
2
+ require 'simplefeed/version'
3
+
4
+ ::Dir.glob(::File.expand_path('../simplefeed/*.rb', __FILE__)).each { |f| require_relative(f) }
5
+
6
+ require 'simplefeed/providers/redis'
7
+ require 'simplefeed/providers/hash'
8
+ require 'simplefeed/dsl'
9
+
10
+ Float.class_eval do
11
+ def near_eql?(other)
12
+ delta = 0.001
13
+ n = (other - self).abs
14
+ delta >= n
15
+ end
16
+ end
17
+
18
+ module SimpleFeed
19
+ @registry = {}
20
+
21
+ def self.registry
22
+ @registry
23
+ end
24
+
25
+ def self.define(name, **options, &block)
26
+ name = name.to_sym unless name.is_a?(Symbol)
27
+ feed = registry[name] ? registry[name] : SimpleFeed::Feed.new(name)
28
+ feed.configure(options) do
29
+ block.call(feed) if block
30
+ end
31
+ registry[name] = feed
32
+ feed
33
+ end
34
+
35
+ def self.get(name)
36
+ registry[name.to_sym]
37
+ end
38
+
39
+ def self.provider(provider_name, *args, **opts, &block)
40
+ provider_class = SimpleFeed::Providers.registry[provider_name]
41
+ raise ArgumentError, "No provider named #{provider_name} was found, #{SimpleFeed::Providers.registry.inspect}" unless provider_class
42
+ provider_class.new(*args, **opts, &block)
43
+ end
44
+
45
+ class << self
46
+ # Forward all other method calls to Provider
47
+ def method_missing(name, *args, &block)
48
+ registry[name] || super
49
+ end
50
+ end
51
+
52
+ # Returns list of class attributes based on the setter methods.
53
+ # Not fool-proof, but works in this context.
54
+ def self.class_attributes(klass)
55
+ klass.instance_methods.grep(%r{[^=!]=$}).map { |m| m.to_s.gsub(/=/, '').to_sym }
56
+ end
57
+
58
+ def self.symbolize!(hash)
59
+ Hashie::Extensions::SymbolizeKeys.symbolize_keys!(hash)
60
+ end
61
+ end
@@ -0,0 +1,14 @@
1
+ module SimpleFeed
2
+ module Activity
3
+ class Base
4
+ attr_reader :feed
5
+
6
+ def initialize(feed:)
7
+ @feed = feed
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative 'multi_user'
14
+ require_relative 'single_user'
@@ -0,0 +1,71 @@
1
+ require 'forwardable'
2
+ require_relative 'base'
3
+
4
+ module SimpleFeed
5
+ module Activity
6
+ class MultiUser < Base
7
+
8
+ attr_reader :user_ids
9
+
10
+ extend Forwardable
11
+ def_delegators :@user_ids, :size, :each, :select, :delete_if, :inject
12
+
13
+ include Enumerable
14
+
15
+ #
16
+ # Multi-user API for the feeds.
17
+ #
18
+ # ```ruby
19
+ # @multi = SimpleFeed.get(:feed_name).for(User.active.map(&:id))
20
+ #
21
+ # @multi.store(value:, at:)
22
+ # @multi.store(event:)
23
+ # # => [Response] { user_id => [Boolean], ... } true if the value was stored, false if it wasn't.
24
+ #
25
+ # @multi.delete(value:, at:)
26
+ # @multi.delete(event:)
27
+ # # => [Response] { user_id => [Boolean], ... } true if the value was deleted, false if it didn't exist
28
+ #
29
+ # @multi.wipe
30
+ # # => [Response] { user_id => [Boolean], ... } true if user activity was found and deleted, false otherwise
31
+ #
32
+ # @multi.paginate(page:, per_page:, peek: false)
33
+ # # => [Response] { user_id => [Array]<Event>, ... }
34
+ #
35
+ # # With (peak: true) does not reset last_read, otherwise it does.
36
+ #
37
+ # @multi.fetch
38
+ # # => [Response] { user_id => [Array]<Event>, ... }
39
+ #
40
+ # @multi.reset_last_read
41
+ # # => [Response] { user_id => [Time] last_read, ... }
42
+ #
43
+ # @multi.total_count
44
+ # # => [Response] { user_id => [Integer] total_count, ... }
45
+ #
46
+ # @multi.unread_count
47
+ # # => [Response] { user_id => [Integer] unread_count, ... }
48
+ #
49
+ # @multi.last_read
50
+ # # => [Response] { user_id => [Time] last_read, ... }
51
+ # ```
52
+
53
+ SimpleFeed::Providers.define_provider_methods(self) do |instance, method, *args, **opts, &block|
54
+ opts.merge!(user_ids: instance.user_ids)
55
+ if opts[:event]
56
+ opts.merge!(value: opts[:event].value, at: opts[:event].at)
57
+ opts.delete(:event)
58
+ end
59
+ response = instance.feed.send(method, *args, **opts, &block)
60
+ yield(response) if block_given?
61
+ raise StandardError, "Nil response from provider #{instance.feed.provider&.provider&.class}, method #{method}(#{opts})" unless response
62
+ response
63
+ end
64
+
65
+ def initialize(feed:, user_ids:)
66
+ super(feed: feed)
67
+ @user_ids = user_ids
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'base'
2
+
3
+ module SimpleFeed
4
+ module Activity
5
+
6
+ # Lazy implementation of SingleUser based on delegating an array of
7
+ # one user_id to +MultiUser+
8
+
9
+ class SingleUser < ::SimpleFeed::Activity::Base
10
+ attr_reader :user_id
11
+ attr_accessor :user_activity
12
+
13
+ include Enumerable
14
+
15
+ def each
16
+ yield(user_id)
17
+ end
18
+
19
+ #
20
+ # Single-user API for the feeds.
21
+ #
22
+ # @ua = SimpleFeed.get(:feed_name).user_activity(current_user.id)
23
+ #
24
+ # @ua.store(value:, at:)
25
+ # # => [Boolean] true if the value was stored, false if it wasn't.
26
+ #
27
+ # @ua.delete(value:, at:)
28
+ # # => [Boolean] true if the value was deleted, false if it didn't exist
29
+ #
30
+ # @ua.wipe
31
+ # # => [Boolean] true
32
+ #
33
+ # @ua.paginate(page:, per_page:, peek: false)
34
+ # # => [Array]<Event>
35
+ # # with peak: true does not reset last_read
36
+ #
37
+ # @ua.fetch
38
+ # # => [Array]<Event>
39
+ #
40
+ # @ua.reset_last_read
41
+ # # => [Time] last_read
42
+ #
43
+ # @ua.total_count
44
+ # # => [Integer] total_count
45
+ #
46
+ # @ua.unread_count
47
+ # # => [Integer] unread_count
48
+ #
49
+ # @ua.last_read
50
+ # # => [Time] last_read
51
+ # ```
52
+
53
+ SimpleFeed::Providers.define_provider_methods(self) do |instance, method, *args, **opts, &block|
54
+ response = instance.user_activity.send(method, *args, **opts, &block)
55
+ unless response.has_user?(instance.user_id)
56
+ raise StandardError, "Nil response from provider #{instance.feed.provider&.provider&.class}, method #{method}(#{opts}) — user_id #{instance.user_id}"
57
+ end
58
+ response = response[instance.user_id]
59
+ yield(response) if block_given?
60
+ response
61
+ end
62
+
63
+ def initialize(user_id:, feed:)
64
+ @feed = feed
65
+ @user_id = user_id
66
+ self.user_activity = MultiUser.new(feed: feed, user_ids: [user_id])
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,38 @@
1
+ require_relative 'providers'
2
+ require 'simplefeed'
3
+ require_relative 'dsl/activities'
4
+
5
+ module SimpleFeed
6
+ # This module offers a convenient DSL-based approach to manipulating user feeds.
7
+ #
8
+ # Usage:
9
+ #
10
+ # require 'simplefeed/dsl'
11
+ # include SimpleFeed::DSL
12
+ #
13
+ # with_activity(SimpleFeed.get(:newsfeed).activity(user_id)) do
14
+ # puts 'success' if store(value: 'hello', at: Time.now)
15
+ # puts fetch
16
+ # puts total_count
17
+ # puts unread_count
18
+ # end
19
+ #
20
+ module DSL
21
+ class << self
22
+ attr_accessor :debug
23
+
24
+ def debug?
25
+ self.debug
26
+ end
27
+ end
28
+
29
+ def with_activity(activity, **opts, &block)
30
+ opts.merge!({ context: self }) unless opts && opts[:context]
31
+ SimpleFeed::DSL::Activities.new(activity, **opts).instance_eval(&block)
32
+ end
33
+
34
+ def event(value, at = Time.now)
35
+ SimpleFeed::Event.new(value, at)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'formatter'
2
+ module SimpleFeed
3
+ module DSL
4
+ class Activities
5
+
6
+ include SimpleFeed::DSL::Formatter
7
+
8
+ attr_accessor :activity, :feed
9
+
10
+ def initialize(activity, **opts)
11
+ self.activity = activity
12
+ self.feed = activity.feed
13
+ opts.each_pair do |key, value|
14
+ self.class.instance_eval do
15
+ attr_accessor key
16
+ end
17
+ self.send("#{key}=".to_sym, value)
18
+ end
19
+ end
20
+
21
+ # Creates wrapper methods around the API and optionally prints both calls and return values
22
+ #
23
+ # def store(event: .., | value:, at: )
24
+ # activity.store(**opts)
25
+ # end
26
+ # etc...
27
+ SimpleFeed::Providers.define_provider_methods(self) do |instance, method, *args, **opts, &block|
28
+ if args&.first
29
+ arg1 = args.shift
30
+ if arg1.is_a?(SimpleFeed::Event)
31
+ event = arg1
32
+ else
33
+ opts[:value] = arg1 unless opts[:value]
34
+ opts[:at] = args.shift unless opts[:value]
35
+ end
36
+ else
37
+ event = opts.delete(:event)
38
+ end
39
+
40
+ opts.merge!(value: event.value, at: event.at) if event
41
+
42
+ response = instance.instance_eval do
43
+ print_debug_info(method, **opts) do
44
+ activity.send(method, *args, **opts)
45
+ end
46
+ end
47
+
48
+ if block
49
+ if instance.context
50
+ instance.context.instance_exec(response, &block)
51
+ else
52
+ block.call(response)
53
+ end
54
+ end
55
+
56
+ response
57
+ end
58
+
59
+ private
60
+
61
+ def print_debug_info(method, **opts)
62
+ brackets = opts.empty? ? ['', ''] : %w{( )}
63
+ printf "\n#{self.feed.name.to_s.blue}.#{method.to_s.cyan.bold}#{brackets[0]}#{opts.to_s.gsub(/[{}]/, '').blue}#{brackets[1]} \n" if SimpleFeed::DSL.debug?
64
+ response = yield if block_given?
65
+ puts response.inspect.yellow if SimpleFeed::DSL.debug?
66
+ response
67
+ end
68
+ end
69
+ end
70
+ end