neon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/README.md +432 -0
- data/lib/helpers/argument_helpers.rb +19 -0
- data/lib/helpers/transaction_helpers.rb +56 -0
- data/lib/neon.rb +23 -0
- data/lib/neon/Gemfile +8 -0
- data/lib/neon/node.rb +48 -0
- data/lib/neon/node/embedded.rb +31 -0
- data/lib/neon/node/rest.rb +109 -0
- data/lib/neon/property_container.rb +277 -0
- data/lib/neon/relationship.rb +38 -0
- data/lib/neon/relationship/embedded.rb +59 -0
- data/lib/neon/relationship/rest.rb +73 -0
- data/lib/neon/session.rb +68 -0
- data/lib/neon/session/embedded.rb +92 -0
- data/lib/neon/session/invalid_session.rb +6 -0
- data/lib/neon/session/rest.rb +53 -0
- data/lib/neon/transaction.rb +28 -0
- data/lib/neon/transaction/placebo.rb +38 -0
- data/lib/neon/transaction/rest.rb +47 -0
- data/lib/neon/version.rb +7 -0
- data/lib/tasks.rb +139 -0
- data/neon.gemspec +33 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 469ec95339c4c2ce07910a785305944e57bd2761
|
4
|
+
data.tar.gz: 4d72f7ae078a337c5459a4155e5271b209dd2254
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 32de8575880707bb0ddc9c1d5cb0e04086b497682dfc155ddd1bc96c435e120eb291472e636272bdbcb7290e56421b3b1e36566f1bd4bbfff84cf5d722e2428e
|
7
|
+
data.tar.gz: 163afab9fcf02403e53e567122e47a5c6ccb302a01ba11d0a62169e28aedfdc2a2ed9e7c1e9bf5515c8ff2d9039085b3ef5d03d3cf79af38ef6f390a51e11681
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,432 @@
|
|
1
|
+
Neon
|
2
|
+
====
|
3
|
+
|
4
|
+
# About
|
5
|
+
Neon is fast, minimal ruby binding for the neo4j. It provides a simple api to manipulate a Neo4J database instance hosted on a server or running as an embedded instance.
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
## Creating Sessions
|
9
|
+
You start a session with a Neo4J database by creating a session. You can create a session with a REST server or an Embedded instance as follows :-
|
10
|
+
### REST session
|
11
|
+
```ruby
|
12
|
+
session = Neon::Session::Rest.new(url, options)
|
13
|
+
```
|
14
|
+
|
15
|
+
* `url` defaults to http://localhost:7474
|
16
|
+
* `options` defaults to {} and takes the following keys :-
|
17
|
+
```ruby
|
18
|
+
options = {
|
19
|
+
directory: '', # Prefix the url with this directory
|
20
|
+
cypher: '/cypher', # Cypher path for the server
|
21
|
+
gremlin: '/ext/GremlinPlugin/graphdb/execute_script', # Gremlin path for the server
|
22
|
+
log: false, # Log activity or not
|
23
|
+
log_path: 'neon.log', # File name used for the log if :log is true
|
24
|
+
threads: 20, # Maximum number of threads to use
|
25
|
+
authentication: nil, # 'basic' or 'digest'
|
26
|
+
username: nil,
|
27
|
+
password: nil,
|
28
|
+
parser: MultiJsonParser
|
29
|
+
}
|
30
|
+
```
|
31
|
+
|
32
|
+
You can also quickly initialize using
|
33
|
+
```ruby
|
34
|
+
session = Neon::Session::Rest.new("http://username:password@localhost:7474/mydirectory")
|
35
|
+
```
|
36
|
+
|
37
|
+
### Embedded session
|
38
|
+
```ruby
|
39
|
+
session = Neon::Session::Embedded.new(path_to_db, auto_tx, impermanent)
|
40
|
+
```
|
41
|
+
|
42
|
+
* `path_to_db` is the path to the embedded database instance. You can use a symbol `:impermanent` to create an impermanent instance.
|
43
|
+
* `auto_tx` is an optional boolean parameter that is true by default.
|
44
|
+
|
45
|
+
The first session created is the default session used by Neon modules. It can be accessed using the current attribute.
|
46
|
+
```ruby
|
47
|
+
Neon::Session.current
|
48
|
+
# Set another session to be current. You get a boolean on whether it was successful or not
|
49
|
+
Neon::Session.set_current another_session # Returns true or false
|
50
|
+
# You can also do this
|
51
|
+
Neon::Session.current = another_session
|
52
|
+
another_session.current? # Check if this session is the current session or not?
|
53
|
+
```
|
54
|
+
## Using Sessions
|
55
|
+
Irrespective of the underlying session, you can use a common api there onwards i.e. you only make a choice on the session type during initialization and then forget about it. All further entities are linked to this session and provide a common interface for all core graph operations.
|
56
|
+
```ruby
|
57
|
+
# Creating a session does not start it. You can start the session as follows
|
58
|
+
started? = session.start
|
59
|
+
|
60
|
+
# Stop a session
|
61
|
+
closed? = session.close
|
62
|
+
|
63
|
+
# Check if a session has been started i.e. is it running?
|
64
|
+
running? = session.running?
|
65
|
+
|
66
|
+
# All instance method calls are also available on Session and are called on the current session e.g.
|
67
|
+
started? = Neon::Session.start
|
68
|
+
close? = Neon::Session.close
|
69
|
+
running? = Neon::Session.running? # and others
|
70
|
+
|
71
|
+
# Location of the database
|
72
|
+
session.location
|
73
|
+
```
|
74
|
+
|
75
|
+
### Auto Transaction support
|
76
|
+
* For REST sessions this has no consequence as auto_tx cannot be disabled. You can though run multiple statements using transactions.
|
77
|
+
* For Embedded sessions auto_tx means all graph operations will be automatically
|
78
|
+
wrapped inside a transaction which will be committed.
|
79
|
+
You can disable auto_tx in which case all operations will need to be wrapped inside a transaction by the developer.
|
80
|
+
```ruby
|
81
|
+
session.auto_tx # Defaults to true
|
82
|
+
session.auto_tx = false
|
83
|
+
Neon::Session.auto_tx = false # Disable auto transaction on the current session
|
84
|
+
```
|
85
|
+
|
86
|
+
## Property Container
|
87
|
+
Both nodes and relationships are property containers and respond in exactly the same way to the following interface :-
|
88
|
+
```ruby
|
89
|
+
# ================================================================
|
90
|
+
# Property Container
|
91
|
+
# These methods are available on Neon::Node and Neon::Relationship
|
92
|
+
# with the same signature and behaviour.
|
93
|
+
# ================================================================
|
94
|
+
|
95
|
+
# Get a container's session
|
96
|
+
session = container.session
|
97
|
+
|
98
|
+
# Get a single key
|
99
|
+
value = container[:key] # Returns the value
|
100
|
+
|
101
|
+
# Get one or more properties
|
102
|
+
value1, value2 = container[:key1, :key2]
|
103
|
+
values = container[:key1, :key2] # Returns an array on a query of multiple propeties
|
104
|
+
|
105
|
+
# Non existing properties return nil
|
106
|
+
value = container[:invalid_key]
|
107
|
+
value, nil_value = container[:key, :invalid_key]
|
108
|
+
# The last argument can be a hash. If it is provided then you can specify a default value
|
109
|
+
# instead of nil for non exisitng keys.
|
110
|
+
value, hello_world = container[:key, :invalid_key] default: 'hello world'
|
111
|
+
|
112
|
+
# Set one or more properties
|
113
|
+
container[:key1, :key2] = value1, value2
|
114
|
+
container[:key1, :key2] = values # an array
|
115
|
+
|
116
|
+
container[:invalid_key] = value # Creates a key :invalid_key and sets it to value
|
117
|
+
container[nil] = value # Calls #to_s on every key. If multiple properties resolve to same name
|
118
|
+
# then the last value
|
119
|
+
# is used.
|
120
|
+
|
121
|
+
container[valid_key] = invalid_value # anything besides String, Symbol, Numeric, true, false or an
|
122
|
+
#array of these (all objects of the same type) is going to throw an exception InvalidValueError.
|
123
|
+
# There is an exception to this where setting a key to nil deletes it. Setting a non existing
|
124
|
+
# key to nil does nothing.
|
125
|
+
|
126
|
+
# Get all keys
|
127
|
+
keys = container.keys
|
128
|
+
|
129
|
+
# Check if a key exists
|
130
|
+
container.key?('a key') # Returns true or false on the basis of wether 'a key' exists or not
|
131
|
+
```
|
132
|
+
|
133
|
+
## Creating Nodes
|
134
|
+
Nodes are created in context to a session which determines their underlying model and data access methods. The only time you need to be think about using sessions during creation. Thereafter you can use the same api irrespective of the database.
|
135
|
+
### Create a new node
|
136
|
+
```ruby
|
137
|
+
# Create a new node
|
138
|
+
node = Neon::Node.new(attributes, label1, label2, session)
|
139
|
+
```
|
140
|
+
|
141
|
+
* `attributes` is an optional hash consisting of the key-value pairs you would like this node to be initialized with. Defaults to {}
|
142
|
+
* `labels` - You can provide a comma separated list of labels or an array of labels
|
143
|
+
* `session` - the last argument is an optional session. It defines the database where you want to create the node and defaults to the current session
|
144
|
+
|
145
|
+
All of these arguments can be used in any combination as long as the order remains the same. Skipping all of them creates an empty node in the current session. Here are all various ways to create a node :-
|
146
|
+
```ruby
|
147
|
+
# Empty node in the current session
|
148
|
+
node = Neon::Node.new
|
149
|
+
|
150
|
+
# A node with attributes in the current session and no labels
|
151
|
+
node = Neon::Node.new(name: :name, email: :email)
|
152
|
+
|
153
|
+
# A node with attributes in the given session
|
154
|
+
node = Neon::Node.new({name: :name}, another_session)
|
155
|
+
|
156
|
+
# A node with attributes and labels in the current session
|
157
|
+
node = Neon::Node.new({name: :name, sex: 'male'}, :User, :Man)
|
158
|
+
node = Neon::Node.new({name: :name, sex: 'male'}, [:User, :Man])
|
159
|
+
|
160
|
+
# A node with attributes and labels in the given session
|
161
|
+
node = Neon::Node.new({name: :name, sex: 'male'}, :User, :Man, another_session)
|
162
|
+
node = Neon::Node.new({name: :name, sex: 'male'}, [:User, :Man], another_session)
|
163
|
+
|
164
|
+
# An empty node in the given session
|
165
|
+
node = Neon::Node.new another_session
|
166
|
+
|
167
|
+
# A node with labels in the current session
|
168
|
+
node = Neon::Node.new(:User)
|
169
|
+
node = Neon::Node.new([:User])
|
170
|
+
|
171
|
+
# A node with labels in the given session
|
172
|
+
node = Neon::Node.new(:User, another_session)
|
173
|
+
node = Neon::Node.new([:User], another_session)
|
174
|
+
```
|
175
|
+
|
176
|
+
### Loading Nodes
|
177
|
+
Existing nodes can be loaded by providing an id. Non existing ids return nil.
|
178
|
+
```ruby
|
179
|
+
# Returns the load with id 5
|
180
|
+
node = Neon::Node.load(5)
|
181
|
+
same_node = Neon::Node.load(node.id)
|
182
|
+
invalid_node = Neon::Node.load(:non_existent_id)
|
183
|
+
```
|
184
|
+
|
185
|
+
## Using Nodes
|
186
|
+
Node instances have a well defined api to work with which is uniform irrespective of the underlying session.
|
187
|
+
```ruby
|
188
|
+
# Get a node's id
|
189
|
+
id = node.id
|
190
|
+
|
191
|
+
# ==============
|
192
|
+
# Node methods
|
193
|
+
# ==============
|
194
|
+
|
195
|
+
# Get all labels of a node
|
196
|
+
node.labels # => ['label', 'User', 'Programmer']
|
197
|
+
|
198
|
+
# Add one or more labels
|
199
|
+
# Labels can be anything that responds to #to_s
|
200
|
+
node.add_labels(:runner, :biker)
|
201
|
+
node.add_labels([:runner, biker])
|
202
|
+
|
203
|
+
# Remove one or more labels
|
204
|
+
node.remove_labels(:runner, :biker)
|
205
|
+
node.remove_labels([:runner, :biker])
|
206
|
+
|
207
|
+
# Check if a node has a label
|
208
|
+
node.label?(:Runner) # => returns true or false
|
209
|
+
|
210
|
+
# Create a relationship to another node
|
211
|
+
node.create_rel_to(another_node, :RELATIONSHIP_TYPE)
|
212
|
+
# If both node and another_node do not have the same database location then it throws a CrossSessionError
|
213
|
+
|
214
|
+
# Get all relationships
|
215
|
+
node.rels
|
216
|
+
|
217
|
+
# Get all relationships in a particular direction
|
218
|
+
node.rels(dir: :incoming)
|
219
|
+
node.rels(dir: :outgoing)
|
220
|
+
|
221
|
+
# Get relationships to nodes with particular labels
|
222
|
+
node.rels(labels: [:friend, :jusband])
|
223
|
+
|
224
|
+
# Get relationships of a particular type by passing a options hash with type key.
|
225
|
+
# It can be anything that responds to #to_s
|
226
|
+
# Both directions
|
227
|
+
node.rels(type: relationship_type) # a variable that responds to #to_s. The response to #to_s is used
|
228
|
+
node.rels(type: [:relationship_type, :another_relationship_type])
|
229
|
+
# Get relationships between two nodes
|
230
|
+
node.rels(between: another_node)
|
231
|
+
# You can mix and match various options to create filters.
|
232
|
+
|
233
|
+
# Get a single relationship if it exists or nil
|
234
|
+
node.rel(:incoming, type: [:RELATIONSHIP_TYPE, :ANOTHER_RELATIONSHIP_TYPE])
|
235
|
+
|
236
|
+
# Check if a node has a particular relationship
|
237
|
+
node.rel? # Any relationships?
|
238
|
+
node.rel?(dir: :incoming) or node.rel?(:outgoing) # Any incoming or outgoing relationships
|
239
|
+
node.rel?(type: :RELATIONSHIP_TYPE) # => true or false
|
240
|
+
node.rel?(dir: :incoming, type: :RELATIONSHIP_TYPE) # => true or false
|
241
|
+
|
242
|
+
# Delete a node its relationships
|
243
|
+
# Raises exception if there are relationships.
|
244
|
+
node.delete # => return true or false if node was deleted
|
245
|
+
|
246
|
+
# Delete a node and it's relationships.
|
247
|
+
node.delete! # => return true or false if node was destroyed or not
|
248
|
+
```
|
249
|
+
|
250
|
+
## Relationships
|
251
|
+
### Creating Relationships
|
252
|
+
Relationships are created in context to a node as specified in the node api.
|
253
|
+
You can load relationships the same way as nodes.
|
254
|
+
|
255
|
+
### Using relationships
|
256
|
+
Relationships are property containers and therefore responds to all methods that node does under the property container section
|
257
|
+
```ruby
|
258
|
+
# Get the nodes
|
259
|
+
start_node, end_node = rel.nodes # => A 2 element array
|
260
|
+
|
261
|
+
# Get start node
|
262
|
+
rel.start
|
263
|
+
|
264
|
+
# Get end node
|
265
|
+
rel.end
|
266
|
+
|
267
|
+
# Get the other node or nil if the supplied node is not part of the relationship
|
268
|
+
rel.other(node)
|
269
|
+
|
270
|
+
# Get the type of a node. This is a object that responds to #to_s
|
271
|
+
rel.type
|
272
|
+
|
273
|
+
# Check if the relationship is of a particular type. Type can be anything that responds to #to_s
|
274
|
+
rel.type?(a_type)
|
275
|
+
```
|
276
|
+
|
277
|
+
## Transactions
|
278
|
+
Besides support for auto transactions, one can run transaction explicitly. Beginning a transaction turns off auto_tx until the end of the transaction. At the end auto_tx is restored to it's original status. You can run at most one transaction per thread.
|
279
|
+
```ruby
|
280
|
+
# Begin a transaction. If a transaction is already on then it is returned.
|
281
|
+
# Run them in a begin-rescue block since they might throw an exception
|
282
|
+
tx = session.begin_tx
|
283
|
+
begin
|
284
|
+
# Do some graph operations
|
285
|
+
tx.failure # mark transaction for failure. Marked by default
|
286
|
+
rescue Exception => e
|
287
|
+
# handle execption
|
288
|
+
else
|
289
|
+
tx.success # mark transaction for success if no exception occured
|
290
|
+
ensure
|
291
|
+
tx.close # commit or rollback on the basis of wether tx was marked for success or failure
|
292
|
+
end
|
293
|
+
|
294
|
+
# Run a quick transaction. This fires a new transaction in a new thread.
|
295
|
+
# t is the transaction. You can call success or failure on it. Don't call close as it will throw
|
296
|
+
# an exception later. An optional session can be passed to run the transaction on. Defaults to current session.
|
297
|
+
Transaction.run(optional_session) do |t|
|
298
|
+
# Do some graph operations.
|
299
|
+
# If you do something here that does not pertain to the session that t belongs to then it will
|
300
|
+
# not be a part of the transaction
|
301
|
+
end
|
302
|
+
|
303
|
+
# t is marked for success by default. This allows you run quick one method calls like
|
304
|
+
# The result of the block is returned.
|
305
|
+
result = Transaction.run { do_something_graphy }
|
306
|
+
```
|
307
|
+
|
308
|
+
## Indexes
|
309
|
+
Indexes can be set upon nodes as well as relationships using legacy indexing.
|
310
|
+
### Node Indexes
|
311
|
+
Legacy indexing on nodes can be performed as following :-
|
312
|
+
#### Fetch all node indexes
|
313
|
+
```ruby
|
314
|
+
# An array of indexes
|
315
|
+
indexes = Neon::Node.indexes
|
316
|
+
```
|
317
|
+
|
318
|
+
#### Check if an index exists
|
319
|
+
```ruby
|
320
|
+
index_exists? = Neon::Node.index?("User")
|
321
|
+
```
|
322
|
+
|
323
|
+
#### Create an index
|
324
|
+
```ruby
|
325
|
+
# Default type is exact and provider is lucene
|
326
|
+
index = Neon::Node.create_index("User", :exact, :lucene)
|
327
|
+
```
|
328
|
+
|
329
|
+
#### Get a node from an index
|
330
|
+
```ruby
|
331
|
+
# Exact match
|
332
|
+
hits = user_index.get(key, value) # hits is an enumerable
|
333
|
+
|
334
|
+
# Get a single node if it exists or nil
|
335
|
+
node_or_nil = hits.single
|
336
|
+
|
337
|
+
# Use lucene queries
|
338
|
+
hits = user_index.query(key, query)
|
339
|
+
|
340
|
+
hits = user_index.query(query)
|
341
|
+
```
|
342
|
+
|
343
|
+
#### Add a node to an index
|
344
|
+
```ruby
|
345
|
+
user_index.add(node, :email, node[:email])
|
346
|
+
```
|
347
|
+
|
348
|
+
#### Remove a node from an index
|
349
|
+
```ruby
|
350
|
+
# Remove a node from user index for the given key-value pair
|
351
|
+
user_index.remove(node, :email, node[:email])
|
352
|
+
|
353
|
+
# Remove a node from user index for the given key
|
354
|
+
user_index.remove(node, :email)
|
355
|
+
|
356
|
+
# Remove a node from user index
|
357
|
+
user_index.remove(node)
|
358
|
+
```
|
359
|
+
|
360
|
+
#### Delete an index
|
361
|
+
```ruby
|
362
|
+
user_index.delete
|
363
|
+
|
364
|
+
# Get the score(number of hits) of the most recently fetched node
|
365
|
+
sore = hits.score
|
366
|
+
```
|
367
|
+
|
368
|
+
#### Auto Indexing
|
369
|
+
```ruby
|
370
|
+
# Get the auto index
|
371
|
+
auto_index = Neon::Node.index
|
372
|
+
|
373
|
+
# Enable the auto_index
|
374
|
+
auto_index.status = true
|
375
|
+
|
376
|
+
# Disable the auto_index
|
377
|
+
auto_index.status = false
|
378
|
+
|
379
|
+
# Add a property to the auto index
|
380
|
+
auto_index.add_property(property)
|
381
|
+
|
382
|
+
# Remove a property to the auto index
|
383
|
+
auto_index.remove_property(property)
|
384
|
+
|
385
|
+
# Get nodes with a key-value pair
|
386
|
+
hits = auto_index.get(key, value)
|
387
|
+
|
388
|
+
# Query nodes
|
389
|
+
hits = auto_index.query(query)
|
390
|
+
```
|
391
|
+
|
392
|
+
### Relationship Indexes
|
393
|
+
Exactly same api as nodes
|
394
|
+
### Schema Indexes
|
395
|
+
This is the preferred way of indexing in Neo4J 2.0. Schema indexes are associated with labels on nodes only and not relationships.
|
396
|
+
#### Create an index on a label and properties
|
397
|
+
```ruby
|
398
|
+
Neon::Node.create_index_on(:Person, property1, property2)
|
399
|
+
```
|
400
|
+
|
401
|
+
#### Find nodes from a schema index
|
402
|
+
```ruby
|
403
|
+
# options is a hash of key-value pairs. All nodes matching all values for the given keys are returned.
|
404
|
+
hits = Neon::Node.index_for(:Person, options)
|
405
|
+
```
|
406
|
+
|
407
|
+
#### Delete an index on a label with properties
|
408
|
+
```ruby
|
409
|
+
Neon::Node.delete_index_on(:Person, property1, propert2)
|
410
|
+
```
|
411
|
+
|
412
|
+
#### Add a unique contraint to a label
|
413
|
+
```ruby
|
414
|
+
# constraint is :unique by default and as not other contraint is supported right now
|
415
|
+
# we do not take a contraint argument. This makes our api backwards compatible
|
416
|
+
Neon::Node.create_contraint_on(:Person, property1, propert2)
|
417
|
+
```
|
418
|
+
|
419
|
+
#### Drop a unique contraint on a label
|
420
|
+
```ruby
|
421
|
+
Neon::Node.drop_contraint_on(:Person, property1, propert2)
|
422
|
+
```
|
423
|
+
|
424
|
+
There is a shorthand to creating a unique node
|
425
|
+
#### Create a unique node
|
426
|
+
```ruby
|
427
|
+
# arguments are the same as Neon::Node.new
|
428
|
+
unique_ndoe = Neon::Node.uniq(arguments)
|
429
|
+
```
|
430
|
+
|
431
|
+
## Traversal
|
432
|
+
TODO
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Neon
|
2
|
+
# Helpers for processing method arguments.
|
3
|
+
module ArgumentHelpers
|
4
|
+
# Extracts a session from the array of arguments if one exists at the end.
|
5
|
+
#
|
6
|
+
# @param args [Array] an array of arguments of any type.
|
7
|
+
#
|
8
|
+
# @return [Session] a session if the last argument is a valid session and pops it out of args.
|
9
|
+
# Otherwise it returns the current session.
|
10
|
+
#
|
11
|
+
def extract_session(args)
|
12
|
+
if args.last.is_a?(Session::Rest) || args.last.is_a?(Session::Embedded)
|
13
|
+
args.pop
|
14
|
+
else
|
15
|
+
Session.current
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|