amico 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,520 +1,543 @@
1
1
  module Amico
2
2
  module Relationships
3
- # Establish a follow relationship between two IDs. After adding the follow
4
- # relationship, it checks to see if the relationship is reciprocated and establishes that
5
- # relationship if so.
6
- #
7
- # @param from_id [String] The ID of the individual establishing the follow relationship.
8
- # @param to_id [String] The ID of the individual to be followed.
9
- # @param scope [String] Scope for the call.
10
- #
11
- # Examples
12
- #
13
- # Amico.follow(1, 11)
14
- def follow(from_id, to_id, scope = Amico.default_scope_key)
15
- return if from_id == to_id
16
- return if blocked?(to_id, from_id, scope)
17
- return if Amico.pending_follow && pending?(from_id, to_id, scope)
18
-
19
- unless Amico.pending_follow
20
- add_following_followers_reciprocated(from_id, to_id, scope)
21
- else
22
- Amico.redis.zadd("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", Time.now.to_i, from_id)
23
- end
24
- end
25
-
26
- # Remove a follow relationship between two IDs. After removing the follow
27
- # relationship, if a reciprocated relationship was established, it is
28
- # also removed.
29
- #
30
- # @param from_id [String] The ID of the individual removing the follow relationship.
31
- # @param to_id [String] The ID of the individual to be unfollowed.
32
- # @param scope [String] Scope for the call.
33
- #
34
- # Examples
35
- #
36
- # Amico.follow(1, 11)
37
- # Amico.unfollow(1, 11)
38
- def unfollow(from_id, to_id, scope = Amico.default_scope_key)
39
- return if from_id == to_id
40
-
41
- Amico.redis.multi do
42
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{from_id}", to_id)
43
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{to_id}", from_id)
44
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{from_id}", to_id)
45
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{to_id}", from_id)
46
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", from_id)
47
- end
48
- end
49
-
50
- # Block a relationship between two IDs. This method also has the side effect
51
- # of removing any follower or following relationship between the two IDs.
52
- #
53
- # @param from_id [String] The ID of the individual blocking the relationship.
54
- # @param to_id [String] The ID of the individual being blocked.
55
- # @param scope [String] Scope for the call.
56
- #
57
- # Examples
58
- #
59
- # Amico.block(1, 11)
60
- def block(from_id, to_id, scope = Amico.default_scope_key)
61
- return if from_id == to_id
62
-
63
- Amico.redis.multi do
64
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{from_id}", to_id)
65
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{to_id}", from_id)
66
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{to_id}", from_id)
67
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{from_id}", to_id)
68
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{from_id}", to_id)
69
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{to_id}", from_id)
70
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{from_id}", to_id)
71
- Amico.redis.zadd("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{from_id}", Time.now.to_i, to_id)
72
- end
73
- end
74
-
75
- # Unblock a relationship between two IDs.
76
- #
77
- # @param from_id [String] The ID of the individual unblocking the relationship.
78
- # @param to_id [String] The ID of the blocked individual.
79
- # @param scope [String] Scope for the call.
80
- #
81
- # Examples
82
- #
83
- # Amico.block(1, 11)
84
- # Amico.unblock(1, 11)
85
- def unblock(from_id, to_id, scope = Amico.default_scope_key)
86
- return if from_id == to_id
87
-
88
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{from_id}", to_id)
89
- end
90
-
91
- # Accept a relationship that is pending between two IDs.
92
- #
93
- # @param from_id [String] The ID of the individual accepting the relationship.
94
- # @param to_id [String] The ID of the individual to be accepted.
95
- # @param scope [String] Scope for the call.
96
- #
97
- # Example
98
- #
99
- # Amico.follow(1, 11)
100
- # Amico.pending?(1, 11) # true
101
- # Amico.accept(1, 11)
102
- # Amico.pending?(1, 11) # false
103
- # Amico.following?(1, 11) #true
104
- def accept(from_id, to_id, scope = Amico.default_scope_key)
105
- return if from_id == to_id
106
-
107
- add_following_followers_reciprocated(from_id, to_id, scope)
108
- end
109
-
110
- # Count the number of individuals that someone is following.
111
- #
112
- # @param id [String] ID of the individual to retrieve following count for.
113
- # @param scope [String] Scope for the call.
114
- #
115
- # Examples
116
- #
117
- # Amico.follow(1, 11)
118
- # Amico.following_count(1)
119
- #
120
- # @return the count of the number of individuals that someone is following.
121
- def following_count(id, scope = Amico.default_scope_key)
122
- Amico.redis.zcard("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}")
123
- end
124
-
125
- # Count the number of individuals that are following someone.
126
- #
127
- # @param id [String] ID of the individual to retrieve followers count for.
128
- # @param scope [String] Scope for the call.
129
- #
130
- # Examples
131
- #
132
- # Amico.follow(11, 1)
133
- # Amico.followers_count(1)
134
- #
135
- # @return the count of the number of individuals that are following someone.
136
- def followers_count(id, scope = Amico.default_scope_key)
137
- Amico.redis.zcard("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}")
138
- end
139
-
140
- # Count the number of individuals that someone has blocked.
141
- #
142
- # @param id [String] ID of the individual to retrieve blocked count for.
143
- # @param scope [String] Scope for the call.
144
- #
145
- # Examples
146
- #
147
- # Amico.block(1, 11)
148
- # Amico.blocked_count(1)
149
- #
150
- # @return the count of the number of individuals that someone has blocked.
151
- def blocked_count(id, scope = Amico.default_scope_key)
152
- Amico.redis.zcard("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}")
153
- end
154
-
155
- # Count the number of individuals that have reciprocated a following relationship.
156
- #
157
- # @param id [String] ID of the individual to retrieve reciprocated following count for.
158
- # @param scope [String] Scope for the call.
159
- #
160
- # Examples
161
- #
162
- # Amico.follow(1, 11)
163
- # Amico.follow(11, 1)
164
- # Amico.reciprocated_count(1)
165
- #
166
- # @return the count of the number of individuals that have reciprocated a following relationship.
167
- def reciprocated_count(id, scope = Amico.default_scope_key)
168
- Amico.redis.zcard("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{id}")
169
- end
170
-
171
- # Count the number of relationships pending for an individual.
172
- #
173
- # @param id [String] ID of the individual to retrieve pending count for.
174
- # @param scope [String] Scope for the call.
175
- #
176
- # Examples
177
- #
178
- # Amico.follow(11, 1)
179
- # Amico.follow(12, 1)
180
- # Amico.pending_count(1) # 2
181
- #
182
- # @return the count of the number of relationships pending for an individual.
183
- def pending_count(id, scope = Amico.default_scope_key)
184
- Amico.redis.zcard("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{id}")
185
- end
186
-
187
- # Check to see if one individual is following another individual.
188
- #
189
- # @param id [String] ID of the individual checking the following status.
190
- # @param following_id [String] ID of the individual to see if they are being followed by id.
191
- # @param scope [String] Scope for the call.
192
- #
193
- # Examples
194
- #
195
- # Amico.follow(1, 11)
196
- # Amico.following?(1, 11)
197
- #
198
- # @return true if id is following following_id, false otherwise
199
- def following?(id, following_id, scope = Amico.default_scope_key)
200
- !Amico.redis.zscore("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}", following_id).nil?
201
- end
202
-
203
- # Check to see if one individual is a follower of another individual.
204
- #
205
- # @param id [String] ID of the individual checking the follower status.
206
- # @param following_id [String] ID of the individual to see if they are following id.
207
- # @param scope [String] Scope for the call.
208
- #
209
- # Examples
210
- #
211
- # Amico.follow(11, 1)
212
- # Amico.follower?(1, 11)
213
- #
214
- # @return true if follower_id is following id, false otherwise
215
- def follower?(id, follower_id, scope = Amico.default_scope_key)
216
- !Amico.redis.zscore("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}", follower_id).nil?
217
- end
218
-
219
- # Check to see if one individual has blocked another individual.
220
- #
221
- # @param id [String] ID of the individual checking the blocked status.
222
- # @param blocked_id [String] ID of the individual to see if they are blocked by id.
223
- # @param scope [String] Scope for the call.
224
- #
225
- # Examples
226
- #
227
- # Amico.block(1, 11)
228
- # Amico.blocked?(1, 11)
229
- #
230
- # @return true if id has blocked blocked_id, false otherwise
231
- def blocked?(id, blocked_id, scope = Amico.default_scope_key)
232
- !Amico.redis.zscore("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}", blocked_id).nil?
233
- end
234
-
235
- # Check to see if one individual has reciprocated in following another individual.
236
- #
237
- # @param from_id [String] ID of the individual checking the reciprocated relationship.
238
- # @param to_id [String] ID of the individual to see if they are following from_id.
239
- # @param scope [String] Scope for the call.
240
- #
241
- # Examples
242
- #
243
- # Amico.follow(1, 11)
244
- # Amico.follow(11, 1)
245
- # Amico.reciprocated?(1, 11)
246
- #
247
- # @return true if both individuals are following each other, false otherwise
248
- def reciprocated?(from_id, to_id, scope = Amico.default_scope_key)
249
- following?(from_id, to_id, scope) && following?(to_id, from_id, scope)
250
- end
251
-
252
- # Check to see if one individual has a pending relationship in following another individual.
253
- #
254
- # @param from_id [String] ID of the individual checking the pending relationships.
255
- # @param to_id [String] ID of the individual to see if they are pending a follow from from_id.
256
- # @param scope [String] Scope for the call.
257
- #
258
- # Examples
259
- #
260
- # Amico.follow(1, 11)
261
- # Amico.pending?(1, 11) # true
262
- #
263
- # @return true if the relationship is pending, false otherwise
264
- def pending?(from_id, to_id, scope = Amico.default_scope_key)
265
- !Amico.redis.zscore("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", from_id).nil?
266
- end
267
-
268
- # Retrieve a page of followed individuals for a given ID.
269
- #
270
- # @param id [String] ID of the individual.
271
- # @param page_options [Hash] Options to be passed for retrieving a page of followed individuals.
272
- # @param scope [String] Scope for the call.
273
- #
274
- # Examples
275
- #
276
- # Amico.follow(1, 11)
277
- # Amico.follow(1, 12)
278
- # Amico.following(1, :page => 1)
279
- #
280
- # @return a page of followed individuals for a given ID.
281
- def following(id, page_options = default_paging_options, scope = Amico.default_scope_key)
282
- members("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}", page_options)
283
- end
284
-
285
- # Retrieve a page of followers for a given ID.
286
- #
287
- # @param id [String] ID of the individual.
288
- # @param page_options [Hash] Options to be passed for retrieving a page of followers.
289
- # @param scope [String] Scope for the call.
290
- #
291
- # Examples
292
- #
293
- # Amico.follow(11, 1)
294
- # Amico.follow(12, 1)
295
- # Amico.followers(1, :page => 1)
296
- #
297
- # @return a page of followers for a given ID.
298
- def followers(id, page_options = default_paging_options, scope = Amico.default_scope_key)
299
- members("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}", page_options)
300
- end
301
-
302
- # Retrieve a page of blocked individuals for a given ID.
303
- #
304
- # @param id [String] ID of the individual.
305
- # @param page_options [Hash] Options to be passed for retrieving a page of blocked individuals.
306
- # @param scope [String] Scope for the call.
307
- #
308
- # Examples
309
- #
310
- # Amico.block(1, 11)
311
- # Amico.block(1, 12)
312
- # Amico.blocked(1, :page => 1)
313
- #
314
- # @return a page of blocked individuals for a given ID.
315
- def blocked(id, page_options = default_paging_options, scope = Amico.default_scope_key)
316
- members("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}", page_options)
317
- end
318
-
319
- # Retrieve a page of individuals that have reciprocated a follow for a given ID.
320
- #
321
- # @param id [String] ID of the individual.
322
- # @param page_options [Hash] Options to be passed for retrieving a page of individuals that have reciprocated a follow.
323
- # @param scope [String] Scope for the call.
324
- #
325
- # Examples
326
- #
327
- # Amico.follow(1, 11)
328
- # Amico.follow(1, 12)
329
- # Amico.follow(11, 1)
330
- # Amico.follow(12, 1)
331
- # Amico.reciprocated(1, :page => 1)
332
- #
333
- # @return a page of individuals that have reciprocated a follow for a given ID.
334
- def reciprocated(id, page_options = default_paging_options, scope = Amico.default_scope_key)
335
- members("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{id}", page_options)
336
- end
337
-
338
- # Retrieve a page of pending relationships for a given ID.
339
- #
340
- # @param id [String] ID of the individual.
341
- # @param page_options [Hash] Options to be passed for retrieving a page of pending relationships.
342
- # @param scope [String] Scope for the call.
343
- #
344
- # Examples
345
- #
346
- # Amico.follow(1, 11)
347
- # Amico.follow(2, 11)
348
- # Amico.pending(1, :page => 1)
349
- #
350
- # @return a page of pending relationships for a given ID.
351
- def pending(id, page_options = default_paging_options, scope = Amico.default_scope_key)
352
- members("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{id}", page_options)
353
- end
354
-
355
- # Count the number of pages of following relationships for an individual.
356
- #
357
- # @param id [String] ID of the individual.
358
- # @param page_size [int] Page size.
359
- # @param scope [String] Scope for the call.
360
- #
361
- # Examples
362
- #
363
- # Amico.follow(1, 11)
364
- # Amico.follow(1, 12)
365
- # Amico.following_page_count(1)
366
- #
367
- # @return the number of pages of following relationships for an individual.
368
- def following_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
369
- total_pages("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}", page_size)
370
- end
371
-
372
- # Count the number of pages of follower relationships for an individual.
373
- #
374
- # @param id [String] ID of the individual.
375
- # @param page_size [int] Page size (default: Amico.page_size).
376
- # @param scope [String] Scope for the call.
377
- #
378
- # Examples
379
- #
380
- # Amico.follow(11, 1)
381
- # Amico.follow(12, 1)
382
- # Amico.followers_page_count(1)
383
- #
384
- # @return the number of pages of follower relationships for an individual.
385
- def followers_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
386
- total_pages("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}", page_size)
387
- end
388
-
389
- # Count the number of pages of blocked relationships for an individual.
390
- #
391
- # @param id [String] ID of the individual.
392
- # @param page_size [int] Page size (default: Amico.page_size).
393
- # @param scope [String] Scope for the call.
394
- #
395
- # Examples
396
- #
397
- # Amico.block(1, 11)
398
- # Amico.block(1, 12)
399
- # Amico.blocked_page_count(1)
400
- #
401
- # @return the number of pages of blocked relationships for an individual.
402
- def blocked_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
403
- total_pages("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}", page_size)
404
- end
405
-
406
- # Count the number of pages of reciprocated relationships for an individual.
407
- #
408
- # @param id [String] ID of the individual.
409
- # @param page_size [int] Page size (default: Amico.page_size).
410
- # @param scope [String] Scope for the call.
411
- #
412
- # Examples
413
- #
414
- # Amico.follow(1, 11)
415
- # Amico.follow(1, 12)
416
- # Amico.follow(11, 1)
417
- # Amico.follow(12, 1)
418
- # Amico.reciprocated_page_count(1)
419
- #
420
- # @return the number of pages of reciprocated relationships for an individual.
421
- def reciprocated_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
422
- total_pages("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{id}", page_size)
423
- end
424
-
425
- # Count the number of pages of pending relationships for an individual.
426
- #
427
- # @param id [String] ID of the individual.
428
- # @param page_size [int] Page size (default: Amico.page_size).
429
- # @param scope [String] Scope for the call.
430
- #
431
- # Examples
432
- #
433
- # Amico.follow(11, 1)
434
- # Amico.follow(12, 1)
435
- # Amico.pending_page_count(1) # 1
436
- #
437
- # @return the number of pages of pending relationships for an individual.
438
- def pending_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
439
- total_pages("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{id}", page_size)
440
- end
441
-
442
- # Retrieve all of the individuals for a given id, type (e.g. following) and scope
443
- #
444
- # @param id [String] ID of the individual.
445
- # @param type [Symbol] One of :following, :followers, :blocked, :reciprocated, :pending
446
- # @param scope [String] Scope for the call.
447
- def all(id, type, scope = Amico.default_scope_key)
448
- valid_types = [:following, :followers, :blocked, :reciprocated, :pending]
449
- raise "Must be one of #{valid_types.to_s}" if !valid_types.include?(type)
450
- count = self.send("#{type.to_s}_count".to_sym, id, scope)
451
- count > 0 ? self.send("#{type}", id, {:page_size => count}, scope) : []
452
- end
453
-
454
- private
455
-
456
- # Default paging options.
457
- #
458
- # @return a hash of the default paging options.
459
- def default_paging_options
460
- {:page_size => Amico.page_size, :page => 1}
461
- end
462
-
463
- # Add the following, followers and check for a reciprocated relationship. To be used from the
464
- # +follow++ and ++accept++ methods.
465
- #
466
- # @param from_id [String] The ID of the individual establishing the follow relationship.
467
- # @param to_id [String] The ID of the individual to be followed.
468
- def add_following_followers_reciprocated(from_id, to_id, scope)
469
- Amico.redis.multi do
470
- Amico.redis.zadd("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{from_id}", Time.now.to_i, to_id)
471
- Amico.redis.zadd("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{to_id}", Time.now.to_i, from_id)
472
- Amico.redis.zrem("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", from_id)
473
- end
474
-
475
- if reciprocated?(from_id, to_id)
476
- Amico.redis.multi do
477
- Amico.redis.zadd("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{from_id}", Time.now.to_i, to_id)
478
- Amico.redis.zadd("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{to_id}", Time.now.to_i, from_id)
479
- end
480
- end
481
- end
482
-
483
- # Count the total number of pages for a given key in a Redis sorted set.
484
- #
485
- # @param key [String] Redis key.
486
- # @param page_size [int] Page size from which to calculate total pages.
487
- #
488
- # @return total number of pages for a given key in a Redis sorted set.
489
- def total_pages(key, page_size)
490
- (Amico.redis.zcard(key) / page_size.to_f).ceil
491
- end
492
-
493
- # Retrieve a page of items from a Redis sorted set without scores.
494
- #
495
- # @param key [String] Redis key.
496
- # @param options [Hash] Default options for paging.
497
- #
498
- # @return a page of items from a Redis sorted set without scores.
499
- def members(key, options = default_paging_options)
500
- options = default_paging_options.dup.merge!(options)
501
- if options[:page] < 1
502
- options[:page] = 1
503
- end
504
-
505
- if options[:page] > total_pages(key, options[:page_size])
506
- options[:page] = total_pages(key, options[:page_size])
507
- end
508
-
509
- index_for_redis = options[:page] - 1
510
- starting_offset = (index_for_redis * options[:page_size])
511
-
512
- if starting_offset < 0
513
- starting_offset = 0
514
- end
515
-
516
- ending_offset = (starting_offset + options[:page_size]) - 1
517
- Amico.redis.zrevrange(key, starting_offset, ending_offset, :with_scores => false)
518
- end
3
+ # Establish a follow relationship between two IDs. After adding the follow
4
+ # relationship, it checks to see if the relationship is reciprocated and establishes that
5
+ # relationship if so.
6
+ #
7
+ # @param from_id [String] The ID of the individual establishing the follow relationship.
8
+ # @param to_id [String] The ID of the individual to be followed.
9
+ # @param scope [String] Scope for the call.
10
+ #
11
+ # Examples
12
+ #
13
+ # Amico.follow(1, 11)
14
+ def follow(from_id, to_id, scope = Amico.default_scope_key)
15
+ return if from_id == to_id
16
+ return if blocked?(to_id, from_id, scope)
17
+ return if Amico.pending_follow && pending?(from_id, to_id, scope)
18
+
19
+ unless Amico.pending_follow
20
+ add_following_followers_reciprocated(from_id, to_id, scope)
21
+ else
22
+ Amico.redis.zadd("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", Time.now.to_i, from_id)
23
+ end
24
+ end
25
+
26
+ # Remove a follow relationship between two IDs. After removing the follow
27
+ # relationship, if a reciprocated relationship was established, it is
28
+ # also removed.
29
+ #
30
+ # @param from_id [String] The ID of the individual removing the follow relationship.
31
+ # @param to_id [String] The ID of the individual to be unfollowed.
32
+ # @param scope [String] Scope for the call.
33
+ #
34
+ # Examples
35
+ #
36
+ # Amico.follow(1, 11)
37
+ # Amico.unfollow(1, 11)
38
+ def unfollow(from_id, to_id, scope = Amico.default_scope_key)
39
+ return if from_id == to_id
40
+
41
+ Amico.redis.multi do
42
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{from_id}", to_id)
43
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{to_id}", from_id)
44
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{from_id}", to_id)
45
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{to_id}", from_id)
46
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", from_id)
47
+ end
48
+ end
49
+
50
+ # Block a relationship between two IDs. This method also has the side effect
51
+ # of removing any follower or following relationship between the two IDs.
52
+ #
53
+ # @param from_id [String] The ID of the individual blocking the relationship.
54
+ # @param to_id [String] The ID of the individual being blocked.
55
+ # @param scope [String] Scope for the call.
56
+ #
57
+ # Examples
58
+ #
59
+ # Amico.block(1, 11)
60
+ def block(from_id, to_id, scope = Amico.default_scope_key)
61
+ return if from_id == to_id
62
+
63
+ Amico.redis.multi do
64
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{from_id}", to_id)
65
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{to_id}", from_id)
66
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{to_id}", from_id)
67
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{from_id}", to_id)
68
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{from_id}", to_id)
69
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{to_id}", from_id)
70
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{from_id}", to_id)
71
+ Amico.redis.zadd("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{from_id}", Time.now.to_i, to_id)
72
+ end
73
+ end
74
+
75
+ # Unblock a relationship between two IDs.
76
+ #
77
+ # @param from_id [String] The ID of the individual unblocking the relationship.
78
+ # @param to_id [String] The ID of the blocked individual.
79
+ # @param scope [String] Scope for the call.
80
+ #
81
+ # Examples
82
+ #
83
+ # Amico.block(1, 11)
84
+ # Amico.unblock(1, 11)
85
+ def unblock(from_id, to_id, scope = Amico.default_scope_key)
86
+ return if from_id == to_id
87
+
88
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{from_id}", to_id)
89
+ end
90
+
91
+ # Accept a relationship that is pending between two IDs.
92
+ #
93
+ # @param from_id [String] The ID of the individual accepting the relationship.
94
+ # @param to_id [String] The ID of the individual to be accepted.
95
+ # @param scope [String] Scope for the call.
96
+ #
97
+ # Example
98
+ #
99
+ # Amico.follow(1, 11)
100
+ # Amico.pending?(1, 11) # true
101
+ # Amico.accept(1, 11)
102
+ # Amico.pending?(1, 11) # false
103
+ # Amico.following?(1, 11) #true
104
+ def accept(from_id, to_id, scope = Amico.default_scope_key)
105
+ return if from_id == to_id
106
+
107
+ add_following_followers_reciprocated(from_id, to_id, scope)
108
+ end
109
+
110
+ # Count the number of individuals that someone is following.
111
+ #
112
+ # @param id [String] ID of the individual to retrieve following count for.
113
+ # @param scope [String] Scope for the call.
114
+ #
115
+ # Examples
116
+ #
117
+ # Amico.follow(1, 11)
118
+ # Amico.following_count(1)
119
+ #
120
+ # @return the count of the number of individuals that someone is following.
121
+ def following_count(id, scope = Amico.default_scope_key)
122
+ Amico.redis.zcard("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}")
123
+ end
124
+
125
+ # Count the number of individuals that are following someone.
126
+ #
127
+ # @param id [String] ID of the individual to retrieve followers count for.
128
+ # @param scope [String] Scope for the call.
129
+ #
130
+ # Examples
131
+ #
132
+ # Amico.follow(11, 1)
133
+ # Amico.followers_count(1)
134
+ #
135
+ # @return the count of the number of individuals that are following someone.
136
+ def followers_count(id, scope = Amico.default_scope_key)
137
+ Amico.redis.zcard("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}")
138
+ end
139
+
140
+ # Count the number of individuals that someone has blocked.
141
+ #
142
+ # @param id [String] ID of the individual to retrieve blocked count for.
143
+ # @param scope [String] Scope for the call.
144
+ #
145
+ # Examples
146
+ #
147
+ # Amico.block(1, 11)
148
+ # Amico.blocked_count(1)
149
+ #
150
+ # @return the count of the number of individuals that someone has blocked.
151
+ def blocked_count(id, scope = Amico.default_scope_key)
152
+ Amico.redis.zcard("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}")
153
+ end
154
+
155
+ # Count the number of individuals that have reciprocated a following relationship.
156
+ #
157
+ # @param id [String] ID of the individual to retrieve reciprocated following count for.
158
+ # @param scope [String] Scope for the call.
159
+ #
160
+ # Examples
161
+ #
162
+ # Amico.follow(1, 11)
163
+ # Amico.follow(11, 1)
164
+ # Amico.reciprocated_count(1)
165
+ #
166
+ # @return the count of the number of individuals that have reciprocated a following relationship.
167
+ def reciprocated_count(id, scope = Amico.default_scope_key)
168
+ Amico.redis.zcard("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{id}")
169
+ end
170
+
171
+ # Count the number of relationships pending for an individual.
172
+ #
173
+ # @param id [String] ID of the individual to retrieve pending count for.
174
+ # @param scope [String] Scope for the call.
175
+ #
176
+ # Examples
177
+ #
178
+ # Amico.follow(11, 1)
179
+ # Amico.follow(12, 1)
180
+ # Amico.pending_count(1) # 2
181
+ #
182
+ # @return the count of the number of relationships pending for an individual.
183
+ def pending_count(id, scope = Amico.default_scope_key)
184
+ Amico.redis.zcard("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{id}")
185
+ end
186
+
187
+ # Check to see if one individual is following another individual.
188
+ #
189
+ # @param id [String] ID of the individual checking the following status.
190
+ # @param following_id [String] ID of the individual to see if they are being followed by id.
191
+ # @param scope [String] Scope for the call.
192
+ #
193
+ # Examples
194
+ #
195
+ # Amico.follow(1, 11)
196
+ # Amico.following?(1, 11)
197
+ #
198
+ # @return true if id is following following_id, false otherwise
199
+ def following?(id, following_id, scope = Amico.default_scope_key)
200
+ !Amico.redis.zscore("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}", following_id).nil?
201
+ end
202
+
203
+ # Check to see if one individual is a follower of another individual.
204
+ #
205
+ # @param id [String] ID of the individual checking the follower status.
206
+ # @param following_id [String] ID of the individual to see if they are following id.
207
+ # @param scope [String] Scope for the call.
208
+ #
209
+ # Examples
210
+ #
211
+ # Amico.follow(11, 1)
212
+ # Amico.follower?(1, 11)
213
+ #
214
+ # @return true if follower_id is following id, false otherwise
215
+ def follower?(id, follower_id, scope = Amico.default_scope_key)
216
+ !Amico.redis.zscore("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}", follower_id).nil?
217
+ end
218
+
219
+ # Check to see if one individual has blocked another individual.
220
+ #
221
+ # @param id [String] ID of the individual checking the blocked status.
222
+ # @param blocked_id [String] ID of the individual to see if they are blocked by id.
223
+ # @param scope [String] Scope for the call.
224
+ #
225
+ # Examples
226
+ #
227
+ # Amico.block(1, 11)
228
+ # Amico.blocked?(1, 11)
229
+ #
230
+ # @return true if id has blocked blocked_id, false otherwise
231
+ def blocked?(id, blocked_id, scope = Amico.default_scope_key)
232
+ !Amico.redis.zscore("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}", blocked_id).nil?
233
+ end
234
+
235
+ # Check to see if one individual has reciprocated in following another individual.
236
+ #
237
+ # @param from_id [String] ID of the individual checking the reciprocated relationship.
238
+ # @param to_id [String] ID of the individual to see if they are following from_id.
239
+ # @param scope [String] Scope for the call.
240
+ #
241
+ # Examples
242
+ #
243
+ # Amico.follow(1, 11)
244
+ # Amico.follow(11, 1)
245
+ # Amico.reciprocated?(1, 11)
246
+ #
247
+ # @return true if both individuals are following each other, false otherwise
248
+ def reciprocated?(from_id, to_id, scope = Amico.default_scope_key)
249
+ following?(from_id, to_id, scope) && following?(to_id, from_id, scope)
250
+ end
251
+
252
+ # Check to see if one individual has a pending relationship in following another individual.
253
+ #
254
+ # @param from_id [String] ID of the individual checking the pending relationships.
255
+ # @param to_id [String] ID of the individual to see if they are pending a follow from from_id.
256
+ # @param scope [String] Scope for the call.
257
+ #
258
+ # Examples
259
+ #
260
+ # Amico.follow(1, 11)
261
+ # Amico.pending?(1, 11) # true
262
+ #
263
+ # @return true if the relationship is pending, false otherwise
264
+ def pending?(from_id, to_id, scope = Amico.default_scope_key)
265
+ !Amico.redis.zscore("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", from_id).nil?
266
+ end
267
+
268
+ # Retrieve a page of followed individuals for a given ID.
269
+ #
270
+ # @param id [String] ID of the individual.
271
+ # @param page_options [Hash] Options to be passed for retrieving a page of followed individuals.
272
+ # @param scope [String] Scope for the call.
273
+ #
274
+ # Examples
275
+ #
276
+ # Amico.follow(1, 11)
277
+ # Amico.follow(1, 12)
278
+ # Amico.following(1, :page => 1)
279
+ #
280
+ # @return a page of followed individuals for a given ID.
281
+ def following(id, page_options = default_paging_options, scope = Amico.default_scope_key)
282
+ members("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}", page_options)
283
+ end
284
+
285
+ # Retrieve a page of followers for a given ID.
286
+ #
287
+ # @param id [String] ID of the individual.
288
+ # @param page_options [Hash] Options to be passed for retrieving a page of followers.
289
+ # @param scope [String] Scope for the call.
290
+ #
291
+ # Examples
292
+ #
293
+ # Amico.follow(11, 1)
294
+ # Amico.follow(12, 1)
295
+ # Amico.followers(1, :page => 1)
296
+ #
297
+ # @return a page of followers for a given ID.
298
+ def followers(id, page_options = default_paging_options, scope = Amico.default_scope_key)
299
+ members("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}", page_options)
300
+ end
301
+
302
+ # Retrieve a page of blocked individuals for a given ID.
303
+ #
304
+ # @param id [String] ID of the individual.
305
+ # @param page_options [Hash] Options to be passed for retrieving a page of blocked individuals.
306
+ # @param scope [String] Scope for the call.
307
+ #
308
+ # Examples
309
+ #
310
+ # Amico.block(1, 11)
311
+ # Amico.block(1, 12)
312
+ # Amico.blocked(1, :page => 1)
313
+ #
314
+ # @return a page of blocked individuals for a given ID.
315
+ def blocked(id, page_options = default_paging_options, scope = Amico.default_scope_key)
316
+ members("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}", page_options)
317
+ end
318
+
319
+ # Retrieve a page of individuals that have reciprocated a follow for a given ID.
320
+ #
321
+ # @param id [String] ID of the individual.
322
+ # @param page_options [Hash] Options to be passed for retrieving a page of individuals that have reciprocated a follow.
323
+ # @param scope [String] Scope for the call.
324
+ #
325
+ # Examples
326
+ #
327
+ # Amico.follow(1, 11)
328
+ # Amico.follow(1, 12)
329
+ # Amico.follow(11, 1)
330
+ # Amico.follow(12, 1)
331
+ # Amico.reciprocated(1, :page => 1)
332
+ #
333
+ # @return a page of individuals that have reciprocated a follow for a given ID.
334
+ def reciprocated(id, page_options = default_paging_options, scope = Amico.default_scope_key)
335
+ members("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{id}", page_options)
336
+ end
337
+
338
+ # Retrieve a page of pending relationships for a given ID.
339
+ #
340
+ # @param id [String] ID of the individual.
341
+ # @param page_options [Hash] Options to be passed for retrieving a page of pending relationships.
342
+ # @param scope [String] Scope for the call.
343
+ #
344
+ # Examples
345
+ #
346
+ # Amico.follow(1, 11)
347
+ # Amico.follow(2, 11)
348
+ # Amico.pending(1, :page => 1)
349
+ #
350
+ # @return a page of pending relationships for a given ID.
351
+ def pending(id, page_options = default_paging_options, scope = Amico.default_scope_key)
352
+ members("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{id}", page_options)
353
+ end
354
+
355
+ # Count the number of pages of following relationships for an individual.
356
+ #
357
+ # @param id [String] ID of the individual.
358
+ # @param page_size [int] Page size.
359
+ # @param scope [String] Scope for the call.
360
+ #
361
+ # Examples
362
+ #
363
+ # Amico.follow(1, 11)
364
+ # Amico.follow(1, 12)
365
+ # Amico.following_page_count(1)
366
+ #
367
+ # @return the number of pages of following relationships for an individual.
368
+ def following_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
369
+ total_pages("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{id}", page_size)
370
+ end
371
+
372
+ # Count the number of pages of follower relationships for an individual.
373
+ #
374
+ # @param id [String] ID of the individual.
375
+ # @param page_size [int] Page size (default: Amico.page_size).
376
+ # @param scope [String] Scope for the call.
377
+ #
378
+ # Examples
379
+ #
380
+ # Amico.follow(11, 1)
381
+ # Amico.follow(12, 1)
382
+ # Amico.followers_page_count(1)
383
+ #
384
+ # @return the number of pages of follower relationships for an individual.
385
+ def followers_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
386
+ total_pages("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{id}", page_size)
387
+ end
388
+
389
+ # Count the number of pages of blocked relationships for an individual.
390
+ #
391
+ # @param id [String] ID of the individual.
392
+ # @param page_size [int] Page size (default: Amico.page_size).
393
+ # @param scope [String] Scope for the call.
394
+ #
395
+ # Examples
396
+ #
397
+ # Amico.block(1, 11)
398
+ # Amico.block(1, 12)
399
+ # Amico.blocked_page_count(1)
400
+ #
401
+ # @return the number of pages of blocked relationships for an individual.
402
+ def blocked_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
403
+ total_pages("#{Amico.namespace}:#{Amico.blocked_key}:#{scope}:#{id}", page_size)
404
+ end
405
+
406
+ # Count the number of pages of reciprocated relationships for an individual.
407
+ #
408
+ # @param id [String] ID of the individual.
409
+ # @param page_size [int] Page size (default: Amico.page_size).
410
+ # @param scope [String] Scope for the call.
411
+ #
412
+ # Examples
413
+ #
414
+ # Amico.follow(1, 11)
415
+ # Amico.follow(1, 12)
416
+ # Amico.follow(11, 1)
417
+ # Amico.follow(12, 1)
418
+ # Amico.reciprocated_page_count(1)
419
+ #
420
+ # @return the number of pages of reciprocated relationships for an individual.
421
+ def reciprocated_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
422
+ total_pages("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{id}", page_size)
423
+ end
424
+
425
+ # Count the number of pages of pending relationships for an individual.
426
+ #
427
+ # @param id [String] ID of the individual.
428
+ # @param page_size [int] Page size (default: Amico.page_size).
429
+ # @param scope [String] Scope for the call.
430
+ #
431
+ # Examples
432
+ #
433
+ # Amico.follow(11, 1)
434
+ # Amico.follow(12, 1)
435
+ # Amico.pending_page_count(1) # 1
436
+ #
437
+ # @return the number of pages of pending relationships for an individual.
438
+ def pending_page_count(id, page_size = Amico.page_size, scope = Amico.default_scope_key)
439
+ total_pages("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{id}", page_size)
440
+ end
441
+
442
+ # Retrieve all of the individuals for a given id, type (e.g. following) and scope
443
+ #
444
+ # @param id [String] ID of the individual.
445
+ # @param type [Symbol] One of :following, :followers, :blocked, :reciprocated, :pending.
446
+ # @param scope [String] Scope for the call.
447
+ def all(id, type, scope = Amico.default_scope_key)
448
+ valid_types = [:following, :followers, :blocked, :reciprocated, :pending]
449
+ raise "Must be one of #{valid_types.to_s}" if !valid_types.include?(type)
450
+ count = self.send("#{type.to_s}_count".to_sym, id, scope)
451
+ count > 0 ? self.send("#{type}", id, {:page_size => count}, scope) : []
452
+ end
453
+
454
+ # Retrieve a count of all of a given type of relationship for the specified id.
455
+ #
456
+ # @param id [String] ID of the individual.
457
+ # @param type [Symbol] One of :following, :followers, :blocked, :reciprocated, :pending.
458
+ # @param scope [String] Scope for the call.
459
+ #
460
+ # @return Count of all of a given type of relationship for the specified id.
461
+ def count(id, type, scope = Amico.default_scope_key)
462
+ self.send("#{type.to_s}_count".to_sym, id, scope)
463
+ end
464
+
465
+ # Retrieve a page count of a given type of relationship for the specified id.
466
+ #
467
+ # @param id [String] ID of the individual.
468
+ # @param type [Symbol] One of :following, :followers, :blocked, :reciprocated, :pending.
469
+ # @param page_size [int] Page size (default: Amico.page_size).
470
+ # @param scope [String] Scope for the call.
471
+ #
472
+ # @return Page count of a given type of relationship for the specified id.
473
+ def page_count(id, type, page_size = Amico.page_size, scope = Amico.default_scope_key)
474
+ self.send("#{type.to_s}_page_count".to_sym, id, page_size, scope)
475
+ end
476
+
477
+ private
478
+
479
+ # Default paging options.
480
+ #
481
+ # @return a hash of the default paging options.
482
+ def default_paging_options
483
+ {:page_size => Amico.page_size, :page => 1}
484
+ end
485
+
486
+ # Add the following, followers and check for a reciprocated relationship. To be used from the
487
+ # +follow++ and ++accept++ methods.
488
+ #
489
+ # @param from_id [String] The ID of the individual establishing the follow relationship.
490
+ # @param to_id [String] The ID of the individual to be followed.
491
+ def add_following_followers_reciprocated(from_id, to_id, scope)
492
+ Amico.redis.multi do
493
+ Amico.redis.zadd("#{Amico.namespace}:#{Amico.following_key}:#{scope}:#{from_id}", Time.now.to_i, to_id)
494
+ Amico.redis.zadd("#{Amico.namespace}:#{Amico.followers_key}:#{scope}:#{to_id}", Time.now.to_i, from_id)
495
+ Amico.redis.zrem("#{Amico.namespace}:#{Amico.pending_key}:#{scope}:#{to_id}", from_id)
496
+ end
497
+
498
+ if reciprocated?(from_id, to_id)
499
+ Amico.redis.multi do
500
+ Amico.redis.zadd("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{from_id}", Time.now.to_i, to_id)
501
+ Amico.redis.zadd("#{Amico.namespace}:#{Amico.reciprocated_key}:#{scope}:#{to_id}", Time.now.to_i, from_id)
502
+ end
503
+ end
504
+ end
505
+
506
+ # Count the total number of pages for a given key in a Redis sorted set.
507
+ #
508
+ # @param key [String] Redis key.
509
+ # @param page_size [int] Page size from which to calculate total pages.
510
+ #
511
+ # @return total number of pages for a given key in a Redis sorted set.
512
+ def total_pages(key, page_size)
513
+ (Amico.redis.zcard(key) / page_size.to_f).ceil
514
+ end
515
+
516
+ # Retrieve a page of items from a Redis sorted set without scores.
517
+ #
518
+ # @param key [String] Redis key.
519
+ # @param options [Hash] Default options for paging.
520
+ #
521
+ # @return a page of items from a Redis sorted set without scores.
522
+ def members(key, options = default_paging_options)
523
+ options = default_paging_options.dup.merge!(options)
524
+ if options[:page] < 1
525
+ options[:page] = 1
526
+ end
527
+
528
+ if options[:page] > total_pages(key, options[:page_size])
529
+ options[:page] = total_pages(key, options[:page_size])
530
+ end
531
+
532
+ index_for_redis = options[:page] - 1
533
+ starting_offset = (index_for_redis * options[:page_size])
534
+
535
+ if starting_offset < 0
536
+ starting_offset = 0
537
+ end
538
+
539
+ ending_offset = (starting_offset + options[:page_size]) - 1
540
+ Amico.redis.zrevrange(key, starting_offset, ending_offset, :with_scores => false)
541
+ end
519
542
  end
520
543
  end