Skip to content

Rails Generator for RubyLLM Models #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
487d4e4
feat: add Rails generator for RubyLLM models
kieranklaassen Mar 27, 2025
3577f31
test: add generator template tests and fix zeitwerk warning
kieranklaassen Mar 27, 2025
24467aa
feat: add cross-database support for JSON columns
kieranklaassen Mar 27, 2025
2d50909
fix: ensure proper migration order for database schema
kieranklaassen Mar 27, 2025
6e68d36
Add README template for generator output
kieranklaassen Mar 27, 2025
dacb148
Remove unnecessary comments from create_tool_calls_migration.rb
kieranklaassen Mar 27, 2025
f469f60
Update generator templates to use .tt extension
kieranklaassen Mar 27, 2025
844614b
Improve test organization and fix style issues
kieranklaassen Mar 27, 2025
131e51c
chore: memory
kieranklaassen Mar 27, 2025
f40d1b4
Delete CLAUDE.md
kieranklaassen Mar 27, 2025
5eeedf9
Update CONTRIBUTING.md
kieranklaassen Mar 28, 2025
9d8c2d6
chore: clean up whitespace and add install generator tests
kieranklaassen Mar 28, 2025
61d66ab
first run at options for model names
jamster Mar 28, 2025
a239b34
fixing generator specs with updated `options` setup
jamster Mar 31, 2025
8316ce5
Merge pull request #1 from jamster/generators
kieranklaassen Mar 31, 2025
cf4a560
Merge branch 'main' into generators
kieranklaassen Apr 1, 2025
492188f
Merge branch 'main' into generators
crmne Apr 2, 2025
52d761a
Merge branch 'main' into generators
kieranklaassen Apr 2, 2025
cc3d23f
Addresses comments in PR
jamster Apr 8, 2025
9eba7bc
Merge pull request #2 from jamster/generators
kieranklaassen Apr 10, 2025
7cded01
Merge branch 'main' into generators
kieranklaassen Apr 10, 2025
21c4ee0
Merge branch 'main' into generators
kieranklaassen Apr 18, 2025
fda4df1
docs
kieranklaassen Apr 18, 2025
6270f22
docs: Update Rails integration guide to include generator usage and i…
kieranklaassen Apr 18, 2025
f5454aa
docs: Update Rails integration guide with user association in chat re…
kieranklaassen Apr 18, 2025
5be36c1
docs: Add ChatStreamJob implementation to Rails integration guide
kieranklaassen Apr 18, 2025
1f8d3b0
Merge branch 'main' into generators
kieranklaassen Apr 21, 2025
a350738
Merge branch 'main' into generators
crmne Apr 23, 2025
1ed3c2e
Address PR comments: rename README.md.tt, fix migration order, update…
kieranklaassen Apr 29, 2025
f16858a
Delete .cursor/rules/add_new_provider.mdc
kieranklaassen Apr 29, 2025
26bcf50
Delete .cursor/rules/ruby_llm_conventions.mdc
kieranklaassen Apr 29, 2025
c352d5f
Delete .cursor/rules/ruby_style_guide.mdc
kieranklaassen Apr 29, 2025
b064713
Delete .cursor/rules/ruby_llm_philosophy.mdc
kieranklaassen Apr 29, 2025
5a9b3b8
fix: address PR comments - remove sleep calls and fix formatting
kieranklaassen Apr 29, 2025
224b24e
fix: revert rails.md to previous version as requested in PR comments
kieranklaassen Apr 29, 2025
1fe4320
fix: remove empty line in rails.md as requested in PR comments
kieranklaassen Apr 29, 2025
882f3fc
fix: revert rails.md to original state as requested in PR review
kieranklaassen Jun 14, 2025
4789f80
test: update generator specs to match INSTALL_INFO rename
kieranklaassen Jun 14, 2025
d2196bd
Merge branch 'main' of https://github.com/crmne/ruby_llm into generators
kieranklaassen Jun 14, 2025
bf2244a
docs: update Rails guide to include quick setup instructions with gen…
kieranklaassen Jun 14, 2025
145d2d2
Merge branch 'main' into generators
crmne Jul 23, 2025
5982876
fix: improve line length formatting in migration templates and specs
crmne Jul 23, 2025
50befbe
docs: update README to streamline chat model persistence instructions
crmne Jul 23, 2025
9d92db3
fix: update tool_calls migration for database compatibility and indexing
crmne Jul 23, 2025
4cc4054
refactor: remove rake tasks loading from Railtie
crmne Jul 23, 2025
3ecfeaf
docs: update INSTALL_INFO.md.tt to enhance setup instructions and add…
crmne Jul 23, 2025
d66c305
docs: update installation instructions to highlight generator availab…
crmne Jul 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ group :development do
# Rails integration dependencies
gem 'activerecord', '>= 6.0', '< 9.0'
gem 'activesupport', '>= 6.0', '< 9.0'
gem 'rails', '>= 7.0.0'

