keymaker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ module Keymaker
2
+
3
+ class Response
4
+
5
+ attr_accessor :request
6
+ attr_accessor :service
7
+ attr_accessor :faraday_response
8
+
9
+ def initialize(service, faraday_response)
10
+ self.service = service
11
+ self.faraday_response = faraday_response
12
+ end
13
+
14
+ def body
15
+ faraday_response.body || {}
16
+ end
17
+
18
+ def status
19
+ faraday_response.status
20
+ end
21
+
22
+ def neo4j_id
23
+ body["self"] && body["self"][/\d+$/].to_i
24
+ end
25
+
26
+ def on_success
27
+ if success?
28
+ yield self
29
+ end
30
+ end
31
+
32
+ def success?
33
+ (200..207).include?(status)
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,46 @@
1
+ module Keymaker::Serialization
2
+ include ActiveModel::Serialization
3
+
4
+ def self.included(base)
5
+ base.define_model_callbacks :save, :create
6
+ end
7
+
8
+ COERCION_PROCS = Hash.new(->(v){v}).tap do |procs|
9
+ procs[Integer] = ->(v){ v.to_i }
10
+ procs[DateTime] = ->(v) do
11
+ case v
12
+ when Time
13
+ Time.at(v)
14
+ when String
15
+ DateTime.strptime(v).to_time
16
+ else
17
+ Time.now.utc
18
+ end
19
+ end
20
+ end
21
+
22
+ def process_attrs(attrs)
23
+ attrs.symbolize_keys!
24
+ self.class.properties.delete_if{|p| p == :node_id}.each do |property|
25
+ if property == :active_record_id
26
+ process_attr(property, attrs[:id].present? ? attrs[:id] : attrs[:active_record_id])
27
+ else
28
+ process_attr(property, attrs[property])
29
+ end
30
+ end
31
+ end
32
+
33
+ def process_attr(key, value)
34
+ send("#{key}=", coerce(value,self.class.property_traits[key]))
35
+ end
36
+
37
+ def coerce(value,type)
38
+ COERCION_PROCS[type].call(value)
39
+ end
40
+
41
+ def attributes
42
+ Hash.new{|h,k| h[k] = send(k) }.tap do |hash|
43
+ self.class.properties.each{|property| hash[property.to_s] }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,147 @@
1
+ require "addressable/uri"
2
+
3
+ module Keymaker
4
+
5
+ class Service
6
+
7
+ attr_accessor :config
8
+
9
+ def initialize(config)
10
+ self.config = config
11
+ end
12
+
13
+ def connection=(connection)
14
+ @connection = connection
15
+ end
16
+
17
+ def connection
18
+ @connection ||= Faraday.new(url: config.service_root) do |conn|
19
+ conn.request :json
20
+ conn.use FaradayMiddleware::ParseJson, content_type: /\bjson$/
21
+ conn.adapter :net_http
22
+ end
23
+ end
24
+
25
+ # Create Node
26
+ def create_node(attrs)
27
+ create_node_request(attrs)
28
+ end
29
+
30
+ def create_node_request(opts)
31
+ CreateNodeRequest.new(self, opts).submit
32
+ end
33
+
34
+ # Update Node properties
35
+ def update_node_properties(node_id, attrs)
36
+ update_node_properties_request({node_id: node_id}.merge(attrs))
37
+ end
38
+
39
+ def update_node_properties_request(opts)
40
+ UpdateNodePropertiesRequest.new(self, opts).submit
41
+ end
42
+
43
+ # Create Relationship
44
+ def create_relationship(rel_type, start_node_id, end_node_id, data={})
45
+ create_relationship_request({node_id: start_node_id, rel_type: rel_type, end_node_id: end_node_id, data: data})
46
+ end
47
+
48
+ def create_relationship_request(opts)
49
+ CreateRelationshipRequest.new(self, opts).submit
50
+ end
51
+
52
+ # Delete Relationship
53
+ def delete_relationship(relationship_id)
54
+ delete_relationship_request(relationship_id: relationship_id)
55
+ end
56
+
57
+ def delete_relationship_request(opts)
58
+ DeleteRelationshipRequest.new(self, opts).submit
59
+ end
60
+
61
+ # Add Node to Index
62
+ def add_node_to_index(index_name, key, value, node_id)
63
+ add_node_to_index_request(index_name: index_name, key: key, value: value, node_id: node_id)
64
+ end
65
+
66
+ def add_node_to_index_request(opts)
67
+ AddNodeToIndexRequest.new(self, opts).submit
68
+ end
69
+
70
+ # Remove Node from Index
71
+ def remove_node_from_index(index_name, key, value, node_id)
72
+ remove_node_from_index_request(index_name: index_name, key: key, value: value, node_id: node_id)
73
+ end
74
+
75
+ def remove_node_from_index_request(opts)
76
+ RemoveNodeFromIndexRequest.new(self, opts).submit
77
+ end
78
+
79
+ # Path Traverse
80
+ def path_traverse(start_node_id, data={})
81
+ path_traverse_request({node_id: start_node_id}.merge(data))
82
+ end
83
+
84
+ def path_traverse_request(opts)
85
+ PathTraverseRequest.new(self, opts).submit
86
+ end
87
+
88
+ # Batch
89
+ ## GET Nodes
90
+ def batch_get_nodes(node_ids)
91
+ batch_get_nodes_request(node_ids)
92
+ end
93
+
94
+ def batch_get_nodes_request(opts)
95
+ BatchGetNodesRequest.new(self, opts).submit
96
+ end
97
+
98
+ # Cypher Query
99
+ def execute_query(query, params)
100
+ execute_cypher_request({query: query, params: params})
101
+ end
102
+
103
+ def execute_cypher_request(opts)
104
+ ExecuteCypherRequest.new(self, opts).submit
105
+ end
106
+
107
+ # Gremlin Script
108
+ def execute_script(script, params={})
109
+ execute_gremlin_request({script: script, params: params})
110
+ end
111
+
112
+ def execute_gremlin_request(opts)
113
+ ExecuteGremlinRequest.new(self, opts).submit
114
+ end
115
+
116
+ # HTTP Verbs
117
+
118
+ def get(url, body)
119
+ faraday_response = connection.get(parse_url(url), body)
120
+ Keymaker::Response.new(self, faraday_response)
121
+ end
122
+
123
+ def delete(url)
124
+ faraday_response = connection.delete(parse_url(url))
125
+ Keymaker::Response.new(self, faraday_response)
126
+ end
127
+
128
+ def post(url, body)
129
+ faraday_response = connection.post(parse_url(url), body)
130
+ Keymaker::Response.new(self, faraday_response)
131
+ end
132
+
133
+ def put(url, body)
134
+ faraday_response = connection.put(parse_url(url), body)
135
+ Keymaker::Response.new(self, faraday_response)
136
+ end
137
+
138
+ def parse_url(url)
139
+ connection.build_url(url).tap do |uri|
140
+ if uri.port != config.port
141
+ raise RuntimeError, "bad port"
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,15 @@
1
+ module Keymaker
2
+
3
+ class UpdateNodePropertiesRequest < Request
4
+
5
+ def submit
6
+ service.put(node_properties_path(opts[:node_id]), node_properties)
7
+ end
8
+
9
+ def node_properties
10
+ opts.except(:node_id)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,189 @@
1
+ require 'spec_helper'
2
+ require "addressable/uri"
3
+ require 'keymaker'
4
+
5
+ describe Keymaker do
6
+
7
+ include_context "John and Sarah nodes"
8
+
9
+ context "indices" do
10
+ include_context "John and Sarah indexed nodes"
11
+
12
+ context "given a bad port number" do
13
+
14
+ let(:url) { john_node_url.dup.gsub("7475", "49152") }
15
+
16
+ after { service.connection = connection }
17
+
18
+ def do_it
19
+ connection.get(url) do |req|
20
+ req.options[:timeout] = 0
21
+ req.options[:open_timeout] = 0
22
+ end
23
+ end
24
+
25
+ it "raises an error" do
26
+ expect { do_it }.to raise_error
27
+ end
28
+
29
+ end
30
+
31
+ context "given an explicit connection" do
32
+
33
+ let(:url) { john_node_url }
34
+
35
+ before { service.connection = test_connection }
36
+ after { service.connection = connection }
37
+
38
+ def do_it
39
+ service.connection.get(url)
40
+ end
41
+
42
+ it "uses the connection for requests" do
43
+ faraday_stubs.get(Addressable::URI.parse(url).path) do
44
+ [200, {}, "{}"]
45
+ end
46
+ do_it
47
+ faraday_stubs.verify_stubbed_calls
48
+ end
49
+
50
+ end
51
+
52
+ describe "#add_node_to_index(index_name, key, value, node_id)" do
53
+
54
+ def do_it
55
+ service.add_node_to_index(:users, :email, email, node_id)
56
+ end
57
+
58
+ context "given existing values" do
59
+
60
+ let(:email) { john_email }
61
+ let(:node_id) { john_node_id }
62
+ let(:index_result) { connection.get(index_query_for_john_url).body[0]["self"] }
63
+
64
+ it "adds the node to the index" do
65
+ do_it
66
+ index_result.should == john_node_url
67
+ end
68
+
69
+ it "returns a status of 201" do
70
+ do_it.status.should == 201
71
+ end
72
+
73
+ end
74
+
75
+ context "given an invalid node id" do
76
+
77
+ let(:email) { john_email }
78
+ let(:node_id) { -22 }
79
+
80
+ it "returns a 500 status" do
81
+ do_it.status.should == 500
82
+ end
83
+
84
+ end
85
+
86
+ end
87
+
88
+ describe "#remove_node_from_index(index_name, key, value, node_id)" do
89
+
90
+ def do_it
91
+ service.remove_node_from_index(:users, :email, email, node_id)
92
+ end
93
+
94
+ context "given existing values" do
95
+
96
+ let(:email) { john_email }
97
+ let(:node_id) { john_node_id }
98
+
99
+ it "removes the node from the index" do
100
+ do_it
101
+ connection.get(index_query_for_john_url).body.should be_empty
102
+ end
103
+
104
+ it "returns a status of 204" do
105
+ do_it.status.should == 204
106
+ end
107
+
108
+ it "keeps the other node indices" do
109
+ do_it
110
+ connection.get(index_query_for_sarah_url).body.should_not be_empty
111
+ end
112
+
113
+ end
114
+
115
+ context "given unmatched values" do
116
+
117
+ let(:email) { "unknown@example.com" }
118
+ let(:node_id) { -22 }
119
+
120
+ it "returns a 404 status" do
121
+ do_it.status.should == 404
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+ describe "#execute_query" do
130
+
131
+ def do_it
132
+ service.execute_query("START user=node:users(email={email}) RETURN user", email: john_email)
133
+ end
134
+
135
+ context "given existing values" do
136
+
137
+ before { service.add_node_to_index(:users, :email, john_email, john_node_id) }
138
+ let(:query_result) { do_it["data"][0][0]["self"] }
139
+
140
+ it "performs the cypher query and responds" do
141
+ query_result.should == john_node_url
142
+ end
143
+
144
+ end
145
+
146
+ end
147
+
148
+ context "nodes" do
149
+
150
+ include_context "Keymaker connections"
151
+
152
+ describe "#create_node" do
153
+
154
+ let(:properties) { { first_name: "john", last_name: "connor", email: "john@resistance.net" } }
155
+
156
+ def do_it
157
+ new_node_id = service.create_node(properties).neo4j_id
158
+ connection.get("/db/data/node/#{new_node_id}/properties").body
159
+ end
160
+
161
+ it "creates a node with properties" do
162
+ do_it.should == {"first_name"=>"john", "email"=>"john@resistance.net", "last_name"=>"connor"}
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+
169
+ context "relationships" do
170
+
171
+ include_context "Keymaker connections"
172
+
173
+ describe "#create_relationship" do
174
+
175
+ def do_it
176
+ service.create_relationship(:loves, john_node_id, sarah_node_id).neo4j_id
177
+ connection.get("/db/data/node/#{john_node_id}/relationships/all/loves").body.first
178
+ end
179
+
180
+ it "creates the relationship between the two nodes" do
181
+ do_it["start"].should == john_node_url
182
+ do_it["end"].should == sarah_node_url
183
+ end
184
+
185
+ end
186
+
187
+ end
188
+
189
+ end
@@ -0,0 +1,7 @@
1
+ ROOT = File.expand_path('../..', __FILE__)
2
+ Dir[File.join(ROOT, 'spec/support/**/*.rb')].each {|f| require f}
3
+ $LOAD_PATH.unshift(File.expand_path('lib', ROOT))
4
+
5
+ RSpec.configure do |config|
6
+ config.mock_with :rspec
7
+ end