Neo4j Ruby Driver
This repository contains 2 implementation of a Neo4j driver for Ruby:
- based on official Java implementation. It provides a thin wrapper over the Java driver (only on jruby).
- pure Ruby implementation. Available on all Ruby versions >= 3.1.
Network communication is handled using Bolt Protocol.
- Getting started
- Installation
- Getting a Neo4j instance
- Quick start example
- Server Compatibility
- Usage
- Connecting to a database
- URI schemes
- Authentication
- Configuration
- Connectivity check
- Sessions & transactions
- Session
- Auto-commit transactions
- Explicit transactions
- Read transactions
- Write transactions
- Working with results
- Accessing Node and Relationship data
- Working with Paths
- Working with temporal types
- Type mapping
- Advanced
- Connection pooling
- Logging
- Connecting to a database
- For Driver Engineers
- Testing
- Contributing
- License
Getting started
Installation
Add this line to your application's Gemfile:
gem 'neo4j-ruby-driver'
And then execute:
bundle install
Or install it yourself as:
gem install neo4j-ruby-driver
Getting a Neo4j instance
You need a running Neo4j database in order to use the driver with it. The easiest way to spin up a local instance is through a Docker container.
The command below runs the latest Neo4j version in Docker, setting the admin username and password to neo4j
and
password
respectively:
docker run \
-p7474:7474 \
-p7687:7687 \
-d \
-e NEO4J_AUTH=neo4j/password \
neo4j:latest
Quick start example
require 'neo4j/driver'
Neo4j::Driver::GraphDatabase.driver(
'bolt://localhost:7687',
Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
) do |driver|
driver.session(database: 'neo4j') do |session|
query_result = session.run('RETURN 2+2 AS value')
puts "2+2 equals #{query_result.single['value']}"
# consume gives the execution summary
create_result = session.run('CREATE (n)').consume
puts "Nodes created: #{create_result.counters.nodes_created}"
end
end
Server Compatibility
The compatibility with Neo4j Server versions is documented in the Neo4j Knowledge Base.
Usage
The API is to highest possible degree consistent with the official Java driver. Please refer to the Neo4j Java Driver Manual, examples in Ruby, and code snippets below to understand how to use it. Neo4j Java Driver API Docs can be helpful as well.
Connecting to a database
URI schemes
The driver supports the following URI schemes:
URI Scheme | Description |
---|---|
neo4j:// |
Connect using routing to a cluster/causal cluster. |
neo4j+s:// |
Same as neo4j:// but with full TLS encryption. |
neo4j+ssc:// |
Same as neo4j:// but with full TLS encryption, without hostname verification. |
bolt:// |
Connect directly to a server using the Bolt protocol. |
bolt+s:// |
Same as bolt:// but with full TLS encryption. |
bolt+ssc:// |
Same as bolt:// but with full TLS encryption, without hostname verification. |
Example:
# Connect to a single instance
driver = Neo4j::Driver::GraphDatabase.driver(
'bolt://localhost:7687',
Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
)
# Connect to a cluster
driver = Neo4j::Driver::GraphDatabase.driver(
'neo4j://graph.example.com:7687',
Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
)
Authentication
The driver provides multiple authentication methods:
# Basic authentication
auth = Neo4j::Driver::AuthTokens.basic('neo4j', 'password')
# With realm specification
auth = Neo4j::Driver::AuthTokens.basic('neo4j', 'password', 'realm')
# Kerberos authentication
auth = Neo4j::Driver::AuthTokens.kerberos('ticket')
# Bearer authentication
auth = Neo4j::Driver::AuthTokens.bearer('token')
# Custom authentication
auth = Neo4j::Driver::AuthTokens.custom('principal', 'credentials', 'realm', 'scheme')
# No authentication
auth = Neo4j::Driver::AuthTokens.none
Configuration
You can configure the driver with additional options:
config = {
connection_timeout: 15.seconds,
connection_acquisition_timeout: 1.minute,
max_transaction_retry_time: 30.seconds,
encryption: true,
trust_strategy: :trust_all_certificates
}
driver = Neo4j::Driver::GraphDatabase.driver(
'neo4j://localhost:7687',
Neo4j::Driver::AuthTokens.basic('neo4j', 'password'),
**config
)
Connectivity check
if driver.verify_connectivity
puts "Driver is connected to the database"
else
puts "Driver cannot connect to the database"
end
Sessions & transactions
The driver provides sessions to interact with the database and to execute queries.
Session
Sessions are lightweight and disposable database connections. Always close your sessions when done:
session = driver.session(database: 'neo4j')
begin
session.run('MATCH (n) RETURN n LIMIT 10')
ensure
session.close
end
Or use a block that automatically closes the session:
driver.session(database: 'neo4j') do |session|
session.run('MATCH (n) RETURN n LIMIT 10')
end
Session options:
# Default database
session = driver.session
# Specific database
session = driver.session(database: 'neo4j')
# With access mode
session = driver.session(database: 'neo4j', default_access_mode: Neo4j::Driver::AccessMode::READ)
# With bookmarks for causal consistency
session = driver.session(
database: 'neo4j',
bookmarks: [Neo4j::Driver::Bookmark.from('bookmark-1')]
)
Auto-commit transactions
For simple, one-off queries, use auto-commit transactions:
session.run('CREATE (n:Person {name: $name})', name: 'Alice')
Explicit transactions
For multiple queries that need to be executed as a unit, use explicit transactions:
tx = session.begin_transaction
begin
tx.run('CREATE (n:Person {name: $name})', name: 'Alice')
tx.run('CREATE (n:Person {name: $name})', name: 'Bob')
tx.commit
rescue
tx.rollback
raise
end
Read transactions
Specifically for read operations:
result = session.read_transaction do |tx|
tx.run('MATCH (n:Person) RETURN n.name').map { |record| record['n.name'] }
end
puts result
Write transactions
Specifically for write operations:
session.write_transaction do |tx|
tx.run('CREATE (n:Person {name: $name})', name: 'Charlie')
end
Working with results
result = session.run('MATCH (n:Person) RETURN n.name AS name, n.age AS age')
# Process results
result.each do |record|
puts "#{record['name']} is #{record['age']} years old"
end
# Check if there are more results
puts "Has more results: #{result.has_next?}"
# Get a single record
single = result.single
puts single['name'] if single
# Get keys available in the result
puts "Keys: #{result.keys}"
# Access by field index
result.each do |record|
puts "First field: #{record[0]}"
end
# Convert to array
records = result.to_a
Accessing Node and Relationship data
Working with graph entities:
result = session.run('MATCH (p:Person)-[r:KNOWS]->(friend) RETURN p, r, friend')
result.each do |record|
# Working with nodes
person = record['p']
puts "Node ID: #{person.id}"
puts "Labels: #{person.labels.join(', ')}"
puts "Properties: #{person.properties}"
puts "Name property: #{person.properties['name']}"
# Working with relationships
relationship = record['r']
puts "Relationship ID: #{relationship.id}"
puts "Type: #{relationship.type}"
puts "Properties: #{relationship.properties}"
# Start and end nodes of the relationship
puts "Relationship: #{relationship.start_node_id} -> #{relationship.end_node_id}"
end
Working with Paths
Processing paths returned from Cypher:
result = session.run('MATCH p = (:Person)-[:KNOWS*]->(:Person) RETURN p')
result.each do |record|
path = record['p']
# Get all nodes in the path
nodes = path.nodes
puts "Nodes in path: #{nodes.map { |n| n.properties['name'] }.join(' -> ')}"
# Get all relationships in the path
relationships = path.relationships
puts "Relationship types: #{relationships.map(&:type).join(', ')}"
# Iterate through the path segments
path.each do |segment|
puts "#{segment.start_node.properties['name']} -[#{segment.relationship.type}]-> #{segment.end_node.properties['name']}"
end
end
Working with temporal types
Creating a node with properties of temporal types:
session.run(
'CREATE (e:Event {datetime: $datetime, duration: $duration})',
datetime: DateTime.new(2025, 5, 5, 5, 55, 55), duration: 1.hour
)
Querying temporal values:
session.run('MATCH (e:Event) LIMIT 1 RETURN e.datetime, e.duration').single.to_h
# => {"e.datetime": 2025-05-05 05:55:55 +0000, "e.duration": 3600 seconds}
Type mapping
The Neo4j Ruby Driver maps Cypher types to Ruby types:
Cypher Type | Ruby Type |
---|---|
null | nil |
List | Enumerable |
Map | Hash (symbolized keys) |
Boolean | TrueClass/FalseClass |
Integer | Integer/String1 |
Float | Float |
String | String/Symbol2 (encoding: UTF-8) |
ByteArray | String (encoding: BINARY) |
Date | Date |
Zoned Time | Neo4j::Driver::Types::OffsetTime |
Local Time | Neo4j::Driver::Types::LocalTime |
Zoned DateTime | Time/ActiveSupport::TimeWithZone/DateTime3 |
Local DateTime | Neo4j::Driver::Types::LocalDateTime |
Duration | ActiveSupport::Duration |
Point | Neo4j::Driver::Types::Point |
Node | Neo4j::Driver::Types::Node |
Relationship | Neo4j::Driver::Types::Relationship |
Path | Neo4j::Driver::Types::Path |
Advanced
Connection pooling
The driver handles connection pooling automatically. Configure the connection pool:
config = {
max_connection_pool_size: 100,
max_connection_lifetime: 1.hour
}
driver = Neo4j::Driver::GraphDatabase.driver('neo4j://localhost:7687', auth, **config)
Logging
Configure logging for the driver:
config = {
logger: Logger.new(STDOUT).tap { |log| log.level = Logger::DEBUG }
}
driver = Neo4j::Driver::GraphDatabase.driver('neo4j://localhost:7687', auth, **config)
For Driver Engineers
This gem includes 2 different implementations: a Java driver wrapper and a pure Ruby driver, so you will have to run this command every time you switch the Ruby engine:
bin/setup
Testing
There are two sets of tests for the driver. To run the specs placed in this repository, use a normal rspec command:
rspec spec
To run the Testkit that is used to test all Neo4j driver implementations, use the following:
git clone git@github.com:neo4j-drivers/testkit.git
cd testkit
export TEST_DRIVER_NAME=ruby
export TEST_DRIVER_REPO=`realpath ../neo4j-ruby-driver`
export TEST_NEO4J_PASS=password
python3 main.py --tests UNIT_TESTS --configs 4.3-enterprise
Please refer to the Testkit documentation to learn more about its features.
Contributing
Suggestions, improvements, bug reports and pull requests are welcome on GitHub at https://github.com/neo4jrb/neo4j-ruby-driver.
License
The gem is available as open source under the terms of the MIT License.
Footnotes
-
An Integer smaller than -2 ** 63 or larger than 2 ** 63 will always be implicitly converted to String ↩
-
A Symbol passed as a parameter will always be implicitly converted to String. All Strings other than BINARY encoded are converted to UTF-8 when stored in Neo4j ↩
-
A Ruby DateTime passed as a parameter will always be implicitly converted to Time ↩