Building ActiveCypher: When Ruby Learns to Speak Graph

Building ActiveCypher: When Ruby Learns to Speak Graph
Sol 1: ActiveCypher started as one of many frameworks. When you build frameworks for a living, you learn to think differently. Not “how do I solve this problem?” but “how do I solve this category of problems?”
The vision: a universal Cypher translator. If it speaks modern Cypher, we speak back. No legacy support. Your 2012 database with its quirky syntax? Use AI to migrate it or hire a real developer.
Sol 42: Learning graph databases was… educational. Every YouTube short made it look simple. Every documentation page told a different story. Half were 404s. The other half wanted my email for a webinar about enterprise solutions.
Meanwhile, that one developer who memorized the entire OpenCypher spec was waiting to roast anyone who dared implement it wrong. Framework builders live in fear of that person. They keep us honest.
Sol 73: First working version. String concatenation everywhere:
def match(pattern)
@query_string += "MATCH #{pattern} "
self
end
It worked. Sometimes. But frameworks cannot be fragile. When someone builds their business on your code, “sometimes” is not acceptable.
Sol 156: The real education came from studying great software. Not tutorials or blog posts, but actual code. How does Arel handle complexity? How does ActiveRecord stay extensible? What makes a framework survive decades instead of months?
The answer was always the same: architecture over features.
Sol 234: Enter the AST. Abstract Syntax Trees are how real compilers work. If it was good enough for GCC, it was good enough for ActiveCypher.
Every Cypher clause became a node:
module AST
# The simplest node - just a value
LiteralNode = Data.define(:value) do
def accept(visitor)
visitor.visit_literal_node(self)
end
end
# Clean, focused, single responsibility
class LimitNode < ClauseNode
attr_reader :expression
def initialize(expression)
@expression = expression
end
end
end
Clean. Predictable. Extensible.
Sol 301: Clause ordering. MATCH before WHERE. WHERE before RETURN. Simple rules, but how to encode them?
I spent an afternoon choosing numbers. Not random numbers. Not sequential numbers. The right numbers. Numbers that would live in the codebase forever, silent guardians of query correctness.
Framework design is full of these moments. Small decisions that echo through time.
Sol 342: The compiler emerged:
def visit_limit_node(node)
@output << 'LIMIT '
render_expression(node.expression)
end
def visit_literal_node(node)
param_key = register_parameter(node.value)
@output << "$#{param_key}"
end
Each node knew how to render itself. The tree structure made invalid queries impossible. This is what separates frameworks from scripts.
Sol 423: Production deployment. ActiveCypher now sits between our applications and both Memgraph and Neo4j. Like a couples therapist, it helps them communicate despite their differences.
query = Cyrel.query
.match(Cyrel.n(:person, :Person))
.where(Cyrel.prop(:person, :age) > 25)
.return_(:person)
# Same query, two databases
memgraph_adapter.run(query.to_cypher, query.params)
neo4j_adapter.run(query.to_cypher, query.params)
Sol 456: Someone asked about supporting an older Cypher syntax. I pointed them to our philosophy: latest protocols only. The graph database world moves fast. Supporting legacy means moving slow.
Harsh? Maybe. But frameworks must choose: support everything poorly or support the future well.
Sol 512: The AST design proved portable. When we need more speed, the same tree structure will work in Go. Or Rust. Or yes, even COBOL (though that remains a theoretical exercise).
The numbers I chose for clause ordering? They travel with the design. Some decisions transcend language boundaries.
Sol 567: Framework building is lonely work. No pull requests on experimental code. No contributors until it proves itself. Just you, the spec, and the fear of that one developer who knows every edge case.
But when it works? When production systems rely on your abstraction? When complex becomes simple? That is why we build frameworks.
Sol 580 (4 months ago): RedisGraph announced its deprecation. “Official support for RedisGraph will end on January 31, 2025. After this date, commands will be disabled on Redis Enterprise Cloud, and no further support will be provided across platforms.”
The architecture of Redis was never ready to receive all that graph data. I had to remove the RedisGraph adapter. Another vendor drops out. This is why frameworks must be vendor-agnostic. Databases come and go. Standards endure.
Sol 640 (2 months ago): Ruby developers started complaining. The neo4j driver had stopped working in its recent version. Vendor lock-in strikes again. It reminded me of the ElasticSearch gem story, where they nuked the adapter to prevent OpenSearch usage.
I contacted the Memgraph team. They were different. They believed in standards. They wanted developers to have choices.
This reinforced my vision: something standard. Something where an AI assistant would not give conflicting solutions because two vendors decided to diverge. One Cypher. Many databases. No politics.
Final Log Entry: ActiveCypher runs in production. It handles real queries for real applications. It speaks fluent Cypher to multiple databases. It refuses to support your legacy syntax.
More articles will come as I learn more. Framework building is never done. There is always another edge case, another optimization, another database claiming Cypher compatibility.
But for now, ActiveCypher does what it promised. It helps Ruby speak graph. Cleanly. Reliably. With carefully chosen numbers ensuring everything happens in the right order.
Even if those numbers have their own secret significance.
Especially then.
Related Posts
My JRuby Saga: Or How I Learned to Stop Worrying and (Almost) Accept the JVM
How working with JRuby 10 and Rails 8 changed my perspective on the JVM, one reluctant commit at a time.
Finding the Road Back Home: Building a Clean PostGIS Gem for Rails 8
How I escaped the zoo of monkey patches and built a clean, Rails 8-friendly PostGIS adapter gem that actually works with modern Rails.
LLMs and the Ossification of APIs: Are We Stuck with Prehistoric Patterns?
How AI coding assistants are influencing API design in Ruby, and whether we're reinforcing old patterns or creating new opportunities.