keymaker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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