redgraph 0.1.4 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +8 -0
- data/lib/redgraph/graph/node_methods.rb +3 -1
- data/lib/redgraph/node_model.rb +15 -52
- data/lib/redgraph/node_model/class_methods.rb +76 -0
- data/lib/redgraph/version.rb +1 -1
- data/test/node_model_class_methods_test.rb +66 -0
- data/test/node_model_labels_test.rb +57 -0
- data/test/node_model_test.rb +27 -61
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5a64b5cffa36368ac0664e813ab61f0329a055237b7e4acd473820d9c73409d
|
4
|
+
data.tar.gz: 371cde906c0a80370e39763a9b3ba9f3fcb83cdd2c19e97f635bfea6e35c5540
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac0481573ed2d41bb20de03ab1dc36d6781f5a121a865358e296654dc09abe168868104df92deeb623594b6d68363a0c9333268a059c7b8caf362504039e1431
|
7
|
+
data.tar.gz: dcb4c293c0a1003926d8776930123c81a5d35981a0896d447a03cc8ee1082f837ce1d73a1f7ca664478980fb485eefb39dad120f703b4584d40ffd6a3ff509c2
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -104,6 +104,14 @@ And this will give you stuff such as
|
|
104
104
|
john.add_to_graph # Will add the node to the graph
|
105
105
|
john.add_relation(type: "ACTED_IN", node: film, properties: {role: "Tony Manero"})
|
106
106
|
|
107
|
+
`NodeModel` models will automatically set a `_type` property to keep track of the object class.
|
108
|
+
|
109
|
+
You will then be able to run custom queries such as:
|
110
|
+
|
111
|
+
Actor.query("MATCH (node) RETURN node ORDER BY node.name")
|
112
|
+
|
113
|
+
And the result rows object will be instances of the classes defined by the `_type` attribute.
|
114
|
+
|
107
115
|
## Development
|
108
116
|
|
109
117
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
@@ -68,7 +68,9 @@ module Redgraph
|
|
68
68
|
node = Node.new(label: label, properties: properties)
|
69
69
|
|
70
70
|
cmd = "MATCH #{node.to_query_string} RETURN COUNT(node)"
|
71
|
-
|
71
|
+
# RedisGraph bug: if there are no matches COUNT returns zero rows
|
72
|
+
# https://github.com/RedisGraph/RedisGraph/issues/1455
|
73
|
+
query(cmd).flatten[0] || 0
|
72
74
|
end
|
73
75
|
|
74
76
|
private
|
data/lib/redgraph/node_model.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require_relative 'node_model/class_methods'
|
2
3
|
|
3
4
|
module Redgraph
|
4
5
|
# This mixin allows you to use an interface similar to ActiveRecord
|
@@ -16,13 +17,17 @@ module Redgraph
|
|
16
17
|
# john = Actor.find(123)
|
17
18
|
# total = Actor.count
|
18
19
|
#
|
20
|
+
# When you create a record it will automatically set the _type property with the class name.
|
21
|
+
# This allows reifying the node into the corresponding NodeModel class.
|
22
|
+
#
|
19
23
|
module NodeModel
|
20
24
|
extend ActiveSupport::Concern
|
21
25
|
|
22
|
-
included do
|
26
|
+
included do |base|
|
23
27
|
@attribute_names = [:id]
|
24
28
|
|
25
29
|
attr_accessor :id
|
30
|
+
attr_accessor :_type
|
26
31
|
|
27
32
|
class << self
|
28
33
|
attr_reader :attribute_names
|
@@ -43,57 +48,8 @@ module Redgraph
|
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
46
|
-
class_methods do
|
47
|
-
# Returns an array of nodes. Options:
|
48
|
-
#
|
49
|
-
# - properties: filter by properties
|
50
|
-
# - order: node.name ASC, node.year DESC
|
51
|
-
# - limit: number of items
|
52
|
-
# - skip: items offset (useful for pagination)
|
53
|
-
#
|
54
|
-
def all(properties: nil, limit: nil, skip: nil, order: nil)
|
55
|
-
graph.nodes(label: label, properties: properties,
|
56
|
-
limit: limit, skip: skip, order: nil).map do |node|
|
57
|
-
new(id: node.id, **node.properties)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# Returns the number of nodes with the current label. Options:
|
62
|
-
#
|
63
|
-
# - properties: filter by properties
|
64
|
-
#
|
65
|
-
def count(properties: nil)
|
66
|
-
graph.count_nodes(label: label, properties: properties)
|
67
|
-
end
|
68
|
-
|
69
|
-
# Finds a node by id. Returns nil if not found
|
70
|
-
#
|
71
|
-
def find(id)
|
72
|
-
node = graph.find_node_by_id(id)
|
73
|
-
return unless node
|
74
|
-
new(id: node.id, **node.properties)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Sets the label for this class of nodes. If missing it will be computed from the class name
|
78
|
-
def label=(x)
|
79
|
-
@label = x
|
80
|
-
end
|
81
|
-
|
82
|
-
# Current label
|
83
|
-
#
|
84
|
-
def label
|
85
|
-
@label ||= default_label
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def default_label
|
91
|
-
name.demodulize.underscore
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
51
|
def initialize(**args)
|
96
|
-
absent_attributes = args.keys.map(&:to_sym) - self.class.attribute_names
|
52
|
+
absent_attributes = args.keys.map(&:to_sym) - self.class.attribute_names - [:_type]
|
97
53
|
|
98
54
|
if absent_attributes.any?
|
99
55
|
raise ArgumentError, "Unknown attribute #{absent_attributes}"
|
@@ -149,7 +105,14 @@ module Redgraph
|
|
149
105
|
end
|
150
106
|
|
151
107
|
def to_node
|
152
|
-
|
108
|
+
props = attributes.except(:id).merge(_type: self.class.name)
|
109
|
+
Redgraph::Node.new(id: id, label: label, properties: props)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Converts a Node object into NodeModel
|
113
|
+
#
|
114
|
+
def reify_from_node(node)
|
115
|
+
self.class.reify_from_node(node)
|
153
116
|
end
|
154
117
|
|
155
118
|
def ==(other)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Redgraph
|
2
|
+
module NodeModel
|
3
|
+
module ClassMethods
|
4
|
+
# Returns an array of nodes. Options:
|
5
|
+
#
|
6
|
+
# - properties: filter by properties
|
7
|
+
# - order: node.name ASC, node.year DESC
|
8
|
+
# - limit: number of items
|
9
|
+
# - skip: items offset (useful for pagination)
|
10
|
+
#
|
11
|
+
def all(properties: {}, limit: nil, skip: nil, order: nil)
|
12
|
+
graph.nodes(label: label, properties: properties_plus_type(properties),
|
13
|
+
limit: limit, skip: skip, order: nil).map do |node|
|
14
|
+
reify_from_node(node)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the number of nodes with the current label. Options:
|
19
|
+
#
|
20
|
+
# - properties: filter by properties
|
21
|
+
#
|
22
|
+
def count(properties: nil)
|
23
|
+
graph.count_nodes(label: label, properties: properties_plus_type(properties))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finds a node by id. Returns nil if not found
|
27
|
+
#
|
28
|
+
def find(id)
|
29
|
+
node = graph.find_node_by_id(id)
|
30
|
+
return unless node
|
31
|
+
reify_from_node(node)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the label for this class of nodes. If missing it will be computed from the class name
|
35
|
+
def label=(x)
|
36
|
+
@label = x
|
37
|
+
end
|
38
|
+
|
39
|
+
# Current label
|
40
|
+
#
|
41
|
+
def label
|
42
|
+
@label ||= default_label
|
43
|
+
end
|
44
|
+
|
45
|
+
# Converts a Node object into NodeModel
|
46
|
+
#
|
47
|
+
def reify_from_node(node)
|
48
|
+
klass = node.properties[:_type].to_s.safe_constantize || self
|
49
|
+
klass.new(id: node.id, **node.properties)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Runs a query on the graph, but converts the nodes to the corresponding ActiveModel class
|
53
|
+
# if available - otherwise they stay NodeObjects.
|
54
|
+
#
|
55
|
+
# Returns an array of rows.
|
56
|
+
#
|
57
|
+
def query(cmd)
|
58
|
+
graph.query(cmd).map do |row|
|
59
|
+
row.map do |item|
|
60
|
+
item.is_a?(Node) ? reify_from_node(item) : item
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def default_label
|
68
|
+
name.demodulize.underscore
|
69
|
+
end
|
70
|
+
|
71
|
+
def properties_plus_type(properties = {})
|
72
|
+
{_type: name}.merge(properties || {})
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/redgraph/version.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
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
|
+
# tests
|
28
|
+
#
|
29
|
+
|
30
|
+
def test_count
|
31
|
+
quick_add_node(label: 'actor', properties: {_type: Actor.name, name: "Al Pacino"})
|
32
|
+
quick_add_node(label: 'actor', properties: {_type: Actor.name, name: "John Travolta"})
|
33
|
+
assert_equal(2, Actor.count)
|
34
|
+
assert_equal(1, Actor.count(properties: {name: "Al Pacino"}))
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_all
|
38
|
+
al = Actor.new(name: "Al Pacino").add_to_graph
|
39
|
+
john = Actor.new(name: "John Travolta").add_to_graph
|
40
|
+
|
41
|
+
items = Actor.all
|
42
|
+
assert_equal(2, items.size)
|
43
|
+
assert_includes(items, al)
|
44
|
+
assert_includes(items, john)
|
45
|
+
|
46
|
+
items = Actor.all(properties: {name: "Al Pacino"})
|
47
|
+
assert_equal(1, items.size)
|
48
|
+
assert_includes(items, al)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_find
|
52
|
+
al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
53
|
+
item = Actor.find(al.id)
|
54
|
+
|
55
|
+
assert_equal(Actor, item.class)
|
56
|
+
assert_predicate(item, :persisted?)
|
57
|
+
assert_equal(al.id, item.id)
|
58
|
+
assert_equal("Al Pacino", item.name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_find_bad_id
|
62
|
+
quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
63
|
+
item = Actor.find("-1")
|
64
|
+
assert_nil(item)
|
65
|
+
end
|
66
|
+
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
|
data/test/node_model_test.rb
CHANGED
@@ -15,6 +15,9 @@ class NodeModelTest < Minitest::Test
|
|
15
15
|
@graph.delete
|
16
16
|
end
|
17
17
|
|
18
|
+
# test classes
|
19
|
+
#
|
20
|
+
|
18
21
|
class Animal
|
19
22
|
include Redgraph::NodeModel
|
20
23
|
attribute :name
|
@@ -24,73 +27,12 @@ class NodeModelTest < Minitest::Test
|
|
24
27
|
class Dog < Animal
|
25
28
|
end
|
26
29
|
|
27
|
-
def test_graph_accessor
|
28
|
-
assert_equal("pippo", Animal.graph)
|
29
|
-
assert_equal("pippo", Animal.new.graph)
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_class_inheritance
|
33
|
-
assert_equal("pippo", Dog.graph)
|
34
|
-
assert_equal("dog", Dog.label)
|
35
|
-
assert_equal([:id, :name], Dog.attribute_names)
|
36
|
-
end
|
37
|
-
|
38
30
|
class Actor
|
39
31
|
include Redgraph::NodeModel
|
40
32
|
self.graph = GRAPH
|
41
33
|
attribute :name
|
42
34
|
end
|
43
35
|
|
44
|
-
def test_count
|
45
|
-
quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
46
|
-
quick_add_node(label: 'actor', properties: {name: "John Travolta"})
|
47
|
-
assert_equal(2, Actor.count)
|
48
|
-
assert_equal(1, Actor.count(properties: {name: "Al Pacino"}))
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_all
|
52
|
-
al = Actor.new(name: "Al Pacino").add_to_graph
|
53
|
-
john = Actor.new(name: "John Travolta").add_to_graph
|
54
|
-
|
55
|
-
items = Actor.all
|
56
|
-
assert_equal(2, items.size)
|
57
|
-
assert_includes(items, al)
|
58
|
-
assert_includes(items, john)
|
59
|
-
|
60
|
-
items = Actor.all(properties: {name: "Al Pacino"})
|
61
|
-
assert_equal(1, items.size)
|
62
|
-
assert_includes(items, al)
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_find
|
66
|
-
al = quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
67
|
-
item = Actor.find(al.id)
|
68
|
-
|
69
|
-
assert_equal(Actor, item.class)
|
70
|
-
assert_predicate(item, :persisted?)
|
71
|
-
assert_equal(al.id, item.id)
|
72
|
-
assert_equal("Al Pacino", item.name)
|
73
|
-
end
|
74
|
-
|
75
|
-
def test_find_bad_id
|
76
|
-
quick_add_node(label: 'actor', properties: {name: "Al Pacino"})
|
77
|
-
item = Actor.find("-1")
|
78
|
-
assert_nil(item)
|
79
|
-
end
|
80
|
-
|
81
|
-
def test_label
|
82
|
-
assert_equal("actor", Actor.label)
|
83
|
-
end
|
84
|
-
|
85
|
-
class Artist
|
86
|
-
include Redgraph::NodeModel
|
87
|
-
self.label = "person"
|
88
|
-
end
|
89
|
-
|
90
|
-
def test_custom_label
|
91
|
-
assert_equal("person", Artist.label)
|
92
|
-
end
|
93
|
-
|
94
36
|
class Film
|
95
37
|
include Redgraph::NodeModel
|
96
38
|
self.graph = GRAPH
|
@@ -98,6 +40,20 @@ class NodeModelTest < Minitest::Test
|
|
98
40
|
attribute :year
|
99
41
|
end
|
100
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
|
+
|
101
57
|
def test_attribute_names
|
102
58
|
assert_equal([:id, :name, :year], Film.attribute_names)
|
103
59
|
|
@@ -157,4 +113,14 @@ class NodeModelTest < Minitest::Test
|
|
157
113
|
assert_equal(2, @graph.count_edges)
|
158
114
|
end
|
159
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
|
+
|
160
126
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redgraph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paolo Zaccagnini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- lib/redgraph/graph/node_methods.rb
|
63
63
|
- lib/redgraph/node.rb
|
64
64
|
- lib/redgraph/node_model.rb
|
65
|
+
- lib/redgraph/node_model/class_methods.rb
|
65
66
|
- lib/redgraph/query_response.rb
|
66
67
|
- lib/redgraph/util.rb
|
67
68
|
- lib/redgraph/version.rb
|
@@ -72,6 +73,8 @@ files:
|
|
72
73
|
- test/graph_node_methods_test.rb
|
73
74
|
- test/graph_queries_test.rb
|
74
75
|
- test/graph_test.rb
|
76
|
+
- test/node_model_class_methods_test.rb
|
77
|
+
- test/node_model_labels_test.rb
|
75
78
|
- test/node_model_test.rb
|
76
79
|
- test/redgraph_test.rb
|
77
80
|
- test/test_helper.rb
|