simple-feed 1.0.4 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +64 -51
- data/lib/simplefeed.rb +38 -21
- data/lib/simplefeed/activity/multi_user.rb +7 -5
- data/lib/simplefeed/activity/single_user.rb +4 -3
- data/lib/simplefeed/feed.rb +18 -8
- data/lib/simplefeed/key/template.rb +52 -0
- data/lib/simplefeed/key/type.rb +26 -0
- data/lib/simplefeed/providers.rb +1 -5
- data/lib/simplefeed/providers/base/provider.rb +14 -7
- data/lib/simplefeed/providers/hash/provider.rb +28 -16
- data/lib/simplefeed/providers/key.rb +72 -0
- data/lib/simplefeed/providers/redis/provider.rb +21 -11
- data/lib/simplefeed/response.rb +1 -1
- data/lib/simplefeed/version.rb +1 -1
- data/man/sf-color-dump.png +0 -0
- data/simple-feed.gemspec +2 -2
- metadata +12 -9
- data/lib/simplefeed/providers/serialization/key.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6f18bbf9e002e7f1ee3fa49a7675ea66320bc4
|
4
|
+
data.tar.gz: 1325dfc4fed1fe485aef9d2fcd0a75442022caee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d90c16eaca736fe56ae19a4c2df0688a9267f00863f18a713ac678ec9150210e94553945636450651563af35706f4ec68dcb113fa451a7109c7376bcb5704363
|
7
|
+
data.tar.gz: 758243acc5437bc4f022dfbeaa5b93d3af01525ec972760fe33cca1756de220e4b2b344c5e2f7e771578f8fdb24aef662e4abad8a9ba09ace74776c0512e37d8
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
[![Code Climate](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/badges/8b899f6df4fc1ed93759/gpa.svg)](https://codeclimate.com/repos/58339a5b3d9faa74ac006b36/feed)
|
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
|
+
[![Inline docs](http://inch-ci.org/github/kigster/simple-feed.svg?branch=master)](http://inch-ci.org/github/kigster/simple-feed)
|
9
10
|
|
10
11
|
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
12
|
|
@@ -35,55 +36,50 @@ What you publish into your feed — i.e. _stories_ or _events_, will depend enti
|
|
35
36
|
|
36
37
|
## Challenges
|
37
38
|
|
38
|
-
Building a personalized activity
|
39
|
+
Building a personalized activity feed tends 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:
|
39
40
|
|
40
41
|
* optimizing the read time performance by pre-computing the feed for each user ahead of time
|
41
42
|
* OR optimizing the various ranking algorithms by computing the feed at read time, with complex forms of caching addressing the performance requirements.
|
42
43
|
|
43
44
|
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.
|
44
45
|
|
45
|
-
For more information about various types of feed, and the typical architectures that power them — please read
|
46
|
+
For more information about various types of feed, and the typical architectures that power them — please read:
|
47
|
+
|
48
|
+
- ["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).
|
49
|
+
- ["Feeding Frenzy: Selectively Materializing Users’ Event Feeds"](http://jeffterrace.com/docs/feeding-frenzy-sigmod10-web.pdf) (Yahoo! Research paper).
|
46
50
|
|
47
51
|
## Overview
|
48
52
|
|
49
53
|
The feed library aims to address the following goals:
|
50
54
|
|
51
|
-
* To define a minimalistic API for a typical event-based simple feed,
|
52
|
-
|
53
|
-
* To make it easy to implement and plug in a new type of provider,
|
54
|
-
eg.using Couchbase or MongoDB
|
55
|
+
* To define a minimalistic API for a typical event-based simple feed, without tying it to any concrete provider implementation
|
56
|
+
* To make it easy to implement and plug in a new type of provider, eg. using *Couchbase* or *MongoDB*
|
55
57
|
* To provide a scalable default provider implementation using Redis, which can support millions of users via sharding
|
56
58
|
* To support multiple simple feeds within the same application, but used for different purposes, eg. simple feed of my followers, versus simple feed of my own actions.
|
57
59
|
|
58
60
|
## Usage
|
59
61
|
|
60
|
-
First you need to configure the Feed with a valid provider
|
61
|
-
implementation and a name.
|
62
|
+
First you need to configure the Feed with a valid provider implementation and a name.
|
62
63
|
|
63
64
|
### Configuration
|
64
65
|
|
65
|
-
Below we configure a feed called `:newsfeed`, which presumably
|
66
|
-
will be populated with the events coming from the followers.
|
66
|
+
Below we configure a feed called `:newsfeed`, which presumably will be populated with the events coming from the followers.
|
67
67
|
|
68
68
|
```ruby
|
69
69
|
require 'simplefeed'
|
70
|
-
require 'simplefeed/providers/redis'
|
71
70
|
|
72
71
|
# Let's define a Redis-based feed, and wrap Redis in a in a ConnectionPool.
|
73
72
|
SimpleFeed.define(:newsfeed) do |f|
|
74
73
|
f.provider = SimpleFeed.provider(:redis,
|
75
74
|
redis: -> { ::Redis.new },
|
76
75
|
pool_size: 10)
|
77
|
-
f.per_page = 50
|
78
|
-
f.batch_size = 10
|
79
|
-
f.namespace = 'nf'
|
76
|
+
f.per_page = 50 # default page size
|
77
|
+
f.batch_size = 10 # default batch size
|
78
|
+
f.namespace = 'nf' # only needed if you use the same redis for more than one feed
|
80
79
|
end
|
81
80
|
```
|
82
81
|
|
83
|
-
After the feed is defined, the gem creates a similarly named method
|
84
|
-
under the `SimpleFeed` namespace to access the feed. For example, given
|
85
|
-
a name such as `:newsfeed` the following are all valid ways of
|
86
|
-
accessing the feed:
|
82
|
+
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:
|
87
83
|
|
88
84
|
* `SimpleFeed.newsfeed`
|
89
85
|
* `SimpleFeed.get(:newsfeed)`
|
@@ -92,21 +88,17 @@ You can also get a full list of currently defined feeds with `SimpleFeed.feed_na
|
|
92
88
|
|
93
89
|
### Reading from and writing to the feed
|
94
90
|
|
95
|
-
For the impatient here is a quick way to get started with the
|
96
|
-
`SimpleFeed`.
|
91
|
+
For the impatient here is a quick way to get started with the `SimpleFeed`.
|
97
92
|
|
98
93
|
```ruby
|
99
|
-
|
100
|
-
|
94
|
+
# This assumes we have previously defined a feed named :newsfeed (see above)
|
95
|
+
activity = SimpleFeed.newsfeed.activity(@current_user.id)
|
101
96
|
# Store directly the value and the optional time stamp
|
102
97
|
activity.store(value: 'hello')
|
103
98
|
# => true
|
104
99
|
|
105
100
|
# or equivalent:
|
106
|
-
@event = SimpleFeed::Event.new(value: 'hello', at: Time.now)
|
107
|
-
# or even simpler:
|
108
101
|
@event = SimpleFeed::Event.new('hello', Time.now)
|
109
|
-
# and then:
|
110
102
|
activity.store(event: @event)
|
111
103
|
# => false # false indicates that the same event is already in the feed.
|
112
104
|
```
|
@@ -124,25 +116,25 @@ activity.paginate(page: 1)
|
|
124
116
|
|
125
117
|
### The Two Forms of the API
|
126
118
|
|
127
|
-
The feed API is offered in
|
119
|
+
The feed API is offered in two forms:
|
120
|
+
|
121
|
+
1. single-user form, and
|
122
|
+
2. a batch (multi-user) form.
|
128
123
|
|
129
|
-
The
|
130
|
-
single user case, the return of, say, `#total_count` is an `Integer`
|
131
|
-
value representing the total count for this user.
|
124
|
+
The method names and signatures are the same. The only difference is in what the methods return:
|
132
125
|
|
133
|
-
In the
|
134
|
-
that can be thought of as a `Hash`, that has the user IDs as the keys,
|
135
|
-
and return results for each user as a value.
|
126
|
+
1. In the single user case, the return of, say, `#total_count` is an `Integer` value representing the total count for this user.
|
127
|
+
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.
|
136
128
|
|
137
|
-
Please see further below the details about the [Batch API](#
|
129
|
+
Please see further below the details about the [Batch API](#batch-api).
|
138
130
|
|
139
131
|
<a name="single-user-api"/>
|
140
132
|
|
141
133
|
##### Single-User API
|
142
134
|
|
143
|
-
In the examples below we show responses based on a single-user usage. As previously mentioned, the multi-user
|
135
|
+
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.
|
144
136
|
|
145
|
-
|
137
|
+
Let's take a look at a ruby session, which demonstrates return values of the feed operations for a single user:
|
146
138
|
|
147
139
|
```ruby
|
148
140
|
require 'simplefeed'
|
@@ -156,7 +148,7 @@ SimpleFeed.define(:followers) do |f|
|
|
156
148
|
end
|
157
149
|
|
158
150
|
# Let's get the Activity instance that wraps this user_id
|
159
|
-
activity = SimpleFeed.
|
151
|
+
activity = SimpleFeed.followers.activity(user_id) # => [... complex object removed for brevity ]
|
160
152
|
# let's clear out this feed to ensure it's empty
|
161
153
|
activity.wipe # => true
|
162
154
|
# Let's verify that the counts for this feed are at zero
|
@@ -164,23 +156,31 @@ activity.total_count # => 0
|
|
164
156
|
activity.unread_count # => 0
|
165
157
|
# Store some events
|
166
158
|
activity.store(value: 'hello') # => true
|
167
|
-
activity.store(value: 'goodbye')
|
159
|
+
activity.store(value: 'goodbye', at: Time.now - 20) # => true
|
168
160
|
activity.unread_count # => 2
|
169
161
|
# Now we can paginate the events, which by default resets "last_read" timestamp the user
|
170
162
|
activity.paginate(page: 1)
|
171
163
|
# [
|
172
|
-
# [0] #<SimpleFeed::Event
|
173
|
-
# [1] #<SimpleFeed::Event
|
164
|
+
# [0] #<SimpleFeed::Event: value=good bye, at=1480475294.0579991>,
|
165
|
+
# [1] #<SimpleFeed::Event: value=hello, at=1480475294.057138>
|
174
166
|
# ]
|
175
167
|
# Now the unread_count should return 0 since the user just "viewed" the feed.
|
176
168
|
activity.unread_count # => 0
|
177
169
|
activity.delete(value: 'hello') # => true
|
178
|
-
|
170
|
+
# the next method yields to a passed in block for each event in the user's feed, and deletes
|
171
|
+
# all events for which the block returns true. The return of this call is the
|
172
|
+
# array of all events that have been deleted for this user.
|
173
|
+
activity.delete_if do |event, user_id|
|
174
|
+
event.value =~ /good/
|
175
|
+
end
|
176
|
+
# => [
|
177
|
+
# [0] #<SimpleFeed::Event: value=good bye, at=1480475294.0579991>
|
178
|
+
# ]
|
179
|
+
activity.total_count # => 0
|
179
180
|
```
|
180
181
|
|
181
|
-
You can fetch all items in the feed using `#fetch`, and you can
|
182
|
-
`#paginate`
|
183
|
-
`peek: true` as a parameter.
|
182
|
+
You can fetch all items (optionally filtered by time) in the feed using `#fetch`, and you can
|
183
|
+
`#paginate` and reset the `last_read` timestamp by passing the `reset_last_read: true` as a parameter.
|
184
184
|
|
185
185
|
<a name="batch-api"/>
|
186
186
|
|
@@ -214,13 +214,13 @@ user.
|
|
214
214
|
end
|
215
215
|
```
|
216
216
|
|
217
|
-
##### DSL
|
217
|
+
##### Activity Feed DSL (Domain-Specific Language)
|
218
218
|
|
219
219
|
The library offers a convenient DSL for adding feed functionality into
|
220
220
|
your current scope.
|
221
221
|
|
222
222
|
To use the module, just include `SimpleFeed::DSL` where needed, which
|
223
|
-
exports just one primary method
|
223
|
+
exports just one primary method `#with_activity`. You call this method
|
224
224
|
and pass an activity object created for a set of users (or a single
|
225
225
|
user), like so:
|
226
226
|
|
@@ -252,6 +252,17 @@ with_activity(activity, countries: data_to_store) do
|
|
252
252
|
end
|
253
253
|
```
|
254
254
|
|
255
|
+
The DSL context has access to two additional methods:
|
256
|
+
|
257
|
+
* `#event(value, at)` returns a fully constructed `SimpleFeed::Event` instance
|
258
|
+
* `#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.
|
259
|
+
|
260
|
+
##### `#color_dump`
|
261
|
+
|
262
|
+
Below is an example output of `color_dump` method, which is intended for the debugging purposes.
|
263
|
+
|
264
|
+
[<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)
|
265
|
+
|
255
266
|
<a name="api"/>
|
256
267
|
|
257
268
|
## Complete API
|
@@ -275,28 +286,30 @@ responses for each user, accessible via `response[user_id]` method.
|
|
275
286
|
@multi.delete(event:)
|
276
287
|
# => [Response] { user_id => [Boolean], ... } true if the value was removed, false if it didn't exist
|
277
288
|
|
278
|
-
@multi.delete_if do |
|
279
|
-
# if the block returns true, the event is deleted
|
289
|
+
@multi.delete_if do |event, user_id|
|
290
|
+
# if the block returns true, the event is deleted and returned
|
280
291
|
end
|
292
|
+
# => [Response] { user_id => [deleted_event1, deleted_event2, ...], ... }
|
281
293
|
|
282
294
|
# Wipe the feed for a given user(s)
|
283
295
|
@multi.wipe
|
284
296
|
# => [Response] { user_id => [Boolean], ... } true if user activity was found and deleted, false otherwise
|
285
297
|
|
286
298
|
# Return a paginated list of all items, optionally with the total count of items
|
287
|
-
@multi.paginate(page:, per_page:,
|
299
|
+
@multi.paginate(page:, per_page:, with_total: false, reset_last_read: false)
|
288
300
|
# => [Response] { user_id => [Array]<Event>, ... }
|
289
301
|
# Options:
|
290
|
-
#
|
302
|
+
# reset_last_read: false — reset last read to Time.now (true), or the provided timestamp
|
291
303
|
# with_total: true — returns a hash for each user_id:
|
292
304
|
# => [Response] { user_id => { events: Array<Event>, total_count: 3 }, ... }
|
293
305
|
|
294
306
|
# Return un-paginated list of all items, optionally filtered
|
295
|
-
@multi.fetch(since: nil)
|
307
|
+
@multi.fetch(since: nil, reset_last_read: false)
|
296
308
|
# => [Response] { user_id => [Array]<Event>, ... }
|
297
309
|
# Options:
|
310
|
+
# reset_last_read: false — reset last read to Time.now (true), or the provided timestamp
|
298
311
|
# since: <timestamp> — if provided, returns all items posted since then
|
299
|
-
# since: :
|
312
|
+
# since: :last_read — if provided, returns all unread items and resets +last_read+
|
300
313
|
|
301
314
|
@multi.reset_last_read
|
302
315
|
# => [Response] { user_id => [Time] last_read, ... }
|
data/lib/simplefeed.rb
CHANGED
@@ -7,35 +7,51 @@ require 'simplefeed/providers/redis'
|
|
7
7
|
require 'simplefeed/providers/hash'
|
8
8
|
require 'simplefeed/dsl'
|
9
9
|
|
10
|
+
# Main namespace module for the SimpleFeed gem. It provides several shortcuts and entry
|
11
|
+
# points into the library, such as ability to define and fetch new feeds via +define+,
|
12
|
+
# and so on.
|
10
13
|
module SimpleFeed
|
11
14
|
@registry = {}
|
12
15
|
|
13
|
-
|
14
|
-
@registry
|
15
|
-
|
16
|
+
class << self
|
17
|
+
# @return [Hash<Symbol, Feed>] the registry of the defined feeds
|
18
|
+
def registry
|
19
|
+
@registry
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
feed
|
21
|
-
|
22
|
+
# @param name [Symbol] feed name
|
23
|
+
# @param options [Hash] any key-value pairs to set on the feed
|
24
|
+
#
|
25
|
+
# @return [Feed] the feed with the given name, and defined via options and a block
|
26
|
+
def define(name, **options, &block)
|
27
|
+
name = name.to_sym unless name.is_a?(Symbol)
|
28
|
+
feed = registry[name] ? registry[name] : SimpleFeed::Feed.new(name)
|
29
|
+
feed.configure(options) do
|
30
|
+
block.call(feed) if block
|
31
|
+
end
|
32
|
+
registry[name] = feed
|
33
|
+
feed
|
22
34
|
end
|
23
|
-
registry[name] = feed
|
24
|
-
feed
|
25
|
-
end
|
26
35
|
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
# @return [Feed] the pre-defined feed with the given name
|
37
|
+
def get(name)
|
38
|
+
registry[name.to_sym]
|
39
|
+
end
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
# A factory method that constructs an instance of a provider based on the provider name and arguments.
|
42
|
+
#
|
43
|
+
# @param provider_name [Symbol] short name of the provider, eg, :redis, :hash, etc.
|
44
|
+
# @params args [Array] constructor array arguments of the provider
|
45
|
+
# @params opts [Hash] constructor hash arguments of the provider
|
46
|
+
#
|
47
|
+
# @return [Provider]
|
48
|
+
def provider(provider_name, *args, **opts, &block)
|
49
|
+
provider_class = SimpleFeed::Providers.registry[provider_name]
|
50
|
+
raise ArgumentError, "No provider named #{provider_name} was found, #{SimpleFeed::Providers.registry.inspect}" unless provider_class
|
51
|
+
provider_class.new(*args, **opts, &block)
|
52
|
+
end
|
36
53
|
|
37
|
-
|
38
|
-
# Forward all other method calls to Provider
|
54
|
+
# Forward all other method calls to the Provider
|
39
55
|
def method_missing(name, *args, &block)
|
40
56
|
registry[name] || super
|
41
57
|
end
|
@@ -47,6 +63,7 @@ module SimpleFeed
|
|
47
63
|
klass.instance_methods.grep(%r{[^=!]=$}).map { |m| m.to_s.gsub(/=/, '').to_sym }
|
48
64
|
end
|
49
65
|
|
66
|
+
# Shortcut method to symbolize hash keys, using Hashie::Extensions
|
50
67
|
def self.symbolize!(hash)
|
51
68
|
Hashie::Extensions::SymbolizeKeys.symbolize_keys!(hash)
|
52
69
|
end
|
@@ -27,24 +27,26 @@ module SimpleFeed
|
|
27
27
|
# @multi.delete(event:)
|
28
28
|
# # => [Response] { user_id => [Boolean], ... } true if the value was removed, false if it didn't exist
|
29
29
|
#
|
30
|
-
# @multi.delete_if do |
|
31
|
-
# # if the block returns true, the event is deleted
|
30
|
+
# @multi.delete_if do |event, user_id|
|
31
|
+
# # if the block returns true, the event is deleted and returned
|
32
32
|
# end
|
33
|
+
# # => [Response] { user_id => [Array]<Event>, ... }
|
33
34
|
#
|
34
35
|
# @multi.wipe
|
35
36
|
# # => [Response] { user_id => [Boolean], ... } true if user activity was found and deleted, false otherwise
|
36
37
|
#
|
37
|
-
# @multi.paginate(page:, per_page:,
|
38
|
+
# @multi.paginate(page:, per_page:, reset_last_read: [Bool | Time], with_total: false)
|
38
39
|
# # => [Response] { user_id => [Array]<Event>, ... }
|
39
40
|
# # Options:
|
40
|
-
# #
|
41
|
+
# # reset_last_read: false — reset last read to Time.now (true), or provided timestamp
|
41
42
|
# # with_total: true — returns a hash for each user_id:
|
42
43
|
# # => [Response] { user_id => { events: Array<Event>, total_count: 3 }, ... }
|
43
44
|
#
|
44
45
|
# # Return un-paginated list of all items, optionally filtered
|
45
|
-
# @multi.fetch(since: nil)
|
46
|
+
# @multi.fetch(since: nil, reset_last_read: [Bool | Time] )
|
46
47
|
# # => [Response] { user_id => [Array]<Event>, ... }
|
47
48
|
# # Options:
|
49
|
+
# # reset_last_read: false — reset last read to Time.now (true), or provided timestamp
|
48
50
|
# # since: <timestamp> — if provided, returns all items posted since then
|
49
51
|
# # since: :unread — if provided, returns all unread items
|
50
52
|
#
|
@@ -34,17 +34,18 @@ module SimpleFeed
|
|
34
34
|
# @activity.wipe
|
35
35
|
# # => [Boolean] true if user activity was found and deleted, false otherwise
|
36
36
|
#
|
37
|
-
# @activity.paginate(page:, per_page:,
|
37
|
+
# @activity.paginate(page:, per_page:, reset_last_read: false, with_total: false)
|
38
38
|
# # => [Array]<Event>
|
39
39
|
# # Options:
|
40
|
-
# #
|
40
|
+
# # reset_last_read: false — reset last read to Time.now (true), or the provided timestamp
|
41
41
|
# # with_total: true — returns a hash for each user_id:
|
42
42
|
# # => { events: Array<Event>, total_count: 3 }
|
43
43
|
#
|
44
44
|
# # Return un-paginated list of all items, optionally filtered
|
45
|
-
# @activity.fetch(since: nil)
|
45
|
+
# @activity.fetch(since: nil, reset_last_read: false)
|
46
46
|
# # => [Array]<Event>
|
47
47
|
# # Options:
|
48
|
+
# # reset_last_read: false — reset last read to Time.now (true), or the provided timestamp
|
48
49
|
# # since: <timestamp> — if provided, returns all items posted since then
|
49
50
|
# # since: :unread — if provided, returns all unread items
|
50
51
|
#
|
data/lib/simplefeed/feed.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative 'providers'
|
2
2
|
require_relative 'activity/base'
|
3
|
+
require 'simplefeed/key/template'
|
4
|
+
|
3
5
|
module SimpleFeed
|
4
6
|
class Feed
|
5
7
|
|
@@ -11,15 +13,19 @@ module SimpleFeed
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def initialize(name)
|
14
|
-
@name
|
15
|
-
@name
|
16
|
+
@name = name
|
17
|
+
@name = name.underscore.to_sym unless name.is_a?(Symbol)
|
16
18
|
# set the defaults if not passed in
|
17
|
-
@meta
|
18
|
-
@namespace
|
19
|
-
@per_page
|
20
|
-
@max_size
|
21
|
-
@batch_size||= 10
|
22
|
-
@proxy
|
19
|
+
@meta = {}
|
20
|
+
@namespace = nil
|
21
|
+
@per_page ||= 50
|
22
|
+
@max_size ||= 1000
|
23
|
+
@batch_size ||= 10
|
24
|
+
@proxy = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def key_template
|
28
|
+
SimpleFeed::Key::Template.new(namespace)
|
23
29
|
end
|
24
30
|
|
25
31
|
def provider=(definition)
|
@@ -32,6 +38,10 @@ module SimpleFeed
|
|
32
38
|
@proxy
|
33
39
|
end
|
34
40
|
|
41
|
+
def key(user_id)
|
42
|
+
SimpleFeed::Providers::Key.new(user_id, key_template)
|
43
|
+
end
|
44
|
+
|
35
45
|
def provider_type
|
36
46
|
SimpleFeed::Providers::Base::Provider.class_to_registry(@proxy.provider.class)
|
37
47
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'base62-rb'
|
2
|
+
require 'hashie/mash'
|
3
|
+
|
4
|
+
module SimpleFeed
|
5
|
+
module Key
|
6
|
+
|
7
|
+
class TextTemplate < Struct.new(:text)
|
8
|
+
def render(params = {})
|
9
|
+
output = self.text.dup
|
10
|
+
params.each_pair do |key, value|
|
11
|
+
output.gsub!(%r[{{\s*#{key}\s*}}], value.to_s)
|
12
|
+
end
|
13
|
+
output
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
DEFAULT_TEXT_TEMPLATE = TextTemplate.new('{{ namespace }}u.{{ base62_user_id }}.{{ key_marker }}')
|
18
|
+
|
19
|
+
class Template
|
20
|
+
attr_accessor :namespace, :key_types, :text_template
|
21
|
+
|
22
|
+
def initialize(namespace,
|
23
|
+
key_types = DEFAULT_TYPES,
|
24
|
+
text_template = DEFAULT_TEXT_TEMPLATE
|
25
|
+
)
|
26
|
+
|
27
|
+
self.namespace = namespace
|
28
|
+
self.key_types = key_types
|
29
|
+
self.text_template = text_template
|
30
|
+
|
31
|
+
self.key_types.each do |type|
|
32
|
+
type.template ||= text_template if text_template
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def render_options
|
37
|
+
h = {}
|
38
|
+
h.merge!({ 'namespace' => namespace ? "#{namespace}|" : '' })
|
39
|
+
h
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns array of key names, such as [:meta, :data]
|
43
|
+
def key_names
|
44
|
+
key_types.map(&:name).map(&:to_s).sort
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'template'
|
2
|
+
|
3
|
+
module SimpleFeed
|
4
|
+
module Key
|
5
|
+
class Type < Struct.new(:name, :marker)
|
6
|
+
attr_accessor :template
|
7
|
+
|
8
|
+
def initialize(name, marker, template = nil)
|
9
|
+
super(name, marker)
|
10
|
+
self.template = template
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(opts = {})
|
14
|
+
self.template.render(opts.merge({ 'key_type' => name, 'key_marker' => marker }))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
DEFAULT_TYPES = [
|
19
|
+
{ name: :data, marker: 'd' },
|
20
|
+
{ name: :meta, marker: 'm' }
|
21
|
+
].map do |type|
|
22
|
+
Type.new(type[:name], type[:marker], DEFAULT_TEXT_TEMPLATE)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/simplefeed/providers.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative 'providers/
|
1
|
+
require_relative 'providers/key'
|
2
2
|
require_relative 'providers/proxy'
|
3
3
|
|
4
4
|
module SimpleFeed
|
@@ -13,10 +13,6 @@ module SimpleFeed
|
|
13
13
|
self.registry[provider_name] = provider_class
|
14
14
|
end
|
15
15
|
|
16
|
-
def self.key(*args)
|
17
|
-
SimpleFeed::Providers::Serialization::Key.new(*args)
|
18
|
-
end
|
19
|
-
|
20
16
|
# These methods must be implemented by each Provider, and operation on a given
|
21
17
|
# set of users passed via the user_ids: parameter.
|
22
18
|
ACTIVITY_METHODS = %i(store delete delete_if wipe reset_last_read last_read paginate fetch total_count unread_count)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'simplefeed/providers/
|
1
|
+
require 'simplefeed/providers/key'
|
2
2
|
|
3
3
|
module SimpleFeed
|
4
4
|
module Providers
|
@@ -28,13 +28,25 @@ module SimpleFeed
|
|
28
28
|
|
29
29
|
protected
|
30
30
|
|
31
|
+
def reset_last_read_value(user_ids:, at: nil)
|
32
|
+
at = [Time, DateTime, Date].include?(at.class) ? at : Time.now
|
33
|
+
at = at.to_time if at.respond_to?(:to_time)
|
34
|
+
at = at.to_f if at.respond_to?(:to_f)
|
35
|
+
|
36
|
+
if self.respond_to?(:reset_last_read)
|
37
|
+
reset_last_read(user_ids: user_ids, at: at)
|
38
|
+
else
|
39
|
+
raise ArgumentError, "Class #{self.class} does not implement #reset_last_read method"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
31
43
|
def tap(value)
|
32
44
|
yield
|
33
45
|
value
|
34
46
|
end
|
35
47
|
|
36
48
|
def key(user_id)
|
37
|
-
|
49
|
+
feed.key(user_id)
|
38
50
|
end
|
39
51
|
|
40
52
|
def to_array(user_ids)
|
@@ -73,11 +85,6 @@ module SimpleFeed
|
|
73
85
|
response
|
74
86
|
end
|
75
87
|
|
76
|
-
def with_result
|
77
|
-
result = yield
|
78
|
-
result = transform_response(nil, result) if self.respond_to?(:transform_response)
|
79
|
-
result
|
80
|
-
end
|
81
88
|
end
|
82
89
|
end
|
83
90
|
end
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
|
10
10
|
require 'simplefeed/event'
|
11
11
|
require_relative 'paginator'
|
12
|
-
require_relative '../
|
12
|
+
require_relative '../key'
|
13
13
|
require_relative '../base/provider'
|
14
14
|
|
15
15
|
module SimpleFeed
|
@@ -47,9 +47,14 @@ module SimpleFeed
|
|
47
47
|
|
48
48
|
def delete_if(user_ids:, &block)
|
49
49
|
with_response_batched(user_ids) do |key|
|
50
|
-
activity(key).
|
51
|
-
|
52
|
-
|
50
|
+
activity(key).map do |event|
|
51
|
+
if yield(event, key.user_id)
|
52
|
+
__delete(key, event)
|
53
|
+
event
|
54
|
+
else
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end.compact
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
@@ -61,27 +66,34 @@ module SimpleFeed
|
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
64
|
-
def
|
69
|
+
def paginate(user_ids:,
|
70
|
+
page:,
|
71
|
+
per_page: feed.per_page,
|
72
|
+
with_total: false,
|
73
|
+
reset_last_read: false)
|
74
|
+
|
75
|
+
reset_last_read_value(user_ids: user_ids, at: reset_last_read) if reset_last_read
|
76
|
+
|
65
77
|
with_response_batched(user_ids) do |key|
|
78
|
+
activity = activity(key)
|
79
|
+
result = (page && page > 0) ? activity[((page - 1) * per_page)...(page * per_page)] : activity
|
80
|
+
with_total ? { events: result, total_count: activity.length } : result
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def fetch(user_ids:, since: nil, reset_last_read: false)
|
85
|
+
response = with_response_batched(user_ids) do |key|
|
66
86
|
if since == :unread
|
67
|
-
|
68
|
-
reset_last_read(user_ids: user_ids)
|
69
|
-
result
|
87
|
+
activity(key).reject { |event| event.at < user_record(key).last_read.to_f }
|
70
88
|
elsif since
|
71
89
|
activity(key).reject { |event| event.at < since.to_f }
|
72
90
|
else
|
73
91
|
activity(key)
|
74
92
|
end
|
75
93
|
end
|
76
|
-
|
94
|
+
reset_last_read_value(user_ids: user_ids, at: reset_last_read) if reset_last_read
|
77
95
|
|
78
|
-
|
79
|
-
reset_last_read(user_ids: user_ids) unless peek
|
80
|
-
with_response_batched(user_ids) do |key|
|
81
|
-
activity = activity(key)
|
82
|
-
result = (page && page > 0) ? activity[((page - 1) * per_page)...(page * per_page)] : activity
|
83
|
-
with_total ? { events: result, total_count: activity.length } : result
|
84
|
-
end
|
96
|
+
response
|
85
97
|
end
|
86
98
|
|
87
99
|
def reset_last_read(user_ids:, at: Time.now)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'base62-rb'
|
2
|
+
require 'hashie/mash'
|
3
|
+
require 'simplefeed/key/template'
|
4
|
+
require 'simplefeed/key/type'
|
5
|
+
|
6
|
+
require 'forwardable'
|
7
|
+
|
8
|
+
module SimpleFeed
|
9
|
+
module Providers
|
10
|
+
# Here is a meta key for a given user ID:
|
11
|
+
#
|
12
|
+
# user 'm' for meta
|
13
|
+
# ↓ ↓
|
14
|
+
# "ff|u.f23098.m"
|
15
|
+
# ↑ ↑
|
16
|
+
# namespace user_id(base62)
|
17
|
+
#
|
18
|
+
class Key
|
19
|
+
attr_accessor :user_id, :key_template
|
20
|
+
|
21
|
+
extend Forwardable
|
22
|
+
def_delegators :@key_template, :key_names, :key_types
|
23
|
+
|
24
|
+
def initialize(user_id, key_template)
|
25
|
+
self.user_id = user_id
|
26
|
+
self.key_template = key_template
|
27
|
+
|
28
|
+
define_key_methods
|
29
|
+
end
|
30
|
+
|
31
|
+
def define_key_methods
|
32
|
+
key_template.key_types.each do |type|
|
33
|
+
key_name = type.name
|
34
|
+
unless self.respond_to?(key_name)
|
35
|
+
self.class.send(:define_method, key_name) do
|
36
|
+
instance_variable_get("@#{key_name}") ||
|
37
|
+
instance_variable_set("@#{key_name}", type.render(render_options))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def base62_user_id
|
45
|
+
@base62_user_id ||= ::Base62.encode(user_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
def keys
|
49
|
+
key_names.map { |name| self.send(name) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def render_options
|
53
|
+
key_template.render_options.merge!({
|
54
|
+
'user_id' => user_id,
|
55
|
+
'base62_user_id' => base62_user_id
|
56
|
+
})
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
super + { user_id: user_id, base62_user_id: base62_user_id, keys: keys }.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def inspect
|
64
|
+
render_options.inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -4,7 +4,7 @@ require 'forwardable'
|
|
4
4
|
require 'redis/pipeline' # defines Redis::Future
|
5
5
|
|
6
6
|
require 'simplefeed/providers/base/provider'
|
7
|
-
require 'simplefeed/providers/
|
7
|
+
require 'simplefeed/providers/key'
|
8
8
|
|
9
9
|
require_relative 'driver'
|
10
10
|
require_relative 'stats'
|
@@ -51,23 +51,29 @@ module SimpleFeed
|
|
51
51
|
def delete_if(user_ids:)
|
52
52
|
raise ArgumentError, '#delete_if must be called with a block that receives (user_id, event) as arguments.' unless block_given?
|
53
53
|
with_response_batched(user_ids) do |key|
|
54
|
-
fetch(user_ids: [key.user_id])[key.user_id].
|
54
|
+
fetch(user_ids: [key.user_id])[key.user_id].map do |event|
|
55
55
|
with_redis do |redis|
|
56
|
-
yield(
|
56
|
+
if yield(event, key.user_id)
|
57
|
+
redis.zrem(key.data, event.value) ? event : nil
|
58
|
+
end
|
57
59
|
end
|
58
|
-
end
|
60
|
+
end.compact
|
59
61
|
end
|
60
62
|
end
|
61
63
|
|
62
64
|
def wipe(user_ids:)
|
63
65
|
with_response_pipelined(user_ids) do |redis, key|
|
64
|
-
|
65
|
-
key.keys.all? { |redis_key| redis.del(redis_key) if should_wipe }
|
66
|
+
key.keys.all? { |redis_key| redis.del(redis_key) }
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
69
|
-
def paginate(user_ids:, page:,
|
70
|
-
|
70
|
+
def paginate(user_ids:, page:,
|
71
|
+
per_page: feed.per_page,
|
72
|
+
with_total: false,
|
73
|
+
reset_last_read: false)
|
74
|
+
|
75
|
+
reset_last_read_value(user_ids: user_ids, at: reset_last_read) if reset_last_read
|
76
|
+
|
71
77
|
with_response_pipelined(user_ids) do |redis, key|
|
72
78
|
events = paginated_events(page, per_page, redis, key)
|
73
79
|
with_total ? { events: events,
|
@@ -75,14 +81,14 @@ module SimpleFeed
|
|
75
81
|
end
|
76
82
|
end
|
77
83
|
|
78
|
-
def fetch(user_ids:, since: nil)
|
84
|
+
def fetch(user_ids:, since: nil, reset_last_read: false)
|
79
85
|
if since == :unread
|
80
86
|
last_read_response = with_response_pipelined(user_ids) do |redis, key|
|
81
87
|
get_users_last_read(redis, key)
|
82
88
|
end
|
83
|
-
reset_last_read(user_ids: user_ids)
|
84
89
|
end
|
85
|
-
|
90
|
+
|
91
|
+
response = with_response_pipelined(user_ids) do |redis, key|
|
86
92
|
if since == :unread
|
87
93
|
redis.zrevrangebyscore(key.data, '+inf', (last_read_response.delete(key.user_id) || 0).to_f, withscores: true)
|
88
94
|
elsif since
|
@@ -91,6 +97,10 @@ module SimpleFeed
|
|
91
97
|
redis.zrevrange(key.data, 0, -1, withscores: true)
|
92
98
|
end
|
93
99
|
end
|
100
|
+
|
101
|
+
reset_last_read_value(user_ids: user_ids, at: reset_last_read) if reset_last_read
|
102
|
+
|
103
|
+
response
|
94
104
|
end
|
95
105
|
|
96
106
|
def reset_last_read(user_ids:, at: Time.now)
|
data/lib/simplefeed/response.rb
CHANGED
@@ -25,7 +25,7 @@ module SimpleFeed
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def for(key_or_user_id, result = nil)
|
28
|
-
user_id = key_or_user_id.is_a?(SimpleFeed::Providers::
|
28
|
+
user_id = key_or_user_id.is_a?(SimpleFeed::Providers::Key) ?
|
29
29
|
key_or_user_id.user_id :
|
30
30
|
key_or_user_id
|
31
31
|
|
data/lib/simplefeed/version.rb
CHANGED
Binary file
|
data/simple-feed.gemspec
CHANGED
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
25
|
spec.add_dependency 'base62-rb'
|
26
|
-
spec.add_dependency 'hiredis'
|
27
|
-
spec.add_dependency 'redis'
|
26
|
+
spec.add_dependency 'hiredis'
|
27
|
+
spec.add_dependency 'redis'
|
28
28
|
spec.add_dependency 'hashie'
|
29
29
|
spec.add_dependency 'connection_pool', '~> 2'
|
30
30
|
spec.add_dependency 'activesupport'
|
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:
|
4
|
+
version: 2.0.1
|
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-
|
11
|
+
date: 2016-12-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base62-rb
|
@@ -28,30 +28,30 @@ dependencies:
|
|
28
28
|
name: hiredis
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0
|
33
|
+
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: redis
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: hashie
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -273,23 +273,26 @@ files:
|
|
273
273
|
- lib/simplefeed/dsl/formatter.rb
|
274
274
|
- lib/simplefeed/event.rb
|
275
275
|
- lib/simplefeed/feed.rb
|
276
|
+
- lib/simplefeed/key/template.rb
|
277
|
+
- lib/simplefeed/key/type.rb
|
276
278
|
- lib/simplefeed/providers.rb
|
277
279
|
- lib/simplefeed/providers/base/provider.rb
|
278
280
|
- lib/simplefeed/providers/hash.rb
|
279
281
|
- lib/simplefeed/providers/hash/paginator.rb
|
280
282
|
- lib/simplefeed/providers/hash/provider.rb
|
283
|
+
- lib/simplefeed/providers/key.rb
|
281
284
|
- lib/simplefeed/providers/proxy.rb
|
282
285
|
- lib/simplefeed/providers/redis.rb
|
283
286
|
- lib/simplefeed/providers/redis/boot_info.yml
|
284
287
|
- lib/simplefeed/providers/redis/driver.rb
|
285
288
|
- lib/simplefeed/providers/redis/provider.rb
|
286
289
|
- lib/simplefeed/providers/redis/stats.rb
|
287
|
-
- lib/simplefeed/providers/serialization/key.rb
|
288
290
|
- lib/simplefeed/response.rb
|
289
291
|
- lib/simplefeed/version.rb
|
290
292
|
- man/activity-feed-action.png
|
291
293
|
- man/running-example-redis-debug.png
|
292
294
|
- man/running-example.png
|
295
|
+
- man/sf-color-dump.png
|
293
296
|
- simple-feed.gemspec
|
294
297
|
homepage: https://github.com/kigster/simple-feed
|
295
298
|
licenses:
|
@@ -1,82 +0,0 @@
|
|
1
|
-
require 'base62-rb'
|
2
|
-
require 'hashie/mash'
|
3
|
-
module SimpleFeed
|
4
|
-
module Providers
|
5
|
-
module Serialization
|
6
|
-
class Key
|
7
|
-
attr_accessor :user_id, :short_id, :prefix, :config
|
8
|
-
|
9
|
-
# This hash defines the paramters for the strategy we use
|
10
|
-
# to create a compact key based on the user id, feed's namespace,
|
11
|
-
# and the functionality, such as 'm' meta — as a hash for storing
|
12
|
-
# arbitrary values (in particular, +last_read+). and 'd' data —
|
13
|
-
# as a sorted set in for storing the actual events.
|
14
|
-
#
|
15
|
-
# ### Examples
|
16
|
-
#
|
17
|
-
# Here is a meta key for a given user ID:
|
18
|
-
#
|
19
|
-
# user 'm' for meta
|
20
|
-
# ↓ ↓
|
21
|
-
# "ff|u.f23098.m"
|
22
|
-
# ↑ ↑
|
23
|
-
# namespace user_id(base62)
|
24
|
-
#
|
25
|
-
KEY_CONFIG = Hashie::Mash.new({
|
26
|
-
separator: '.',
|
27
|
-
prefix: '',
|
28
|
-
namespace_divider: '|',
|
29
|
-
namespace: nil,
|
30
|
-
primary: ->(user_id) { ::Base62.encode(user_id) },
|
31
|
-
primary_marker: 'u',
|
32
|
-
secondary_markers: {
|
33
|
-
data: 'd',
|
34
|
-
meta: 'm'
|
35
|
-
}
|
36
|
-
})
|
37
|
-
|
38
|
-
def initialize(user_id, namespace = nil, optional_key_config = {})
|
39
|
-
optional_key_config.merge!(namespace: namespace) if namespace
|
40
|
-
self.config = KEY_CONFIG.dup.merge!(optional_key_config)
|
41
|
-
|
42
|
-
self.user_id = user_id
|
43
|
-
self.short_id = config.primary[user_id]
|
44
|
-
|
45
|
-
self.prefix = configure_prefix
|
46
|
-
|
47
|
-
config.secondary_markers.each_pair do |type, character|
|
48
|
-
self.class.send(:define_method, type) do
|
49
|
-
instance_variable_get("@#{type}") || instance_variable_set("@#{type}", "#{prefix}#{config.separator}#{character}")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def keys
|
55
|
-
config.secondary_markers.map { |k, v| [k, self.send(k)] }
|
56
|
-
end
|
57
|
-
|
58
|
-
def for(type)
|
59
|
-
"#{prefix}#{config.separator}#{type.to_s}"
|
60
|
-
end
|
61
|
-
|
62
|
-
def to_s
|
63
|
-
super.gsub(/SimpleFeed::Providers::Serialization/, '...*') + { user_id: user_id, short_id: short_id, keys: keys }.to_json
|
64
|
-
end
|
65
|
-
|
66
|
-
def inspect
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
# eg. ff|u.123498
|
73
|
-
def configure_prefix
|
74
|
-
namespace = config.namespace ? "#{config.namespace}#{config.namespace_divider}" : ''
|
75
|
-
"#{namespace}#{config.primary_marker}#{config.separator}#{short_id}"
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|