related 0.3.1 → 0.4.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.
- data/CHANGELOG +6 -0
- data/README.md +56 -0
- data/lib/related/exceptions.rb +8 -1
- data/lib/related/helpers.rb +5 -0
- data/lib/related/node.rb +4 -3
- data/lib/related/relationship.rb +52 -12
- data/lib/related/root.rb +10 -0
- data/lib/related/version.rb +1 -1
- data/lib/related.rb +1 -0
- data/test/active_model_test.rb +5 -0
- data/test/model_test.rb +32 -0
- data/test/related_test.rb +18 -8
- metadata +6 -5
data/CHANGELOG
CHANGED
data/README.md
CHANGED
|
@@ -112,6 +112,26 @@ like this to get a random selection of nodes:
|
|
|
112
112
|
node.outgoing(:friends).nodes.limit(5)
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
The root node
|
|
116
|
+
-------------
|
|
117
|
+
|
|
118
|
+
Related provides a special kind of node called the "root" node. It's always
|
|
119
|
+
accessible using the `Related.root` helper and you can create a relationship
|
|
120
|
+
between any node and the root node, which is useful if you want to easily
|
|
121
|
+
access a set of nodes without knowing the IDs of those nodes.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
Related::Relationship.create(:example, Related.root, node)
|
|
125
|
+
Related.root.outgoing(:example)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
You can even add attributes to the root node if you want.
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
Related.root.name = 'The root'
|
|
132
|
+
Related.root.save
|
|
133
|
+
```
|
|
134
|
+
|
|
115
135
|
Properties
|
|
116
136
|
----------
|
|
117
137
|
|
|
@@ -161,6 +181,42 @@ node.outgoing(:attending).options(
|
|
|
161
181
|
)
|
|
162
182
|
```
|
|
163
183
|
|
|
184
|
+
Weight
|
|
185
|
+
------
|
|
186
|
+
|
|
187
|
+
All relationships have an associated weight on its incoming and outgoing
|
|
188
|
+
links. By default the weight is set to the time when the relationship was
|
|
189
|
+
created. That makes the result from a query that fetches relationships always
|
|
190
|
+
sorted so that newer relationships appear first, which is nice. If you create
|
|
191
|
+
a custom Related::Relationship sub-class you can define how the weight is
|
|
192
|
+
generated for a relationship.
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
class Comment < Related::Relationship
|
|
196
|
+
property :created_at, Time
|
|
197
|
+
property :points, Integer
|
|
198
|
+
weight do |direction|
|
|
199
|
+
if direction == :in
|
|
200
|
+
self.created_at
|
|
201
|
+
elsif direction == :out
|
|
202
|
+
self.points
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The weight is always an integer and is sorted in descending order.
|
|
209
|
+
|
|
210
|
+
The weight for the links get updated every time the relationship is saved. So
|
|
211
|
+
if you update the points for a Comment in the example above, the weight is
|
|
212
|
+
automatically updated. You can access the weight and rank (0 based position)
|
|
213
|
+
of a relationship like this:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
comment.weight(:out)
|
|
217
|
+
comment.rank(:in)
|
|
218
|
+
```
|
|
219
|
+
|
|
164
220
|
ActiveModel
|
|
165
221
|
-----------
|
|
166
222
|
|
data/lib/related/exceptions.rb
CHANGED
|
@@ -2,5 +2,12 @@ module Related
|
|
|
2
2
|
class RelatedException < RuntimeError; end
|
|
3
3
|
class NotFound < RelatedException; end
|
|
4
4
|
class InvalidQuery < RelatedException; end
|
|
5
|
-
class ValidationsFailed < RelatedException
|
|
5
|
+
class ValidationsFailed < RelatedException
|
|
6
|
+
attr_reader :object
|
|
7
|
+
def initialize(object)
|
|
8
|
+
@object = object
|
|
9
|
+
errors = @object.errors.full_messages.to_sentence
|
|
10
|
+
super(errors)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
6
13
|
end
|
data/lib/related/helpers.rb
CHANGED
data/lib/related/node.rb
CHANGED
|
@@ -46,6 +46,7 @@ module Related
|
|
|
46
46
|
def page(nr)
|
|
47
47
|
query = self.query
|
|
48
48
|
query.page = nr
|
|
49
|
+
query.result_type = :relationships
|
|
49
50
|
query
|
|
50
51
|
end
|
|
51
52
|
|
|
@@ -176,7 +177,7 @@ module Related
|
|
|
176
177
|
@page && @page.to_i != 1 ? (@page.to_i * @limit.to_i) - @limit.to_i : 0
|
|
177
178
|
else
|
|
178
179
|
rel = @page.is_a?(String) ? Related::Relationship.find(@page) : @page
|
|
179
|
-
rel.rank + 1
|
|
180
|
+
rel.rank(@direction) + 1
|
|
180
181
|
end
|
|
181
182
|
end
|
|
182
183
|
|
|
@@ -186,9 +187,9 @@ module Related
|
|
|
186
187
|
|
|
187
188
|
def key(node=nil)
|
|
188
189
|
if @result_type == :nodes
|
|
189
|
-
"#{node ? node.to_s : @node.to_s}:
|
|
190
|
+
"#{node ? node.to_s : @node.to_s}:n:#{@relationship_type}:#{@direction}"
|
|
190
191
|
else
|
|
191
|
-
"#{node ? node.to_s : @node.to_s}:
|
|
192
|
+
"#{node ? node.to_s : @node.to_s}:r:#{@relationship_type}:#{@direction}"
|
|
192
193
|
end
|
|
193
194
|
end
|
|
194
195
|
|
data/lib/related/relationship.rb
CHANGED
|
@@ -18,8 +18,16 @@ module Related
|
|
|
18
18
|
@end_node ||= Related::Node.find(end_node_id)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def rank
|
|
22
|
-
Related.redis.zrevrank(
|
|
21
|
+
def rank(direction)
|
|
22
|
+
Related.redis.zrevrank(r_key(direction), self.id)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def weight(direction)
|
|
26
|
+
Related.redis.zscore(r_key(direction), self.id).to_i
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.weight(&block)
|
|
30
|
+
@weight = block
|
|
23
31
|
end
|
|
24
32
|
|
|
25
33
|
def self.create(label, node1, node2, attributes = {})
|
|
@@ -32,26 +40,58 @@ module Related
|
|
|
32
40
|
|
|
33
41
|
private
|
|
34
42
|
|
|
43
|
+
def r_key(direction)
|
|
44
|
+
if direction.to_sym == :out
|
|
45
|
+
"#{self.start_node_id}:r:#{self.label}:out"
|
|
46
|
+
elsif direction.to_sym == :in
|
|
47
|
+
"#{self.end_node_id}:r:#{self.label}:in"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def n_key(direction)
|
|
52
|
+
if direction.to_sym == :out
|
|
53
|
+
"#{self.start_node_id}:n:#{self.label}:out"
|
|
54
|
+
elsif direction.to_sym == :in
|
|
55
|
+
"#{self.end_node_id}:n:#{self.label}:in"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.weight_for(relationship, direction)
|
|
60
|
+
if @weight
|
|
61
|
+
relationship.instance_exec(direction, &@weight).to_i
|
|
62
|
+
else
|
|
63
|
+
Time.parse(relationship.created_at).to_i
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
35
67
|
def create
|
|
36
68
|
Related.redis.multi do
|
|
37
69
|
super
|
|
38
|
-
|
|
39
|
-
Related.redis.zadd(
|
|
40
|
-
Related.redis.
|
|
70
|
+
Related.redis.zadd(r_key(:out), self.class.weight_for(self, :out), self.id)
|
|
71
|
+
Related.redis.zadd(r_key(:in), self.class.weight_for(self, :in), self.id)
|
|
72
|
+
Related.redis.sadd(n_key(:out), self.end_node_id)
|
|
73
|
+
Related.redis.sadd(n_key(:in), self.start_node_id)
|
|
74
|
+
end
|
|
75
|
+
self
|
|
76
|
+
end
|
|
41
77
|
|
|
42
|
-
|
|
43
|
-
|
|
78
|
+
def update
|
|
79
|
+
Related.redis.multi do
|
|
80
|
+
super
|
|
81
|
+
Related.redis.zadd(r_key(:out), self.class.weight_for(self, :out), self.id)
|
|
82
|
+
Related.redis.zadd(r_key(:in), self.class.weight_for(self, :in), self.id)
|
|
83
|
+
Related.redis.sadd(n_key(:out), self.end_node_id)
|
|
84
|
+
Related.redis.sadd(n_key(:in), self.start_node_id)
|
|
44
85
|
end
|
|
45
86
|
self
|
|
46
87
|
end
|
|
47
88
|
|
|
48
89
|
def delete
|
|
49
90
|
Related.redis.multi do
|
|
50
|
-
Related.redis.zrem(
|
|
51
|
-
Related.redis.zrem(
|
|
52
|
-
|
|
53
|
-
Related.redis.srem(
|
|
54
|
-
Related.redis.srem("#{self.end_node_id}:nodes:#{self.label}:in", self.start_node_id)
|
|
91
|
+
Related.redis.zrem(r_key(:out), self.id)
|
|
92
|
+
Related.redis.zrem(r_key(:in), self.id)
|
|
93
|
+
Related.redis.srem(n_key(:out), self.end_node_id)
|
|
94
|
+
Related.redis.srem(n_key(:in), self.start_node_id)
|
|
55
95
|
super
|
|
56
96
|
end
|
|
57
97
|
self
|
data/lib/related/root.rb
ADDED
data/lib/related/version.rb
CHANGED
data/lib/related.rb
CHANGED
data/test/active_model_test.rb
CHANGED
|
@@ -85,6 +85,11 @@ class ActiveModelTest < ActiveModel::TestCase
|
|
|
85
85
|
assert_raises Related::ValidationsFailed do
|
|
86
86
|
like.save
|
|
87
87
|
end
|
|
88
|
+
begin
|
|
89
|
+
like.save
|
|
90
|
+
rescue Related::ValidationsFailed => e
|
|
91
|
+
assert_equal like, e.object
|
|
92
|
+
end
|
|
88
93
|
like.how_much = 1.0
|
|
89
94
|
assert_equal true, like.valid?
|
|
90
95
|
assert_nothing_raised do
|
data/test/model_test.rb
CHANGED
|
@@ -12,6 +12,12 @@ class ModelTest < ActiveModel::TestCase
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
class Like < Related::Relationship
|
|
16
|
+
weight do |dir|
|
|
17
|
+
dir == :in ? in_score : out_score
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
def setup
|
|
16
22
|
Related.redis.flushall
|
|
17
23
|
end
|
|
@@ -46,4 +52,30 @@ class ModelTest < ActiveModel::TestCase
|
|
|
46
52
|
assert_equal Event, nodes.first.class
|
|
47
53
|
end
|
|
48
54
|
|
|
55
|
+
def test_custom_weight
|
|
56
|
+
node1 = Related::Node.create
|
|
57
|
+
node2 = Related::Node.create
|
|
58
|
+
like = Like.create(:like, node1, node2, :in_score => 42, :out_score => 10)
|
|
59
|
+
assert_equal 42, like.weight(:in)
|
|
60
|
+
assert_equal 10, like.weight(:out)
|
|
61
|
+
like.in_score = 50
|
|
62
|
+
like.save
|
|
63
|
+
assert_equal 50, like.weight(:in)
|
|
64
|
+
assert_equal 10, like.weight(:out)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_weight_sorting
|
|
68
|
+
node1 = Related::Node.create
|
|
69
|
+
node2 = Related::Node.create
|
|
70
|
+
node3 = Related::Node.create
|
|
71
|
+
rel1 = Like.create(:like, node1, node2, :in_score => 1, :out_score => 1)
|
|
72
|
+
rel2 = Like.create(:like, node1, node3, :in_score => 2, :out_score => 2)
|
|
73
|
+
rel3 = Like.create(:like, node2, node1, :in_score => 1, :out_score => 1)
|
|
74
|
+
rel4 = Like.create(:like, node3, node1, :in_score => 2, :out_score => 2)
|
|
75
|
+
assert_equal [rel2,rel1], node1.outgoing(:like).relationships.options(:model => lambda { Like }).to_a
|
|
76
|
+
assert_equal [rel4,rel3], node1.incoming(:like).relationships.options(:model => lambda { Like }).to_a
|
|
77
|
+
assert_equal [rel1], node1.outgoing(:like).relationships.options(:model => lambda { Like }).per_page(2).page(rel2).to_a
|
|
78
|
+
assert_equal [rel3], node1.incoming(:like).relationships.options(:model => lambda { Like }).per_page(2).page(rel4).to_a
|
|
79
|
+
end
|
|
80
|
+
|
|
49
81
|
end
|
data/test/related_test.rb
CHANGED
|
@@ -102,9 +102,9 @@ class RelatedTest < Test::Unit::TestCase
|
|
|
102
102
|
def test_can_create_a_relationship_with_attributes
|
|
103
103
|
node1 = Related::Node.create(:name => 'One')
|
|
104
104
|
node2 = Related::Node.create(:name => 'Two')
|
|
105
|
-
rel = Related::Relationship.create(:friends, node1, node2, :
|
|
105
|
+
rel = Related::Relationship.create(:friends, node1, node2, :score => 2.5)
|
|
106
106
|
rel = Related::Relationship.find(rel.id)
|
|
107
|
-
assert_equal '2.5', rel.
|
|
107
|
+
assert_equal '2.5', rel.score
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def test_can_delete_a_relationship
|
|
@@ -141,6 +141,7 @@ class RelatedTest < Test::Unit::TestCase
|
|
|
141
141
|
node3 = Related::Node.create
|
|
142
142
|
node4 = Related::Node.create
|
|
143
143
|
node5 = Related::Node.create
|
|
144
|
+
node6 = Related::Node.create
|
|
144
145
|
rel1 = Related::Relationship.create(:friends, node1, node2, :name => 'rel1')
|
|
145
146
|
sleep(1)
|
|
146
147
|
rel2 = Related::Relationship.create(:friends, node1, node3, :name => 'rel2')
|
|
@@ -149,12 +150,12 @@ class RelatedTest < Test::Unit::TestCase
|
|
|
149
150
|
sleep(1)
|
|
150
151
|
rel4 = Related::Relationship.create(:friends, node1, node5, :name => 'rel4')
|
|
151
152
|
sleep(1)
|
|
152
|
-
rel5 = Related::Relationship.create(:friends, node1,
|
|
153
|
-
assert_equal [rel5,rel4,rel3], node1.outgoing(:friends).
|
|
154
|
-
assert_equal [rel2,rel1], node1.outgoing(:friends).
|
|
155
|
-
assert_equal [rel5,rel4,rel3], node1.outgoing(:friends).
|
|
156
|
-
assert_equal [rel4,rel3,rel2], node1.outgoing(:friends).
|
|
157
|
-
assert_equal [rel2,rel1], node1.outgoing(:friends).
|
|
153
|
+
rel5 = Related::Relationship.create(:friends, node1, node6, :name => 'rel5')
|
|
154
|
+
assert_equal [rel5,rel4,rel3], node1.outgoing(:friends).per_page(3).page(1).to_a
|
|
155
|
+
assert_equal [rel2,rel1], node1.outgoing(:friends).per_page(3).page(2).to_a
|
|
156
|
+
assert_equal [rel5,rel4,rel3], node1.outgoing(:friends).per_page(3).page(nil).to_a
|
|
157
|
+
assert_equal [rel4,rel3,rel2], node1.outgoing(:friends).per_page(3).page(rel5).to_a
|
|
158
|
+
assert_equal [rel2,rel1], node1.outgoing(:friends).per_page(3).page(rel3).to_a
|
|
158
159
|
end
|
|
159
160
|
|
|
160
161
|
def test_can_count_the_number_of_related_nodes
|
|
@@ -285,4 +286,13 @@ class RelatedTest < Test::Unit::TestCase
|
|
|
285
286
|
end
|
|
286
287
|
end
|
|
287
288
|
|
|
289
|
+
def test_root
|
|
290
|
+
assert_kind_of Related::Node, Related.root
|
|
291
|
+
node = Related::Node.create
|
|
292
|
+
rel = Related::Relationship.create(:friend, Related.root, node)
|
|
293
|
+
assert_equal [node], Related.root.outgoing(:friend).to_a
|
|
294
|
+
Related.root.name = 'Test'
|
|
295
|
+
Related.root.save
|
|
296
|
+
end
|
|
297
|
+
|
|
288
298
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: related
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 15
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
version: 0.
|
|
8
|
+
- 4
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.4.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Niklas Holmgren
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-10-
|
|
18
|
+
date: 2011-10-11 00:00:00 +02:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|
|
@@ -84,6 +84,7 @@ files:
|
|
|
84
84
|
- lib/related/helpers.rb
|
|
85
85
|
- lib/related/node.rb
|
|
86
86
|
- lib/related/relationship.rb
|
|
87
|
+
- lib/related/root.rb
|
|
87
88
|
- lib/related/version.rb
|
|
88
89
|
- lib/related.rb
|
|
89
90
|
- test/active_model_test.rb
|