related 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README.md +44 -1
- data/lib/related/exceptions.rb +1 -0
- data/lib/related/follower.rb +43 -0
- data/lib/related/node.rb +103 -29
- data/lib/related/relationship.rb +15 -10
- data/lib/related/version.rb +1 -1
- data/test/follower_test.rb +43 -0
- data/test/related_test.rb +70 -5
- data/test/test_helper.rb +1 -0
- metadata +10 -3
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -56,12 +56,55 @@ To get the results from a query:
|
|
56
56
|
node.outgoing(:friends).to_a
|
57
57
|
node.outgoing(:friends).count (or .size, which is memoized)
|
58
58
|
|
59
|
-
You can also do set operations, like union, diff and intersect
|
59
|
+
You can also do set operations, like union, diff and intersect:
|
60
60
|
|
61
61
|
node1.outgoing(:friends).union(node2.outgoing(:friends))
|
62
62
|
node1.outgoing(:friends).diff(node2.outgoing(:friends))
|
63
63
|
node1.outgoing(:friends).intersect(node2.outgoing(:friends))
|
64
64
|
|
65
|
+
Relationships are sorted based on when they were created, which means you can
|
66
|
+
paginate them:
|
67
|
+
|
68
|
+
node.outgoing(:friends).relationship.per_page(100).page(1)
|
69
|
+
node.outgoing(:friends).relationship.per_page(100).page(rel)
|
70
|
+
|
71
|
+
The second form paginates based on the id of the last relationship on the
|
72
|
+
previous page. Useful for cases where explicit page numbers are not
|
73
|
+
appropriate.
|
74
|
+
|
75
|
+
Pagination only works for relationships. If you want to access nodes directly
|
76
|
+
without going through the extra step of iterating through the relationship
|
77
|
+
objects you will only get random nodes. Thus you can use .limit (or .per_page)
|
78
|
+
like this to get a random selection of nodes:
|
79
|
+
|
80
|
+
node.outgoing(:friends).nodes.limit(5)
|
81
|
+
|
82
|
+
Follower
|
83
|
+
--------
|
84
|
+
|
85
|
+
Related includes a helper module called Related::Follower that you can include
|
86
|
+
in your node sub-class to get basic Twitter-like follow functionality:
|
87
|
+
|
88
|
+
class User < Related::Node
|
89
|
+
include Related::Follower
|
90
|
+
end
|
91
|
+
|
92
|
+
user1 = User.create
|
93
|
+
user2 = User.create
|
94
|
+
|
95
|
+
user1.follow!(user2)
|
96
|
+
user1.unfollow!(user2)
|
97
|
+
user2.followers
|
98
|
+
user1.following
|
99
|
+
user1.friends
|
100
|
+
user2.followed_by?(user1)
|
101
|
+
user1.following?(user2)
|
102
|
+
user2.followers_count
|
103
|
+
user1.following_count
|
104
|
+
|
105
|
+
The two nodes does not need to be of the same type. You can for example have
|
106
|
+
a User following a Page or whatever makes sense in your app.
|
107
|
+
|
65
108
|
Development
|
66
109
|
-----------
|
67
110
|
|
data/lib/related/exceptions.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Related
|
2
|
+
module Follower
|
3
|
+
def follow!(user)
|
4
|
+
Related::Relationship.create(:follow, self, user)
|
5
|
+
end
|
6
|
+
|
7
|
+
def unfollow!(user)
|
8
|
+
self.following.relationships.each do |rel|
|
9
|
+
if rel.end_node_id == user.id
|
10
|
+
rel.destroy
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def followers
|
16
|
+
self.incoming(:follow)
|
17
|
+
end
|
18
|
+
|
19
|
+
def following
|
20
|
+
self.outgoing(:follow)
|
21
|
+
end
|
22
|
+
|
23
|
+
def friends
|
24
|
+
self.followers.intersect(self.following)
|
25
|
+
end
|
26
|
+
|
27
|
+
def followed_by?(user)
|
28
|
+
self.followers.include?(user)
|
29
|
+
end
|
30
|
+
|
31
|
+
def following?(user)
|
32
|
+
self.following.include?(user)
|
33
|
+
end
|
34
|
+
|
35
|
+
def followers_count
|
36
|
+
self.followers.size
|
37
|
+
end
|
38
|
+
|
39
|
+
def following_count
|
40
|
+
self.following.size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/related/node.rb
CHANGED
@@ -3,13 +3,13 @@ module Related
|
|
3
3
|
module QueryMethods
|
4
4
|
def relationships
|
5
5
|
query = self.query
|
6
|
-
query.
|
6
|
+
query.result_type = :relationships
|
7
7
|
query
|
8
8
|
end
|
9
9
|
|
10
10
|
def nodes
|
11
11
|
query = self.query
|
12
|
-
query.
|
12
|
+
query.result_type = :nodes
|
13
13
|
query
|
14
14
|
end
|
15
15
|
|
@@ -33,6 +33,16 @@ module Related
|
|
33
33
|
query
|
34
34
|
end
|
35
35
|
|
36
|
+
def per_page(count)
|
37
|
+
self.limit(count)
|
38
|
+
end
|
39
|
+
|
40
|
+
def page(nr)
|
41
|
+
query = self.query
|
42
|
+
query.page = nr
|
43
|
+
query
|
44
|
+
end
|
45
|
+
|
36
46
|
def depth(depth)
|
37
47
|
query = self.query
|
38
48
|
query.depth = depth
|
@@ -65,10 +75,13 @@ module Related
|
|
65
75
|
class Query
|
66
76
|
include QueryMethods
|
67
77
|
|
68
|
-
|
78
|
+
attr_reader :result
|
79
|
+
|
80
|
+
attr_writer :result_type
|
69
81
|
attr_writer :relationship_type
|
70
82
|
attr_writer :direction
|
71
83
|
attr_writer :limit
|
84
|
+
attr_writer :page
|
72
85
|
attr_writer :depth
|
73
86
|
attr_writer :include_start_node
|
74
87
|
attr_writer :destination
|
@@ -76,7 +89,7 @@ module Related
|
|
76
89
|
|
77
90
|
def initialize(node)
|
78
91
|
@node = node
|
79
|
-
@
|
92
|
+
@result_type = :nodes
|
80
93
|
@depth = 4
|
81
94
|
end
|
82
95
|
|
@@ -89,28 +102,18 @@ module Related
|
|
89
102
|
end
|
90
103
|
|
91
104
|
def to_a
|
92
|
-
|
93
|
-
if @
|
94
|
-
|
95
|
-
res.shift unless @include_start_node
|
96
|
-
return Related::Node.find(res)
|
105
|
+
perform_query unless @result
|
106
|
+
if @result_type == :nodes
|
107
|
+
Related::Node.find(@result)
|
97
108
|
else
|
98
|
-
|
99
|
-
res = (1..@limit.to_i).map { Related.redis.srandmember(key) }
|
100
|
-
else
|
101
|
-
res = Related.redis.smembers(key)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
res = Relationship.find(res)
|
105
|
-
if @entity_type == :nodes
|
106
|
-
res = Related::Node.find(res.map {|rel| @direction == :in ? rel.start_node_id : rel.end_node_id })
|
107
|
-
res.unshift(@node) if @include_start_node
|
109
|
+
Related::Relationship.find(@result)
|
108
110
|
end
|
109
|
-
res
|
110
111
|
end
|
111
112
|
|
112
113
|
def count
|
113
|
-
@count =
|
114
|
+
@count = @result_type == :nodes ?
|
115
|
+
Related.redis.scard(key) :
|
116
|
+
Related.redis.zcard(key)
|
114
117
|
@limit && @count > @limit ? @limit : @count
|
115
118
|
end
|
116
119
|
|
@@ -118,24 +121,96 @@ module Related
|
|
118
121
|
@count || count
|
119
122
|
end
|
120
123
|
|
124
|
+
def include?(entity)
|
125
|
+
if @destination
|
126
|
+
self.to_a.include?(entity)
|
127
|
+
else
|
128
|
+
if entity.is_a?(Related::Node)
|
129
|
+
@result_type = :nodes
|
130
|
+
Related.redis.sismember(key, entity.to_s)
|
131
|
+
elsif entity.is_a?(Related::Relationship)
|
132
|
+
@result_type = :relationships
|
133
|
+
Related.redis.sismember(key, entity.to_s)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def union(query)
|
139
|
+
@result_type = :nodes
|
140
|
+
@result = Related.redis.sunion(key, query.key)
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def diff(query)
|
145
|
+
@result_type = :nodes
|
146
|
+
@result = Related.redis.sdiff(key, query.key)
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
def intersect(query)
|
151
|
+
@result_type = :nodes
|
152
|
+
@result = Related.redis.sinter(key, query.key)
|
153
|
+
self
|
154
|
+
end
|
155
|
+
|
121
156
|
protected
|
122
157
|
|
123
|
-
def
|
124
|
-
|
158
|
+
def page_start
|
159
|
+
if @page.nil? || @page.to_i.to_s == @page.to_s
|
160
|
+
@page && @page.to_i != 1 ? (@page.to_i * @limit.to_i) - @limit.to_i : 0
|
161
|
+
else
|
162
|
+
rel = @page.is_a?(String) ? Related::Relationship.find(@page) : @page
|
163
|
+
rel.rank + 1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def page_end
|
168
|
+
page_start + @limit.to_i - 1
|
169
|
+
end
|
170
|
+
|
171
|
+
def key(node=nil)
|
172
|
+
if @result_type == :nodes
|
173
|
+
"#{node ? node.to_s : @node.to_s}:nodes:#{@relationship_type}:#{@direction}"
|
174
|
+
else
|
175
|
+
"#{node ? node.to_s : @node.to_s}:rel:#{@relationship_type}:#{@direction}"
|
176
|
+
end
|
125
177
|
end
|
126
178
|
|
127
179
|
def query
|
128
180
|
self
|
129
181
|
end
|
130
182
|
|
183
|
+
def perform_query
|
184
|
+
@result = []
|
185
|
+
if @destination
|
186
|
+
@result_type = :nodes
|
187
|
+
@result = self.send(@search_algorithm, [@node.id])
|
188
|
+
@result.shift unless @include_start_node
|
189
|
+
@result
|
190
|
+
else
|
191
|
+
if @result_type == :nodes
|
192
|
+
if @limit
|
193
|
+
@result = (1..@limit.to_i).map { Related.redis.srandmember(key) }
|
194
|
+
else
|
195
|
+
@result = Related.redis.smembers(key)
|
196
|
+
end
|
197
|
+
else
|
198
|
+
if @limit
|
199
|
+
@result = Related.redis.zrange(key, page_start, page_end)
|
200
|
+
else
|
201
|
+
@result = Related.redis.zrange(key, 0, -1)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
131
207
|
def depth_first(nodes, depth = 0)
|
132
208
|
return [] if depth > @depth
|
133
209
|
nodes.each do |node|
|
134
|
-
key
|
135
|
-
if Related.redis.sismember(key, @destination.id)
|
210
|
+
if Related.redis.sismember(key(node), @destination.id)
|
136
211
|
return [node, @destination.id]
|
137
212
|
else
|
138
|
-
res = depth_first(Related.redis.smembers(key), depth+1)
|
213
|
+
res = depth_first(Related.redis.smembers(key(node)), depth+1)
|
139
214
|
return [node] + res unless res.empty?
|
140
215
|
end
|
141
216
|
end
|
@@ -146,11 +221,10 @@ module Related
|
|
146
221
|
return [] if depth > @depth
|
147
222
|
shortest_path = []
|
148
223
|
nodes.each do |node|
|
149
|
-
key
|
150
|
-
if Related.redis.sismember(key, @destination.id)
|
224
|
+
if Related.redis.sismember(key(node), @destination.id)
|
151
225
|
return [node, @destination.id]
|
152
226
|
else
|
153
|
-
res = dijkstra(Related.redis.smembers(key), depth+1)
|
227
|
+
res = dijkstra(Related.redis.smembers(key(node)), depth+1)
|
154
228
|
if res.size > 0
|
155
229
|
res = [node] + res
|
156
230
|
if res.size < shortest_path.size || shortest_path.size == 0
|
data/lib/related/relationship.rb
CHANGED
@@ -16,9 +16,13 @@ module Related
|
|
16
16
|
@end_node ||= Related::Node.find(end_node_id)
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
19
|
+
def rank
|
20
|
+
Related.redis.zrank("#{self.start_node_id}:rel:#{self.label}:out", self.id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.create(label, node1, node2, attributes = {})
|
20
24
|
self.new(attributes.merge(
|
21
|
-
:
|
25
|
+
:label => label,
|
22
26
|
:start_node_id => node1.to_s,
|
23
27
|
:end_node_id => node2.to_s
|
24
28
|
)).save
|
@@ -29,22 +33,23 @@ module Related
|
|
29
33
|
def create
|
30
34
|
Related.redis.multi do
|
31
35
|
super
|
32
|
-
|
33
|
-
Related.redis.
|
36
|
+
score = Time.now.to_i
|
37
|
+
Related.redis.zadd("#{self.start_node_id}:rel:#{self.label}:out", score, self.id)
|
38
|
+
Related.redis.zadd("#{self.end_node_id}:rel:#{self.label}:in", score, self.id)
|
34
39
|
|
35
|
-
Related.redis.sadd("#{self.start_node_id}:nodes:#{
|
36
|
-
Related.redis.sadd("#{self.end_node_id}:nodes:#{
|
40
|
+
Related.redis.sadd("#{self.start_node_id}:nodes:#{self.label}:out", self.end_node_id)
|
41
|
+
Related.redis.sadd("#{self.end_node_id}:nodes:#{self.label}:in", self.start_node_id)
|
37
42
|
end
|
38
43
|
self
|
39
44
|
end
|
40
45
|
|
41
46
|
def delete
|
42
47
|
Related.redis.multi do
|
43
|
-
Related.redis.
|
44
|
-
Related.redis.
|
48
|
+
Related.redis.zrem("#{self.start_node_id}:rel:#{self.label}:out", self.id)
|
49
|
+
Related.redis.zrem("#{self.end_node_id}:rel:#{self.label}:in", self.id)
|
45
50
|
|
46
|
-
Related.redis.srem("#{self.start_node_id}:nodes:#{
|
47
|
-
Related.redis.srem("#{self.end_node_id}:nodes:#{
|
51
|
+
Related.redis.srem("#{self.start_node_id}:nodes:#{self.label}:out", self.end_node_id)
|
52
|
+
Related.redis.srem("#{self.end_node_id}:nodes:#{self.label}:in", self.start_node_id)
|
48
53
|
super
|
49
54
|
end
|
50
55
|
self
|
data/lib/related/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path('test/test_helper')
|
2
|
+
require 'related/follower'
|
3
|
+
|
4
|
+
class User < Related::Node
|
5
|
+
include Related::Follower
|
6
|
+
end
|
7
|
+
|
8
|
+
class FollowerTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
Related.redis.flushall
|
12
|
+
@user1 = User.create
|
13
|
+
@user2 = User.create
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_can_follow
|
17
|
+
@user1.follow!(@user2)
|
18
|
+
assert @user1.following?(@user2)
|
19
|
+
assert @user2.followed_by?(@user1)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_can_unfollow
|
23
|
+
@user1.follow!(@user2)
|
24
|
+
@user1.unfollow!(@user2)
|
25
|
+
assert_equal false, @user1.following?(@user2)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_can_count_followers_and_following
|
29
|
+
@user1.follow!(@user2)
|
30
|
+
assert_equal 1, @user1.following_count
|
31
|
+
assert_equal 0, @user1.followers_count
|
32
|
+
assert_equal 0, @user2.following_count
|
33
|
+
assert_equal 1, @user2.followers_count
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_can_compute_friends
|
37
|
+
@user1.follow!(@user2)
|
38
|
+
@user2.follow!(@user1)
|
39
|
+
assert_equal [@user2], @user1.friends.to_a
|
40
|
+
assert_equal [@user1], @user2.friends.to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/test/related_test.rb
CHANGED
@@ -131,7 +131,29 @@ class RelatedTest < Test::Unit::TestCase
|
|
131
131
|
Related::Relationship.create(:friends, node1, node3)
|
132
132
|
Related::Relationship.create(:friends, node1, node4)
|
133
133
|
Related::Relationship.create(:friends, node1, node5)
|
134
|
-
assert_equal 3, node1.outgoing(:friends).limit(3).to_a.size
|
134
|
+
assert_equal 3, node1.outgoing(:friends).nodes.limit(3).to_a.size
|
135
|
+
assert_equal 3, node1.outgoing(:friends).relationships.limit(3).to_a.size
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_can_paginate_the_results_from_a_query
|
139
|
+
node1 = Related::Node.create
|
140
|
+
node2 = Related::Node.create
|
141
|
+
node3 = Related::Node.create
|
142
|
+
node4 = Related::Node.create
|
143
|
+
node5 = Related::Node.create
|
144
|
+
rel1 = Related::Relationship.create(:friends, node1, node2)
|
145
|
+
sleep(1)
|
146
|
+
rel2 = Related::Relationship.create(:friends, node1, node3)
|
147
|
+
sleep(1)
|
148
|
+
rel3 = Related::Relationship.create(:friends, node1, node4)
|
149
|
+
sleep(1)
|
150
|
+
rel4 = Related::Relationship.create(:friends, node1, node5)
|
151
|
+
sleep(1)
|
152
|
+
rel5 = Related::Relationship.create(:friends, node1, node5)
|
153
|
+
assert_equal [rel1,rel2,rel3], node1.outgoing(:friends).relationships.per_page(3).page(1).to_a
|
154
|
+
assert_equal [rel4,rel5], node1.outgoing(:friends).relationships.per_page(3).page(2).to_a
|
155
|
+
assert_equal [rel2,rel3,rel4], node1.outgoing(:friends).relationships.per_page(3).page(rel1).to_a
|
156
|
+
assert_equal [rel4,rel5], node1.outgoing(:friends).relationships.per_page(3).page(rel3).to_a
|
135
157
|
end
|
136
158
|
|
137
159
|
def test_can_count_the_number_of_related_nodes
|
@@ -144,10 +166,14 @@ class RelatedTest < Test::Unit::TestCase
|
|
144
166
|
rel1 = Related::Relationship.create(:friends, node1, node3)
|
145
167
|
rel1 = Related::Relationship.create(:friends, node1, node4)
|
146
168
|
rel1 = Related::Relationship.create(:friends, node1, node5)
|
147
|
-
assert_equal 4, node1.outgoing(:friends).count
|
148
|
-
assert_equal 4, node1.outgoing(:friends).size
|
149
|
-
assert_equal 3, node1.outgoing(:friends).limit(3).count
|
150
|
-
assert_equal 4, node1.outgoing(:friends).limit(5).count
|
169
|
+
assert_equal 4, node1.outgoing(:friends).nodes.count
|
170
|
+
assert_equal 4, node1.outgoing(:friends).nodes.size
|
171
|
+
assert_equal 3, node1.outgoing(:friends).nodes.limit(3).count
|
172
|
+
assert_equal 4, node1.outgoing(:friends).nodes.limit(5).count
|
173
|
+
assert_equal 4, node1.outgoing(:friends).relationships.count
|
174
|
+
assert_equal 4, node1.outgoing(:friends).relationships.size
|
175
|
+
assert_equal 3, node1.outgoing(:friends).relationships.limit(3).count
|
176
|
+
assert_equal 4, node1.outgoing(:friends).relationships.limit(5).count
|
151
177
|
end
|
152
178
|
|
153
179
|
def test_can_find_path_between_two_nodes
|
@@ -200,4 +226,43 @@ class RelatedTest < Test::Unit::TestCase
|
|
200
226
|
assert_equal [node1,node2,node5,node8], node1.shortest_path_to(node8).outgoing(:friends).depth(5).include_start_node.to_a
|
201
227
|
end
|
202
228
|
|
229
|
+
def test_can_union
|
230
|
+
node1 = Related::Node.create
|
231
|
+
node2 = Related::Node.create
|
232
|
+
node3 = Related::Node.create
|
233
|
+
node4 = Related::Node.create
|
234
|
+
Related::Relationship.create(:friends, node1, node3)
|
235
|
+
Related::Relationship.create(:friends, node2, node4)
|
236
|
+
Related::Relationship.create(:friends, node2, node3)
|
237
|
+
nodes = node1.outgoing(:friends).union(node2.outgoing(:friends)).to_a
|
238
|
+
assert_equal 2, nodes.size
|
239
|
+
assert nodes.include?(node3)
|
240
|
+
assert nodes.include?(node4)
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_can_diff
|
244
|
+
node1 = Related::Node.create
|
245
|
+
node2 = Related::Node.create
|
246
|
+
node3 = Related::Node.create
|
247
|
+
node4 = Related::Node.create
|
248
|
+
Related::Relationship.create(:friends, node1, node3)
|
249
|
+
Related::Relationship.create(:friends, node2, node4)
|
250
|
+
Related::Relationship.create(:friends, node2, node3)
|
251
|
+
nodes = node2.outgoing(:friends).diff(node1.outgoing(:friends)).to_a
|
252
|
+
assert_equal 1, nodes.size
|
253
|
+
assert nodes.include?(node4)
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_can_intersect
|
257
|
+
node1 = Related::Node.create
|
258
|
+
node2 = Related::Node.create
|
259
|
+
Related::Relationship.create(:friends, node1, node2)
|
260
|
+
Related::Relationship.create(:friends, node2, node1)
|
261
|
+
assert_equal [node2], node1.outgoing(:friends).intersect(node1.incoming(:friends)).to_a
|
262
|
+
assert_equal [node1], node2.outgoing(:friends).intersect(node2.incoming(:friends)).to_a
|
263
|
+
node3 = Related::Node.create
|
264
|
+
Related::Relationship.create(:friends, node1, node3)
|
265
|
+
assert_equal [node1], node2.incoming(:friends).intersect(node3.incoming(:friends)).to_a
|
266
|
+
end
|
267
|
+
|
203
268
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: related
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
version: "0.
|
8
|
+
- 2
|
9
|
+
version: "0.2"
|
9
10
|
platform: ruby
|
10
11
|
authors:
|
11
12
|
- Niklas Holmgren
|
@@ -13,7 +14,7 @@ autorequire:
|
|
13
14
|
bindir: bin
|
14
15
|
cert_chain: []
|
15
16
|
|
16
|
-
date: 2011-09-
|
17
|
+
date: 2011-09-14 00:00:00 +02:00
|
17
18
|
default_executable:
|
18
19
|
dependencies:
|
19
20
|
- !ruby/object:Gem::Dependency
|
@@ -24,6 +25,7 @@ dependencies:
|
|
24
25
|
requirements:
|
25
26
|
- - ">"
|
26
27
|
- !ruby/object:Gem::Version
|
28
|
+
hash: 15
|
27
29
|
segments:
|
28
30
|
- 2
|
29
31
|
- 0
|
@@ -39,6 +41,7 @@ dependencies:
|
|
39
41
|
requirements:
|
40
42
|
- - ">"
|
41
43
|
- !ruby/object:Gem::Version
|
44
|
+
hash: 63
|
42
45
|
segments:
|
43
46
|
- 0
|
44
47
|
- 8
|
@@ -62,11 +65,13 @@ files:
|
|
62
65
|
- CHANGELOG
|
63
66
|
- lib/related/entity.rb
|
64
67
|
- lib/related/exceptions.rb
|
68
|
+
- lib/related/follower.rb
|
65
69
|
- lib/related/helpers.rb
|
66
70
|
- lib/related/node.rb
|
67
71
|
- lib/related/relationship.rb
|
68
72
|
- lib/related/version.rb
|
69
73
|
- lib/related.rb
|
74
|
+
- test/follower_test.rb
|
70
75
|
- test/performance_test.rb
|
71
76
|
- test/redis-test.conf
|
72
77
|
- test/related_test.rb
|
@@ -85,6 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
90
|
requirements:
|
86
91
|
- - ">="
|
87
92
|
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
88
94
|
segments:
|
89
95
|
- 0
|
90
96
|
version: "0"
|
@@ -93,6 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
99
|
requirements:
|
94
100
|
- - ">="
|
95
101
|
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
96
103
|
segments:
|
97
104
|
- 0
|
98
105
|
version: "0"
|