crmne / crmne/ruby_llm

[FEATURE] Let the tables RubyLLM owns live in another database

Open
#947 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Ruby
Stars
4.4k
Forks
504
Avg merge
1d 8h
Merged PRs (30d)
11

Description

Scope check
  • This is core LLM communication (not application logic)
  • This benefits most users (not just my use case)
  • This can't be solved in application code with current RubyLLM
  • I read the Contributing Guide
Due diligence
  • I searched existing issues
  • I checked the documentation
What problem does this solve?

RubyLLM's four record classes (Model, ToolCall, Usage, Batch) inherit from ::ActiveRecord::Base, so they always resolve to the application's default connection.

An application whose chats and messages live in a secondary database cannot put RubyLLM's tables next to them. connects_to raises NotImplementedError on a concrete class, and establish_connection has to be repeated on all four and gives each its own pool.

The generated schema already says the two table sets belong together: create_chats_migration.rb.tt declares a foreign key from chats to ruby_llm_models. So the application picks the database for its half, and the gem pins its half to the default one. When those differ, the foreign key the install generator writes cannot be created.

Proposed solution

Add RubyLLM::ActiveRecord::Record as the abstract base class of the four, and change nothing else:

class Record < ::ActiveRecord::Base
  self.abstract_class = true
end

The four keep their self.table_name assignments. Nothing changes by default: Record does not call connects_to, so all four stay on ActiveRecord::Base's pool. I measured an identical workload before and after — same 29 queries, same 4 transactions, one connection pool.

It gives an application one place to configure the connection:

# move them to another database
RubyLLM::ActiveRecord::Record.connects_to database: { writing: :llm }

# or join a pool the application already has, which keeps RubyLLM's
# writes inside the application's transactions
RubyLLM::ActiveRecord::Record.connection_specification_name =
  SecondaryRecord.connection_specification_name

I verified the second form on Rails 7.1 through 8.1: the four classes land on the application's pool, and a rollback on the application's class takes the row RubyLLM wrote with it.

Why this belongs in RubyLLM

It cannot be solved in application code. Rails allows connects_to only on ActiveRecord::Base or an abstract class, and RubyLLM owns these four classes, so only RubyLLM can give them an abstract ancestor. Everything an application can reach for today is either a :nodoc: internal (connection_specification_name, connection_class) assigned four times over, or establish_connection per class, which fragments the pool.

This is the same problem every Rails framework gem with its own tables has, and they all solve it the same way: ActiveStorage::Record, ActionMailbox::Record, ActionText::Record, SolidQueue::Record, SolidCache::Record. All five are an abstract class and nothing else.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Locate the four record classes—Model, ToolCall, Usage, and Batch—and the create_chats_migration.rb.tt template; start by tracing their current ActiveRecord::Base inheritance and the generated foreign key. Done means one shared configurable connection applies to all four, default behavior remains unchanged, and the Rails 7.1–8.1 connection and rollback cases described in the issue work.

Written by the indexing model from the issue text.

Assessment

Tech stack
rails, ruby
Domain
database
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.