ActiveCypher is a Ruby gem that provides an ActiveRecord-like interface for interacting with graph databases using the OpenCypher query language. It aims to simplify graph database operations within Ruby and Ruby on Rails applications by leveraging familiar patterns from ActiveModel.
The core of ActiveCypher includes:
- Cyrel: A powerful Domain Specific Language (DSL) for building Cypher queries programmatically in Ruby.
- ActiveModel Integration: Leverages ActiveModel conventions for a familiar development experience.
- Rails Engine: Seamlessly integrates into Ruby on Rails applications.
Installation
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
$ gem install activecypher
Database Requirements
ActiveCypher supports the following graph databases:
| Database | Minimum Version | Notes |
|---|---|---|
| Memgraph | 3.7+ | Required for composite indexes, vector search, text indexes |
| Neo4j | 2025 (Cypher 25) | Calendar versioning; vector relationship indexes supported |
Note: Earlier versions may work but are untested. Memgraph v3.7+ is recommended for full feature support.
Configuration
No manual initialization required!
ActiveCypher automatically loads your database configuration from config/cypher_databases.yml (similar to how Rails uses database.yml). You do not need to manually initialize or configure adapters in an initializer.
To configure your graph database connections, simply run the install generator:
bin/rails generate active_cypher:install
This will create a config/cypher_databases.yml file in your Rails application. You can then define connections for different environments and roles. For example:
# config/cypher_databases.yml development: primary: url: memgraph://memgraph:activecypher@localhost:17688 neo4j: url: <%= ENV.fetch('NEO4J_URL', 'neo4j://neo4j:activecypher@localhost:17687') %> migrations_paths: graphdb/neo4j test: primary: url: memgraph://memgraph:activecypher@localhost:17688 neo4j: url: <%= ENV.fetch('NEO4J_URL', 'neo4j://neo4j:activecypher@localhost:17687') %> migrations_paths: graphdb/neo4j production: primary: url: memgraph+ssc://user:pass@memgraph:7687
ActiveCypher will automatically pick up the correct configuration for the current Rails environment.
You do not need to call any setup code in your test helper or application initializer. Connections are managed automatically, just like ActiveRecord.
Environment Variable Configuration
Similar to ActiveRecord's DATABASE_URL, ActiveCypher supports the GRAPHDB_URL environment variable as a standard way to configure your primary graph database connection. The adapter type is automatically detected from the URL scheme:
neo4j://→ Neo4j adapter (no SSL)neo4j+ssl://→ Neo4j adapter (SSL with CA-signed certificate)neo4j+ssc://orneo4j+s://→ Neo4j adapter (SSL with self-signed certificate)memgraph://→ Memgraph adapter (no SSL)memgraph+ssl://→ Memgraph adapter (SSL with CA-signed certificate)memgraph+ssc://ormemgraph+s://→ Memgraph adapter (SSL with self-signed certificate)
Example:
# For Neo4j export GRAPHDB_URL="neo4j://username:password@localhost:17687/database_name" # For Memgraph export GRAPHDB_URL="memgraph://username:password@localhost:17688"
When GRAPHDB_URL is set, it takes precedence over the corresponding entry in cypher_databases.yml for the primary connection.
Connecting Models to Different Databases
You can configure each node (model) class to use a specific connection by using the connects_to class method. This allows you to route different models to different databases or roles.
There are two syntaxes for connects_to:
- Full mapping (separate writing/reading roles):
class ApplicationGraphNode < ActiveCypher::Base self.abstract_class = true connects_to writing: :primary, reading: :primary end
- Shorthand (same key for both roles):
class Neo4jRecord < ActiveCypher::Base self.abstract_class = true # Equivalent to writing: :neo4j, reading: :neo4j connects_to :neo4j end
- All models inheriting from
ApplicationGraphNodewill use theprimaryconnection (e.g., Memgraph). - Models inheriting from
Neo4jRecord(or usingconnects_to :neo4j) will use theneo4jconnection.
This makes it easy to work with multiple databases in the same application, and to direct reads/writes as needed.
Refer to the test files (e.g., test/bolt/connection_test.rb and test/bolt/session_test.rb) for more usage examples.
Usage
ActiveCypher allows you to define models representing nodes and relationships in your graph, using Ruby classes. See below for real examples from test/dummy/app/graph:
Example: Defining Node and Relationship Models
# app/graph/person_node.rb class PersonNode < ApplicationGraphNode attribute :id, :string attribute :name, :string attribute :age, :integer attribute :active, :boolean, default: true validates :name, presence: true end # app/graph/conspiracy_node.rb class ConspiracyNode < ApplicationGraphNode attribute :name, :string attribute :description, :string attribute :believability_index, :integer has_many :followers, class_name: 'PersonNode', relationship: 'BELIEVES_IN', direction: :in, relationship_class: 'BelievesInRel' end # app/graph/believes_in_rel.rb class BelievesInRel < ApplicationGraphRelationship from_class 'PersonNode' to_class 'ConspiracyNode' type 'BELIEVES_IN' attribute :reddit_karma_spent, :integer attribute :level_of_devotion, :string # "casual", "zealot", "makes merch" end
Relationship Base and Node Base Convention (a.k.a. “Let’s Not Repeat Ourselves, Please”)
ActiveCypher is here to save you from yourself (and your future self at 3am). When you define an abstract relationship base class—say, ApplicationGraphRelationship—ActiveCypher will, by convention, automatically pair it up with the corresponding abstract node base class (like ApplicationGraphNode). It’s like Tinder for your base classes, but with less ghosting and more database connections.
- If your relationship base class is named
XxxRelationship, and you have a node base class namedXxxNodein the same namespace, ActiveCypher will ship them together faster than your favorite fandom. - You do not need to manually configure this association in most cases. Go ahead, be lazy. We encourage it.
For example:
# app/graph/application_graph_node.rb class ApplicationGraphNode < ActiveCypher::Base self.abstract_class = true connects_to writing: :primary, reading: :primary end # app/graph/application_graph_relationship.rb class ApplicationGraphRelationship < ActiveCypher::Relationship self.abstract_class = true # No need to specify node_base_class; ActiveCypher will play matchmaker and default to ApplicationGraphNode end
If you’re a control freak (or just like to break the rules), you can explicitly set the node base class in your relationship base using:
class MyRelationshipBase < ActiveCypher::Relationship self.abstract_class = true node_base_class MyNodeBase end
This ensures all relationships inheriting from your abstract relationship base will always use the correct connection, and—just like your favorite coffee shop’s WiFi—cannot be overridden by random subclasses.
Example: Creating and Querying Nodes and Relationships
# Create a new person node person = PersonNode.create(name: 'Alice', age: 30) # Create a new conspiracy node conspiracy = ConspiracyNode.create(name: 'Flat Earth', description: 'The earth is flat.', believability_index: 1) # Create a relationship between person and conspiracy BelievesInRelationship.create( from: person, to: conspiracy, reddit_karma_spent: 100, level_of_devotion: 'casual' ) # Find all people who believe in a specific conspiracy followers = conspiracy.followers # Find all conspiracies a person believes in conspiracies = person.believes_in_relationships.map(&:to)
Example: Querying with Cyrel
# Find all people named 'Alice' people = PersonNode.where(name: 'Alice') # Find all conspiracies with a believability index greater than 5 believable_conspiracies = ConspiracyNode.where('believability_index > ?', 5)
Examples and Tutorials
For comprehensive examples of building graph applications with nodes and relationships, see EXAMPLES.md. This guide walks through creating a coffee supply chain application, demonstrating:
- Creating nodes (coffee beans, roasteries, coffee shops)
- Defining relationships with custom attributes (supplies, roasts, serves)
- Connection inheritance and customization
- Complex querying patterns
- Migration examples
(See also detailed usage and advanced queries in the models and test files in test/dummy/app/graph.)
Generators and Naming Conventions
ActiveCypher provides Rails generators for quickly scaffolding node and relationship classes with consistent naming conventions.
Node Generator
By default, node classes are suffixed with Node. For example:
bin/rails generate active_cypher:node Person
Generates:
app/graph/person_node.rb- Class:
PersonNode < ApplicationGraphNode
If you specify a name that already ends with Node, the generator will not double the suffix.
You can customize the suffix with --suffix=CustomSuffix.
Adding Attributes
You can pass attributes as name:type arguments, just like Rails generators:
bin/rails generate active_cypher:node Planet name:string mass:float
This generates a node class with attribute :name, :string and attribute :mass, :float.
Adding Labels
You can specify Cypher labels with the --labels option (comma-separated):
bin/rails generate active_cypher:node SpaceStation --labels=Orbital,Habitat
This will add label :Orbital and label :Habitat to the generated class.
Relationship Generator
By default, relationship classes are suffixed with Rel. For example:
bin/rails generate active_cypher:relationship BelievesIn --from=PersonNode --to=ConspiracyNode
Generates:
app/graph/believes_in_rel.rb- Class:
BelievesInRel < ApplicationGraphRelationship
If you specify a name that already ends with Rel, the generator will not double the suffix.
You can customize the suffix with --suffix=CustomSuffix.
ActiveCypher GraphDB Migrations
ActiveCypher ships with a lightweight migration system for managing indexes and
constraints in your graph databases. By default, migration files live under
graphdb/migrate for database-agnostic changes, with optional DB-specific
folders like graphdb/neo4j.
Configuring Migration Paths
You can customize the locations of migration files by configuring multiple migrations_paths entries. This is useful if you want to organize migrations by adapter or other criteria. Use YAML array syntax in your configuration file (e.g., config/graphdb.yml):
migrations_paths: - graphdb/migrate - graphdb/neo4j - custom_migrations/adapter_specific Create the migration tracking constraint once: ```cypher CREATE CONSTRAINT graph_schema_migration IF NOT EXISTS FOR (m:SchemaMigration) REQUIRE m.version IS UNIQUE;
Write migrations using the small DSL:
class AddFacilityIndexes < ActiveCypher::Migration up do create_uniqueness_constraint :Facility, :commonid, name: :facility_commonid_unique create_node_index :Facility, :country_id, :kind, name: :facility_country_kind_idx create_rel_index :FACILITY_REL, :rel_type, name: :facility_rel_type_idx execute <<~CYPHER CREATE INDEX IF NOT EXISTS FOR (f:Facility) ON (f.created_at) CYPHER end end
Run pending migrations with:
bin/rails graphdb:migrate bin/rails graphdb:migrate:status
Migrations are append-only and should not be modified once created.
Advanced Migration Features
ActiveCypher supports advanced index types for both Memgraph and Neo4j:
Composite Indexes (Multiple Properties)
# Create index on multiple properties (Memgraph 3.2+) create_node_index :Person, :first_name, :last_name # Memgraph: CREATE INDEX ON :Person(first_name, last_name) # Neo4j: CREATE INDEX FOR (n:Person) ON (n.first_name, n.last_name) # Force separate indexes instead of composite create_node_index :Person, :email, :phone, composite: false
Vector Indexes (Embeddings Search)
# Node vector index (Memgraph 3.4+, Neo4j 2025) create_vector_index :doc_embeddings, :Document, :embedding, dimension: 384, metric: :cosine # :cosine, :euclidean, :dot_product # With quantization for memory reduction (Memgraph 3.4+) create_vector_index :embeddings, :Node, :vec, dimension: 128, quantization: :scalar # Relationship vector index (Memgraph 3.4+, Neo4j 2025) create_vector_rel_index :similarity_scores, :SIMILAR_TO, :embedding, dimension: 256
Text/Fulltext Indexes
# Fulltext index on nodes (both databases) create_fulltext_index :search_idx, :Article, :title, :body # Text index on edges (Memgraph 3.6+ only) create_text_edge_index :comment_search, :COMMENTED, :text # Fulltext index on relationships (Neo4j only) create_fulltext_rel_index :rel_search, :REVIEWED, :summary, :notes
Bulk Operations (Memgraph 3.6+)
# Drop all indexes (use with caution!) drop_all_indexes # Drop all constraints drop_all_constraints
Database Setup and Direct Access
For setting up Neo4j and Memgraph databases and direct command-line access, see GRAPH_REFERENCE.md.
Sanity Check
Run bin/sanity to verify that Memgraph and Neo4j servers are reachable on
ports 17688 and 17687. The script exits with an error if either service is not
listening.
Features
- Cyrel DSL: Intuitive Ruby DSL for constructing complex Cypher queries (see detailed Cyrel documentation).
- ActiveRecord-like Patterns: Familiar interface for developers accustomed to ActiveRecord.
- Rails Integration: Easy integration into Rails projects via Rails Engine.
- Comprehensive Cypher Support: Aims to support a wide range of Cypher clauses and functions (refer to
CYREL.mdandtest/cyrel/for examples).
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/seuros/activecypher. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.