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.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rubocop.yml +33 -0
  3. data/.github/workflows/ruby.yml +35 -0
  4. data/.relaxed-rubocop-2.4.yml +174 -0
  5. data/.rspec +1 -1
  6. data/.rubocop.yml +42 -1149
  7. data/.rubocop_todo.yml +134 -0
  8. data/.travis.yml +15 -8
  9. data/Gemfile +2 -0
  10. data/Guardfile +3 -3
  11. data/{README.md → README.adoc} +205 -140
  12. data/Rakefile +6 -7
  13. data/bin/console +1 -0
  14. data/examples/shared/provider_example.rb +10 -3
  15. data/lib/simple-feed.rb +2 -0
  16. data/lib/simple_feed.rb +2 -0
  17. data/lib/simplefeed.rb +9 -7
  18. data/lib/simplefeed/activity/base.rb +2 -0
  19. data/lib/simplefeed/activity/multi_user.rb +8 -6
  20. data/lib/simplefeed/activity/single_user.rb +5 -4
  21. data/lib/simplefeed/dsl.rb +3 -1
  22. data/lib/simplefeed/dsl/activities.rb +4 -3
  23. data/lib/simplefeed/dsl/formatter.rb +12 -12
  24. data/lib/simplefeed/event.rb +4 -3
  25. data/lib/simplefeed/feed.rb +19 -22
  26. data/lib/simplefeed/key/template.rb +5 -9
  27. data/lib/simplefeed/key/type.rb +4 -2
  28. data/lib/simplefeed/providers.rb +24 -9
  29. data/lib/simplefeed/providers/base/provider.rb +6 -3
  30. data/lib/simplefeed/providers/hash.rb +2 -0
  31. data/lib/simplefeed/providers/hash/paginator.rb +4 -2
  32. data/lib/simplefeed/providers/hash/provider.rb +11 -21
  33. data/lib/simplefeed/providers/key.rb +20 -11
  34. data/lib/simplefeed/providers/proxy.rb +13 -12
  35. data/lib/simplefeed/providers/redis.rb +2 -0
  36. data/lib/simplefeed/providers/redis/driver.rb +23 -23
  37. data/lib/simplefeed/providers/redis/provider.rb +35 -33
  38. data/lib/simplefeed/providers/redis/stats.rb +12 -13
  39. data/lib/simplefeed/response.rb +4 -2
  40. data/lib/simplefeed/version.rb +3 -1
  41. data/simple-feed.gemspec +16 -14
  42. metadata +58 -41
@@ -0,0 +1,134 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2020-05-23 01:49:13 -0700 using RuboCop version 0.83.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: EnforcedStyleAlignWith, AutoCorrect, Severity.
12
+ # SupportedStylesAlignWith: keyword, variable, start_of_line
13
+ Layout/EndAlignment:
14
+ Exclude:
15
+ - 'lib/simplefeed/dsl/formatter.rb'
16
+
17
+ # Offense count: 2
18
+ Lint/ShadowingOuterLocalVariable:
19
+ Exclude:
20
+ - 'lib/simplefeed/providers/redis/provider.rb'
21
+
22
+ # Offense count: 1
23
+ # Configuration parameters: AllowComments.
24
+ Lint/SuppressedException:
25
+ Exclude:
26
+ - 'lib/simplefeed/providers/hash/provider.rb'
27
+
28
+ # Offense count: 3
29
+ # Configuration parameters: AllowKeywordBlockArguments.
30
+ Lint/UnderscorePrefixedVariableName:
31
+ Exclude:
32
+ - 'lib/simplefeed/dsl/formatter.rb'
33
+ - 'lib/simplefeed/providers/redis/provider.rb'
34
+
35
+ # Offense count: 1
36
+ # Configuration parameters: CheckForMethodsWithNoSideEffects.
37
+ Lint/Void:
38
+ Exclude:
39
+ - 'lib/simplefeed/feed.rb'
40
+
41
+ # Offense count: 25
42
+ # Configuration parameters: CountComments, ExcludedMethods.
43
+ # ExcludedMethods: refine
44
+ Metrics/BlockLength:
45
+ Max: 185
46
+
47
+ # Offense count: 1
48
+ # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
49
+ # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
50
+ Naming/FileName:
51
+ Exclude:
52
+ - 'lib/simple-feed.rb'
53
+
54
+ # Offense count: 1
55
+ # Configuration parameters: EnforcedStyleForLeadingUnderscores.
56
+ # SupportedStylesForLeadingUnderscores: disallowed, required, optional
57
+ Naming/MemoizedInstanceVariableName:
58
+ Exclude:
59
+ - 'lib/simplefeed/providers/redis/stats.rb'
60
+
61
+ # Offense count: 5
62
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
63
+ # AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp
64
+ Naming/MethodParameterName:
65
+ Exclude:
66
+ - 'lib/simplefeed/dsl/formatter.rb'
67
+ - 'lib/simplefeed/providers/redis/driver.rb'
68
+ - 'spec/support/mock_feed.rb'
69
+ - 'spec/support/shared_examples_for_providers.rb'
70
+
71
+ # Offense count: 1
72
+ # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros.
73
+ # NamePrefix: is_, has_, have_
74
+ # ForbiddenPrefixes: is_, has_, have_
75
+ # AllowedMethods: is_a?
76
+ # MethodDefinitionMacros: define_method, define_singleton_method
77
+ Naming/PredicateName:
78
+ Exclude:
79
+ - 'spec/**/*'
80
+ - 'lib/simplefeed/response.rb'
81
+
82
+ # Offense count: 9
83
+ # Configuration parameters: EnforcedStyle.
84
+ # SupportedStyles: annotated, template, unannotated
85
+ Style/FormatStringToken:
86
+ Exclude:
87
+ - 'lib/simplefeed/dsl/formatter.rb'
88
+ - 'lib/simplefeed/providers/redis/driver.rb'
89
+
90
+ # Offense count: 3
91
+ # Configuration parameters: MinBodyLength.
92
+ Style/GuardClause:
93
+ Exclude:
94
+ - 'lib/simplefeed/event.rb'
95
+ - 'lib/simplefeed/providers/base/provider.rb'
96
+ - 'lib/simplefeed/providers/hash/provider.rb'
97
+
98
+ # Offense count: 1
99
+ # Configuration parameters: AllowIfModifier.
100
+ Style/IfInsideElse:
101
+ Exclude:
102
+ - 'lib/simplefeed/response.rb'
103
+
104
+ # Offense count: 2
105
+ Style/MethodMissingSuper:
106
+ Exclude:
107
+ - 'lib/simplefeed/providers/proxy.rb'
108
+ - 'lib/simplefeed/providers/redis/driver.rb'
109
+
110
+ # Offense count: 4
111
+ Style/MissingRespondToMissing:
112
+ Exclude:
113
+ - 'lib/simplefeed.rb'
114
+ - 'lib/simplefeed/providers/proxy.rb'
115
+ - 'lib/simplefeed/providers/redis/driver.rb'
116
+
117
+ # Offense count: 3
118
+ Style/MultilineTernaryOperator:
119
+ Exclude:
120
+ - 'lib/simplefeed/feed.rb'
121
+ - 'lib/simplefeed/providers/redis/provider.rb'
122
+ - 'lib/simplefeed/response.rb'
123
+
124
+ # Offense count: 1
125
+ Style/OptionalArguments:
126
+ Exclude:
127
+ - 'lib/simplefeed/providers/redis/provider.rb'
128
+
129
+ # Offense count: 3
130
+ Style/StructInheritance:
131
+ Exclude:
132
+ - 'lib/simplefeed/key/template.rb'
133
+ - 'lib/simplefeed/key/type.rb'
134
+ - 'lib/simplefeed/providers/redis/driver.rb'
@@ -1,14 +1,21 @@
1
1
  cache: bundler
