neography 0.0.6 → 0.0.7
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.
- data/Gemfile.lock +1 -1
- data/README.rdoc +23 -1
- data/examples/facebook.rb +1 -0
- data/examples/facebook_v2.rb +26 -0
- data/examples/linkedin.rb +1 -0
- data/examples/linkedin_v2.rb +23 -0
- data/lib/neography.rb +2 -1
- data/lib/neography/node.rb +1 -0
- data/lib/neography/node_path.rb +29 -0
- data/lib/neography/node_traverser.rb +10 -0
- data/lib/neography/path_traverser.rb +94 -0
- data/lib/neography/version.rb +1 -1
- data/spec/integration/node_path_spec.rb +222 -0
- metadata +8 -3
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -10,12 +10,23 @@ If you want to the full power of Neo4j, you will want to use JRuby and the excel
|
|
10
10
|
|
11
11
|
gem install 'neography'
|
12
12
|
|
13
|
-
After that, in your ruby script
|
13
|
+
After that, in your ruby script:
|
14
14
|
|
15
15
|
require 'rubygems'
|
16
16
|
require 'neography'
|
17
17
|
|
18
18
|
in order to access the functionality.
|
19
|
+
|
20
|
+
=== Try it now!
|
21
|
+
|
22
|
+
I am hosting an instance of Neo4j (1.2 M5) at neography.org for you to try out.
|
23
|
+
|
24
|
+
You can see the administration at: {Neo4j Web Admin}[http://neography.org]
|
25
|
+
|
26
|
+
# Add this to your ruby file to use it
|
27
|
+
Neography::Config.server = 'neography.org'
|
28
|
+
|
29
|
+
|
19
30
|
=== Dependencies
|
20
31
|
|
21
32
|
for use:
|
@@ -197,6 +208,17 @@ The Neo4j ID is available by using node.neo_id .
|
|
197
208
|
n1.rels(:friends).incoming # Get incoming friends relationships
|
198
209
|
n1.rels(:friends,:work) # Get friends and work relationships
|
199
210
|
n1.rels(:friends,:work).outgoing # Get outgoing friends and work relationships
|
211
|
+
|
212
|
+
n1.all_paths_to(n2).incoming(:friends).depth(4) # Gets all paths of a specified type
|
213
|
+
n1.all_simple_paths_to(n2).incoming(:friends).depth(4) # for the relationships defined
|
214
|
+
n1.all_shortest_paths_to(n2).incoming(:friends).depth(4) # at a maximum depth
|
215
|
+
n1.path_to(n2).incoming(:friends).depth(4) # Same as above, but just one path.
|
216
|
+
n1.simple_path_to(n2).incoming(:friends).depth(4)
|
217
|
+
n1.shortest_path_to(n2).incoming(:friends).depth(4)
|
218
|
+
|
219
|
+
n1.shortest_path_to(n2).incoming(:friends).depth(4).rels # Gets just relationships in path
|
220
|
+
n1.shortest_path_to(n2).incoming(:friends).depth(4).nodes # Gets just nodes in path
|
221
|
+
|
200
222
|
|
201
223
|
See Neo4j API for:
|
202
224
|
* {Order}[http://components.neo4j.org/neo4j-examples/1.2.M04/apidocs/org/neo4j/graphdb/Traverser.Order.html]
|
data/examples/facebook.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'neography'
|
3
|
+
|
4
|
+
Neography::Config.server = 'neography.org'
|
5
|
+
@neo = Neography::Rest.new
|
6
|
+
|
7
|
+
def suggestions_for(node)
|
8
|
+
node.incoming(:friends).order("breadth first").uniqueness("node global").filter("position.length() == 2;").depth(2)
|
9
|
+
end
|
10
|
+
|
11
|
+
johnathan = Neography::Node.create("name" =>'Johnathan')
|
12
|
+
mark = Neography::Node.create("name" =>'Mark')
|
13
|
+
phill = Neography::Node.create("name" =>'Phill')
|
14
|
+
mary = Neography::Node.create("name" =>'Mary')
|
15
|
+
luke = Neography::Node.create("name" =>'Luke')
|
16
|
+
|
17
|
+
johnathan.both(:friends) << mark
|
18
|
+
mark.both(:friends) << mary
|
19
|
+
mark.both(:friends) << phill
|
20
|
+
phill.both(:friends) << mary
|
21
|
+
phill.both(:friends) << luke
|
22
|
+
|
23
|
+
puts "Johnathan should become friends with #{suggestions_for(johnathan).map{|n| n.name }.join(', ')}"
|
24
|
+
|
25
|
+
# RESULT
|
26
|
+
# Johnathan should become friends with Mary, Phill
|
data/examples/linkedin.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'neography'
|
3
|
+
|
4
|
+
Neography::Config.server = 'neography.org'
|
5
|
+
@neo = Neography::Rest.new
|
6
|
+
|
7
|
+
johnathan = Neography::Node.create("name" =>'Johnathan')
|
8
|
+
mark = Neography::Node.create("name" =>'Mark')
|
9
|
+
phill = Neography::Node.create("name" =>'Phill')
|
10
|
+
mary = Neography::Node.create("name" =>'Mary')
|
11
|
+
|
12
|
+
johnathan.both(:friends) << mark
|
13
|
+
mark.both(:friends) << phill
|
14
|
+
phill.both(:friends) << mary
|
15
|
+
mark.both(:friends) << mary
|
16
|
+
|
17
|
+
johnathan.all_simple_paths_to(mary).incoming(:friends).depth(4).nodes.each do |node|
|
18
|
+
puts node.map{|n| n.name }.join(' => friends => ')
|
19
|
+
end
|
20
|
+
|
21
|
+
# RESULT
|
22
|
+
# Johnathan => friends => Mark => friends => Phill => friends => Mary
|
23
|
+
# Johnathan => friends => Mark => friends => Mary
|
data/lib/neography.rb
CHANGED
@@ -29,11 +29,12 @@ require 'neography/neography'
|
|
29
29
|
require 'neography/property_container'
|
30
30
|
require 'neography/property'
|
31
31
|
require 'neography/node_relationship'
|
32
|
+
require 'neography/node_path'
|
32
33
|
require 'neography/relationship_traverser'
|
33
34
|
require 'neography/node_traverser'
|
35
|
+
require 'neography/path_traverser'
|
34
36
|
require 'neography/equal'
|
35
37
|
|
36
|
-
|
37
38
|
require 'neography/node'
|
38
39
|
require 'neography/relationship'
|
39
40
|
|
data/lib/neography/node.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Neography
|
2
|
+
module NodePath
|
3
|
+
|
4
|
+
def all_paths_to(to)
|
5
|
+
PathTraverser.new(self, to, "allPaths", true)
|
6
|
+
end
|
7
|
+
|
8
|
+
def all_simple_paths_to(to)
|
9
|
+
PathTraverser.new(self, to, "allSimplePaths", true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def all_shortest_paths_to(to)
|
13
|
+
PathTraverser.new(self, to, "shortestPath", true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def path_to(to)
|
17
|
+
PathTraverser.new(self, to, "allPaths", false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def simple_path_to(to)
|
21
|
+
PathTraverser.new(self, to, "allSimplePaths", false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def shortest_path_to(to)
|
25
|
+
PathTraverser.new(self, to, "shortestPath", false)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Neography
|
2
|
+
class PathTraverser
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :depth, :algorithm, :relationships, :get
|
6
|
+
|
7
|
+
def initialize(from, to, algorithm, all=false, types = nil, dir = "all" )
|
8
|
+
@from = from
|
9
|
+
@to = to
|
10
|
+
@algorithm = algorithm
|
11
|
+
@all = all
|
12
|
+
@relationships = Array.new
|
13
|
+
types.each do |type|
|
14
|
+
@relationships << {"type" => type.to_s, "direction" => dir.to_s }
|
15
|
+
end unless types.nil?
|
16
|
+
@get = ["node","rel"]
|
17
|
+
@loaded = Array.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def nodes
|
21
|
+
@get = ["node"]
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def relationships
|
26
|
+
@get = ["rel"]
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :rels, :relationships
|
31
|
+
|
32
|
+
def both(type)
|
33
|
+
@relationships << {"type" => type.to_s, "direction" => "all"}
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def outgoing(type)
|
38
|
+
@relationships << {"type" => type.to_s, "direction" => "out"}
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def incoming(type)
|
43
|
+
@relationships << {"type" => type.to_s, "direction" => "in"}
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def depth(d)
|
48
|
+
d = 2147483647 if d == :all
|
49
|
+
@depth = d
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def size
|
54
|
+
[*self].size
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :length, :size
|
58
|
+
|
59
|
+
def each
|
60
|
+
iterator.each do |path|
|
61
|
+
paths = Array.new
|
62
|
+
|
63
|
+
if @get.include?("node")
|
64
|
+
path["nodes"].each_with_index do |n, i|
|
65
|
+
@loaded[n.split('/').last.to_i] = Neography::Node.load(n) if @loaded.at(n.split('/').last.to_i).nil?
|
66
|
+
paths[i * 2] = @loaded[n.split('/').last.to_i]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
if @get.include?("rel")
|
71
|
+
path["relationships"].each_with_index do |r, i|
|
72
|
+
@loaded[r.split('/').last.to_i] = Neography::Relationship.load(r) if @loaded.at(r.split('/').last.to_i).nil?
|
73
|
+
paths[i * 2 + 1] = @loaded[r.split('/').last.to_i]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
yield paths.compact
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def empty?
|
82
|
+
first == nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def iterator
|
86
|
+
if @all.nil?
|
87
|
+
@from.neo_server.get_path(@from, @to, @relationships, @depth, @algorithm)
|
88
|
+
else
|
89
|
+
@from.neo_server.get_paths(@from, @to, @relationships, @depth, @algorithm)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
data/lib/neography/version.rb
CHANGED
@@ -0,0 +1,222 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Neography::NodePath do
|
4
|
+
|
5
|
+
def create_nodes
|
6
|
+
johnathan = Neography::Node.create("name" =>'Johnathan')
|
7
|
+
mark = Neography::Node.create("name" =>'Mark')
|
8
|
+
phill = Neography::Node.create("name" =>'Phill')
|
9
|
+
mary = Neography::Node.create("name" =>'Mary')
|
10
|
+
|
11
|
+
johnathan.both(:friends) << mark
|
12
|
+
mark.both(:friends) << mary
|
13
|
+
mark.both(:friends) << phill
|
14
|
+
phill.both(:friends) << mary
|
15
|
+
|
16
|
+
[johnathan, mark, phill, mary]
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "all_paths" do
|
20
|
+
it "can return nodes" do
|
21
|
+
johnathan, mark, phill, mary = create_nodes
|
22
|
+
|
23
|
+
johnathan.all_paths_to(mary).incoming(:friends).depth(4).nodes.each do |path|
|
24
|
+
path.map{|n| n.is_a?(Neography::Node).should be_true}
|
25
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_false}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can return relationships" do
|
30
|
+
johnathan, mark, phill, mary = create_nodes
|
31
|
+
|
32
|
+
johnathan.all_paths_to(mary).incoming(:friends).depth(4).rels.each do |path|
|
33
|
+
path.map{|n| n.is_a?(Neography::Node).should be_false}
|
34
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_true}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "can return both" do
|
39
|
+
johnathan, mark, phill, mary = create_nodes
|
40
|
+
|
41
|
+
johnathan.all_paths_to(mary).incoming(:friends).depth(4).each do |path|
|
42
|
+
path.each_with_index do |n,i|
|
43
|
+
if i.even?
|
44
|
+
n.is_a?(Neography::Node).should be_true
|
45
|
+
else
|
46
|
+
n.is_a?(Neography::Relationship).should be_true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "all_simple_paths" do
|
54
|
+
it "can return nodes" do
|
55
|
+
johnathan, mark, phill, mary = create_nodes
|
56
|
+
|
57
|
+
johnathan.all_simple_paths_to(mary).incoming(:friends).depth(4).nodes.each do |path|
|
58
|
+
path.map{|n| n.is_a?(Neography::Node).should be_true}
|
59
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_false}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "can return relationships" do
|
64
|
+
johnathan, mark, phill, mary = create_nodes
|
65
|
+
|
66
|
+
johnathan.all_simple_paths_to(mary).incoming(:friends).depth(4).rels.each do |path|
|
67
|
+
path.map{|n| n.is_a?(Neography::Node).should be_false}
|
68
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_true}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "can return both" do
|
73
|
+
johnathan, mark, phill, mary = create_nodes
|
74
|
+
|
75
|
+
johnathan.all_simple_paths_to(mary).incoming(:friends).depth(4).each do |path|
|
76
|
+
path.each_with_index do |n,i|
|
77
|
+
if i.even?
|
78
|
+
n.is_a?(Neography::Node).should be_true
|
79
|
+
else
|
80
|
+
n.is_a?(Neography::Relationship).should be_true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "all_shortest_paths_to" do
|
88
|
+
it "can return nodes" do
|
89
|
+
johnathan, mark, phill, mary = create_nodes
|
90
|
+
|
91
|
+
johnathan.all_shortest_paths_to(mary).incoming(:friends).depth(4).nodes.each do |path|
|
92
|
+
path.map{|n| n.is_a?(Neography::Node).should be_true}
|
93
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_false}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "can return relationships" do
|
98
|
+
johnathan, mark, phill, mary = create_nodes
|
99
|
+
|
100
|
+
johnathan.all_shortest_paths_to(mary).incoming(:friends).depth(4).rels.each do |path|
|
101
|
+
path.map{|n| n.is_a?(Neography::Node).should be_false}
|
102
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_true}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "can return both" do
|
107
|
+
johnathan, mark, phill, mary = create_nodes
|
108
|
+
|
109
|
+
johnathan.all_shortest_paths_to(mary).incoming(:friends).depth(4).each do |path|
|
110
|
+
path.each_with_index do |n,i|
|
111
|
+
if i.even?
|
112
|
+
n.is_a?(Neography::Node).should be_true
|
113
|
+
else
|
114
|
+
n.is_a?(Neography::Relationship).should be_true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "path_to" do
|
122
|
+
it "can return nodes" do
|
123
|
+
johnathan, mark, phill, mary = create_nodes
|
124
|
+
|
125
|
+
johnathan.path_to(mary).incoming(:friends).depth(4).nodes.each do |path|
|
126
|
+
path.map{|n| n.is_a?(Neography::Node).should be_true}
|
127
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_false}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it "can return relationships" do
|
132
|
+
johnathan, mark, phill, mary = create_nodes
|
133
|
+
|
134
|
+
johnathan.path_to(mary).incoming(:friends).depth(4).rels.each do |path|
|
135
|
+
path.map{|n| n.is_a?(Neography::Node).should be_false}
|
136
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_true}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "can return both" do
|
141
|
+
johnathan, mark, phill, mary = create_nodes
|
142
|
+
|
143
|
+
johnathan.path_to(mary).incoming(:friends).depth(4).each do |path|
|
144
|
+
path.each_with_index do |n,i|
|
145
|
+
if i.even?
|
146
|
+
n.is_a?(Neography::Node).should be_true
|
147
|
+
else
|
148
|
+
n.is_a?(Neography::Relationship).should be_true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "simple_path_to" do
|
156
|
+
it "can return nodes" do
|
157
|
+
johnathan, mark, phill, mary = create_nodes
|
158
|
+
|
159
|
+
johnathan.simple_path_to(mary).incoming(:friends).depth(4).nodes.each do |path|
|
160
|
+
path.map{|n| n.is_a?(Neography::Node).should be_true}
|
161
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_false}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it "can return relationships" do
|
166
|
+
johnathan, mark, phill, mary = create_nodes
|
167
|
+
|
168
|
+
johnathan.simple_path_to(mary).incoming(:friends).depth(4).rels.each do |path|
|
169
|
+
path.map{|n| n.is_a?(Neography::Node).should be_false}
|
170
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_true}
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it "can return both" do
|
175
|
+
johnathan, mark, phill, mary = create_nodes
|
176
|
+
|
177
|
+
johnathan.simple_path_to(mary).incoming(:friends).depth(4).each do |path|
|
178
|
+
path.each_with_index do |n,i|
|
179
|
+
if i.even?
|
180
|
+
n.is_a?(Neography::Node).should be_true
|
181
|
+
else
|
182
|
+
n.is_a?(Neography::Relationship).should be_true
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "shortest_path_to" do
|
190
|
+
it "can return nodes" do
|
191
|
+
johnathan, mark, phill, mary = create_nodes
|
192
|
+
|
193
|
+
johnathan.shortest_path_to(mary).incoming(:friends).depth(4).nodes.each do |path|
|
194
|
+
path.map{|n| n.is_a?(Neography::Node).should be_true}
|
195
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_false}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it "can return relationships" do
|
200
|
+
johnathan, mark, phill, mary = create_nodes
|
201
|
+
|
202
|
+
johnathan.shortest_path_to(mary).incoming(:friends).depth(4).rels.each do |path|
|
203
|
+
path.map{|n| n.is_a?(Neography::Node).should be_false}
|
204
|
+
path.map{|n| n.is_a?(Neography::Relationship).should be_true}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "can return both" do
|
209
|
+
johnathan, mark, phill, mary = create_nodes
|
210
|
+
|
211
|
+
johnathan.shortest_path_to(mary).incoming(:friends).depth(4).each do |path|
|
212
|
+
path.each_with_index do |n,i|
|
213
|
+
if i.even?
|
214
|
+
n.is_a?(Neography::Node).should be_true
|
215
|
+
else
|
216
|
+
n.is_a?(Neography::Relationship).should be_true
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 7
|
9
|
+
version: 0.0.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Max De Marzi
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-12-
|
17
|
+
date: 2010-12-11 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -109,15 +109,19 @@ files:
|
|
109
109
|
- README.rdoc
|
110
110
|
- Rakefile
|
111
111
|
- examples/facebook.rb
|
112
|
+
- examples/facebook_v2.rb
|
112
113
|
- examples/linkedin.rb
|
114
|
+
- examples/linkedin_v2.rb
|
113
115
|
- examples/traversal_example1.rb
|
114
116
|
- lib/neography.rb
|
115
117
|
- lib/neography/config.rb
|
116
118
|
- lib/neography/equal.rb
|
117
119
|
- lib/neography/neography.rb
|
118
120
|
- lib/neography/node.rb
|
121
|
+
- lib/neography/node_path.rb
|
119
122
|
- lib/neography/node_relationship.rb
|
120
123
|
- lib/neography/node_traverser.rb
|
124
|
+
- lib/neography/path_traverser.rb
|
121
125
|
- lib/neography/property.rb
|
122
126
|
- lib/neography/property_container.rb
|
123
127
|
- lib/neography/relationship.rb
|
@@ -126,6 +130,7 @@ files:
|
|
126
130
|
- lib/neography/version.rb
|
127
131
|
- neography.gemspec
|
128
132
|
- spec/integration/neography_spec.rb
|
133
|
+
- spec/integration/node_path_spec.rb
|
129
134
|
- spec/integration/node_relationship_spec.rb
|
130
135
|
- spec/integration/node_spec.rb
|
131
136
|
- spec/integration/relationship_spec.rb
|