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.
data/lib/keymaker.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'active_model'
4
+
5
+ require 'keymaker/request'
6
+ require 'keymaker/response'
7
+ require 'keymaker/configuration'
8
+ require 'keymaker/service'
9
+
10
+ require 'keymaker/add_node_to_index_request'
11
+ require 'keymaker/batch_get_nodes_request'
12
+ require 'keymaker/create_node_request'
13
+ require 'keymaker/create_relationship_request'
14
+ require 'keymaker/delete_relationship_request'
15
+ require 'keymaker/execute_cypher_request'
16
+ require 'keymaker/execute_gremlin_request'
17
+ require 'keymaker/path_traverse_request'
18
+ require 'keymaker/remove_node_from_index_request'
19
+ require 'keymaker/update_node_properties_request'
20
+
21
+ require 'keymaker/indexing'
22
+ require 'keymaker/serialization'
23
+ require 'keymaker/node'
24
+
25
+ module Keymaker
26
+
27
+ VERSION = "0.0.1"
28
+
29
+ def self.service
30
+ @service ||= Keymaker::Service.new(Keymaker::Configuration.new)
31
+ end
32
+
33
+ def self.configure
34
+ @configuration = Keymaker::Configuration.new
35
+ yield @configuration
36
+ @service = Keymaker::Service.new(@configuration)
37
+ end
38
+
39
+ def self.configuration
40
+ @configuration
41
+ end
42
+
43
+ end
@@ -0,0 +1,9 @@
1
+ module Keymaker
2
+ class AddNodeToIndexRequest < Request
3
+
4
+ def submit
5
+ service.post(node_index_path(opts[:index_name]), {key: opts[:key], value: opts[:value], uri: node_uri(opts[:node_id])})
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Keymaker
2
+
3
+ class BatchGetNodesRequest < Request
4
+
5
+ def submit
6
+ service.post(batch_path, batch_get_nodes_properties)
7
+ end
8
+
9
+ def batch_get_nodes_properties
10
+ [].tap do |batch_request|
11
+ opts.each_with_index do |node_id, request_id|
12
+ batch_request << {id: request_id, to: node_uri(node_id), method: "GET"}
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,90 @@
1
+ require "addressable/uri"
2
+
3
+ module Keymaker
4
+ class Configuration
5
+
6
+ attr_accessor :protocol, :server, :port,
7
+ :data_directory, :cypher_path, :gremlin_path,
8
+ :log_file, :log_enabled, :logger,
9
+ :authentication, :username, :password
10
+
11
+ def initialize(attrs={})
12
+ self.protocol = attrs.fetch(:protocol) {'http'}
13
+ self.server = attrs.fetch(:server) {'localhost'}
14
+ self.port = attrs.fetch(:port) {7474}
15
+ self.data_directory = attrs.fetch(:data_directory) {'db/data'}
16
+ self.cypher_path = attrs.fetch(:cypher_path) {'cypher'}
17
+ self.gremlin_path = attrs.fetch(:gremlin_path) {'ext/GremlinPlugin/graphdb/execute_script'}
18
+ self.authentication = attrs.fetch(:authentication) {{}}
19
+ self.username = attrs.fetch(:username) {nil}
20
+ self.password = attrs.fetch(:password) {nil}
21
+ end
22
+
23
+ def service_root
24
+ service_root_url.to_s
25
+ end
26
+
27
+ def service_root_url
28
+ Addressable::URI.new(url_opts)
29
+ end
30
+
31
+ def url_opts
32
+ {}.tap do |url_opts|
33
+ url_opts[:scheme] = protocol
34
+ url_opts[:host] = server
35
+ url_opts[:port] = port
36
+ url_opts[:user] = username if username
37
+ url_opts[:password] = password if password
38
+ end
39
+ end
40
+
41
+ def full_cypher_path
42
+ [service_root, data_directory, cypher_path].join("/")
43
+ end
44
+
45
+ def full_gremlin_path
46
+ [service_root, data_directory, gremlin_path].join("/")
47
+ end
48
+
49
+ def node_path
50
+ [data_directory, "node"].join("/")
51
+ end
52
+
53
+ def node_properties_path(node_id)
54
+ [node_path, node_id.to_s, "properties"].join("/")
55
+ end
56
+
57
+ def path_traverse_node_path(node_id)
58
+ [node_path, node_id.to_s, "traverse", "path"].join("/")
59
+ end
60
+
61
+ def batch_node_path(node_id)
62
+ ["/node", node_id.to_s].join("/")
63
+ end
64
+
65
+ def batch_path
66
+ [data_directory, "batch"].join("/")
67
+ end
68
+
69
+ def relationship_path(relationship_id)
70
+ [data_directory, "relationship", relationship_id.to_s].join("/")
71
+ end
72
+
73
+ def relationships_path_for_node(node_id)
74
+ [node_path, node_id.to_s, "relationships"].join("/")
75
+ end
76
+
77
+ def node_full_index_path(index_name, key, value, node_id)
78
+ [node_index_path(index_name), key, value, node_id].join("/")
79
+ end
80
+
81
+ def node_index_path(index_name)
82
+ [data_directory, "index", "node", index_name.to_s].join("/")
83
+ end
84
+
85
+ def node_uri(node_id)
86
+ [service_root, node_path, node_id.to_s].join("/")
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,11 @@
1
+ module Keymaker
2
+
3
+ class CreateNodeRequest < Request
4
+
5
+ def submit
6
+ service.post(node_path, opts)
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,26 @@
1
+ module Keymaker
2
+
3
+ # Example request
4
+
5
+ # POST http://localhost:7474/db/data/node/85/relationships
6
+ # Accept: application/json
7
+ # Content-Type: application/json
8
+ # {"to" : "http://localhost:7474/db/data/node/84", "type" : "LOVES", "data" : {"foo" : "bar"}}
9
+
10
+ class CreateRelationshipRequest < Request
11
+
12
+ def submit
13
+ service.post(relationships_path_for_node(opts[:node_id]), rel_properties)
14
+ end
15
+
16
+ def rel_properties
17
+ {}.tap do |properties|
18
+ properties[:to] = node_uri(opts[:end_node_id])
19
+ properties[:type] = opts[:rel_type]
20
+ properties[:data] = opts[:data] if opts[:data]
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,17 @@
1
+ module Keymaker
2
+
3
+ # Example request
4
+
5
+ # DELETE http://localhost:7474/db/data/relationship/85
6
+ # Accept: application/json
7
+ # Content-Type: application/json
8
+
9
+ class DeleteRelationshipRequest < Request
10
+
11
+ def submit
12
+ service.delete(relationship_path(opts[:relationship_id]))
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,9 @@
1
+ module Keymaker
2
+ class ExecuteCypherRequest < Request
3
+
4
+ def submit
5
+ service.post(full_cypher_path, opts).body
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Keymaker
2
+ class ExecuteGremlinRequest < Request
3
+
4
+ def submit
5
+ service.post(full_gremlin_path, opts).body
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ module Keymaker
2
+
3
+ module Indexing
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def index_row(index_name)
12
+ indices_traits[index_name] = indices_traits.fetch(index_name, [])
13
+ end
14
+
15
+ # index :threds, on: :name, with: :sanitized_name
16
+ # data structure:
17
+ # { threds: [{ :index_key => :name, :value => :sanitized_name }], users: [{ :index_key => :email, :value => :email }, { :index_key => :username, :value => :username }] }
18
+ def index(index_name,options)
19
+ index_row(index_name.to_s) << { :index_key => options[:on].to_s, :value => options.fetch(:with, options[:on]) }
20
+ end
21
+ end
22
+
23
+ def update_indices
24
+ self.class.indices_traits.each do |index,traits|
25
+ traits.each do |trait|
26
+ neo_service.remove_node_from_index(index, trait[:index_key], send(trait[:value]), node_id)
27
+ neo_service.add_node_to_index(index, trait[:index_key], send(trait[:value]), node_id)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,111 @@
1
+ require 'forwardable'
2
+
3
+ module Keymaker
4
+
5
+ module Node
6
+
7
+ def self.included(base)
8
+ base.extend ActiveModel::Callbacks
9
+ base.extend ClassMethods
10
+
11
+ base.class_eval do
12
+ attr_writer :new_node
13
+ include ActiveModel::MassAssignmentSecurity
14
+
15
+ include Keymaker::Indexing
16
+ include Keymaker::Serialization
17
+
18
+ attr_protected :created_at, :updated_at
19
+ end
20
+
21
+ base.after_save :update_indices
22
+
23
+ base.class_attribute :property_traits
24
+ base.class_attribute :indices_traits
25
+
26
+ base.property_traits = {}
27
+ base.indices_traits = {}
28
+
29
+ base.property :active_record_id, Integer
30
+ base.property :node_id, Integer
31
+ base.property :created_at, DateTime
32
+ base.property :updated_at, DateTime
33
+ end
34
+
35
+ module ClassMethods
36
+
37
+ extend Forwardable
38
+
39
+ def_delegator :Keymaker, :service, :neo_service
40
+
41
+ def properties
42
+ property_traits.keys
43
+ end
44
+
45
+ def property(attribute,type=String)
46
+ property_traits[attribute] = type
47
+ attr_accessor attribute
48
+ end
49
+
50
+ def execute_cypher(query, params={}, return_type=:results_only)
51
+ executed_query = neo_service.execute_query(query, params)
52
+ if executed_query.present?
53
+ case return_type
54
+ when :results_only
55
+ executed_query["data"].flatten
56
+ # TODO: Make this less specific
57
+ when :full_user
58
+ {"user" => executed_query["data"].flatten[0]["data"], "neo_id" => executed_query["data"].flatten[1]}
59
+ end
60
+ else
61
+ return []
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ def initialize(attrs)
68
+ @new_node = true
69
+ process_attrs(attrs) if attrs.present?
70
+ end
71
+
72
+ def neo_service
73
+ self.class.neo_service
74
+ end
75
+
76
+ def new?
77
+ @new_node
78
+ end
79
+
80
+ def sanitize(attrs)
81
+ serializable_hash(except: :node_id).merge(attrs.except('node_id')).reject {|k,v| v.blank?}
82
+ end
83
+
84
+ def save
85
+ create_or_update
86
+ end
87
+
88
+ def create_or_update
89
+ run_callbacks :save do
90
+ new? ? create : update(attributes)
91
+ end
92
+ end
93
+
94
+ def create
95
+ run_callbacks :create do
96
+ neo_service.create_node(sanitize(attributes)).on_success do |response|
97
+ self.node_id = response.neo4j_id
98
+ self.new_node = false
99
+ self
100
+ end
101
+ end
102
+ end
103
+
104
+ def update(attrs)
105
+ process_attrs(sanitize(attrs.merge(updated_at: Time.now.utc.to_i)))
106
+ neo_service.update_node_properties(node_id, sanitize(attributes))
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,34 @@
1
+ module Keymaker
2
+ class PathTraverseRequest < Request
3
+
4
+ # Example request
5
+
6
+ # POST http://localhost:7474/db/data/node/9/traverse/path
7
+ # Accept: application/json
8
+ # Content-Type: application/json
9
+ # {"order":"breadth_first","uniqueness":"none","return_filter":{"language":"builtin","name":"all"}}
10
+
11
+ def submit
12
+ service.post(path_traverse_node_path(opts[:node_id]), path_traverse_properties)
13
+ end
14
+
15
+ def path_traverse_properties
16
+ # :order - breadth_first or depth_first
17
+ # :relationships - all, in, or out
18
+ # :uniqueness - node_global, none, relationship_global, node_path, or relationship_path
19
+ # :prune_evaluator
20
+ # :return_filter
21
+ # :max_depth
22
+
23
+ {}.tap do |properties|
24
+ properties[:order] = opts.fetch(:order, "breadth_first")
25
+ properties[:relationships] = opts.fetch(:relationships, "all")
26
+ properties[:uniqueness] = opts.fetch(:uniqueness, "relationship_global")
27
+ properties[:prune_evaluator] = opts[:prune_evaluator] if opts[:prune_evaluator]
28
+ properties[:return_filter] = opts[:return_filter] if opts[:return_filter]
29
+ properties[:max_depth] = opts[:max_depth] if opts[:max_depth]
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module Keymaker
2
+ class RemoveNodeFromIndexRequest < Request
3
+
4
+ def submit
5
+ service.delete(node_full_index_path(opts[:index_name], opts[:key], opts[:value], opts[:node_id]))
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ module Keymaker
2
+ class Request
3
+
4
+ extend Forwardable
5
+
6
+
7
+ def_delegator :config, :batch_node_path
8
+ def_delegator :config, :batch_path
9
+ def_delegator :config, :full_cypher_path
10
+ def_delegator :config, :full_gremlin_path
11
+ def_delegator :config, :node_full_index_path
12
+ def_delegator :config, :node_index_path
13
+ def_delegator :config, :node_path
14
+ def_delegator :config, :node_properties_path
15
+ def_delegator :config, :node_uri
16
+ def_delegator :config, :path_traverse_node_path
17
+ def_delegator :config, :relationship_path
18
+ def_delegator :config, :relationships_path_for_node
19
+
20
+ def_delegator :response, :body
21
+ def_delegator :response, :status
22
+ def_delegator :response, :faraday_response
23
+ def_delegator :response, :faraday_response=
24
+
25
+ attr_accessor :service, :config, :opts
26
+
27
+ def initialize(service, options)
28
+ self.config = service.config
29
+ self.opts = options
30
+ self.service = service
31
+ end
32
+
33
+ end
34
+ end