2
2
  rvm:
3
- - 2.3.3
4
- - 2.4.0
3
+ - 2.3.8
4
+ - 2.4.10
5
+ - 2.5.3
6
+ - 2.6.2
7
+ - 2.7.1
5
8
  services:
6
9
  - redis-server
7
10
  before_script:
8
- script: bundle exec rspec
9
- after_success:
10
- - bundle exec codeclimate-test-reporter
11
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
12
+ - chmod +x ./cc-test-reporter
13
+ - ./cc-test-reporter before-build
14
+ script: bundle exec rspec --format=documentation
15
+ after_script:
16
+ - bash <(curl -s https://codecov.io/bash)
17
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
11
18
  env:
12
- notifications:
13
- slack:
14
- secure: QaaD9WpCYoGFITVd8ku3cjkw1ADJ/rqsPgFGsbRxjPOj1HDzkkUVs20IK0AysxeI6CPY31rfWqbBcE4I4dUE49uIhpOoBXZ8GDYunUONeEmqGwrqZtV/8NqMLNc0Ouu5Jp/KVlnMO93Pcg97BBW81t1koxqyvgSU5oEmJdTmQZIsRNprhczeCj7T6jbb12LEA8uKi2qnp+FAhL5NJczHxqWCd2pWThtbA+hiRmH/mU0480n1eRForN51jDYUrgxb4PamhfUUG1xkzMPzGsvJTMCQDOG4eyHaq71vlZ3z9BbQG6vbcjfEQHVwfrPRz1k0luIlxMXJxszCFnnLVqeRYs85y4FOQSrLsDfsyLCU+1NgLqtseN6jNKOtY624Ok7YvYzsVYez/CPWUG1d2NIRMYQ+BoL2VQ3SDfvr4bYQ02QmqlhM1bKqmBoM+WmiH1FQk6CKOFcmlSkgkFilLi6YSB/EisQ5V5jo7DnzD38VVACo6J6SgVk2D6soONE3zev34Qa6PIOuwTTlXl6JKH0cpiG058lI+Oza+0xi0jg58sZG3jxeGM7m4wg66htgyN1oRQjIukSR+IJ3PZlFKTLvx2rCUcYz+8fRpPCMTSIVLeju5oPqOm0VRUni2dG5JGyNACV8EhI4ZMcfl7+uFTIagCnllRftBx8lY6AxWN3WpDE=
19
+ global:
20
+ CODECOV_TOKEN="2085c087-f833-49c7-a105-703bce882653"
21
+ secure: cdUlrz0XNCu0HB5lNLYZBF6yaukIkDZRlVsAbeeaNhPKCVj7MOvuGEUZ9TzQP0yXRaGXHy3xXem0MdDbnli5WYvHxj2MTrRVEDBiRm4jaZFHe3Rewr2mWmLqK1c4g9gvOTgjPNPTesdft87+wQuPedQLpfMGuedA86tkKzA2Tkc4J69DI93C02h6H89iiQFHL9ISNk5DzZh3FJo+fyi0JQlD6CAzWrVplCqf088qGdAk3Hy77Q2GKkVEdOb/P0cdmDLnFjekdh4b0TPI/uaqgu4cZeoebWQgg61K/enGLqxY2Lp0f4DsXQev+06d14KxIJMYHLuDgvlVwYMDInxQU7kXgTRgAnaxRdm0S7NbP89uBVkfIQiNGan7Qkvw2ayGhYCcu7Vgvjo6C2btZWLpxT0XvpQGfpvHrRrPnzFbFLLtG4rhJd3iiTbSa5LPDEi4Qx9uwGNy4iR6IB69R6dPwAR8P45XZE4JnJVUvrM/X9v9Yv2hAxnuCb48whalIozGqpwavLdYrgCf2pQecls74uw4mCxvDAg0EoskrDYuk4DYeZ507ajor4zX1avLUVfYk/0igpp6KeLENY5ozl1zsEvRcOldfdV8R9tKEGxliIgffe67Hc5/ZP+C/53lcK0nIxXYrB1I7Q+CVlEDNutNQNZRDfrNBYJVe/+RvmmdD+w=
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in activity-feed.gemspec
data/Guardfile CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  require 'guard/rspec'
3
5
 
4
6
  guard :rspec,
@@ -8,11 +10,9 @@ guard :rspec,
8
10
  all_after_pass: false,
9
11
  all_on_start: false,
10
12
  keep_failed: false do
11
-
12
- watch(%r{.*\.gemspec}) { 'spec' }
13
+ watch(/.*\.gemspec/) { 'spec' }
13
14
  watch(%r{^lib/(.+)\.rb$}) { 'spec' }
14
15
  watch(%r{^spec/.+_spec\.rb$})
15
16
  watch('spec/spec_helper.rb') { 'spec' }
16
17
  watch(%r{spec/support/.*}) { 'spec' }
17
18
  end
18
-
@@ -1,83 +1,137 @@
1
- # SimpleFeed — Scalable, easy to use activity feed implementation.
1
+ = SimpleFeed
2
+ :doctype: book
3
+ :toc:
4
+ :toclevels: 5
5
+ :sectnums:
2
6
 
7
+ == Scalable, Easy to Use Activity Feed Implementation.
3
8
 
4
- [![Gem Version](https://img.shields.io/gem/v/simple-feed.svg)](https://rubygems.org/gems/simple-feed)
5
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kigster/simple-feed/master/LICENSE.txt)
9
+ image:https://img.shields.io/gem/v/simple-feed.svg[Gem Version,link=https://rubygems.org/gems/simple-feed]
10
+ image:https://img.shields.io/badge/license-MIT-blue.svg[MIT licensed,link=https://github.com/kigster/simple-feed/master/LICENSE.txt]
6
11
 
7
- [![Build Status](https://travis-ci.org/kigster/simple-feed.svg?branch=master)](https://travis-ci.org/kigster/simple-feed)
8
- [![Code Climate](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/badges/8b899f6df4fc1ed93759/gpa.svg)](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/feed)
9
- [![Test Coverage](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/badges/8b899f6df4fc1ed93759/coverage.svg)](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/coverage)
10
- [![Issue Count](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/badges/8b899f6df4fc1ed93759/issue_count.svg)](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/feed)
11
- [![Inline docs](http://inch-ci.org/github/kigster/simple-feed.svg?branch=master)](http://inch-ci.org/github/kigster/simple-feed)
12
- [![Talk on Gitter](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/kigster/simple-feed)
12
+ image:https://github.com/kigster/simple-feed/workflows/Ruby/badge.svg?branch=master[Ruby,link=https://github.com/kigster/simple-feed/actions?query=workflow%3ARuby]
13
+ image:https://github.com/kigster/simple-feed/workflows/Rubocop/badge.svg?branch=master[Ruby,link=https://github.com/kigster/simple-feed/actions?query=workflow%3ARubocop]
13
14
 
14
- ---
15
+ image:https://travis-ci.org/kigster/simple-feed.svg?branch=master[Build Status,link=https://travis-ci.org/kigster/simple-feed]
16
+ image:https://api.codeclimate.com/v1/badges/a11061820895fcde635e/maintainability[Maintainability,link=https://codeclimate.com/github/kigster/simple-feed/maintainability]
17
+ image:https://api.codeclimate.com/v1/badges/a11061820895fcde635e/test_coverage[Test Coverage,link=https://codeclimate.com/github/kigster/simple-feed/test_coverage]
15
18
 
16
- **February 20th, 2017**: Please read the blog post [Feeding Frenzy with SimpleFeed](http://kig.re/2017/02/19/feeding-frenzy-with-simple-feed-activity-feed-ruby-gem.html) launching this library. Please leave comments or questions in the discussion thread at the bottom of that post. Thanks!
19
+ image:http://inch-ci.org/github/kigster/simple-feed.svg?branch=master[Inline docs,link=http://inch-ci.org/github/kigster/simple-feed]
20
+ image:https://img.shields.io/gitter/room/gitterHQ/gitter.svg[Talk on Gitter,link=https://gitter.im/kigster/simple-feed]
17
21
 
18
- ---
22
+ IMPORTANT: Please read the (somewhat outdated) blog post http://kig.re/2017/02/19/feeding-frenzy-with-simple-feed-activity-feed-ruby-gem.html[Feeding Frenzy with SimpleFeed] launching this library. Please leave comments or questions in the discussion thread at the bottom of that post. Thanks!
19
23
 
20
24
  If you like to see this project grow, your donation of any amount is much appreciated.
21
25
 
22
- [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FSFYYNEQ8RKWU)
26
+ image::https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif[Donate,link=https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FSFYYNEQ8RKWU]
27
+ '''
23
28
 
24
- ---
29
+ This is a fast, pure-ruby implementation of an activity feed concept commonly used in social networking applications. The implementation is optimized for *read-time performance* and high concurrency (lots of users), and can be extended with custom backend providers. Two providers come bundled: the production-ready Redis provider, and a naive Ruby Hash-based provider.
25
30
 
26
- This is a fast, pure-ruby implementation of an activity feed concept commonly used in social networking applications. The implementation is optimized for **read-time performance** and high concurrency (lots of users), and can be extended with custom backend providers. Two providers come bundled: the production-ready Redis provider, and a naive Ruby Hash-based provider.
31
+ *Important Notes and Acknowledgements:*
27
32
 
28
- __Important Notes and Acknowledgements:__
33
+ * SimpleFeed _does not depend on Ruby on Rails_ and is a *pure-ruby* implementation
34
+ * SimpleFeed requires ruby 2.3 or later
35
+ * SimpleFeed is currently live in production
36
+ * SimpleFeed is open source thanks to the generosity of *http://simbi.com[Simbi, Inc]*.
29
37
 
30
- * SimpleFeed *does not depend on Ruby on Rails* and is a __pure-ruby__ implementation
31
- * SimpleFeed requires ruby 2.3 or later
32
- * SimpleFeed is currently live in production
33
- * SimpleFeed is open source thanks to the generosity of __[Simbi, Inc](http://simbi.com)__.
38
+ == Features
34
39
 
35
- ## Background, Examples, Serialization, etc
40
+ SimpleFeed offers the following features:
36
41
 
37
- Please read the additional documentation, including the examples, on the [project's Github Wiki](https://github.com/kigster/simple-feed/wiki).
42
+ * Highly performant Redis-based activity feed
38
43
 
39
- ## Usage
44
+ * Scales to millions of users (may need to use Twemproxy to shard across several Redis instances)
45
+
46
+ * Stores a fixed number of events per unique activity ID (eg, a user) — the default is 2000. When the full list is used, the oldest events are bumped off the feed (and are effectively trashed).
47
+
48
+ * Store user_id as either a string or an integer (the latter is base62 encoded when serialized, so very large users_ids are OK)
49
+
50
+ * Updating feeds of N users is roughly a O(N * log(N)) operation
51
+
52
+ * Reading feed for one user (or one type of user) is a O(1) operation
53
+
54
+ * For each activity (user) read the number of total and new items in the feed, computed since the user last saw their feed.
55
+
56
+ * Delete items from user's feed selectively (for instance, if a user unfollows someone they shouldn't see their events anymore).
57
+
58
+ * Automatically reset the timestamp when the user last read their feed when reading from the feed (or not, it's an argument). As soon as the user reads their feed (you call the `paginate` method), the "unread counter" is reset to 0.
59
+
60
+ * You can create as many different types of feeds per application as you like. No Singletons are used.
61
+
62
+ * Thread-safe implementation.
63
+
64
+ * Zero assumptions about what you are storing: the "data" is just a string. Serialize it with JSON, Marshall, YAML, or whatever.
65
+
66
+ == Background, Examples, Serialization, etc
67
+
68
+ Please read the additional documentation, including the examples, on the https://github.com/kigster/simple-feed/wiki[project's Github Wiki].
69
+
70
+ Below is a screen shot of an actual activity feed powered by this library.
71
+
72
+ image::https://raw.githubusercontent.com/kigster/simple-feed/master/man/activity-feed-action.png[usage]
73
+
74
+ == Usage
40
75
 
41
76
  A key concept to understanding SimpleFeed gem, is that of a _provider_, which is effectively a persistence implementation for the events belonging to each user.
42
77
 
43
78
  Two providers are supplied with this gem:
44
79
 
45
- * The production-ready `:redis` provider, which uses the [sorted set Redis data type](https://redislabs.com/ebook/redis-in-action/part-2-core-concepts-2/chapter-3-commands-in-redis/3-5-sorted-sets) to store and fetch the events, scored by time (but not necessarily).
46
-
47
- * The naïve `:hash` provider based on the ruby `Hash` class, that can be useful in unit tests, or in simple simulations.
80
+ * The production-ready `:redis` provider, which uses the https://redislabs.com/ebook/redis-in-action/part-2-core-concepts-2/chapter-3-commands-in-redis/3-5-sorted-sets[sorted set Redis data type] to store and fetch the events, scored by time (but not necessarily).
81
+ * The naïve `:hash` provider based on the ruby `Hash` class, that can be useful in unit tests, or in simple simulations.
48
82
 
49
83
  You initialize a provider by using the `SimpleFeed.provider([Symbol])` method.
50
84
 
51
- ### Configuration
85
+ === Configuration
52
86
 
53
- Below we configure a feed called `:newsfeed`, which in this example will be populated with the various events coming from the followers.
87
+ Below we configure a feed called `:newsfeed`, which in this example will be populated with the various events coming from the followers.
54
88
 
55
- ```ruby
89
+ [source,ruby]
90
+ ----
56
91
  require 'simplefeed'
57
92
 
58
- # Let's define a Redis-based feed, and wrap Redis in a in a ConnectionPool.
93
+ # Let's define a Redis-based feed, and wrap Redis in a in a ConnectionPool.
59
94
  SimpleFeed.define(:newsfeed) do |f|
60
- f.provider = SimpleFeed.provider(:redis,
95
+ f.provider = SimpleFeed.provider(:redis,
61
96
  redis: -> { ::Redis.new },
62
97
  pool_size: 10)
63
98
  f.per_page = 50 # default page size
64
99
  f.batch_size = 10 # default batch size
65
100
  f.namespace = 'nf' # only needed if you use the same redis for more than one feed
66
101
  end
67
- ```
102
+ ----
68
103
 
69
104
  After the feed is defined, the gem creates a similarly named method under the `SimpleFeed` namespace to access the feed. For example, given a name such as `:newsfeed` the following are all valid ways of accessing the feed:
70
105
 
71
- * `SimpleFeed.newsfeed`
72
- * `SimpleFeed.get(:newsfeed)`
106
+ * `SimpleFeed.newsfeed`
107
+ * `SimpleFeed.get(:newsfeed)`
73
108
 
74
109
  You can also get a full list of currently defined feeds with `SimpleFeed.feed_names` method.
75
110
 
76
- ### Reading from and writing to the feed
111
+ === User IDs
112
+
113
+ In the following section you will see the examples of reading and writing activity for users based on their ID.
114
+
115
+ SimpleFeed supports user IDs that are either numeric (integer) or string-based (eg, UUID).
116
+
117
+ If your User IDs are numeric, they generate redis keys using Base62 encoding (which makes them shorter, and more compact).
118
+
119
+ For string User IDs, the only transformation performed is the basic link:https://en.wikipedia.org/wiki/ROT13[`rot13`] in case user ids are semi-sensitive.
77
120
 
78
- For the impatient here is a quick way to get started with the `SimpleFeed`.
121
+ ==== Partitioning Schema
79
122
 
80
- ```ruby
123
+ You can take advantage of string user IDs for situations where your feed requires a composite keys for instance. Just remember that SimpleFeed does not care about what's in your user ID, and even what you call "a user". It's convenient to think of the activities in terms of users, because typically each user has a unique feed that only they see.
124
+
125
+ But you can just as easily use zip code as the unique activity ID, and create one feed of events per geographical location, that all folks living in that zip code share. But what about other countries?
126
+
127
+ Now you use partioning scheme: make "user_id" a combination `iso_country_code.postal_code`, eg for San Francisco, you'd use `us.94107`, but for Australia you could use, eg `au.3148`.
128
+
129
+ === Reading from and writing to the feed
130
+
131
+ For the impatient, here is a quick way to get started with the `SimpleFeed`.
132
+
133
+ [source,ruby]
134
+ ----
81
135
  # This assumes we have previously defined a feed named :newsfeed (see above)
82
136
  activity = SimpleFeed.newsfeed.activity(@current_user.id)
83
137
  # Store directly the value and the optional time stamp
@@ -88,7 +142,7 @@ activity.store(value: 'hello')
88
142
  @event = SimpleFeed::Event.new('hello', Time.now)
89
143
  activity.store(event: @event)
90
144
  # => false # false indicates that the same event is already in the feed.
91
- ```
145
+ ----
92
146
 
93
147
  As we've added events for this user, we can request them back, sorted by
94
148
  the time and paginated. If you are using a distributed provider, such as
@@ -96,34 +150,37 @@ the time and paginated. If you are using a distributed provider, such as
96
150
  application, not just the one that published the event (which is the
97
151
  case for the "toy" `Hash::Provider`.
98
152
 
99
- ```ruby
153
+ [source,ruby]
154
+ ----
100
155
  activity.paginate(page: 1, reset_last_read: true)
101
156
  # => [ <SimpleFeed::Event#0x2134afa value='hello' at='2016-11-20 23:32:56 -0800'> ]
102
- ```
157
+ ----
103
158
 
104
- ### The Two Forms of the API
159
+ === The Two Forms of the API
105
160
 
106
161
  The feed API is offered in two forms:
107
162
 
108
- 1. single-user form, and
109
- 2. a batch (multi-user) form.
163
+ . single-user form, and
164
+ . a batch (multi-user) form.
110
165
 
111
166
  The method names and signatures are the same. The only difference is in what the methods return:
112
167
 
113
- 1. In the single user case, the return of, say, `#total_count` is an `Integer` value representing the total count for this user.
114
- 2. In the multi-user case, the return is a `SimpleFeed::Response` instance, that can be thought of as a `Hash`, that has the user IDs as the keys, and return results for each user as a value.
168
+ . In the single user case, the return of, say, `#total_count` is an `Integer` value representing the total count for this user.
169
+ . In the multi-user case, the return is a `SimpleFeed::Response` instance, that can be thought of as a `Hash`, that has the user IDs as the keys, and return results for each user as a value.
115
170
 
116
- Please see further below the details about the [Batch API](#batch-api).
171
+ Please see further below the details about the <<batch-api,Batch API>>.
117
172
 
118
- <a name="single-user-api"/>
173
+ +++<a name="single-user-api">++++++</a>+++
119
174
 
120
- ##### Single-User API
175
+ [discrete]
176
+ ===== Single-User API
121
177
 
122
178
  In the examples below we show responses based on a single-user usage. As previously mentioned, the multi-user usage is the same, except what the response values are, and is discussed further down below.
123
179
 
124
180
  Let's take a look at a ruby session, which demonstrates return values of the feed operations for a single user:
125
181
 
126
- ```ruby
182
+ [source,ruby]
183
+ ----
127
184
  require 'simplefeed'
128
185
 
129
186
  # Define the feed using an in-memory Hash provider, which uses
@@ -134,17 +191,21 @@ SimpleFeed.define(:followers) do |f|
134
191
  f.per_page = 2
135
192
  end
136
193
 
137
- # Let's get the Activity instance that wraps this user_id
138
- activity = SimpleFeed.followers.activity(user_id) # => [... complex object removed for brevity ]
194
+ # Let's get the Activity instance that wraps this
195
+ activity = SimpleFeed.followers.activity(user_id) # => [... complex object removed for brevity ]
196
+
139
197
  # let's clear out this feed to ensure it's empty
140
198
  activity.wipe # => true
199
+
141
200
  # Let's verify that the counts for this feed are at zero
142
201
  activity.total_count # => 0
143
202
  activity.unread_count # => 0
203
+
144
204
  # Store some events
145
205
  activity.store(value: 'hello') # => true
146
206
  activity.store(value: 'goodbye', at: Time.now - 20) # => true
147
207
  activity.unread_count # => 2
208
+
148
209
  # Now we can paginate the events, while resetting this user's last-read timestamp:
149
210
  activity.paginate(page: 1, reset_last_read: true)
150
211
  # [
@@ -159,49 +220,54 @@ activity.delete(value: 'hello') # => true
159
220
  # array of all events that have been deleted for this user.
160
221
  activity.delete_if do |event, user_id|
161
222
  event.value =~ /good/
162
- end
223
+ end
163
224
  # => [
164
225
  # [0] #<SimpleFeed::Event: value=good bye, at=1480475294.0579991>
165
- # ]
226
+ # ]
166
227
  activity.total_count # => 0
167
- ```
228
+ ----
168
229
 
169
- You can fetch all items (optionally filtered by time) in the feed using `#fetch`,
230
+ You can fetch all items (optionally filtered by time) in the feed using `#fetch`,
170
231
  `#paginate` and reset the `last_read` timestamp by passing the `reset_last_read: true` as a parameter.
171
232
 
172
- <a name="batch-api"/>
233
+ +++<a name="batch-api">++++++</a>+++
173
234
 
174
- ##### Batch (Multi-User) API
235
+ [discrete]
236
+ ===== Batch (Multi-User) API
175
237
 
176
238
  This API should be used when dealing with an array of users (or, in the
177
- future, a Proc or an ActiveRecord relation).
239
+ future, a Proc or an ActiveRecord relation).
240
+
241
+ ____
242
+ There are several reasons why this API should be preferred for
243
+ operations that perform a similar action across a range of users:
244
+ _various provider implementations can be heavily optimized for
245
+ concurrency, and performance_.
178
246
 
179
- > There are several reasons why this API should be preferred for
180
- > operations that perform a similar action across a range of users:
181
- > _various provider implementations can be heavily optimized for
182
- > concurrency, and performance_.
183
- >
184
- > The Redis Provider, for example, uses a notion of `pipelining` to send
185
- > updates for different users asynchronously and concurrently.
247
+ The Redis Provider, for example, uses a notion of `pipelining` to send
248
+ updates for different users asynchronously and concurrently.
249
+ ____
186
250
 
187
251
  Multi-user operations return a `SimpleFeed::Response` object, which can
188
252
  be used as a hash (keyed on user_id) to fetch the result of a given
189
253
  user.
190
254
 
191
- ```ruby
255
+ [source,ruby]
256
+ ----
192
257
  # Using the Feed API with, eg #find_in_batches
193
258
  @event_producer.followers.find_in_batches do |group|
194
-
259
+
195
260
  # Convert a group to the array of IDs and get ready to store
196
261
  activity = SimpleFeed.get(:followers).activity(group.map(&:id))
197
262
  activity.store(value: "#{@event_producer.name} liked an article")
198
-
199
- # => [Response] { user_id1 => [Boolean], user_id2 => [Boolean]... }
263
+
264
+ # => [Response] { user_id1 => [Boolean], user_id2 => [Boolean]... }
200
265
  # true if the value was stored, false if it wasn't.
201
266
  end
202
- ```
267
+ ----
203
268
 
204
- ##### Activity Feed DSL (Domain-Specific Language)
269
+ [discrete]
270
+ ===== Activity Feed DSL (Domain-Specific Language)
205
271
 
206
272
  The library offers a convenient DSL for adding feed functionality into
207
273
  your current scope.
@@ -211,7 +277,8 @@ exports just one primary method `#with_activity`. You call this method
211
277
  and pass an activity object created for a set of users (or a single
212
278
  user), like so:
213
279
 
214
- ```ruby
280
+ [source,ruby]
281
+ ----
215
282
  require 'simplefeed/dsl'
216
283
  include SimpleFeed::DSL
217
284
 
@@ -226,43 +293,46 @@ end
226
293
  with_activity(activity, countries: data_to_store) do
227
294
  # we can use countries as a variable because it was passed above in **opts
228
295
  countries.each do |country|
229
- # we can call #store without a receiver because the block is passed to
296
+ # we can call #store without a receiver because the block is passed to
230
297
  # instance_eval
231
298
  store(value: country) { |result| report(result ? 'success' : 'failure') }
232
- # we can call #report inside the proc because it is evaluated in the
299
+ # we can call #report inside the proc because it is evaluated in the
233
300
  # outside context of the #with_activity
234
-
235
- # now let's print a color ASCII dump of the entire feed for this user:
236
- color_dump
237
- end
301
+
302
+ # now let's print a color ASCII dump of the entire feed for this user:
303
+ color_dump
304
+ end
238
305
  printf "Activity counts are: %d unread of %d total\n", unread_count, total_count
239
306
  end
240
- ```
307
+ ----
241
308
 
242
- The DSL context has access to two additional methods:
309
+ The DSL context has access to two additional methods:
243
310
 
244
- * `#event(value, at)` returns a fully constructed `SimpleFeed::Event` instance
245
- * `#color_dump` prints to STDOUT the ASCII text dump of the current user's activities (events), as well as the counts and the `last_read` shown visually on the time line.
246
-
247
- ##### `#color_dump`
311
+ * `#event(value, at)` returns a fully constructed `SimpleFeed::Event` instance
312
+ * `#color_dump` prints to STDOUT the ASCII text dump of the current user's activities (events), as well as the counts and the `last_read` shown visually on the time line.
313
+
314
+ [discrete]
315
+ ===== `#color_dump`
248
316
 
249
317
  Below is an example output of `color_dump` method, which is intended for the debugging purposes.
250
318
 
251
- [<img src="https://raw.githubusercontent.com/kigster/simple-feed/master/man/sf-color-dump.png" width="450" alt="color_dump output" style="width: 300px; max-width:100%;">](https://raw.githubusercontent.com/kigster/simple-feed/master/man/sf-color-dump.png)
319
+ [image:https://raw.githubusercontent.com/kigster/simple-feed/master/man/sf-color-dump.png[color_dump output,450]](https://raw.githubusercontent.com/kigster/simple-feed/master/man/sf-color-dump.png)
252
320
 
253
- <a name="api"/>
321
+ +++<a name="api">++++++</a>+++
254
322
 
255
- ## Complete API
323
+ == Complete API
256
324
 
257
- For completeness sake we'll show the multi-user API responses only. For a single-user use-case the response is typically a scalar, and the input is a singular `user_id`, not an array of ids.
325
+ For completeness sake we'll show the multi-user API responses only. For a single-user use-case the response is typically a scalar, and the input is a singular `user_id`, not an array of ids.
258
326
 
259
- #### Multi-User (Batch) API
327
+ [discrete]
328
+ ==== Multi-User (Batch) API
260
329
 
261
330
  Each API call at this level expects an array of user IDs, therefore the
262
331
  return value is an object, `SimpleFeed::Response`, containing individual
263
332
  responses for each user, accessible via `response[user_id]` method.
264
333
 
265
- ```ruby
334
+ [source,ruby]
335
+ ----
266
336
  @multi = SimpleFeed.get(:feed_name).activity(User.active.map(&:id))
267
337
 
268
338
  @multi.store(value:, at:)
@@ -274,7 +344,7 @@ responses for each user, accessible via `response[user_id]` method.
274
344
  # => [Response] { user_id => [Boolean], ... } true if the value was removed, false if it didn't exist
275
345
 
276
346
  @multi.delete_if do |event, user_id|
277
- # if the block returns true, the event is deleted and returned
347
+ # if the block returns true, the event is deleted and returned
278
348
  end
279
349
  # => [Response] { user_id => [deleted_event1, deleted_event2, ...], ... }
280
350
 
@@ -283,15 +353,15 @@ end
283
353
  # => [Response] { user_id => [Boolean], ... } true if user activity was found and deleted, false otherwise
284
354
 
285
355
  # Return a paginated list of all items, optionally with the total count of items
286
- @multi.paginate(page: 1,
287
- per_page: @multi.feed.per_page,
288
- with_total: false,
356
+ @multi.paginate(page: 1,
357
+ per_page: @multi.feed.per_page,
358
+ with_total: false,
289
359
  reset_last_read: false)
290
360
  # => [Response] { user_id => [Array]<Event>, ... }
291
361
  # Options:
292
362
  # reset_last_read: false — reset last read to Time.now (true), or the provided timestamp
293
363
  # with_total: true — returns a hash for each user_id:
294
- # => [Response] { user_id => { events: Array<Event>, total_count: 3 }, ... }
364
+ # => [Response] { user_id => { events: Array<Event>, total_count: 3 }, ... }
295
365
 
296
366
  # Return un-paginated list of all items, optionally filtered
297
367
  @multi.fetch(since: nil, reset_last_read: false)
@@ -305,111 +375,106 @@ end
305
375
  # => [Response] { user_id => [Time] last_read, ... }
306
376
 
307
377
  @multi.total_count
308
- # => [Response] { user_id => [Integer] total_count, ... }
378
+ # => [Response] { user_id => [Integer, String] total_count, ... }
309
379
 
310
380
  @multi.unread_count
311
- # => [Response] { user_id => [Integer] unread_count, ... }
381
+ # => [Response] { user_id => [Integer, String] unread_count, ... }
312
382
 
313
383
  @multi.last_read
314
384
  # => [Response] { user_id => [Time] last_read, ... }
385
+ ----
315
386
 
316
- ```
317
-
318
- ## Providers in Depth
387
+ == Providers
319
388
 
320
- As we've discussed above, a provider is an underlying persistence mechanism implementation.
389
+ As we've discussed above, a provider is an underlying persistence mechanism implementation.
321
390
 
322
391
  It is the intention of this gem that:
323
392
 
324
- * it should be easy to write new providers
325
- * it should be easy to swap out providers
393
+ * it should be easy to write new providers
394
+ * it should be easy to swap out providers
326
395
 
327
396
  To create a new provider please use `SimpleFeed::Providers::Hash::Provider` class as a starting point.
328
397
 
329
398
  Two providers are available with this gem:
330
399
 
331
- ### `SimpleFeed::Providers::Redis::Provider`
400
+ === `SimpleFeed::Providers::Redis::Provider`
332
401
 
333
- Redis Provider is a production-ready persistence adapter that uses the [sorted set Redis data type](https://redislabs.com/ebook/redis-in-action/part-2-core-concepts-2/chapter-3-commands-in-redis/3-5-sorted-sets).
402
+ Redis Provider is a production-ready persistence adapter that uses the https://redislabs.com/ebook/redis-in-action/part-2-core-concepts-2/chapter-3-commands-in-redis/3-5-sorted-sets[sorted set Redis data type].
334
403
 
335
- This provider is optimized for large writes and can use either a single Redis instance for all users of your application, or any number of Redis [shards](https://en.wikipedia.org/wiki/Shard_(database_architecture)) by using a [_Twemproxy_](https://github.com/twitter/twemproxy) in front of the Redis shards.
404
+ This provider is optimized for large writes and can use either a single Redis instance for all users of your application, or any number of Redis https://en.wikipedia.org/wiki/Shard_(database_architecture)[shards] by using a https://github.com/twitter/twemproxy[_Twemproxy_] in front of the Redis shards.
336
405
 
337
- While future
406
+ === `SimpleFeed::Providers::HashProvider`
338
407
 
339
- * `SimpleFeed::Providers::HashProvider` is a pure Hash-like implementation of a provider that can be useful in unit tests of a host application. This provider could be used to write and read events within a single ruby process, can be serialized to and from a YAML file, and is therefore intended primarily for Feed emulations in automated tests.
340
-
408
+ This is a pure Hash-like implementation of a provider that can be useful in unit tests of a host application. This provider could be used to write and read events within a single ruby process, can be serialized to and from a YAML file, and is therefore intended primarily for Feed emulations in automated tests.
341
409
 
342
- ### Redis Provider
410
+ == Redis Provider
343
411
 
344
412
  If you set environment variable `REDIS_DEBUG` to `true` and run the example (see below) you will see every operation redis performs. This could be useful in debugging an issue or submitting a bug report.
345
-
346
- ## Running the Examples
413
+
414
+ == Running the Examples
347
415
 
348
416
  Source code for the gem contains the `examples` folder with an example file that can be used to test out the providers, and see what they do under the hood.
349
417
 
350
418
  To run it, checkout the source of the library, and then:
351
419
 
352
- ```bash
420
+ [source,bash]
421
+ ----
353
422
  git clone https://github.com/kigster/simple-feed.git
354
423
  cd simple-feed
355
424
  bundle
356
425
  be rspec # make sure tests are passing
357
- ruby examples/redis_provider_example.rb
358
- ```
426
+ ruby examples/redis_provider_example.rb
427
+ ----
359
428
 
360
- The above command will help you download, setup all dependencies, and run the examples for a single user:
429
+ The above command will help you download, setup all dependencies, and run the examples for a single user:
361
430
 
362
- [![Example](https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example.png)](https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example.png)
431
+ image::https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example.png[Example,link=https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example.png]
363
432
 
364
433
  If you set `REDIS_DEBUG` variable prior to running the example, you will be able to see every single Redis command executed as the example works its way through. Below is a sample output:
365
434
 
366
- [![Example with Debugging](https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example-redis-debug.png)](https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example-redis-debug.png)
435
+ image::https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example-redis-debug.png[Example with Debugging,link=https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-example-redis-debug.png]
367
436
 
368
- ### Generating Ruby API Documentation
437
+ === Generating Ruby API Documentation
369
438
 
370
- ```bash
439
+ [source,bash]
440
+ ----
371
441
  rake doc
372
- ```
442
+ ----
373
443
 
374
444
  This should use Yard to generate the documentation, and open your browser once it's finished.
375
445
 
376
- ### Installation
446
+ === Installation
377
447
 
378
448
  Add this line to your application's Gemfile:
379
449
 
380
- ```ruby
450
+ [source,ruby]
451
+ ----
381
452
  gem 'simple-feed'
382
- ```
453
+ ----
383
454
 
384
455
  And then execute:
385
456
 
386
- ```
387
- $ bundle
388
- ```
457
+ $ bundle
389
458
 
390
459
  Or install it yourself as:
391
460
 
392
- ```
393
- $ gem install simple-feed
394
- ```
461
+ $ gem install simple-feed
395
462
 
396
- ### Development
463
+ === Development
397
464
 
398
465
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
399
466
 
400
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
467
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org].
401
468
 
402
- ### Contributing
469
+ === Contributing
403
470
 
404
471
  Bug reports and pull requests are welcome on GitHub at https://github.com/kigster/simple-feed
405
472
 
406
- ### License
407
-
408
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
409
-
410
- ### Acknowledgements
473
+ === License
411
474
 
412
- * This project is conceived and sponsored by [Simbi, Inc.](https://simbi.com).
413
- * Author's personal experience at [Wanelo, Inc.](https://wanelo.com) has served as an inspiration.
475
+ The gem is available as open source under the terms of the http://opensource.org/licenses/MIT[MIT License].
414
476
 
477
+ === Acknowledgements
415
478
 
479
+ * This project is conceived and sponsored by https://simbi.com[Simbi, Inc.].
480
+ * Author's personal experience at https://wanelo.com[Wanelo, Inc.] has served as an inspiration.