hari 0.0.3 → 0.0.4
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.
- checksums.yaml +6 -14
- data/LICENSE +17 -15
- data/README.md +131 -11
- data/lib/hari/entity/property.rb +1 -2
- data/lib/hari/entity/serialization.rb +8 -8
- data/lib/hari/entity.rb +5 -0
- data/lib/hari/keys/key.rb +48 -0
- data/lib/hari/keys/list.rb +132 -0
- data/lib/hari/keys/set.rb +101 -0
- data/lib/hari/keys/sorted_set.rb +115 -0
- data/lib/hari/keys/string.rb +81 -0
- data/lib/hari/keys.rb +9 -0
- data/lib/hari/node/queries/relation/backend/sorted_set.rb +14 -4
- data/lib/hari/node/queries/relation.rb +13 -7
- data/lib/hari/node/queries/type.rb +62 -0
- data/lib/hari/node/queries.rb +10 -8
- data/lib/hari/relation/sorted_set.rb +33 -4
- data/lib/hari/relation.rb +0 -3
- data/lib/hari/version.rb +1 -1
- data/lib/hari.rb +10 -3
- data/spec/hari/keys/key_spec.rb +41 -0
- data/spec/hari/{node → keys}/lists_spec.rb +1 -1
- data/spec/hari/{node → keys}/sets_spec.rb +1 -1
- data/spec/hari/{node → keys}/sorted_sets_spec.rb +1 -1
- data/spec/hari/keys/string_spec.rb +57 -0
- data/spec/hari/node_spec.rb +45 -0
- data/spec/hari_spec.rb +13 -0
- metadata +28 -24
- data/lib/hari/node/queries/list.rb +0 -147
- data/lib/hari/node/queries/relation/backend/list.rb +0 -35
- data/lib/hari/node/queries/set.rb +0 -116
- data/lib/hari/node/queries/sorted_set.rb +0 -130
- data/lib/hari/relation/linked_list.rb +0 -16
@@ -0,0 +1,81 @@
|
|
1
|
+
module Hari
|
2
|
+
module Keys
|
3
|
+
class String < Key
|
4
|
+
|
5
|
+
def string(name)
|
6
|
+
@name = name
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def string!(name)
|
11
|
+
@name = name
|
12
|
+
to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
Hari.redis.get key
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(value)
|
20
|
+
Hari.redis.set key, value
|
21
|
+
end
|
22
|
+
|
23
|
+
def length
|
24
|
+
Hari.redis.strlen key
|
25
|
+
end
|
26
|
+
|
27
|
+
alias :size :length
|
28
|
+
|
29
|
+
def range(start = nil, stop = nil)
|
30
|
+
start ||= 0
|
31
|
+
stop ||= -1
|
32
|
+
Hari.redis.getrange key, start, stop
|
33
|
+
end
|
34
|
+
|
35
|
+
def at(index)
|
36
|
+
Hari.redis.getrange key, index, index
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](*args)
|
40
|
+
arg = args.first
|
41
|
+
|
42
|
+
if args.size == 2
|
43
|
+
range *args
|
44
|
+
elsif arg.kind_of? Integer
|
45
|
+
at arg
|
46
|
+
elsif arg.kind_of? Range
|
47
|
+
range arg.first, arg.last
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def <<(value)
|
52
|
+
Hari.redis.append key, value
|
53
|
+
end
|
54
|
+
|
55
|
+
def +(i)
|
56
|
+
Hari.redis.incrby key, i
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def -(i)
|
61
|
+
Hari.redis.decrby key, i
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def bitcount(start = nil, stop = nil)
|
66
|
+
start ||= 0
|
67
|
+
stop ||= -1
|
68
|
+
Hari.redis.bitcount key, start, stop
|
69
|
+
end
|
70
|
+
|
71
|
+
def getbit(offset)
|
72
|
+
Hari.redis.getbit key, offset
|
73
|
+
end
|
74
|
+
|
75
|
+
def setbit(offset, value)
|
76
|
+
Hari.redis.setbit key, offset, value
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/hari/keys.rb
ADDED
@@ -10,10 +10,14 @@ class Hari::Node::Queries::Relation
|
|
10
10
|
|
11
11
|
def fetch_relations_ids(set, options)
|
12
12
|
from, limit = options.values_at(:from, :limit)
|
13
|
+
limit = limit.try(:to_i)
|
13
14
|
|
14
|
-
if from.present?
|
15
|
-
set.range_by_score from, '+inf', desc: true, limit: [0, limit]
|
15
|
+
if from.present? && from[:direction] == 'up'
|
16
|
+
set.range_by_score from[:score], '+inf', desc: true, limit: [0, limit]
|
17
|
+
elsif from.present? && from[:direction] == 'down'
|
18
|
+
set.range_by_score '-inf', from[:score], desc: true, limit: [0, limit]
|
16
19
|
else
|
20
|
+
limit -= 1 unless limit <= 0
|
17
21
|
set.range from, limit, desc: true
|
18
22
|
end
|
19
23
|
end
|
@@ -35,7 +39,7 @@ class Hari::Node::Queries::Relation
|
|
35
39
|
def step(start_node, nodes_ids, options = {})
|
36
40
|
stream = start_node.sorted_set("stream:#{SecureRandom.hex(6)}")
|
37
41
|
direction = options[:direction] == :in ? 0 : 2
|
38
|
-
limit = options.fetch(:limit, -1)
|
42
|
+
limit = options.fetch(:limit, -1).try :to_i
|
39
43
|
|
40
44
|
nodes_ids.each_with_index do |node_id, index|
|
41
45
|
prune, stop = true, options.fetch(:step, 5)
|
@@ -50,7 +54,13 @@ class Hari::Node::Queries::Relation
|
|
50
54
|
set = Hari(node_id).sorted_set set_name(options)
|
51
55
|
|
52
56
|
if from = options[:from].presence
|
53
|
-
|
57
|
+
args = { desc: true, with_scores: true, limit: [start, stop] }
|
58
|
+
|
59
|
+
if from[:direction] == 'up'
|
60
|
+
scored_relations_ids = set.range_by_score(from[:score], '+inf', args)
|
61
|
+
elsif from[:direction] == 'down'
|
62
|
+
scored_relations_ids = set.range_by_score('-inf', from[:score], args)
|
63
|
+
end
|
54
64
|
else
|
55
65
|
scored_relations_ids = set.range(start, stop, desc: true, with_scores: true)
|
56
66
|
end
|
@@ -12,6 +12,8 @@ module Hari
|
|
12
12
|
|
13
13
|
attr_reader :parent, :direction, :relation, :level, :options
|
14
14
|
|
15
|
+
alias :name :relation
|
16
|
+
|
15
17
|
def initialize(parent, direction, relation, *args)
|
16
18
|
@parent, @direction, @relation = parent, direction, relation
|
17
19
|
@level = parent.level + 1
|
@@ -26,24 +28,22 @@ module Hari
|
|
26
28
|
end
|
27
29
|
|
28
30
|
def calculate_limit
|
29
|
-
options[:limit]
|
31
|
+
options[:limit] || -1
|
30
32
|
end
|
31
33
|
|
32
|
-
%w(limit
|
34
|
+
%w(limit step).each do |method|
|
33
35
|
define_method method do |value|
|
34
36
|
options[method.to_sym] = value
|
35
37
|
self
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
options[:
|
41
|
+
def from(score, direction = nil)
|
42
|
+
direction ||= :up
|
43
|
+
options[:from] = { score: score, direction: direction.to_s }
|
42
44
|
self
|
43
45
|
end
|
44
46
|
|
45
|
-
alias :type :types
|
46
|
-
|
47
47
|
%w(nodes_ids relations_ids nodes).each do |result_type|
|
48
48
|
define_method result_type do
|
49
49
|
options[:result_type] = result_type.to_sym
|
@@ -60,6 +60,12 @@ module Hari
|
|
60
60
|
alias :rel_ids :relations_ids
|
61
61
|
alias :rids :relations_ids
|
62
62
|
|
63
|
+
def type(name)
|
64
|
+
fail 'type not supported for chained queries' if level > 1
|
65
|
+
|
66
|
+
Type.new self, name
|
67
|
+
end
|
68
|
+
|
63
69
|
def count
|
64
70
|
options[:result_type] = :count
|
65
71
|
result
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Hari
|
2
|
+
class Node < Entity
|
3
|
+
module Queries
|
4
|
+
class Type
|
5
|
+
|
6
|
+
attr_reader :relation, :name
|
7
|
+
|
8
|
+
def initialize(relation, name)
|
9
|
+
@relation, @name = relation, name
|
10
|
+
end
|
11
|
+
|
12
|
+
def intersect_count(type)
|
13
|
+
intersect_key = interstore(type)
|
14
|
+
|
15
|
+
Hari.redis.zcard(intersect_key).tap do
|
16
|
+
Hari.redis.del intersect_key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def intersect(type, start = nil, stop = nil)
|
21
|
+
start ||= 0
|
22
|
+
stop ||= -1
|
23
|
+
|
24
|
+
intersect_key = interstore(type)
|
25
|
+
|
26
|
+
Hari.redis.zrevrange(intersect_key, start, stop).tap do
|
27
|
+
Hari.redis.del intersect_key
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sort_by(type, offset = nil, count = nil, order = nil)
|
32
|
+
offset ||= 0
|
33
|
+
count ||= Hari.redis.zcard(key)
|
34
|
+
order = 'desc' unless order.to_s == 'asc'
|
35
|
+
|
36
|
+
Hari.redis.sort key, by: type.sort_key, order: "#{order} alpha", limit: [offset, count]
|
37
|
+
end
|
38
|
+
|
39
|
+
def interstore(type)
|
40
|
+
intersect_key(type).tap do |destination|
|
41
|
+
Hari.redis.zinterstore destination, [key, type.key]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def key
|
46
|
+
start_key = Hari.node_key(relation.parent.node)
|
47
|
+
"#{start_key}:#{relation.name}:#{relation.direction}:#{name}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def sort_key
|
51
|
+
start_key = Hari.node_key(relation.parent.node)
|
52
|
+
"#{start_key}:#{relation.name}:#{name}#*"
|
53
|
+
end
|
54
|
+
|
55
|
+
def intersect_key(type)
|
56
|
+
"inter:#{key}:#{type.key}"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/hari/node/queries.rb
CHANGED
@@ -1,30 +1,32 @@
|
|
1
|
-
require 'hari/node/queries/list'
|
2
|
-
require 'hari/node/queries/set'
|
3
|
-
require 'hari/node/queries/sorted_set'
|
4
|
-
require 'hari/node/queries/relation'
|
5
|
-
|
6
1
|
module Hari
|
7
2
|
class Node < Entity
|
8
3
|
module Queries
|
4
|
+
autoload :Relation, 'hari/node/queries/relation'
|
5
|
+
autoload :Type, 'hari/node/queries/type'
|
9
6
|
|
10
7
|
delegate :in, :out, to: :relation_query
|
11
8
|
|
12
9
|
delegate :set, :set!, to: :set_query
|
13
10
|
delegate :sorted_set, :sorted_set!, to: :sorted_set_query
|
14
11
|
delegate :list, :list!, to: :list_query
|
12
|
+
delegate :string, :string!, to: :string_query
|
15
13
|
|
16
14
|
private
|
17
15
|
|
18
16
|
def set_query
|
19
|
-
|
17
|
+
Keys::Set.new query_node
|
20
18
|
end
|
21
19
|
|
22
20
|
def sorted_set_query
|
23
|
-
|
21
|
+
Keys::SortedSet.new query_node
|
24
22
|
end
|
25
23
|
|
26
24
|
def list_query
|
27
|
-
|
25
|
+
Keys::List.new query_node
|
26
|
+
end
|
27
|
+
|
28
|
+
def string_query
|
29
|
+
Keys::String.new query_node
|
28
30
|
end
|
29
31
|
|
30
32
|
def relation_query
|
@@ -3,12 +3,41 @@ module Hari
|
|
3
3
|
module SortedSet
|
4
4
|
extend self
|
5
5
|
|
6
|
-
def create(
|
7
|
-
Relation::DIRECTIONS.each
|
6
|
+
def create(relation)
|
7
|
+
Relation::DIRECTIONS.each do |direction|
|
8
|
+
key = relation.key(direction)
|
9
|
+
weight = relation.weight(direction)
|
10
|
+
|
11
|
+
Hari.redis.zadd key, weight, relation.id
|
12
|
+
Hari.redis.zadd type_key(relation, direction), weight, type_id(relation, direction)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(relation)
|
17
|
+
Relation::DIRECTIONS.each do |direction|
|
18
|
+
key = relation.key(direction)
|
19
|
+
|
20
|
+
Hari.redis.zrem key, relation.id
|
21
|
+
Hari.redis.zrem type_key(relation, direction), type_id(relation, direction)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def type_key(relation, direction)
|
28
|
+
case direction.to_s
|
29
|
+
when 'out'
|
30
|
+
"#{relation.start_node_id}:#{relation.label}:out:#{Hari.node_type(relation.end_node_id)}"
|
31
|
+
when 'in'
|
32
|
+
"#{relation.end_node_id}:#{relation.label}:in:#{Hari.node_type(relation.start_node_id)}"
|
33
|
+
end
|
8
34
|
end
|
9
35
|
|
10
|
-
def
|
11
|
-
|
36
|
+
def type_id(relation, direction)
|
37
|
+
case direction.to_s
|
38
|
+
when 'in' then Hari.node_id(relation.start_node_id)
|
39
|
+
when 'out' then Hari.node_id(relation.end_node_id)
|
40
|
+
end
|
12
41
|
end
|
13
42
|
|
14
43
|
end
|
data/lib/hari/relation.rb
CHANGED
data/lib/hari/version.rb
CHANGED
data/lib/hari.rb
CHANGED
@@ -12,12 +12,15 @@ require 'ostruct'
|
|
12
12
|
require 'hari/version'
|
13
13
|
require 'hari/configuration'
|
14
14
|
require 'hari/errors'
|
15
|
-
require 'hari/entity'
|
16
|
-
require 'hari/node'
|
17
|
-
require 'hari/relation'
|
18
15
|
|
19
16
|
module Hari
|
20
17
|
extend self
|
18
|
+
|
19
|
+
autoload :Entity, 'hari/entity'
|
20
|
+
autoload :Keys, 'hari/keys'
|
21
|
+
autoload :Node, 'hari/node'
|
22
|
+
autoload :Relation, 'hari/relation'
|
23
|
+
|
21
24
|
extend Configuration
|
22
25
|
extend Hari::Node::Queries
|
23
26
|
|
@@ -68,6 +71,10 @@ module Hari
|
|
68
71
|
Relation.create type, from, target
|
69
72
|
end
|
70
73
|
|
74
|
+
def remove_relation!(type, from, target)
|
75
|
+
relation!(type, from, target).delete
|
76
|
+
end
|
77
|
+
|
71
78
|
end
|
72
79
|
|
73
80
|
def Hari(arg)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hari::Keys::Key do
|
4
|
+
|
5
|
+
let(:node) { Hari.node user: 10 }
|
6
|
+
subject { node.list :friends }
|
7
|
+
|
8
|
+
specify { subject.instance_of? Hari::Keys::Key }
|
9
|
+
|
10
|
+
before { subject.add 1 }
|
11
|
+
|
12
|
+
specify '#exists? + #delete!' do
|
13
|
+
subject.exists?.should be_true
|
14
|
+
|
15
|
+
subject.delete!
|
16
|
+
|
17
|
+
subject.exists?.should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
specify '#type' do
|
21
|
+
subject.type.should eq('list')
|
22
|
+
end
|
23
|
+
|
24
|
+
specify '#expire + #persist + #ttl' do
|
25
|
+
subject.expire 80 # ms
|
26
|
+
(subject.ttl.to_i < 80).should be_true
|
27
|
+
|
28
|
+
sleep 0.1
|
29
|
+
|
30
|
+
subject.exists?.should be_false
|
31
|
+
|
32
|
+
subject.add 12
|
33
|
+
subject.expire 100
|
34
|
+
subject.persist
|
35
|
+
|
36
|
+
sleep 0.2
|
37
|
+
|
38
|
+
subject.exists?.should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hari::Keys::String do
|
4
|
+
|
5
|
+
let(:node) { Hari.node user: 10 }
|
6
|
+
subject { node.string :bio }
|
7
|
+
|
8
|
+
specify '#string! + #to_s + #set' do
|
9
|
+
subject.set 'lol'
|
10
|
+
node.string!(:bio).should eq('lol')
|
11
|
+
|
12
|
+
subject.to_s.should eq('lol')
|
13
|
+
end
|
14
|
+
|
15
|
+
specify '#length' do
|
16
|
+
subject.set 'lol'
|
17
|
+
subject.length.should eq(3)
|
18
|
+
subject.size.should eq(3)
|
19
|
+
end
|
20
|
+
|
21
|
+
specify '#at + #range + #[]' do
|
22
|
+
subject.set 'de his omnibus non cogitavi'
|
23
|
+
|
24
|
+
subject.at(7).should eq('o')
|
25
|
+
subject[7].should eq('o')
|
26
|
+
|
27
|
+
subject.range(7, 13).should eq('omnibus')
|
28
|
+
subject[7, 13].should eq('omnibus')
|
29
|
+
subject[7..13].should eq('omnibus')
|
30
|
+
end
|
31
|
+
|
32
|
+
specify '#<<' do
|
33
|
+
subject.set 'lol'
|
34
|
+
subject << 'omg'
|
35
|
+
subject << 'bbq'
|
36
|
+
|
37
|
+
subject.to_s.should eq('lolomgbbq')
|
38
|
+
end
|
39
|
+
|
40
|
+
specify '#+ + #-' do
|
41
|
+
subject.set 1
|
42
|
+
subject + 4
|
43
|
+
subject.to_s.should eq('5')
|
44
|
+
|
45
|
+
subject - 7
|
46
|
+
subject.to_s.should eq('-2')
|
47
|
+
end
|
48
|
+
|
49
|
+
specify '#bitcount + #getbit + #setbit' do
|
50
|
+
subject.set 'lol'
|
51
|
+
subject.bitcount.should eq(14)
|
52
|
+
subject.getbit(3).should eq(0)
|
53
|
+
subject.setbit(4, 1)
|
54
|
+
subject.getbit(4).should eq(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/spec/hari/node_spec.rb
CHANGED
@@ -65,6 +65,12 @@ describe Hari::Node do
|
|
65
65
|
it 'paginates queries' do
|
66
66
|
following = joao.out(:follow).from(15.minutes.ago.to_f).to_a
|
67
67
|
following.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
|
68
|
+
|
69
|
+
following = joao.out(:follow).from(15.minutes.ago.to_f, 'down').to_a
|
70
|
+
following.map(&:id).sort.should eq [maria, joaquim, lili].map(&:id).sort
|
71
|
+
|
72
|
+
following = joao.out(:follow).limit(1).from(15.minutes.ago.to_f, 'down').to_a
|
73
|
+
following.map(&:id).sort.should eq [maria.id]
|
68
74
|
end
|
69
75
|
|
70
76
|
it 'paginates chained queries' do
|
@@ -73,6 +79,45 @@ describe Hari::Node do
|
|
73
79
|
following = teresa.out(:follow).out(:follow).from(15.minutes.ago.to_f)
|
74
80
|
following.to_a.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
|
75
81
|
following.nodes!.map(&:id).sort.should eq [teresa, raimundo].map(&:id).sort
|
82
|
+
|
83
|
+
following = teresa.out(:follow).out(:follow).from(15.minutes.ago.to_f, 'down')
|
84
|
+
following.to_a.map(&:id).sort.should eq [maria, joaquim, lili].map(&:id).sort
|
85
|
+
|
86
|
+
following = teresa.out(:follow).out(:follow).limit(1).from(15.minutes.ago.to_f, 'down')
|
87
|
+
following.to_a.map(&:id).sort.should eq [maria.id]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'queries by type' do
|
92
|
+
before do
|
93
|
+
Hari.relation! :follow, 'user#1', 'celeb#x'
|
94
|
+
Hari.relation! :follow, 'user#2', 'user#1'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'can intersect type queries' do
|
98
|
+
friends = Hari('user#2').out(:follow).type(:user)
|
99
|
+
fans = Hari('celeb#x').in(:follow).type(:user)
|
100
|
+
|
101
|
+
fans.intersect_count(friends).should eq(1)
|
102
|
+
friends.intersect_count(fans).should eq(1)
|
103
|
+
|
104
|
+
fans.intersect(friends).should eq %w(1)
|
105
|
+
|
106
|
+
Hari.relation! :follow, 'user#3', 'celeb#x'
|
107
|
+
Hari.relation! :follow, 'user#4', 'celeb#x'
|
108
|
+
Hari.relation! :follow, 'user#5', 'celeb#x'
|
109
|
+
Hari.relation! :follow, 'user#6', 'celeb#x'
|
110
|
+
Hari.relation! :follow, 'user#7', 'celeb#x'
|
111
|
+
Hari.relation! :follow, 'user#8', 'celeb#x'
|
112
|
+
|
113
|
+
Hari.relation! :follow, 'user#2', 'user#4'
|
114
|
+
Hari.relation! :follow, 'user#2', 'user#5'
|
115
|
+
Hari.relation! :follow, 'user#2', 'user#6'
|
116
|
+
Hari.relation! :follow, 'user#2', 'user#7'
|
117
|
+
|
118
|
+
fans.intersect(friends, 1, 3).should eq %w(6 5 4)
|
119
|
+
|
120
|
+
fans.sort_by(friends).take(5).should eq %w(7 6 5 4 1)
|
76
121
|
end
|
77
122
|
end
|
78
123
|
|
data/spec/hari_spec.rb
CHANGED
@@ -34,4 +34,17 @@ describe Hari do
|
|
34
34
|
node.node_type.should eq('user')
|
35
35
|
end
|
36
36
|
|
37
|
+
specify '.relation!' do
|
38
|
+
Hari.relation! :follow, 'user#1', 'user#2'
|
39
|
+
|
40
|
+
Hari(user: 1).out(:follow).nodes_ids!.should eq %w(user#2)
|
41
|
+
end
|
42
|
+
|
43
|
+
specify '.remove_relation!' do
|
44
|
+
Hari.relation! :follow, 'user#6', 'user#9'
|
45
|
+
Hari.remove_relation! :follow, 'user#6', 'user#9'
|
46
|
+
|
47
|
+
Hari(user: 1).out(:follow).nodes_ids!.should be_empty
|
48
|
+
end
|
49
|
+
|
37
50
|
end
|