neo4r 0.0.3

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.
@@ -0,0 +1,154 @@
1
+ module Neo4r
2
+ # Node traverser class
3
+ class NodeTraverser
4
+ include Enumerable
5
+
6
+ attr_accessor :order, :uniqueness, :depth, :prune, :filter, :relationships
7
+ attr_reader :rest
8
+
9
+ def initialize(from, types = nil, dir = "all")
10
+ @rest = RestWrapper.new
11
+ @from = from
12
+ @order = "depth first"
13
+ @uniqueness = "none"
14
+ @relationships = []
15
+ types.each do |type|
16
+ @relationships << { "type" => type.to_s, "direction" => dir.to_s }
17
+ end unless types.nil?
18
+ end
19
+
20
+ def <<(other_node)
21
+ create(other_node)
22
+ self
23
+ end
24
+
25
+ def create(other_node)
26
+ case @relationships.first["direction"]
27
+ when "outgoing", "out"
28
+ rel = Relationship.create(
29
+ type: @relationships.first["type"], start: @from, end: other_node)
30
+ when "incoming", "in"
31
+ rel = Relationship.create(
32
+ type: @relationships.first["type"], start: other_node, end: @from)
33
+ else
34
+ rel = []
35
+ rel << Relationship.create(
36
+ type: @relationships.first["type"], start: @from, end: other_node)
37
+ rel << Relationship.create(
38
+ type: @relationships.first["type"], start: other_node, end: @from)
39
+ end
40
+ rel
41
+ end
42
+
43
+ def both(type)
44
+ @relationships << { "type" => type.to_s, "direction" => "all" }
45
+ self
46
+ end
47
+
48
+ def outgoing(type)
49
+ @relationships << { "type" => type.to_s, "direction" => "out" }
50
+ self
51
+ end
52
+
53
+ def incoming(type)
54
+ @relationships << { "type" => type.to_s, "direction" => "in" }
55
+ self
56
+ end
57
+
58
+ def uniqueness(u)
59
+ @uniqueness = u
60
+ self
61
+ end
62
+
63
+ def order(o)
64
+ @order = o
65
+ self
66
+ end
67
+
68
+ def filter(body)
69
+ @filter = {
70
+ "language" => "javascript",
71
+ "body" => body
72
+ }
73
+ self
74
+ end
75
+
76
+ def prune(body)
77
+ @prune = {
78
+ "language" => "javascript",
79
+ "body" => body
80
+ }
81
+ self
82
+ end
83
+
84
+ def depth(d)
85
+ d = 2_147_483_647 if d == :all
86
+ @depth = d
87
+ self
88
+ end
89
+
90
+ def include_start_node
91
+ @filter = {
92
+ "language" => "builtin",
93
+ "name" => "all"
94
+ }
95
+ self
96
+ end
97
+
98
+ def size
99
+ [*self].size
100
+ end
101
+
102
+ alias_method :length, :size
103
+
104
+ def [](index)
105
+ each_with_index { |node, i| break node if index == i }
106
+ end
107
+
108
+ def empty?
109
+ first.nil?
110
+ end
111
+
112
+ def each
113
+ nodes = []
114
+ iterator.each do |i|
115
+ node = @from.class.new(i)
116
+ nodes << node
117
+ yield node
118
+ end
119
+ nodes
120
+ end
121
+
122
+ def iterator
123
+ options = {
124
+ "order" => @order,
125
+ "uniqueness" => @uniqueness,
126
+ "relationships" => @relationships
127
+ }
128
+ options["prune evaluator"] = @prune unless @prune.nil?
129
+ options["return filter"] = @filter unless @filter.nil?
130
+ options["depth"] = @depth unless @depth.nil?
131
+
132
+ if @relationships[0]["type"].empty?
133
+ rels = rest.get_node_relationships(
134
+ @from.neo_id, @relationships[0]["direction"]) || []
135
+ case @relationships[0]["direction"]
136
+ when "in"
137
+ rels.map { |r| rest.get_node(r["start"]) }
138
+ when "out"
139
+ rels.map { |r| rest.get_node(r["end"]) }
140
+ else
141
+ rels.map do |r|
142
+ if @from.neo_id == r["start"].split('/').last.to_i
143
+ rest.get_node(r["end"])
144
+ else
145
+ rest.get_node(r["start"])
146
+ end
147
+ end
148
+ end
149
+ else
150
+ rest.traverse(@from.neo_id, "nodes", options)
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,25 @@
1
+ module Neo4r
2
+ # The class provides the pagination based on the given source.
3
+ # The source must be an Enumerable implementing methods drop,
4
+ # first and count (or size).
5
+ # This can be used to paginage any Enumerable collection and
6
+ # provides the integration point for other gems, like
7
+ # will_paginate and kaminari.
8
+ class Paginated
9
+ include Enumerable
10
+ attr_accessor :items, :total, :current_page
11
+ delegate :each, to: :items
12
+
13
+ def initialize(items, total, current_page)
14
+ @items = items
15
+ @total = total
16
+ @current_page = current_page
17
+ end
18
+
19
+ def self.create_from(source, page, per_page)
20
+ dup = source.dup
21
+ partial = dup.offset((page - 1) * per_page).limit(per_page)
22
+ Paginated.new(partial, dup.count, page)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ module Neo4r
2
+ # Peoperty container class
3
+ class PropertyContainer < OpenStruct
4
+ include Neo4r::Attributes
5
+
6
+ attr_reader :neo_id, :rest
7
+
8
+ def initialize(args = {})
9
+ @rest = RestWrapper.new
10
+ if args["self"] && args["data"]
11
+ @neo_id = args["self"].split("/").last.to_i
12
+ super(self.class.to_ruby(args["data"]))
13
+ else
14
+ super(args)
15
+ end
16
+ end
17
+
18
+ def is_new?
19
+ @neo_id.nil?
20
+ end
21
+
22
+ def to_id(target)
23
+ if target.is_a?(String)
24
+ target.split("/").last.to_i
25
+ elsif target.class < PropertyContainer
26
+ target.neo_id
27
+ else
28
+ target
29
+ end
30
+ end
31
+
32
+ def inspect
33
+ str = super
34
+ str.sub!(/ /, "[#{@neo_id}] ") unless @neo_id.nil?
35
+ str
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,174 @@
1
+ require "neo4r/will_paginate"
2
+ require "neo4r/rest_wrapper"
3
+
4
+ module Neo4r
5
+ # Neo4r relation class
6
+ class Relation
7
+ include Enumerable
8
+ include ::Neo4r::WillPaginate
9
+
10
+ alias_method :all, :to_a
11
+ alias_method :size, :count
12
+
13
+ def initialize(klass)
14
+ @klass = klass
15
+ @labels = klass.name.split("::")
16
+ @props = {}
17
+ @has = []
18
+ @order_by = []
19
+ @limit = nil
20
+ @offset = nil
21
+ end
22
+
23
+ def clear
24
+ @labels = []
25
+ @props = {}
26
+ @has = []
27
+ @order_by = []
28
+ @limit = nil
29
+ @offset = nil
30
+ self
31
+ end
32
+
33
+ def count
34
+ rest = RestWrapper.new
35
+ rest.execute_query(to_cypher(return: :count))["data"].first.first
36
+ end
37
+
38
+ def each
39
+ rest = RestWrapper.new
40
+ data = rest.execute_query(to_cypher)["data"]
41
+ data.map do |d|
42
+ yield @klass.new(d.first)
43
+ end
44
+ end
45
+
46
+ def labels(*labels)
47
+ @labels.concat(labels).uniq!
48
+ self
49
+ end
50
+
51
+ def where(hash)
52
+ @props.merge!(hash)
53
+ self
54
+ end
55
+
56
+ def asc(*props)
57
+ props.each do |prop|
58
+ @order_by << "a.#{prop}"
59
+ end
60
+ self
61
+ end
62
+
63
+ def desc(*props)
64
+ props.each do |prop|
65
+ @order_by << "a.#{prop} DESC"
66
+ end
67
+ self
68
+ end
69
+
70
+ def limit(n)
71
+ @limit = n
72
+ self
73
+ end
74
+
75
+ def offset(n)
76
+ @offset = n
77
+ self
78
+ end
79
+
80
+ def has(*props)
81
+ @has.concat(props).uniq!
82
+ self
83
+ end
84
+
85
+ def to_cypher(options = {})
86
+ [
87
+ to_match,
88
+ to_where,
89
+ to_return(options),
90
+ to_order_by(options),
91
+ to_skip(options),
92
+ to_limit(options)
93
+ ].compact.join("\n")
94
+ end
95
+
96
+ private
97
+
98
+ def to_match
99
+ "MATCH (a#{to_labels}#{to_props})"
100
+ end
101
+
102
+ def to_labels
103
+ @labels.reduce("") do |result, label|
104
+ result + ":#{label}"
105
+ end
106
+ end
107
+
108
+ def to_props
109
+ format_props = @props.map do |name, value|
110
+ "#{name}: #{format_value(value)}"
111
+ end.join(", ")
112
+
113
+ if format_props.length > 0
114
+ " {#{format_props}}"
115
+ else
116
+ ""
117
+ end
118
+ end
119
+
120
+ def format_value(value)
121
+ case value
122
+ when String
123
+ '"' + value.gsub('"', '\"') + '"'
124
+ else
125
+ value.to_s
126
+ end
127
+ end
128
+
129
+ def to_return(options)
130
+ case options[:return]
131
+ when :count
132
+ "RETURN count(a)"
133
+ else
134
+ "RETURN a"
135
+ end
136
+ end
137
+
138
+ def to_order_by(options)
139
+ if @order_by.length > 0 && options[:return].nil?
140
+ "ORDER BY " + @order_by.join(", ")
141
+ else
142
+ nil
143
+ end
144
+ end
145
+
146
+ def to_skip(options)
147
+ if @offset && options[:return].nil?
148
+ "SKIP #{@offset}"
149
+ else
150
+ nil
151
+ end
152
+ end
153
+
154
+ def to_limit(options)
155
+ if @limit && options[:return].nil?
156
+ "LIMIT #{@limit}"
157
+ else
158
+ nil
159
+ end
160
+ end
161
+
162
+ def to_where
163
+ expr = @has.map do |prop|
164
+ "HAS (a.#{prop})"
165
+ end.join(" AND ")
166
+
167
+ if expr.length > 0
168
+ "WHERE #{expr}"
169
+ else
170
+ nil
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,51 @@
1
+ module Neo4r
2
+ class Relationship < PropertyContainer
3
+ def self.create(args)
4
+ rel = Relationship.new(args)
5
+ if rel.save
6
+ rel
7
+ else
8
+ nil
9
+ end
10
+ end
11
+
12
+ attr_reader :start_node_id, :end_node_id, :rel_type
13
+
14
+ def initialize(args)
15
+ super(args)
16
+ if is_new?
17
+ @start_node_id = to_id(delete_field(:start))
18
+ @end_node_id = to_id(delete_field(:end))
19
+ @rel_type = delete_field(:type)
20
+ else
21
+ @start_node_id = to_id(args["start"])
22
+ @end_node_id = to_id(args["end"])
23
+ @rel_type = args["type"]
24
+ end
25
+ end
26
+
27
+ def save
28
+ if is_new?
29
+ rel = rest.create_relationship(
30
+ @rel_type, @start_node_id, @end_node_id, @table)
31
+ @neo_id = rel["self"].split("/").last.to_i
32
+ else
33
+ rest.reset_relationship_properties(@neo_id, @table)
34
+ end
35
+ true
36
+ end
37
+
38
+ def destroy
39
+ rest.delete_relationship(neo_id)
40
+ true
41
+ end
42
+
43
+ def start_node
44
+ @start_node ||= Experiment.find(@start_node_id)
45
+ end
46
+
47
+ def end_node
48
+ @end_node ||= Experiment.find(@end_node_id)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,86 @@
1
+ require "neo4r/relationship"
2
+
3
+ module Neo4r
4
+ # Relationship traverser class
5
+ class RelationshipTraverser
6
+ include Enumerable
7
+
8
+ attr_reader :rest
9
+
10
+ def initialize(node, types, direction)
11
+ @rest = RestWrapper.new
12
+ @node = node
13
+ @types = [types]
14
+ @direction = direction
15
+ end
16
+
17
+ def to_s
18
+ if @types.size == 1 && !@types.empty?
19
+ "#{self.class} [type: #{@type} dir:#{@direction}]"
20
+ elsif !@types.empty?
21
+ "#{self.class} [types: #{@types.join(',')} dir:#{@direction}]"
22
+ else
23
+ "#{self.class} [types: ANY dir:#{@direction}]"
24
+ end
25
+ end
26
+
27
+ def each
28
+ rels = []
29
+ iterator.each do |i|
30
+ rel = Relationship.new(i)
31
+ rels << rel
32
+ yield rel if match_to_other?(rel)
33
+ end
34
+ rels
35
+ end
36
+
37
+ def empty?
38
+ first.nil?
39
+ end
40
+
41
+ def iterator
42
+ Array(rest.get_node_relationships(@node.neo_id, @direction, @types))
43
+ end
44
+
45
+ def match_to_other?(rel)
46
+ if @to_other.nil?
47
+ true
48
+ elsif @direction == :outgoing
49
+ rel.end_node_id == @to_other.neo_id
50
+ elsif @direction == :incoming
51
+ rel.start_node_id == @to_other.neo_id
52
+ else
53
+ rel.start_node_id == @to_other.neo_id || rel.end_node_id == @to_other.neo_id
54
+ end
55
+ end
56
+
57
+ def to_other(to_other)
58
+ @to_other = to_other
59
+ self
60
+ end
61
+
62
+ def destroy
63
+ each { |rel| rel.destroy }
64
+ end
65
+
66
+ def size
67
+ [*self].size
68
+ end
69
+
70
+ def both
71
+ @direction = :both
72
+ self
73
+ end
74
+
75
+ def incoming
76
+ @direction = :incoming
77
+ self
78
+ end
79
+
80
+ def outgoing
81
+ @direction = :outgoing
82
+ self
83
+ end
84
+
85
+ end
86
+ end