simple-feed 1.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2d1f13cb5671b4c38135e9be14526c583c570ec7
4
- data.tar.gz: 9fdbf9d3a2b0c6cf760a0a7b61e3549ec22e22b5
3
+ metadata.gz: 37f4c35a87e3c3ed5b0a3cbdc88b6a88edc795c3
4
+ data.tar.gz: c88031baedfb60e6376d561621d2547b2db37978
5
5
  SHA512:
6
- metadata.gz: ad4538dabc594f68988783d01237e6446de5fd6550f1b5cc56fb27e908c31bef7b5baf692fbb5eae7b5bed90a2b4a288db273f99affbbc08dd6b5b7157229e22
7
- data.tar.gz: 6c65c68b8d4d6cb244b55c6510ec87182dad4fa93d59002008ce30422609674d4529d418a0ee8b7212d0d5c61f3c71a4aa144c5668052bac56b6ef85744be39a
6
+ metadata.gz: eb887ba71541e037975ae5800842b2f5a824901c5134ad6e38cd91489917fb62971edefac865fa5d2f7e21452417d08c77d164caa84d84eb16c0f681bb12a239
7
+ data.tar.gz: db3fad51130186c6199e6874b54e4659eedada7cf4e995781a5103e720e6beb712e0a610d30f5a2eed9abc9f8b23336f33007539e3825dc2ee3dad5901acce79
@@ -17,6 +17,8 @@ engines:
17
17
  enabled: false
18
18
  Rubocop/Metrics/CyclomaticComplexity:
19
19
  enabled: false
20
+ Rubocop/Lint/UnderscorePrefixedVariableName:
21
+ enabled: false
20
22
  ratings:
21
23
  paths:
22
24
  - "**.inc"
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --format progress
2
2
  --color
3
+ --profile 1
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  DisabledByDefault: true
3
+ TargetRubyVersion: 2.3
3
4
 
4
5
  #################### Lint ################################
5
6
 
data/README.md CHANGED
@@ -7,42 +7,43 @@
7
7
  [![Test Coverage](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/badges/8b899f6df4fc1ed93759/coverage.svg)](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/coverage)
8
8
  [![Issue Count](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/badges/8b899f6df4fc1ed93759/issue_count.svg)](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/feed)
9
9
 
10
- This is a ruby implementation of a fast simple feed commonly used in a typical social network-like applications. The implementation is optimized for **read-time performance** and high concurrency (lots of users). A default Redis-based provider implementation is provided, with the API supporting new providers very easily.
10
+ 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 pure Hash-based provider.
11
11
 
12
- <div style="border: 2px solid #222; padding: 10px; background: #f5f5f5; font-family: 'HelveticaNeue-CondensedBold'; font-size: 14pt;">
13
- <ol>
14
- <li>Please note that this project is under <em>active development</em>, and is not yet completed.<br/></li>
15
- <li>We thank <em><a href="http://simbi.com">Simbi, Inc.</a></em> for sponsoring the development of this open source library.</li>
16
- </div>
12
+ __Important Notes and Acknowledgements:__
13
+
14
+ * SimpleFeed *does not depend on Ruby on Rails* and is a __pure-ruby__ implementation.
15
+ * __SimpleFeed requires ruby 2.3 or later.__
16
+ * SimpleFeed is currently live in production.
17
+ * We'd like to thank __[Simbi, Inc — Symbiotic Economy](http://simbi.com)__ for their sponsorship of the development of this open source library.
17
18
 
18
19
  ## What is an activity feed?
19
20
 
20
21
  > Activity feed is a visual representation of a time-ordered, reverse chronological list of events which can be:
21
22
  >
22
23
  > * personalized for a given user or a group, or global
23
- > * filtered by a certain characteristic, such as, eg.
24
- > * the source of the events — i.e. people you follow
25
- > * type of event (i.e. posts, likes, and updates)
26
- > * the target of the event i.e. my own activity as opposed to from those I follow.
27
24
  > * aggregated across several actors for a similar event type, eg. "John, Mary, etc.. followed George"
25
+ > * filtered by a certain characteristic, such as:
26
+ > * the actor producing an event — i.e. people you follow on a social network, or "yourself" for your own activity
27
+ > * the type of an event (i.e. posts, likes, comments, stories, etc)
28
+ > * the target of an event (commonly a user, but can also be a thing you are interested in, e.g. a github repo you are watching)
28
29
 
29
- Here is an example of a text-based simple feed that is very common today on social networking sites.
30
+ Here is an example of a real feed powered by this library, and which is very common on today's social media sites:
30
31
 
