wewoo 0.1.5

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,233 @@
1
+ require 'typhoeus'
2
+ require 'json'
3
+ require 'map'
4
+ require 'wewoo/adapter'
5
+
6
+ module Wewoo
7
+ class Graph
8
+ include Adapter
9
+
10
+ class UnknownGraphError < RuntimeError; end
11
+ class GraphElementNotFoundError < RuntimeError; end
12
+
13
+ attr_reader :url, :name
14
+
15
+ def self.available_graphs
16
+ res = Adapter::get( "#{Configuration.url}/graphs" )
17
+ res.graphs
18
+ end
19
+
20
+ def initialize( graph_name, host:nil, port:nil )
21
+ Configuration.url( host, port )
22
+
23
+ @name = graph_name
24
+ @base_url = "#{Configuration.url}/graphs"
25
+ @url = "#{@base_url}/#{@name}"
26
+ validate
27
+ end
28
+
29
+ def save( filename )
30
+ dir = File.dirname( filename )
31
+ FileUtils.mkdir_p( dir ) unless File.exists?( dir )
32
+
33
+ q( "g.saveGraphML( '#{filename}' )" )
34
+ end
35
+
36
+ def load( filename, append:false )
37
+ raise "Graph file does not exist. #{filename}" unless File.exists?(filename)
38
+
39
+ clear unless append
40
+ q( "g.loadGraphML( '#{filename}' )" )
41
+ end
42
+
43
+ def clear
44
+ query( 'g.V.remove()')
45
+ end
46
+
47
+ def key_indices
48
+ Map[ *get( u(:keyindices ) ).flatten]
49
+ end
50
+
51
+ def set_key_index( type, key )
52
+ post( u( %W[keyindices #{type} #{key}] ) )
53
+ end
54
+
55
+ def drop_index( index_name )
56
+ delete( u( %W[indices #{index_name}] ) )
57
+ end
58
+
59
+ def ensure_index( key, type=:vertex )
60
+ unless key_indices[type.to_s].include? key.to_s
61
+ post( u( %W[keyindices #{type} #{key}] ) )
62
+ end
63
+ end
64
+
65
+ def index( index_name, clazz:'vertex', options:{} )
66
+ post( u(%W[indices #{index_name}]), params: {class: clazz}.merge(options))
67
+ end
68
+
69
+ def query( command, page:nil, per_page:nil )
70
+ # command = "g." + command unless command[/\Ag\./]
71
+ ResultSet.new( self, get( u(%w[tp gremlin]),
72
+ params:{script: command}.merge(page_params(page, per_page)),
73
+ headers: { 'Content-Type'=> 'application/json'} ) ).hydrate
74
+ end
75
+ alias :q :query
76
+
77
+ def add_vertex( props={} )
78
+ vertex = Vertex.from_hash( self,
79
+ post( u( %W[vertices] ) ) )
80
+ update_vertex( vertex.id, props )
81
+ end
82
+
83
+ # BOZO!! - not really an update as all props will be replaced!
84
+ def update_vertex( id, props )
85
+ v = find_vertex( id )
86
+ props = v.props.merge( props )
87
+ res = put( u( %W[vertices #{id}] ),
88
+ body: props.to_json,
89
+ headers: { 'Content-Type'=> 'application/json'} )
90
+ Vertex.from_hash( self, res )
91
+ end
92
+
93
+ def vertices( page:nil, per_page:nil )
94
+ get( u(:vertices), {params: page_params(page, per_page)} ).map do |res|
95
+ Vertex.from_hash( self, res )
96
+ end
97
+ end
98
+ alias :V :vertices
99
+
100
+ def find_vertices( key, value, page:nil, per_page:nil )
101
+ params = { key: key, value: map_value(value) }.merge page_params( page, per_page )
102
+ res = get( u(:vertices), params: params )
103
+
104
+ res.map do |res|
105
+ Vertex.from_hash( self, res )
106
+ end
107
+ end
108
+ def find_first_vertex( key, value )
109
+ find_vertices( key, value ).first
110
+ end
111
+ def find_vertex_by_gid( gid )
112
+ find_first_vertex( :gid, gid )
113
+ end
114
+ def find_vertex( id )
115
+ Vertex.from_hash( self, get( u %W[vertices #{id}] ) )
116
+ rescue InvalidRequestError => ex
117
+ raise GraphElementNotFoundError, ex.message
118
+ end
119
+ alias :v :find_vertex
120
+
121
+ def vertex_exists?( id )
122
+ v(id) && true
123
+ rescue
124
+ false
125
+ end
126
+
127
+ def edge_exists?( id )
128
+ e(id) && true
129
+ true
130
+ rescue
131
+ false
132
+ end
133
+
134
+ def remove_vertex( id )
135
+ delete( u(%W[vertices #{id}] ) )
136
+ end
137
+
138
+ def update_edge( id, props )
139
+ e = find_edge( id )
140
+ props = e.props.merge( props )
141
+
142
+ res = put( u( %W[edges #{id}] ),
143
+ body: props.to_json,
144
+ headers: { 'Content-Type'=> 'application/json'} )
145
+ Edge.from_hash( self, res )
146
+ end
147
+
148
+ def add_edge( from, to, label, props={} )
149
+ params = {
150
+ '_outV' => (from.is_a? Vertex) ? from.id : from,
151
+ '_inV' => (to.is_a? Vertex) ? to.id : to,
152
+ '_label' => label
153
+ }.merge( props )
154
+ res = post( u( %W[edges] ),
155
+ body: params.to_json,
156
+ headers: { 'Content-Type'=> 'application/json'} )
157
+ Edge.from_hash( self, res )
158
+ end
159
+
160
+ def remove_edge( id )
161
+ delete( u %W[edges #{id}] )
162
+ end
163
+
164
+ def find_edge( id )
165
+ Edge.from_hash( self, get( u %W[edges #{id}] ) )
166
+ rescue InvalidRequestError => ex
167
+ raise GraphElementNotFoundError, ex.message
168
+ end
169
+ alias :e :find_edge
170
+
171
+ def find_edges( key, value, page:nil, per_page:nil )
172
+ params = { key: key,
173
+ value: map_value( value )
174
+ }.merge page_params( page, per_page )
175
+ get( u(:edges), params: params ).map { |e| Edge.from_hash( self, e ) }
176
+ end
177
+
178
+ def find_first_edge( key, value )
179
+ find_edges( key, value, page:1, per_page:1 ).first
180
+ end
181
+
182
+ def edges( page:nil, per_page:nil )
183
+ get( u(:edges), params: page_params(page, per_page) ).map do |res|
184
+ Edge.from_hash( self, res )
185
+ end
186
+ end
187
+ alias :E :edges
188
+
189
+ def to_s
190
+ res = get( url, headers: { 'Content-Type' =>
191
+ 'application/vnd.rexster-typed-v1+json' } )
192
+ stats = res.graph.match( /.*\[vertices:(\d+) edges:(\d+)\]/ ).captures
193
+
194
+ "#{name} [Vertices:#{stats.first}, Edges:#{stats.last}]"
195
+ end
196
+ alias inspect to_s
197
+
198
+ private
199
+
200
+ def map_value( value )
201
+ case value
202
+ when TrueClass
203
+ when FalseClass
204
+ "(b,#{value})"
205
+ when Fixnum
206
+ "(i,#{value})"
207
+ when Float
208
+ "(d,#{value})"
209
+ else
210
+ value
211
+ end
212
+ end
213
+
214
+ def u( path )
215
+ File.join( url, ((path.is_a? Array) ? path : path.to_s ) )
216
+ end
217
+
218
+ def validate
219
+ res = get( @base_url )
220
+
221
+ unless res.graphs.include?( @name.to_s )
222
+ raise UnknownGraphError, "Unable to locate graph named `#{@name}"
223
+ end
224
+ end
225
+
226
+ def page_params( page, per_page )
227
+ return {} unless page and per_page
228
+
229
+ { 'rexster.offset.start' => (page-1)*per_page,
230
+ 'rexster.offset.end' => page*per_page }
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,99 @@
1
+ # BOZO !! Refactor
2
+ module Wewoo
3
+ class ResultSet
4
+
5
+ class NoGraphElementError < RuntimeError; end
6
+
7
+ def initialize( graph, results )
8
+ @results = results
9
+ @graph = graph
10
+ end
11
+
12
+ def hydrate
13
+ collapse( _hydrate( @results ) )
14
+ end
15
+
16
+ def graph_element?( item )
17
+ item.is_a? Hash and
18
+ item.key?('_type') and
19
+ %w(vertex edge).include? item['_type']
20
+ end
21
+
22
+ def build_element( item )
23
+ type = item['_type']
24
+ Object.const_get( "Wewoo::#{type.capitalize}" )
25
+ .from_hash( @graph, item )
26
+ rescue => boom
27
+ raise NoGraphElementError, "Unbuildable"
28
+ end
29
+
30
+ private
31
+
32
+ def collapse( res )
33
+ (res.is_a? Enumerable and
34
+ res.count == 1 and
35
+ res.first.is_a? Enumerable) ? res.first : res
36
+ end
37
+
38
+ def usable( item )
39
+ graph_element?( item ) ? build_element( item ) : item
40
+ end
41
+
42
+ def value_hash?( hash )
43
+ hash.values.first.is_a? Hash
44
+ end
45
+
46
+ def simple_response?( res )
47
+ res.is_a? Hash and res.key?('success')
48
+ end
49
+
50
+ def key_value_hash?( res )
51
+ res.is_a? Hash and res.key?('_key') and res.key?('_value')
52
+ end
53
+
54
+ def _deep_hydrate( item, acc )
55
+ if graph_element?( item )
56
+ acc << build_element( item )
57
+ elsif item.is_a? Array
58
+ tuples = []
59
+ item.each do |row|
60
+ _deep_hydrate( row, tuples )
61
+ end
62
+ acc << tuples
63
+ elsif item.is_a? Hash
64
+ if key_value_hash?( item )
65
+ key = usable(item['_key'])
66
+ if item['_value'].is_a? Enumerable
67
+ acc[key] = []
68
+ item['_value'].each do |row|
69
+ acc[key] << usable(row)
70
+ end
71
+ else
72
+ acc[key] = usable(item['_value'])
73
+ end
74
+ elsif item.values.first.is_a? Hash
75
+ res = {}
76
+ item.values.each do |item|
77
+ _deep_hydrate( item, res )
78
+ end
79
+ acc << res
80
+ else
81
+ acc << item
82
+ end
83
+ else
84
+ acc << item
85
+ end
86
+ end
87
+
88
+ def _hydrate( res )
89
+ return res['success'] if simple_response?( res )
90
+ if res.is_a? Array
91
+ acc = []
92
+ res.each{ |row| _deep_hydrate( row, acc ) }
93
+ acc
94
+ else
95
+ raise "Unexpected match!!"
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module Wewoo
2
+ VERSION = "0.1.5"
3
+ end
@@ -0,0 +1,63 @@
1
+ module Wewoo
2
+ # BOZO !! Need to support chaining!
3
+ class Vertex < Wewoo::Element
4
+ include Adapter
5
+
6
+ def outE ( *labels ); @outE || get_edges( :outE , labels ); end
7
+ def inE ( *labels ); @inE || get_edges( :inE , labels ); end
8
+ def bothE( *labels ); @bothE || get_edges( :bothE, labels ); end
9
+
10
+ def out ( *labels ); @out || get_vertices( :out , labels ); end
11
+ def in ( *labels ); @in || get_vertices( :in , labels ); end
12
+ def both( *labels ); @both || get_vertices( :both, labels ); end
13
+
14
+ def ==( o )
15
+ self.class == o.class and self.id == o.id and self.props == o.props
16
+ end
17
+ alias :eql? :==
18
+ def hash; id; end
19
+ def <=>(o); self.class == o.class ? self.id <=> o.id : super; end
20
+
21
+ def destroy
22
+ graph.remove_vertex( id )
23
+ end
24
+
25
+ def to_s
26
+ "v(#{self.id})"
27
+ end
28
+ alias :inspect :to_s
29
+
30
+ def dump
31
+ props.clone().merge( id: id )
32
+ end
33
+
34
+ private
35
+
36
+ def get_edges( direction, labels )
37
+ str_labels = labels ? labels.map(&:to_s) : []
38
+ get( u direction ).map { |hash|
39
+ if labels.empty? or str_labels.include? hash['_label']
40
+ Edge.from_hash( graph, hash )
41
+ end
42
+ }.compact
43
+ end
44
+
45
+ def get_vertices( direction, labels=[] )
46
+ get_edges( "#{direction}E",labels ).map{ |e|
47
+ if direction == :both
48
+ e.in == self ? e.out : e.in
49
+ else
50
+ e.get_vertex( direction==:in ? :out : :in )
51
+ end
52
+ }
53
+ end
54
+
55
+ def self.from_hash( graph, hash )
56
+ new( graph, hash.delete('_id'), Map( hash ) )
57
+ end
58
+
59
+ def u( path )
60
+ File.join( graph.url, %W[vertices #{id} #{path}] )
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ require 'simplecov'
2
+ if ENV['COV']
3
+ SimpleCov.start do
4
+ end
5
+ end
6
+
7
+ require 'wewoo'
8
+ require 'support/graph_sets'
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.filter_run focus: true
13
+ config.run_all_when_everything_filtered = true
14
+ end
@@ -0,0 +1,33 @@
1
+ def build_test_graph( g )
2
+ g.clear
3
+
4
+ @v1 = g.add_vertex( gid: 1 , name:'fred', age:20, rating: 30.5, busy: true )
5
+ @v2 = g.add_vertex( gid: 2 , name:'joe' , age:30, rating: 20.5, busy: false )
6
+ @v3 = g.add_vertex( gid: 3 , name:'max' , age:40, rating: 10.5, busy: true )
7
+ @v4 = g.add_vertex( gid: "4", name:'blee', age:50, rating: 5.5 , busy: true )
8
+
9
+ @e1 = g.add_edge( @v1, @v2, :friend, rating: 1.5 )
10
+ @e2 = g.add_edge( @v1, @v3, :friend, rating: 2.5 )
11
+ @e3 = g.add_edge( @v1, @v4, :love , rating: 3.5 )
12
+ @e4 = g.add_edge( @v2, @v3, :friend, rating: 5.5 )
13
+ @e5 = g.add_edge( @v3, @v4, :friend, rating: 5.5 )
14
+ end
15
+
16
+ def build_sample_graph( g )
17
+ g.clear
18
+ #g.ensure_index(:name,:vertex)
19
+
20
+ @v1 = g.add_vertex( name: :marko , age: 29 )
21
+ @v2 = g.add_vertex( name: :vadas , age: 27 )
22
+ @v3 = g.add_vertex( name: :lop , lang: :java )
23
+ @v4 = g.add_vertex( name: :josh , age: 32 )
24
+ @v5 = g.add_vertex( name: :ripple, lang: :java )
25
+ @v6 = g.add_vertex( name: :peter , age: 35 )
26
+
27
+ @e7 = g.add_edge( @v1, @v2, :knows , weight: 0.5 )
28
+ @e8 = g.add_edge( @v1, @v4, :knows , weight: 1.0 )
29
+ @e9 = g.add_edge( @v1, @v3, :created, weight: 0.4 )
30
+ @e10 = g.add_edge( @v4, @v5, :created, weight: 1.0 )
31
+ @e11 = g.add_edge( @v4, @v3, :created, weight: 0.4 )
32
+ @e12 = g.add_edge( @v6, @v3, :created, weight: 0.2 )
33
+ end