related 0.1 → 0.2
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.
- 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"
|