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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7866e9db857e11a68ddbcf8eef90b839ddc2e6fac110edfc87bfec3dd54edec7
4
- data.tar.gz: 1c911439cd1f3f815db5a42f348e3a67449ef9aae128b4f48f9ee36934d2874d
3
+ metadata.gz: a5a64b5cffa36368ac0664e813ab61f0329a055237b7e4acd473820d9c73409d
4
+ data.tar.gz: 371cde906c0a80370e39763a9b3ba9f3fcb83cdd2c19e97f635bfea6e35c5540
5
5
  SHA512:
6
- metadata.gz: 78a668833d8a16ba61fca88f0b58e4d6ebac5e33b68e6698c03649c5fb66bb13ff33437ac7bcb9265792059d745999148dfee47d85d5d1ae96265f650ef5f068
7
- data.tar.gz: 85a15bfdb886959114ccb13eed27adc9b3e83e7a77783be1d5cf65d4c33c40a27501e34d5a5af82350a3c43e08178d159e7732da3b9ad86f4944930751a5e8f9
6
+ metadata.gz: ac0481573ed2d41bb20de03ab1dc36d6781f5a121a865358e296654dc09abe168868104df92deeb623594b6d68363a0c9333268a059c7b8caf362504039e1431
7
+ data.tar.gz: dcb4c293c0a1003926d8776930123c81a5d35981a0896d447a03cc8ee1082f837ce1d73a1f7ca664478980fb485eefb39dad120f703b4584d40ffd6a3ff509c2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.2.0]
2
+
3
+ - revamp the NodeModel mixin, the Node to model mapping is now handled by the `_type` property
4
+
1
5
  ## [0.1.4]
2
6
 
3
7
  - add NodeModel mixin for a basic ActiveRecord-like syntax
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- redgraph (0.1.4)
4
+ redgraph (0.2.0)
5
5
  activesupport (>= 3.0.0)
6
6
  redis (~> 4)
7
7
 
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
- query(cmd).flatten[0]
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
@@ -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
- Redgraph::Node.new(id: id, label: label, properties: attributes.except(:id))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Redgraph
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -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
@@ -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.1.4
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-15 00:00:00.000000000 Z
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