neon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem "neo4j-community", "~> 2.0.0", platforms: :jruby
5
+
6
+ gem "byebug", platforms: :ruby
@@ -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