keymaker 0.0.1

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,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