amico 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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