redgraph 0.1.1 → 0.2.1
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 +4 -4
- data/.github/workflows/main.yml +8 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +22 -1
- data/README.md +109 -15
- data/lib/redgraph.rb +16 -0
- data/lib/redgraph/edge.rb +17 -4
- data/lib/redgraph/graph.rb +44 -86
- data/lib/redgraph/graph/edge_methods.rb +105 -0
- data/lib/redgraph/graph/node_methods.rb +123 -0
- data/lib/redgraph/node.rb +11 -3
- data/lib/redgraph/node_model.rb +99 -0
- data/lib/redgraph/node_model/class_methods.rb +83 -0
- data/lib/redgraph/node_model/graph_manipulation.rb +24 -0
- data/lib/redgraph/node_model/persistence.rb +56 -0
- data/lib/redgraph/query_response.rb +91 -5
- data/lib/redgraph/util.rb +21 -0
- data/lib/redgraph/version.rb +1 -1
- data/redgraph.gemspec +2 -1
- data/test/graph_edge_methods_test.rb +100 -0
- data/test/graph_manipulation_test.rb +59 -0
- data/test/graph_node_methods_test.rb +112 -0
- data/test/graph_queries_test.rb +21 -66
- data/test/graph_test.rb +26 -0
- data/test/node_model_class_methods_test.rb +93 -0
- data/test/node_model_labels_test.rb +57 -0
- data/test/node_model_persistence_test.rb +70 -0
- data/test/node_model_test.rb +136 -0
- data/test/test_helper.rb +21 -0
- metadata +30 -3
data/test/graph_queries_test.rb
CHANGED
@@ -3,88 +3,43 @@
|
|
3
3
|
require "test_helper"
|
4
4
|
|
5
5
|
class GraphQueriesTest < Minitest::Test
|
6
|
+
include TestHelpers
|
7
|
+
|
6
8
|
def setup
|
7
9
|
@graph = Redgraph::Graph.new("movies", url: $REDIS_URL)
|
8
10
|
|
9
|
-
@al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
10
|
-
@john = quick_add_node(label: 'actor', properties: {name: "John Travolta"})
|
11
|
+
@al = quick_add_node(label: 'actor', properties: {name: "Al Pacino", born: 1940})
|
12
|
+
@john = quick_add_node(label: 'actor', properties: {name: "John Travolta", born: 1954})
|
11
13
|
end
|
12
14
|
|
13
15
|
def teardown
|
14
16
|
@graph.delete
|
15
17
|
end
|
16
18
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
refute_nil(node)
|
21
|
-
assert_equal("actor", node.label)
|
22
|
-
assert_equal("Al Pacino", node.properties["name"])
|
23
|
-
assert_equal(@al.id, node.id)
|
19
|
+
def test_query_string_attribute
|
20
|
+
result = @graph.query("MATCH (n) RETURN n.name ORDER BY n.name")
|
21
|
+
assert_equal([["Al Pacino"], ["John Travolta"]], result)
|
24
22
|
end
|
25
23
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
assert_nil(node)
|
24
|
+
def test_query_string_and_number_attributes
|
25
|
+
result = @graph.query("MATCH (n) RETURN n.name, n.born ORDER BY n.born")
|
26
|
+
assert_equal([["Al Pacino", 1940], ["John Travolta", 1954]], result)
|
30
27
|
end
|
31
28
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
assert_equal(2, actors.size)
|
36
|
-
assert_includes(actors, @al)
|
37
|
-
assert_includes(actors, @john)
|
29
|
+
def test_query_nodes
|
30
|
+
result = @graph.query("MATCH (n) RETURN n ORDER BY n.born")
|
31
|
+
assert_equal([[@al], [@john]], result)
|
38
32
|
end
|
39
33
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
assert_equal(2, actors.size)
|
45
|
-
assert_includes(actors, @al)
|
46
|
-
assert_includes(actors, @john)
|
47
|
-
|
48
|
-
films = @graph.nodes(label: 'film')
|
49
|
-
assert_equal(1, films.size)
|
50
|
-
assert_includes(films, film)
|
34
|
+
def test_query_edge
|
35
|
+
edge = quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 1980})
|
36
|
+
result = @graph.query("MATCH (src)-[edge]->(dest) RETURN edge")
|
37
|
+
assert_equal([[edge]], result)
|
51
38
|
end
|
52
39
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
dramas = @graph.nodes(properties: {genre: "drama"})
|
59
|
-
|
60
|
-
assert_equal(2, dramas.size)
|
61
|
-
assert_includes(dramas, scarface)
|
62
|
-
assert_includes(dramas, casino)
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_limit_nodes
|
66
|
-
10.times do |i|
|
67
|
-
quick_add_node(label: 'token', properties: {number: i})
|
68
|
-
end
|
69
|
-
|
70
|
-
items = @graph.nodes(label: 'token', limit: 5)
|
71
|
-
assert_equal(5, items.size)
|
72
|
-
assert_equal([0,1,2,3,4], items.map{|item| item.properties["number"]})
|
73
|
-
end
|
74
|
-
|
75
|
-
def test_skip_nodes
|
76
|
-
10.times do |i|
|
77
|
-
quick_add_node(label: 'token', properties: {number: i})
|
78
|
-
end
|
79
|
-
|
80
|
-
items = @graph.nodes(label: 'token', limit: 3, skip: 3)
|
81
|
-
assert_equal(3, items.size)
|
82
|
-
assert_equal([3,4,5], items.map{|item| item.properties["number"]})
|
83
|
-
end
|
84
|
-
|
85
|
-
private
|
86
|
-
|
87
|
-
def quick_add_node(label:, properties:)
|
88
|
-
@graph.add_node(Redgraph::Node.new(label: label, properties: properties))
|
40
|
+
def test_query_node_and_edge
|
41
|
+
edge = quick_add_edge(type: 'FRIEND_OF', src: @al, dest: @john, properties: {since: 1980})
|
42
|
+
result = @graph.query("MATCH (src)-[edge:FRIEND_OF]->(dest) RETURN src, edge")
|
43
|
+
assert_equal([[@al, edge]], result)
|
89
44
|
end
|
90
45
|
end
|
data/test/graph_test.rb
CHANGED
@@ -12,11 +12,13 @@ class GraphTest < Minitest::Test
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_list
|
15
|
+
skip unless graph_list_supported?
|
15
16
|
list = @graph.list
|
16
17
|
assert_includes(list, "foobar")
|
17
18
|
end
|
18
19
|
|
19
20
|
def test_delete
|
21
|
+
skip unless graph_list_supported?
|
20
22
|
assert_includes(@graph.list, "foobar")
|
21
23
|
|
22
24
|
@graph.delete
|
@@ -42,6 +44,19 @@ class GraphTest < Minitest::Test
|
|
42
44
|
assert_equal(["name", "age"], @graph.properties)
|
43
45
|
end
|
44
46
|
|
47
|
+
def test_relationship_types
|
48
|
+
@graph = create_sample_graph("foobar")
|
49
|
+
|
50
|
+
actor = Redgraph::Node.new(label: "actor", properties: {"name": "Harrison Ford"})
|
51
|
+
@graph.add_node(actor)
|
52
|
+
film = Redgraph::Node.new(label: "film", properties: {"name": "Star Wars"})
|
53
|
+
@graph.add_node(film)
|
54
|
+
edge = Redgraph::Edge.new(type: "ACTED_IN", src: actor, dest: film)
|
55
|
+
@graph.add_edge(edge)
|
56
|
+
|
57
|
+
assert_equal(["ACTED_IN"], @graph.relationship_types)
|
58
|
+
end
|
59
|
+
|
45
60
|
private
|
46
61
|
|
47
62
|
def create_sample_graph(name)
|
@@ -53,4 +68,15 @@ class GraphTest < Minitest::Test
|
|
53
68
|
)
|
54
69
|
graph
|
55
70
|
end
|
71
|
+
|
72
|
+
# This command is only supported in the latest version
|
73
|
+
def graph_list_supported?
|
74
|
+
@graph.list
|
75
|
+
rescue Redis::CommandError => e
|
76
|
+
if e.message =~ /ERR unknown command `GRAPH.LIST`/
|
77
|
+
false
|
78
|
+
else
|
79
|
+
true
|
80
|
+
end
|
81
|
+
end
|
56
82
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class NodeModelClassMethodsTest < Minitest::Test
|
6
|
+
include TestHelpers
|
7
|
+
|
8
|
+
GRAPH = Redgraph::Graph.new("movies", url: $REDIS_URL)
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@graph = GRAPH
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
@graph.delete
|
16
|
+
end
|
17
|
+
|
18
|
+
# test classes
|
19
|
+
#
|
20
|
+
|
21
|
+
class Actor
|
22
|
+
include Redgraph::NodeModel
|
23
|
+
self.graph = GRAPH
|
24
|
+
attribute :name
|
25
|
+
end
|
26
|
+
|
27
|
+
class MissingGraphActor
|
28
|
+
include Redgraph::NodeModel
|
29
|
+
attribute :name
|
30
|
+
end
|
31
|
+
|
32
|
+
# tests
|
33
|
+
#
|
34
|
+
|
35
|
+
def test_count
|
36
|
+
quick_add_node(label: 'actor', properties: {_type: Actor.name, name: "Al Pacino"})
|
37
|
+
quick_add_node(label: 'actor', properties: {_type: Actor.name, name: "John Travolta"})
|
38
|
+
assert_equal(2, Actor.count)
|
39
|
+
assert_equal(1, Actor.count(properties: {name: "Al Pacino"}))
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_all
|
43
|
+
al = Actor.new(name: "Al Pacino").add_to_graph
|
44
|
+
john = Actor.new(name: "John Travolta").add_to_graph
|
45
|
+
|
46
|
+
items = Actor.all
|
47
|
+
assert_equal(2, items.size)
|
48
|
+
assert_includes(items, al)
|
49
|
+
assert_includes(items, john)
|
50
|
+
|
51
|
+
items = Actor.all(properties: {name: "Al Pacino"})
|
52
|
+
assert_equal(1, items.size)
|
53
|
+
assert_includes(items, al)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_find
|
57
|
+
al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
58
|
+
item = Actor.find(al.id)
|
59
|
+
|
60
|
+
assert_equal(Actor, item.class)
|
61
|
+
assert_predicate(item, :persisted?)
|
62
|
+
assert_equal(al.id, item.id)
|
63
|
+
assert_equal("Al Pacino", item.name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_find_bad_id
|
67
|
+
quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
68
|
+
item = Actor.find("-1")
|
69
|
+
assert_nil(item)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_create
|
73
|
+
assert_equal(0, Actor.count)
|
74
|
+
|
75
|
+
actor = Actor.create(name: "Harrison Ford")
|
76
|
+
|
77
|
+
assert_equal(1, Actor.count)
|
78
|
+
assert_predicate(actor, :persisted?)
|
79
|
+
assert_equal("Harrison Ford", actor.name)
|
80
|
+
assert_equal("actor", actor.label)
|
81
|
+
|
82
|
+
found = Actor.find(actor.id)
|
83
|
+
assert_equal("Harrison Ford", found.name)
|
84
|
+
assert_equal("actor", found.label)
|
85
|
+
assert_equal(Actor.name, found._type)
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_create_with_missing_graph
|
89
|
+
assert_raises(Redgraph::MissingGraphError) do
|
90
|
+
MissingGraphActor.create(name: "Harrison Ford")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class NodeModelLabelsTest < Minitest::Test
|
6
|
+
include TestHelpers
|
7
|
+
|
8
|
+
GRAPH = Redgraph::Graph.new("movies", url: $REDIS_URL)
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@graph = GRAPH
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
@graph.delete
|
16
|
+
end
|
17
|
+
|
18
|
+
# test classes
|
19
|
+
#
|
20
|
+
|
21
|
+
class Actor
|
22
|
+
include Redgraph::NodeModel
|
23
|
+
self.graph = GRAPH
|
24
|
+
attribute :name
|
25
|
+
end
|
26
|
+
|
27
|
+
class Artist
|
28
|
+
include Redgraph::NodeModel
|
29
|
+
self.label = "_artist"
|
30
|
+
end
|
31
|
+
|
32
|
+
class Painter < Artist
|
33
|
+
end
|
34
|
+
|
35
|
+
class Pianist < Artist
|
36
|
+
self.label = "pianist"
|
37
|
+
end
|
38
|
+
|
39
|
+
# tests
|
40
|
+
#
|
41
|
+
|
42
|
+
def test_label
|
43
|
+
assert_equal("actor", Actor.label)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_custom_label
|
47
|
+
assert_equal("_artist", Artist.label)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_default_label_when_inherited
|
51
|
+
assert_equal("painter", Painter.label)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_custom_label_when_inherited
|
55
|
+
assert_equal("pianist", Pianist.label)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class NodeModelPersistenceTest < Minitest::Test
|
6
|
+
include TestHelpers
|
7
|
+
|
8
|
+
GRAPH = Redgraph::Graph.new("movies", url: $REDIS_URL)
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@graph = GRAPH
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
@graph.delete
|
16
|
+
end
|
17
|
+
|
18
|
+
# test classes
|
19
|
+
#
|
20
|
+
|
21
|
+
class Film
|
22
|
+
include Redgraph::NodeModel
|
23
|
+
self.graph = GRAPH
|
24
|
+
attribute :name
|
25
|
+
end
|
26
|
+
|
27
|
+
# tests
|
28
|
+
#
|
29
|
+
|
30
|
+
def test_save_new
|
31
|
+
assert_equal(0, Film.count)
|
32
|
+
|
33
|
+
film = Film.new(name: "Star Wars")
|
34
|
+
film.save
|
35
|
+
|
36
|
+
assert_predicate(film, :persisted?)
|
37
|
+
assert_equal(1, Film.count)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_save_existing
|
41
|
+
film = Film.create(name: "Star Wars")
|
42
|
+
film.name = "Commando"
|
43
|
+
film.save
|
44
|
+
|
45
|
+
assert_equal(1, Film.count)
|
46
|
+
item = Film.find(film.id)
|
47
|
+
assert_equal("Commando", item.name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_reload
|
51
|
+
film = Film.create(name: "Star Wars")
|
52
|
+
copy = Film.find(film.id)
|
53
|
+
|
54
|
+
assert_equal("Star Wars", copy.name)
|
55
|
+
|
56
|
+
film.name = "Commando"
|
57
|
+
film.save
|
58
|
+
|
59
|
+
assert_equal("Star Wars", copy.name)
|
60
|
+
assert_equal("Commando", copy.reload.name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_destroy
|
64
|
+
film = Film.create(name: "Star Wars")
|
65
|
+
assert_equal(1, Film.count)
|
66
|
+
film.destroy
|
67
|
+
assert_predicate(film, :destroyed?)
|
68
|
+
assert_equal(0, Film.count)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class NodeModelTest < Minitest::Test
|
6
|
+
include TestHelpers
|
7
|
+
|
8
|
+
GRAPH = Redgraph::Graph.new("movies", url: $REDIS_URL)
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@graph = GRAPH
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
@graph.delete
|
16
|
+
end
|
17
|
+
|
18
|
+
# test classes
|
19
|
+
#
|
20
|
+
|
21
|
+
class Animal
|
22
|
+
include Redgraph::NodeModel
|
23
|
+
attribute :name
|
24
|
+
self.graph = "pippo"
|
25
|
+
end
|
26
|
+
|
27
|
+
class Dog < Animal
|
28
|
+
end
|
29
|
+
|
30
|
+
class Actor
|
31
|
+
include Redgraph::NodeModel
|
32
|
+
self.graph = GRAPH
|
33
|
+
attribute :name
|
34
|
+
end
|
35
|
+
|
36
|
+
class Film
|
37
|
+
include Redgraph::NodeModel
|
38
|
+
self.graph = GRAPH
|
39
|
+
attribute :name
|
40
|
+
attribute :year
|
41
|
+
end
|
42
|
+
|
43
|
+
# tests
|
44
|
+
#
|
45
|
+
|
46
|
+
def test_graph_accessor
|
47
|
+
assert_equal("pippo", Animal.graph)
|
48
|
+
assert_equal("pippo", Animal.new.graph)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_class_inheritance
|
52
|
+
assert_equal("pippo", Dog.graph)
|
53
|
+
assert_equal("dog", Dog.label)
|
54
|
+
assert_equal([:id, :name], Dog.attribute_names)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_attribute_names
|
58
|
+
assert_equal([:id, :name, :year], Film.attribute_names)
|
59
|
+
|
60
|
+
film = Film.new(name: "Star Wars", year: 1977)
|
61
|
+
assert_equal("Star Wars", film.name)
|
62
|
+
assert_equal(1977, film.year)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_attributes
|
66
|
+
film = Film.new(name: "Star Wars", year: 1977)
|
67
|
+
assert_equal({id: nil, name: "Star Wars", year: 1977}, film.attributes)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_add_to_graph
|
71
|
+
assert_equal(0, Film.count)
|
72
|
+
film = Film.new(name: "Star Wars", year: 1977)
|
73
|
+
item = film.add_to_graph
|
74
|
+
assert_predicate(item, :persisted?)
|
75
|
+
|
76
|
+
assert_equal(1, Film.count)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_merge_into_graph
|
80
|
+
film = Film.new(name: "Star Wars", year: 1977)
|
81
|
+
item = film.add_to_graph(allow_duplicates: false)
|
82
|
+
assert_predicate(item, :persisted?)
|
83
|
+
|
84
|
+
assert_equal(1, Film.count)
|
85
|
+
|
86
|
+
film = Film.new(name: "Star Wars", year: 1977)
|
87
|
+
item = film.add_to_graph(allow_duplicates: false)
|
88
|
+
assert_predicate(item, :persisted?)
|
89
|
+
|
90
|
+
assert_equal(1, Film.count)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_add_relation
|
94
|
+
film = Film.new(name: "Star Wars", year: 1977).add_to_graph
|
95
|
+
actor = Actor.new(name: "Harrison Ford").add_to_graph
|
96
|
+
edge = actor.add_relation(type: "ACTED_IN", node: film, properties: {role: "Han Solo"})
|
97
|
+
|
98
|
+
assert_predicate(edge, :persisted?)
|
99
|
+
assert_equal("ACTED_IN", edge.type)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_add_relation_with_duplicate_control
|
103
|
+
film = Film.new(name: "Star Wars", year: 1977).add_to_graph
|
104
|
+
actor = Actor.new(name: "Harrison Ford").add_to_graph
|
105
|
+
|
106
|
+
actor.add_relation(type: "ACTED_IN", node: film, properties: {role: "Han Solo"}, allow_duplicates: true)
|
107
|
+
assert_equal(1, @graph.count_edges)
|
108
|
+
|
109
|
+
actor.add_relation(type: "ACTED_IN", node: film, properties: {role: "Han Solo"}, allow_duplicates: true)
|
110
|
+
assert_equal(2, @graph.count_edges)
|
111
|
+
|
112
|
+
actor.add_relation(type: "ACTED_IN", node: film, properties: {role: "Han Solo"}, allow_duplicates: false)
|
113
|
+
assert_equal(2, @graph.count_edges)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_casting_query
|
117
|
+
Film.new(name: "Star Wars", year: 1977).add_to_graph
|
118
|
+
Actor.new(name: "Harrison Ford").add_to_graph
|
119
|
+
|
120
|
+
items = Film.query("MATCH (node) RETURN node ORDER BY node.name")
|
121
|
+
assert_equal(2, items.size)
|
122
|
+
assert_kind_of(Actor, items[0][0])
|
123
|
+
assert_kind_of(Film, items[1][0])
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_casting_query_from_model
|
127
|
+
film = Film.create(name: "Star Wars", year: 1977)
|
128
|
+
Actor.create(name: "Harrison Ford")
|
129
|
+
|
130
|
+
items = film.query("MATCH (node) RETURN node ORDER BY node.name")
|
131
|
+
assert_equal(2, items.size)
|
132
|
+
assert_kind_of(Actor, items[0][0])
|
133
|
+
assert_kind_of(Film, items[1][0])
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|