31
- [![Example](https://raw.githubusercontent.com/kigster/simple-feed/master/man/sf-example.png)](https://raw.githubusercontent.com/kigster/simple-feed/master/man/sf-example.png)
32
+ [![Example](https://raw.githubusercontent.com/kigster/simple-feed/master/man/activity-feed-action.png)](https://raw.githubusercontent.com/kigster/simple-feed/master/man/activity-feed-action.png)
32
33
 
33
- The _stories_ in the feed depend entirely on the application using this
34
- library, therefore to integrate with SimpleFeed requires implementing
35
- several _glue points_ in your code.
34
+ What you publish into your feed — i.e. _stories_ or _events_, will depend entirely on your application. SimpleFeed should be able to power the most demanding *write-time* feeds.
36
35
 
37
- ## Challenges
36
+ ## Challenges
38
37
 
39
- Activity feeds tend to be challenging due to the large number of event types that it typically includes, and the requirement for it to scale to massive numbers of concurrent users. Therefore common implementations tend to focus on either:
38
+ Building a personalized activity feeds tend to be a challenging task, due to the diversity of event types that it often includes, the personalization requirement, and the need for it to often scale to very large numbers of concurrent users. Therefore common implementations tend to focus on either:
40
39
 
41
40
  * optimizing the read time performance by pre-computing the feed for each user ahead of time
42
41
  * OR optimizing the various ranking algorithms by computing the feed at read time, with complex forms of caching addressing the performance requirements.
43
42
 
44
43
  The first type of feed is much simpler to implement on a large scale (up to a point), and it scales well if the data is stored in a light-weight in-memory storage such as Redis. This is exactly the approach this library takes.
45
44
 
45
+ For more information about various types of feed, and the typical architectures that power them — please read ["How would you go about building an activity feed like Facebook?"](https://hashnode.com/post/architecture-how-would-you-go-about-building-an-activity-feed-like-facebook-cioe6ea7q017aru53phul68t1/answer/ciol0lbaa02q52s530vfqea0t) by [Lee Byron](https://hashnode.com/@leebyron).
46
+
46
47
  ## Overview
47
48
 
48
49
  The feed library aims to address the following goals:
@@ -103,6 +104,9 @@ activity.store(value: 'hello')
103
104
 
104
105
  # or equivalent:
105
106
  @event = SimpleFeed::Event.new(value: 'hello', at: Time.now)
107
+ # or even simpler:
108
+ @event = SimpleFeed::Event.new('hello', Time.now)
109
+ # and then:
106
110
  activity.store(event: @event)
107
111
  # => false # false indicates that the same event is already in the feed.
108
112
  ```
@@ -122,7 +126,7 @@ activity.paginate(page: 1)
122
126
 
123
127
  The feed API is offered in a single-user and a batch (multi-user) forms.
124
128
 
125
- The only and primary difference is in what the methods return. In the
129
+ The main and only difference is in what the methods return. In the
126
130
  single user case, the return of, say, `#total_count` is an `Integer`
127
131
  value representing the total count for this user.
128
132
 
@@ -134,56 +138,44 @@ Please see further below the details about the [Batch API](#bach-api).
134
138
 
135
139
  <a name="single-user-api"/>
136
140
 
137
- ##### Single-User API
141
+ ##### Single-User API
138
142
 
139
- This API should be used typically for _read_ operations, and is accessed
140
- via the `SimpleFeed::Feed#for` instance method. Optimized for simplicity
141
- of data retrieval of a single-user, this method strives for simplicity
142
- and ease of use.
143
+ In the examples below we show responses based on a single-user usage. As previously mentioned, the multi-user case is the same, except for the response values, and is discussed further below.
143
144
 
144
- Below is a user session that demonstrates simple return values from the
145
- Feed operations for a given user:
145
+ Below is a user session that demonstrates simple return values from the feed operations for a given user:
146
146
 
147
147
  ```ruby
148
148
  require 'simplefeed'
149
149
 
150
150
  # Define the feed using an in-memory Hash provider, which uses
151
151
  # SortedSet to keep user's events sorted.
152
- SimpleFeed.define(:notifications) do |f|
152
+ SimpleFeed.define(:followers) do |f|
153
153
  f.provider = SimpleFeed.provider(:hash)
154
154
  f.per_page = 50
155
155
  f.per_page = 2
156
156
  end
157
157
 
158
158
  # Let's get the Activity instance that wraps this user_id
159
- activity = SimpleFeed.get(:notifications).activity(user_id)
160
- # => [... complex object removed for brevity ]
161
-
159
+ activity = SimpleFeed.get(:followers).activity(user_id) # => [... complex object removed for brevity ]
162
160
  # let's clear out this feed to ensure it's empty
163
- activity.wipe
164
- # => true
165
-
161
+ activity.wipe # => true
166
162
  # Let's verify that the counts for this feed are at zero
167
- activity.total_count
168
- #=> 0
169
-
170
- activity.unread_count
171
- #=> 0
172
-
163
+ activity.total_count # => 0
164
+ activity.unread_count # => 0
173
165
  # Store some events
174
- activity.store(value: 'hello')
175
- activity.store(value: 'goodbye')
176
-
166
+ activity.store(value: 'hello') # => true
167
+ activity.store(value: 'goodbye') # => true
168
+ activity.unread_count # => 2
177
169
  # Now we can paginate the events, which by default resets "last_read" timestamp the user
178
170
  activity.paginate(page: 1)
179
171
  # [
180
172
  # [0] #<SimpleFeed::Event#70138821650220 {"value":"goodbye","at":1480475294.0579991}>,
181
173
  # [1] #<SimpleFeed::Event#70138821649420 {"value":"hello","at":1480475294.057138}>
182
174
  # ]
183
-
184
175
  # Now the unread_count should return 0 since the user just "viewed" the feed.
185
- activity.unread_count
186
- #=> 0
176
+ activity.unread_count # => 0
177
+ activity.delete(value: 'hello') # => true
178
+ activity.total_count # => 1
187
179
  ```
188
180
 
189
181
  You can fetch all items in the feed using `#fetch`, and you can
@@ -252,6 +244,9 @@ with_activity(activity, countries: data_to_store) do
252
244
  store(value: country) { |result| report(result ? 'success' : 'failure') }
253
245
  # we can call #report inside the proc because it is evaluated in the
254
246
  # outside context of the #with_activity
247
+
248
+ # now let's print a color ASCII dump of the entire feed for this user:
249
+ color_dump
255
250
  end
256
251
  printf "Activity counts are: %d unread of %d total\n", unread_count, total_count
257
252
  end
@@ -261,56 +256,9 @@ end
261
256
 
262
257
  ## Complete API
263
258
 
264
- ### Single User
265
-
266
- For a single user, via the instance of
267
- `SimpleFeed::Activity::UserActivity` class:
268
-
269
- ```ruby
270
- require 'simplefeed'
259
+ 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.
271
260
 
272
- @ua = SimpleFeed.get(:news).activity(current_user.id)
273
-
274
- @ua.store(event:)
275
- @ua.store(value:, at:)
276
- # => [Boolean] true if the value was stored, false if it wasn't.
277
-
278
- @ua.delete(event:)
279
- @ua.delete(value:, at:)
280
- # => [Boolean] true if the value was removed, false if it didn't exist
281
-
282
- @ua.delete_if do |user_id, event|
283
- # if the block returns true, the event is deleted
284
- end
285
-
286
- @ua.wipe
287
- # => [Boolean] true
288
-
289
- # Options:
290
- # with options[:peak] = true it does not reset last_read
291
- # with options[:with_total] = true it returns a hash with a total:
292
- # @return:
293
- @ua.paginate(page:, per_page:, **options)
294
- # @return: [Array]<Event> (without options[:with_total])
295
- # @return: { events: [Array]<Event, total_count: 3242 }
296
-
297
- @ua.fetch
298
- # => [Array]<Event> – returns all events up to Feed.max_size
299
-
300
- @ua.reset_last_read
301
- # => [Time] last_read
302
-
303
- @ua.total_count
304
- # => [Integer] total_count
305
-
306
- @ua.unread_count
307
- # => [Integer] unread_count
308
-
309
- @ua.last_read
310
- # => [Time] last_read
311
- ```
312
-
313
- #### Batch User API
261
+ #### Multi-User (Batch) API
314
262
 
315
263
  Each API call at this level expects an array of user IDs, therefore the
316
264
  return value is an object, `SimpleFeed::Response`, containing individual
@@ -331,15 +279,24 @@ responses for each user, accessible via `response[user_id]` method.
331
279
  # if the block returns true, the event is deleted
332
280
  end
333
281
 
282
+ # Wipe the feed for a given user(s)
334
283
  @multi.wipe
335
284
  # => [Response] { user_id => [Boolean], ... } true if user activity was found and deleted, false otherwise
336
285
 
337
- @multi.paginate(page:, per_page:, peek: false)
286
+ # Return a paginated list of all items, optionally with the total count of items
287
+ @multi.paginate(page:, per_page:, peek: false, with_total: false)
338
288
  # => [Response] { user_id => [Array]<Event>, ... }
339
- # With (peak: true) does not reset last_read, otherwise it does.
289
+ # Options:
290
+ # peek: true — does not reset last_read, otherwise it does.
291
+ # with_total: true — returns a hash for each user_id:
292
+ # => [Response] { user_id => { events: Array<Event>, total_count: 3 }, ... }
340
293
 
341
- @multi.fetch
294
+ # Return un-paginated list of all items, optionally filtered
295
+ @multi.fetch(since: nil)
342
296
  # => [Response] { user_id => [Array]<Event>, ... }
297
+ # Options:
298
+ # since: <timestamp> — if provided, returns all items posted since then
299
+ # since: :unread — if provided, returns all unread items and resets +last_read+
343
300
 
344
301
  @multi.reset_last_read
345
302
  # => [Response] { user_id => [Time] last_read, ... }
@@ -376,11 +333,11 @@ Two providers are available with this gem:
376
333
 
377
334
  ### Redis Provider
378
335
 
379
- If you set environment variable `REDIS_DEBUG` 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.
336
+ 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.
380
337
 
381
- ## Examples
338
+ ## Running the Examples
382
339
 
383
- Source code for the gem contains the `examples` folder with an example file that can be used to measure the performance of the Redis-based provider.
340
+ 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.
384
341
 
385
342
  To run it, checkout the source of the library, and then:
386
343
 
@@ -392,20 +349,13 @@ be rspec # make sure tests are passing
392
349
  ruby examples/redis_provider_example.rb
393
350
  ```
394
351
 
395
- The above command will help you download, setup all dependencies, and run the examples for a single user. To run examples for multiple users, just __just pass a number as a second argument__:, for example:
352
+ The above command will help you download, setup all dependencies, and run the examples for a single user:
396
353
 
397
- ``` bash
398
- ruby examples/redis_provider_example.rb 10
399
- ```
400
-
401
- Or to measure the time:
402
- ```bash
403
- time ruby examples/redis_provider_example.rb 1000 > /dev/null
404
- ```
354
+ [![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)
405
355
 
406
- Below is a an example output shown for a single user:
356
+ 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:
407
357
 
408
- [![Example](https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-the-example.png)](https://raw.githubusercontent.com/kigster/simple-feed/master/man/running-the-example.png)
358
+ [![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)
409
359
 
410
360
  ### Generating Ruby API Documentation
411
361
 
@@ -24,43 +24,44 @@ def p(*args)
24
24
  end
25
25
 
26
26
  with_activity(@activity) do
27
- header "#{@activity.feed.provider_type.to_s} provider example"
28
- wipe { puts 'wiping feed...' }
29
27
 
30
- store('value one') { p 'storing', 'value one' }
31
- store('value two') { p 'storing', 'value two' }
32
- store('value three') { p 'storing', 'value three' }
28
+ header "#{@activity.feed.provider_type} provider example"
29
+
30
+ wipe
31
+
32
+ store('value one') { p 'storing new value', 'value one' }
33
+ store('value two') { p 'storing new value', 'value two' }
34
+ store('value three') { p 'storing new value', 'value three' }
35
+
33
36
  hr
34
37
 
35
38
  total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
36
39
  unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
37
40
 
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) }
41
+ header 'paginate(page: 1, per_page: 2)'
42
+ paginate(page: 1, per_page: 2) { |r| puts r[@uid].map(&:to_color_s) }
43
+ header 'paginate(page: 2, per_page: 2)'
44
+ paginate(page: 2, per_page: 2) { |r| puts r[@uid].map(&:to_color_s) }
44
45
 
45
46
  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}" }
47
+
48
+ total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
49
+ unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
48
50
 
49
51
  hr
50
52
  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
53
 
54
54
  color_dump
55
55
 
56
- hr
56
+ header 'deleting'
57
+
57
58
  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}" }
59
+ total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
60
+ unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
60
61
  hr
61
62
  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}" }
63
+ total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
64
+ unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
64
65
 
65
66
  end
66
67
 
@@ -7,14 +7,6 @@ require 'simplefeed/providers/redis'
7
7
  require 'simplefeed/providers/hash'
8
8
  require 'simplefeed/dsl'
9
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
10
  module SimpleFeed
19
11
  @registry = {}
20
12
 
@@ -13,10 +13,11 @@ module SimpleFeed
13
13
  include Enumerable
14
14
 
15
15
  #
16
- # Multi-user API for the feeds.
16
+ # API Examples
17
+ # ============
17
18
  #
18
- # ```ruby
19
- # @multi = SimpleFeed.get(:feed_name).for(User.active.map(&:id))
19
+ #```ruby
20
+ # @multi = SimpleFeed.get(:feed_name).activity(User.active.map(&:id))
20
21
  #
21
22
  # @multi.store(value:, at:)
22
23
  # @multi.store(event:)
@@ -24,18 +25,28 @@ module SimpleFeed
24
25
  #
25
26
  # @multi.delete(value:, at:)
26
27
  # @multi.delete(event:)
27
- # # => [Response] { user_id => [Boolean], ... } true if the value was deleted, false if it didn't exist
28
+ # # => [Response] { user_id => [Boolean], ... } true if the value was removed, false if it didn't exist
29
+ #
30
+ # @multi.delete_if do |user_id, event|
31
+ # # if the block returns true, the event is deleted
32
+ # end
28
33
  #
29
34
  # @multi.wipe
30
35
  # # => [Response] { user_id => [Boolean], ... } true if user activity was found and deleted, false otherwise
31
36
  #
32
- # @multi.paginate(page:, per_page:, peek: false)
37
+ # @multi.paginate(page:, per_page:, peek: false, with_total: false)
33
38
  # # => [Response] { user_id => [Array]<Event>, ... }
39
+ # # Options:
40
+ # # peek: true — does not reset last_read, otherwise it does.
41
+ # # with_total: true — returns a hash for each user_id:
42
+ # # => [Response] { user_id => { events: Array<Event>, total_count: 3 }, ... }
34
43
  #
35
- # # With (peak: true) does not reset last_read, otherwise it does.
36
- #
37
- # @multi.fetch
44
+ # # Return un-paginated list of all items, optionally filtered
45
+ # @multi.fetch(since: nil)
38
46
  # # => [Response] { user_id => [Array]<Event>, ... }
47
+ # # Options:
48
+ # # since: <timestamp> — if provided, returns all items posted since then
49
+ # # since: :unread — if provided, returns all unread items
39
50
  #
40
51
  # @multi.reset_last_read
41
52
  # # => [Response] { user_id => [Time] last_read, ... }
@@ -48,7 +59,8 @@ module SimpleFeed
48
59
  #
49
60
  # @multi.last_read
50
61
  # # => [Response] { user_id => [Time] last_read, ... }
51
- # ```
62
+ #
63
+ #```
52
64
 
53
65
  SimpleFeed::Providers.define_provider_methods(self) do |instance, method, *args, **opts, &block|
54
66
  opts.merge!(user_ids: instance.user_ids)
@@ -16,39 +16,50 @@ module SimpleFeed
16
16
  yield(user_id)
17
17
  end
18
18
 
19
+ #```ruby
20
+ # @activity = SimpleFeed.get(:feed_name).activity(current_user.id)
19
21
  #
20
- # Single-user API for the feeds.
22
+ # @activity.store(value:, at:)
23
+ # @activity.store(event:)
24
+ # # => [Boolean] true if the value was stored, false if it wasn't.
21
25
  #
22
- # @ua = SimpleFeed.get(:feed_name).user_activity(current_user.id)
26
+ # @activity.delete(value:, at:)
27
+ # @activity.delete(event:)
28
+ # # => [Boolean] true if the value was removed, false if it didn't exist
23
29
  #
24
- # @ua.store(value:, at:)
25
- # # => [Boolean] true if the value was stored, false if it wasn't.
30
+ # @activity.delete_if do |user_id, event|
31
+ # # if the block returns true, the event is deleted
32
+ # end
26
33
  #
27
- # @ua.delete(value:, at:)
28
- # # => [Boolean] true if the value was deleted, false if it didn't exist
34
+ # @activity.wipe
35
+ # # => [Boolean] true if user activity was found and deleted, false otherwise
29
36
  #
30
- # @ua.wipe
31
- # # => [Boolean] true
37
+ # @activity.paginate(page:, per_page:, peek: false, with_total: false)
38
+ # # => [Array]<Event>
39
+ # # Options:
40
+ # # peek: true — does not reset last_read, otherwise it does.
41
+ # # with_total: true — returns a hash for each user_id:
42
+ # # => { events: Array<Event>, total_count: 3 }
32
43
  #
33
- # @ua.paginate(page:, per_page:, peek: false)
34
- # # => [Array]<Event>
35
- # # with peak: true does not reset last_read
44
+ # # Return un-paginated list of all items, optionally filtered
45
+ # @activity.fetch(since: nil)
46
+ # # => [Array]<Event>
47
+ # # Options:
48
+ # # since: <timestamp> — if provided, returns all items posted since then
49
+ # # since: :unread — if provided, returns all unread items
36
50
  #
37
- # @ua.fetch
38
- # # => [Array]<Event>
51
+ # @activity.reset_last_read
52
+ # # => [Time] last_read
39
53
  #
40
- # @ua.reset_last_read
41
- # # => [Time] last_read
54
+ # @activity.total_count
55
+ # # => [Integer] total_count
42
56
  #
43
- # @ua.total_count
44
- # # => [Integer] total_count
57
+ # @activity.unread_count
58
+ # # => [Integer] unread_count
45
59
  #
46
- # @ua.unread_count
47
- # # => [Integer] unread_count
48
- #
49
- # @ua.last_read
50
- # # => [Time] last_read
51
- # ```
60
+ # @activity.last_read
61
+ # # => [Time] last_read
62
+
52
63
 
53
64
  SimpleFeed::Providers.define_provider_methods(self) do |instance, method, *args, **opts, &block|
54
65
  response = instance.user_activity.send(method, *args, **opts, &block)
@@ -11,10 +11,10 @@ module SimpleFeed
11
11
  # include SimpleFeed::DSL
12
12
  #
13
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
14
+ # store(value: 'hello', at: Time.now) #=> true
15
+ # fetch # => [ Event, Event, ... ]
16
+ # total_count # => 12
17
+ # unread_count # => 4
18
18
  # end
19
19
  #
20
20
  module DSL
@@ -3,16 +3,19 @@ require 'simplefeed/activity/single_user'
3
3
  require 'simplefeed/activity/multi_user'
4
4
  module SimpleFeed
5
5
  module DSL
6
+ # This module exports method #color_dump which receives an activity and
7
+ # then prints out a report about the activity, including the event
8
+ # data found for a given user.
6
9
  module Formatter
7
10
  include SimpleFeed::DSL
8
11
 
9
12
  attr_accessor :activity, :feed
10
13
 
11
- def color_dump(_activity = activity)
12
- _activity = if _activity.is_a?(SimpleFeed::Activity::SingleUser)
13
- _activity.feed.activity([_activity.user_id])
14
+ def color_dump(this_activity = activity)
15
+ this_activity = if this_activity.is_a?(SimpleFeed::Activity::SingleUser)
16
+ this_activity.feed.activity([this_activity.user_id])
14
17
  else
15
- _activity
18
+ this_activity
16
19
  end
17
20
  _puts
18
21
 
@@ -22,34 +25,34 @@ module SimpleFeed
22
25
  field('Max Size', feed.max_size, "\n")
23
26
  end
24
27
 
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
28
+ with_activity(this_activity) do
29
+ this_activity.each do |user_id|
30
+ this_last_event_at = nil
31
+ this_last_read = (last_read[user_id] || 0.0).to_f
29
32
 
30
33
  [['User ID', user_id, "\n"],
31
34
  ['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'],
35
+ ['Last Read', this_last_read ? Time.at(this_last_read) : 'N/A'],
33
36
  ].each do |field, value, *args|
34
37
  field(field, value, *args)
35
38
  end
36
39
 
37
40
  _puts; hr '¨'
38
41
 
39
- _events = fetch[user_id]
40
- _events_count = _events.size
41
- _events.each_with_index do |_event, _index|
42
+ this_events = fetch[user_id]
43
+ this_events_count = this_events.size
44
+ this_events.each_with_index do |_event, _index|
42
45
 
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)
46
+ if this_last_event_at.nil? && _event.at < this_last_read
47
+ print_last_read_separator(this_last_read)
48
+ elsif this_last_event_at && this_last_read < this_last_event_at && this_last_read > _event.at
49
+ print_last_read_separator(this_last_read)
47
50
  end
48
51
 
49
- _last_event_at = _event.at # float
52
+ this_last_event_at = _event.at # float
50
53
  _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)
54
+ if _index == this_events_count - 1 && this_last_read < _event.at
55
+ print_last_read_separator(this_last_read)
53
56
  end
54
57
  end
55
58
  end
@@ -97,13 +100,17 @@ module SimpleFeed
97
100
  end
98
101
 
99
102
  def hr(char = '—')
100
- _print (char * 75 + "\n").magenta
103
+ _print(_hr(char).magenta)
104
+ end
105
+
106
+ def _hr(char = '—')
107
+ char * 75 + "\n"
101
108
  end
102
109
 
103
110
  def header(message = nil)
104
- hr
105
- block_given? ? yield : _print(message.capitalize.magenta.bold + "\n")
106
- hr
111
+ _print(_hr.green.bold)
112
+ block_given? ? yield : _print(message.green.italic + "\n")
113
+ _print(_hr.green.bold)
107
114
  end
108
115
  end
109
116
  end
@@ -8,12 +8,12 @@ module SimpleFeed
8
8
  def initialize(*args, value: nil, at: Time.now)
9
9
  if args && args.size > 0
10
10
  self.value = args[0]
11
- self.at = args[1] || at
12
- else
13
- self.value = value
14
- self.at = at
11
+ self.at = args[1]
15
12
  end
16
13
 
14
+ self.value ||= value
15
+ self.at ||= at
16
+
17
17
  self.at = self.at.to_f
18
18
 
19
19
  validate!
@@ -53,22 +53,21 @@ module SimpleFeed
53
53
  end
54
54
 
55
55
  def to_s
56
- "Event Value: [#{value}], epoch: [#{at}], time: [#{time}]"
56
+ "<SimpleFeed::Event: value='#{value}', at='#{at}', time='#{time}'>"
57
57
  end
58
58
 
59
59
  def to_color_s
60
60
  counter = 0
61
- to_s.gsub(/,/, '-').split(/[\[\]]/).map do |word|
61
+ to_s.split(/[']/).map do |word|
62
62
  counter += 1
63
- counter.even? ? word.bold.yellow + "\n": word.blue
64
- end.join('').gsub(/[\n] /, '')
63
+ counter.even? ? word.yellow.bold : word.blue
64
+ end.join('')
65
65
  end
66
66
 
67
67
  def inspect
68
68
  super
69
69
  end
70
70
 
71
-
72
71
  private
73
72
 
74
73
  def validate!
@@ -77,11 +76,5 @@ module SimpleFeed
77
76
  end
78
77
  end
79
78
 
80
- def copy(&block)
81
- copy = self.clone
82
- copy.instance_eval(&block)
83
- copy
84
- end
85
-
86
79
  end
87
80
  end
@@ -1,7 +1,11 @@
1
1
  require 'base62-rb'
2
2
  require 'hashie'
3
3
  require 'set'
4
- require 'knjrbfw'
4
+
5
+ begin
6
+ require 'knjrbfw'
7
+ rescue LoadError
8
+ end
5
9
 
6
10
  require 'simplefeed/event'
7
11
  require_relative 'paginator'
@@ -57,17 +61,26 @@ module SimpleFeed
57
61
  end
58
62
  end
59
63
 
60
- def fetch(user_ids:)
64
+ def fetch(user_ids:, since: nil)
61
65
  with_response_batched(user_ids) do |key|
62
- activity(key)
66
+ if since == :unread
67
+ result = activity(key).reject { |event| event.at < user_record(key).last_read.to_f }
68
+ reset_last_read(user_ids: user_ids)
69
+ result
70
+ elsif since
71
+ activity(key).reject { |event| event.at < since.to_f }
72
+ else
73
+ activity(key)
74
+ end
63
75
  end
64
76
  end
65
77
 
66
- def paginate(user_ids:, page:, per_page: feed.per_page, **options)
67
- reset_last_read(user_ids: user_ids) unless options[:peek]
78
+ def paginate(user_ids:, page:, per_page: feed.per_page, with_total: false, peek: false)
79
+ reset_last_read(user_ids: user_ids) unless peek
68
80
  with_response_batched(user_ids) do |key|
69
81
  activity = activity(key)
70
- (page && page > 0) ? activity[((page - 1) * per_page)...(page * per_page)] : activity
82
+ result = (page && page > 0) ? activity[((page - 1) * per_page)...(page * per_page)] : activity
83
+ with_total ? { events: result, total_count: activity.length } : result
71
84
  end
72
85
  end
73
86
 
@@ -97,8 +110,12 @@ module SimpleFeed
97
110
  end
98
111
 
99
112
  def total_memory_bytes
100
- analyzer = Knj::Memory_analyzer::Object_size_counter.new(self.h)
101
- analyzer.calculate_size
113
+ if defined?(::Knj)
114
+ analyzer = Knj::Memory_analyzer::Object_size_counter.new(self.h)
115
+ analyzer.calculate_size
116
+ else
117
+ raise LoadError, 'Please run "gem install knjrbfw" to get accurate hash size'
118
+ end
102
119
  end
103
120
 
104
121
  def total_users
@@ -15,6 +15,14 @@ module SimpleFeed
15
15
  self.debug
16
16
  end
17
17
 
18
+ def self.with_debug(&block)
19
+ previous_value = SimpleFeed::Providers::Redis.debug
20
+ SimpleFeed::Providers::Redis.debug = true
21
+ result = yield if block_given?
22
+ SimpleFeed::Providers::Redis.debug = previous_value
23
+ result
24
+ end
25
+
18
26
  class << self
19
27
  attr_accessor :debug
20
28
  end
@@ -24,10 +32,32 @@ module SimpleFeed
24
32
  end
25
33
 
26
34
  class LoggingRedis < Struct.new(:redis)
35
+ @stream = STDOUT
36
+ @disable_color = false
37
+ class << self
38
+ # in case someone might prefer to dump it into STDOUT instead, just set
39
+ # SimpleFeed::Providers::Redis::Driver::LoggingRedis.stream = STDOUT | STDERR | etc...
40
+ attr_accessor :stream, :disable_color
41
+ end
42
+
27
43
  def method_missing(m, *args, &block)
28
44
  if redis.respond_to?(m)
29
- result = redis.send(m, *args, &block)
30
- STDERR.printf "%40s %s\n", "#{m.to_s.upcase.bold.red}", "#{args.inspect.gsub(/[\[\]]/, '').magenta}"
45
+ t1 = Time.now
46
+ result = redis.send(m, *args, &block)
47
+ delta = Time.now - t1
48
+ colors = [:blue, nil, :blue, :blue, :yellow, :cyan, nil, :blue]
49
+ components = [
50
+ Time.now.strftime('%H:%M:%S.%L'), ' rtt=',
51
+ (sprintf '%.5f', delta*1000), ' ms ',
52
+ (sprintf '%15s ', m.to_s.upcase),
53
+ (sprintf '%-40s', args.inspect.gsub(/[",\[\]]/, '')), ' ⇒ ',
54
+ (result.is_a?(::Redis::Future) ? '' : result.to_s)]
55
+ components.each_with_index do |component, index|
56
+ color = self.class.disable_color ? nil : colors[index]
57
+ component = component.send(color) if color
58
+ self.class.stream.printf component
59
+ end
60
+ self.class.stream.puts
31
61
  result
32
62
  else
33
63
  super
@@ -75,10 +75,21 @@ module SimpleFeed
75
75
  end
76
76
  end
77
77
 
78
-
79
- def fetch(user_ids:)
78
+ def fetch(user_ids:, since: nil)
79
+ if since == :unread
80
+ last_read_response = with_response_pipelined(user_ids) do |redis, key|
81
+ get_users_last_read(redis, key)
82
+ end
83
+ reset_last_read(user_ids: user_ids)
84
+ end
80
85
  with_response_pipelined(user_ids) do |redis, key|
81
- redis.zrevrange(key.data, 0, -1, withscores: true)
86
+ if since == :unread
87
+ redis.zrevrangebyscore(key.data, '+inf', (last_read_response.delete(key.user_id) || 0).to_f, withscores: true)
88
+ elsif since
89
+ redis.zrevrangebyscore(key.data, '+inf', since.to_f, withscores: true)
90
+ else
91
+ redis.zrevrange(key.data, 0, -1, withscores: true)
92
+ end
82
93
  end
83
94
  end
84
95
 
@@ -1,3 +1,3 @@
1
1
  module SimpleFeed
2
- VERSION = '1.0.2'
2
+ VERSION = '1.0.4'
3
3
  end
Binary file
@@ -29,14 +29,12 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency 'connection_pool', '~> 2'
30
30
  spec.add_dependency 'activesupport'
31
31
  spec.add_dependency 'colored2'
32
- spec.add_dependency 'knjrbfw'
33
32
 
34
33
  spec.add_development_dependency 'awesome_print'
35
34
  spec.add_development_dependency 'yard'
36
35
  spec.add_development_dependency 'simplecov', '~> 0.12'
37
36
  spec.add_development_dependency 'codeclimate-test-reporter', '~> 1.0'
38
-
39
- spec.add_development_dependency 'ventable'
37
+ spec.add_development_dependency 'knjrbfw'
40
38
  spec.add_development_dependency 'bundler'
41
39
  spec.add_development_dependency 'rake', '~> 10.0'
42
40
  spec.add_development_dependency 'rspec', '~> 3.5'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-feed
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-08 00:00:00.000000000 Z
11
+ date: 2016-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base62-rb
@@ -108,20 +108,6 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: knjrbfw
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :runtime
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: awesome_print
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -179,7 +165,7 @@ dependencies:
179
165
  - !ruby/object:Gem::Version
180
166
  version: '1.0'
181
167
  - !ruby/object:Gem::Dependency
182
- name: ventable
168
+ name: knjrbfw
183
169
  requirement: !ruby/object:Gem::Requirement
184
170
  requirements:
185
171
  - - ">="
@@ -301,8 +287,9 @@ files:
301
287
  - lib/simplefeed/providers/serialization/key.rb
302
288
  - lib/simplefeed/response.rb
303
289
  - lib/simplefeed/version.rb
304
- - man/running-the-example.png
305
- - man/sf-example.png
290
+ - man/activity-feed-action.png
291
+ - man/running-example-redis-debug.png
292
+ - man/running-example.png
306
293
  - simple-feed.gemspec
307
294
  homepage: https://github.com/kigster/simple-feed
308
295
  licenses:
Binary file
Binary file