Chronicle connects Datomic to Ruby on Rails Active Record. It maps Active Record models to Datomic facts and exposes immutable history through normal Rails query patterns. Check Datomic page
- Ruby 3.4.5 or newer.
- Rails Active Record 7.0 or newer.
- Datomic Pro for the Docker examples and JRuby integrations.
- Java 17 for the Datomic container.
- Java 21 for JRuby 10 workloads.
- Active Record integration through
Chronicle::Model. - Datomic attributes with types and schema options.
- Datalog compilation for equality, comparison,
IN, projection, and pull queries. - Association preloading through Datomic pull results.
- Time travel with
.as_ofand.since. - Datomic schema and migration helpers.
- Cross-database transactions with compensating Datomic retractions.
- Retry handling with exponential backoff and jitter.
- Circuit breaker support.
- Fiber-aware backoff when a Ruby scheduler is active.
- CRuby transport through Datomic REST using EDN.
- JRuby Client API transport through a Datomic peer server.
- JRuby Peer API transport with direct access to the Datomic transactor.
- Rails initializer and migration generators.
Add Active Chronicle to the application Gemfile:
gem "active_chronicle"Then install the bundle:
bundle installGenerate an initializer:
bin/rails generate chronicle:initializerConfigure a Datomic connection in config/database.yml.
Chronicle's CRuby transport uses Datomic's REST service. Datomic marks REST as a legacy interface, but it remains useful for existing integrations and supports the Ruby transport.
datomic:
adapter: datomic
uri: datomic:dev://localhost:4334/app_dev
client_endpoint: https://localhost:8001
rest: trueThe Client API connects to a Datomic peer server. It requires the peer-server endpoint, access key, secret, and database name.
datomic:
adapter: datomic
uri: datomic:dev://localhost:4334/app_dev
client_endpoint: localhost:8998
access_key: chronicle-dev
secret: chronicle-dev-secretThe Peer API connects directly to the transactor. Select it with transport: peer.
datomic:
adapter: datomic
uri: datomic:dev://localhost:4334/app_dev
transport: peerThe included Compose examples mount the Datomic distribution at /opt/datomic.
Chronicle loads peer-*.jar from the distribution root and its dependencies from
lib/*.jar. Set DATOMIC_HOME when the distribution is installed elsewhere.
The Peer jar is not inside lib: for Datomic Pro 1.0.7705 it is
/opt/datomic/peer-1.0.7705.jar, corresponding to Maven artifact
com.datomic:peer:1.0.7705. Alternatively, resolve that artifact and its runtime
dependencies onto the application JVM classpath. Startup raises an error if
datomic.Peer cannot be resolved or Peer.connect returns no connection.
Include Chronicle::Model and declare the attributes stored in Datomic:
class DatomicRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { writing: :datomic, reading: :datomic }
end
class HistoricalRecord < DatomicRecord
include Chronicle::Model
datomic_attribute :event_name, :string
datomic_attribute :user_id, :integer, index: true
datomic_attribute :payload, :string
endconnects_to must be declared on an abstract Active Record class. Rails 8 rejects it on a concrete model. Keep SQLite-backed models on ApplicationRecord and inherit Datomic-backed models from the abstract Datomic base.
Chronicle::Model also provides to_datoms, datomic_entity_id, and model-level .as_of and .since query entry points.
Generate a Datomic migration:
bin/rails generate chronicle:migration create_historical_records \
event_name:string user_id:integer:indexA generated migration uses the Chronicle table definition:
class CreateHistoricalRecords < ActiveRecord::Migration[8.1]
def change
create_datomic_schema :historical_record do |table|
table.string :event_name
table.integer :user_id, index: true
table.timestamps
end
end
endRun it with:
bin/rails db:migrateDatomic never overwrites a fact. Each transaction produces a new database value and a transaction time. Chronicle exposes that history through relation scopes:
past = HistoricalRecord.as_of(2.hours.ago).where(user_id: 42)
recent = HistoricalRecord.since(10040).where(event_name: "login")The transport applies as_of and since to the Datomic database snapshot before it executes the query.
Chronicle::TransactionCoordinator coordinates a Datomic write and a relational write. If the relational operation fails, it sends compensating retractions to Datomic and raises Chronicle::TransactionError.
Chronicle::TransactionCoordinator.transaction do |transaction|
transaction.datomic(record.to_datoms)
transaction.postgres do
AuditLog.create!(datomic_basis_t: transaction.basis_t)
end
endThe repository contains three Rails applications. They are excluded from the published gem.
| Example | Ruby | Datomic API | Port | Purpose |
|---|---|---|---|---|
news_feed |
CRuby | REST | 3001 | Publish stories and inspect revision history. |
wallet |
JRuby 10 | Client API | 3000 | Record deposits and withdrawals over time. |
animal_tracker |
JRuby 10 | Peer API | 3002 | Record coordinates and draw the historical path on a map. |
cross_store |
CRuby | REST + SQLite | 3003 | Reference a Datomic customer from a SQLite purchase and benchmark coordinated writes. |
The Compose stack downloads and installs Datomic inside the Datomic container. It runs the transactor, peer server, REST service, and all four applications:
cd examples
docker compose up --buildThen open:
http://localhost:3000for the wallet.http://localhost:3001for the news feed.http://localhost:3002for the animal tracker.http://localhost:3003for the cross-store example.
The Datomic peer server listens on port 8998. The REST service listens on port 8001. The transactor uses ports 4334 and 4335.
Run the cross-store benchmark with:
docker compose -f examples/docker-compose.yml exec cross_store \
bundle exec ruby benchmark/cross_store_benchmark.rbRun the full local validation:
bundle exec rake qualityThis runs RuboCop and RSpec. The tracked pre-commit hook runs the same checks:
git config core.hooksPath .githooksThe CI workflow runs RuboCop, RSpec, and uploads SimpleCov results to Codecov.
The gem contains only lib/, README.md, CHANGELOG.md, and LICENSE.txt. It excludes specs, CI configuration, examples, the Gemfile, the Rakefile, and the gemspec.
Build the package with:
gem build chronicle.gemspec- The CRuby REST transport and the JRuby Client API transport use different Datomic endpoints.
- The JRuby Peer API requires the Datomic distribution jars and a JVM.
- The Docker examples use Datomic Pro distribution downloads. Review Datomic licensing and distribution terms before use.
- JRuby and Datomic containers are not required to run the CRuby unit test suite.
- Thanks to the creators of Diametric, it was the spark of inspiration for this project.
Chronicle is available under the MIT License.