# Development dependencies
gem 'bundler', '>= 2.0'
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,32 @@ See the [Installation Guide](https://rubyllm.com/installation) for full details.

Add persistence to your chat models effortlessly:

### Option 1: Rails Generate

Simply run the generator to set up all required models:

```bash
rails generate ruby_llm:install
```

This creates all necessary migrations, models, and database tables. Then just use them:

```ruby
# In your controller
chat = Chat.create!(model_id: "gpt-4o-mini")
chat.ask("What's your favorite Ruby gem?") do |chunk|
Turbo::StreamsChannel.broadcast_append_to(
chat, target: "response", partial: "messages/chunk", locals: { chunk: chunk }
)
end

# That's it - chat history is automatically saved
```

### Option 2: Manually

Or, add RubyLLM to your existing ActiveRecord models:

```ruby
# app/models/chat.rb
class Chat < ApplicationRecord
Expand Down
99 changes: 77 additions & 22 deletions docs/guides/rails.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,67 @@ permalink: /guides/rails
---

# Rails Integration

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove empty line

{: .no_toc }

RubyLLM offers seamless integration with Ruby on Rails applications through helpers for ActiveRecord models. This allows you to easily persist chat conversations, including messages and tool interactions, directly in your database.
{: .fs-6 .fw-300 }
RubyLLM offers seamless integration with Ruby on Rails applications through helpers for ActiveRecord models. This allows you to easily persist chat conversations, including messages and tool interactions, directly in your database. {: .fs-6 .fw-300 }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stick to previous version


## Table of contents

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove empty line

{: .no_toc .text-delta }

1. TOC
{:toc}
1. TOC {:toc}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stick to previous version


---

After reading this guide, you will know:

* How to set up ActiveRecord models for persisting chats and messages.
* How to use `acts_as_chat` and `acts_as_message`.
* How chat interactions automatically persist data.
* A basic approach for integrating streaming responses with Hotwire/Turbo Streams.
- How to set up ActiveRecord models for persisting chats and messages.
- How to use `acts_as_chat` and `acts_as_message`.
- How chat interactions automatically persist data.
- A basic approach for integrating streaming responses with Hotwire/Turbo Streams.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary change


## Setup

### Create Migrations
### Using the Generator

The simplest way to set up RubyLLM in your Rails application is to use the provided generator:

```bash
# Generate all necessary models, migrations, and configuration
rails generate ruby_llm:install
```

This will create:

- A `Chat` model for storing chat sessions
- A `Message` model for storing individual messages
- A `ToolCall` model for storing tool calls
- Migrations for all these models
- A RubyLLM initializer

If you need to customize model names to avoid namespace collisions, you can provide options:

```bash
rails generate ruby_llm:install \
--chat-model-name=Conversation \
--message-model-name=ChatMessage \
--tool-call-model-name=FunctionCall
```

After running the generator, simply run the migrations:

```bash
rails db:migrate
```

### Manual Setup (Alternative)

If you prefer to set up the models manually, follow these steps:

First, generate migrations for your `Chat` and `Message` models. You'll also need a `ToolCall` model if you plan to use [Tools]({% link guides/tools.md %}).
#### Create Migrations

Generate migrations for your `Chat` and `Message` models. You'll also need a `ToolCall` model if you plan to use [Tools]({% link guides/tools.md %}).

```bash
# Generate basic models and migrations
Expand Down Expand Up @@ -88,7 +124,7 @@ end

Run the migrations: `rails db:migrate`

### Set Up Models with `acts_as` Helpers
#### Set Up Models with `acts_as` Helpers

Include the RubyLLM helpers in your ActiveRecord models.

Expand Down Expand Up @@ -121,13 +157,21 @@ class ToolCall < ApplicationRecord
end
```

{: .note }
The `acts_as` helpers primarily handle loading history and saving messages/tool calls related to the chat interaction. Add your application-specific logic (associations, validations, scopes, callbacks) as usual.
{: .note } The `acts_as` helpers primarily handle loading history and saving messages/tool calls related to the chat interaction. Add your application-specific logic (associations, validations, scopes, callbacks) as usual.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stick to previous version


### Configure RubyLLM

Ensure your RubyLLM configuration (API keys, etc.) is set up, typically in `config/initializers/ruby_llm.rb`. See the [Installation Guide]({% link installation.md %}) for details.

```ruby
# config/initializers/ruby_llm.rb
RubyLLM.configure do |config|
config.openai_api_key = ENV["OPENAI_API_KEY"]
config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
# Add other provider keys as needed
end
```

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for this, we have the configuration guide for it

## Basic Usage

The `acts_as_chat` helper delegates common `RubyLLM::Chat` methods to your `Chat` model. When you call these methods on an ActiveRecord `Chat` instance, RubyLLM automatically handles persistence.
Expand Down Expand Up @@ -181,6 +225,18 @@ You can combine `acts_as_chat` with streaming and Turbo Streams for real-time UI
Here's a simplified approach using a background job:

```ruby
# app/jobs/chat_job.rb
class ChatJob < ApplicationJob
def perform(chat_id, question)
chat = Chat.find(chat_id)
chat.ask(question) do |chunk|
Turbo::StreamsChannel.broadcast_append_to(
chat, target: "response", partial: "messages/chunk", locals: { chunk: chunk }
)
end
end
end

# app/models/chat.rb
class Chat < ApplicationRecord
acts_as_chat
Expand Down Expand Up @@ -235,33 +291,32 @@ end
<!-- Your form to submit new messages -->
<%= form_with(url: chat_messages_path(@chat), method: :post) do |f| %>
<%= f.text_area :content %>
<%= f.submit "Send" %>
<%= f.button "Send", disable_with: "Sending..." %>
<% end %>

<%# app/views/messages/_message.html.erb %>
<%= turbo_frame_tag message do %>
<div class="message <%= message.role %>">
<strong><%= message.role.capitalize %>:</strong>
<%# Target div for streaming content %>
<div id="<%= dom_id(message, "content") %>" style="display: inline;">
<div id="<%= dom_id(message, "content") %>" class="message-content">
<%# Render initial content if not streaming, otherwise job appends here %>
<%= simple_format(message.content) %>
</div>
</div>
<% end %>
```

{: .note }
This example shows the core idea. You'll need to adapt the broadcasting, targets, and partials for your specific UI needs (e.g., handling Markdown rendering, adding styling, showing typing indicators). See the [Streaming Responses Guide]({% link guides/streaming.md %}) for more on streaming itself.
{: .note } This example shows the core idea. You'll need to adapt the broadcasting, targets, and partials for your specific UI needs (e.g., handling Markdown rendering, adding styling, showing typing indicators). See the [Streaming Responses Guide]({% link guides/streaming.md %}) for more on streaming itself.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stick to previous version


## Customizing Models

Your `Chat`, `Message`, and `ToolCall` models are standard ActiveRecord models. You can add any other associations, validations, scopes, callbacks, or methods as needed for your application logic. The `acts_as` helpers provide the core persistence bridge to RubyLLM without interfering with other model behavior.

## Next Steps

* [Chatting with AI Models]({% link guides/chat.md %})
* [Using Tools]({% link guides/tools.md %})
* [Streaming Responses]({% link guides/streaming.md %})
* [Working with Models]({% link guides/models.md %})
* [Error Handling]({% link guides/error-handling.md %})
- [Chatting with AI Models]({% link guides/chat.md %})
- [Using Tools]({% link guides/tools.md %})
- [Streaming Responses]({% link guides/streaming.md %})
- [Working with Models]({% link guides/models.md %})
- [Error Handling]({% link guides/error-handling.md %})
75 changes: 75 additions & 0 deletions lib/generators/ruby_llm/install/templates/README.md.tt
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be annoying to have a new README? I would delete this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name "README" is a bit misleading here. This is the text generated AFTER you run the generator to explain what you did, whereas the readme for RubyLLM remains the same and untouched. Originally, this did overwrite the README, but has been corrected to be more of an ephemeral display post install.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can see it in the test added too. It's to give direction after using the generator.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# RubyLLM Rails Setup Complete!

Thanks for installing RubyLLM in your Rails application. Here's what was created:

## Models

- `<%= options[:chat_model_name] %>` - Stores chat sessions and their associated model ID
- `<%= options[:message_model_name] %>` - Stores individual messages in a chat
- `<%= options[:tool_call_model_name] %>` - Stores tool calls made by language models

## Configuration Options

The generator supports the following options to customize model names:

```bash
rails generate ruby_llm:install \
--chat-model-name=Conversation \
--message-model-name=ChatMessage \
--tool-call-model-name=FunctionCall
```

This is useful when you need to avoid namespace collisions with existing models in your application. Table names will be automatically derived from the model names following Rails conventions.

## Next Steps

1. **Run migrations:**
```bash
rails db:migrate
```

2. **Set your API keys** in `config/initializers/ruby_llm.rb` or using environment variables:
```ruby
# config/initializers/ruby_llm.rb
RubyLLM.configure do |config|
config.openai_api_key = ENV["OPENAI_API_KEY"]
config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
# etc.
end
```

3. **Start using RubyLLM in your code:**
```ruby
# In a background job
class ChatJob < ApplicationJob
def perform(chat_id, question)
chat = <%= options[:chat_model_name] %>.find(chat_id)
chat.ask(question) do |chunk|
Turbo::StreamsChannel.broadcast_append_to(
chat, target: "response", partial: "messages/chunk", locals: { chunk: chunk }
)
end
end
end

# Queue the job
chat = <%= options[:chat_model_name] %>.create!(model_id: "gpt-4o-mini")
ChatJob.perform_later(chat.id, "What's your favorite Ruby gem?")
```

4. **For streaming responses** with ActionCable or Turbo:
```ruby
chat.ask("Tell me about Ruby on Rails") do |chunk|
Turbo::StreamsChannel.broadcast_append_to(
chat, target: "response", partial: "messages/chunk", locals: { chunk: chunk }
)
end
```

## Advanced Usage

- Add more fields to your models as needed
- Customize the views to match your application design
- Create a controller for chat interactions

For more information, visit the [RubyLLM Documentation](https://github.com/crmne/ruby_llm)
3 changes: 3 additions & 0 deletions lib/generators/ruby_llm/install/templates/chat_model.rb.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class <%= options[:chat_model_name] %> < ApplicationRecord
<%= acts_as_chat_declaration %>
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Create<%= options[:chat_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
def change
create_table :<%= options[:chat_model_name].tableize %> do |t|
t.string :model_id
t.timestamps
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This migration must be run AFTER create_<%= options[:chat_model_name].tableize %> and create_<%= options[:tool_call_model_name].tableize %> migrations
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to enforce this constraint with the timestamp of the migration? Disclaimer: I'm new to making templates for migrations.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments on latest PR below

# to ensure proper foreign key references
class Create<%= options[:message_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
def change
create_table :<%= options[:message_model_name].tableize %> do |t|
t.references :<%= options[:chat_model_name].tableize.singularize %>, null: false, foreign_key: true
t.string :role
t.text :content
t.string :model_id
t.integer :input_tokens
t.integer :output_tokens
t.references :<%= options[:tool_call_model_name].tableize.singularize %>
t.timestamps
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%#- # Migration for creating tool_calls table with database-specific JSON handling -%>
class Create<%= options[:tool_call_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
def change
create_table :<%= options[:tool_call_model_name].tableize %> do |t|
t.references :<%= options[:message_model_name].tableize.singularize %>, null: false, foreign_key: true
t.string :tool_call_id, null: false
t.string :name, null: false
t.<%= postgresql? ? 'jsonb' : 'json' %> :arguments, default: {}
t.timestamps
end

add_index :<%= options[:tool_call_model_name].tableize %>, :tool_call_id
end
end
14 changes: 14 additions & 0 deletions lib/generators/ruby_llm/install/templates/initializer.rb.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# RubyLLM configuration
RubyLLM.configure do |config|
# Set your API keys here or use environment variables
# config.openai_api_key = ENV["OPENAI_API_KEY"]
# config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
# config.gemini_api_key = ENV["GEMINI_API_KEY"]
# config.deepseek_api_key = ENV["DEEPSEEK_API_KEY"]

# Uncomment to set a default model
# config.default_model = "gpt-4o-mini"

# Uncomment to set default options
# config.default_options = { temperature: 0.7 }
end
3 changes: 3 additions & 0 deletions lib/generators/ruby_llm/install/templates/message_model.rb.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class <%= options[:message_model_name] %> < ApplicationRecord
<%= acts_as_message_declaration %>
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class <%= options[:tool_call_model_name] %> < ApplicationRecord
<%= acts_as_tool_call_declaration %>
end
Loading