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