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 all 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
25 changes: 11 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,34 +123,31 @@ See the [Installation Guide](https://rubyllm.com/installation) for full details.

Add persistence to your chat models effortlessly:

```bash
# Generate models and migrations (available in v1.4.0)
rails generate ruby_llm:install
```

```ruby
# app/models/chat.rb
# Or add to existing models
class Chat < ApplicationRecord
acts_as_chat # Automatically saves messages & tool calls
# ... your other model logic ...
end

# app/models/message.rb
class Message < ApplicationRecord
acts_as_message
# ...
end

# app/models/tool_call.rb (if using tools)
class ToolCall < ApplicationRecord
acts_as_tool_call
# ...
end

# Now interacting with a Chat record persists the conversation:
chat_record = Chat.create!(model_id: "gpt-4.1-nano")
chat_record.ask("Explain Active Record callbacks.") # User & Assistant messages saved

# Works seamlessly with file attachments - types automatically detected
chat_record.ask("What's in this file?", with: "report.pdf")
chat_record.ask("Analyze these", with: ["image.jpg", "data.csv", "notes.txt"])
# Now chats persist automatically
chat = Chat.create!(model_id: "gpt-4.1-nano")
chat.ask("What's in this file?", with: "report.pdf")
```
Check the [Rails Integration Guide](https://rubyllm.com/guides/rails) for more.

See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for details.

## Learn More

Expand Down
53 changes: 48 additions & 5 deletions docs/guides/rails.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,46 @@ This approach has one important consequence: **you cannot use `validates :conten

## Setting Up Your Rails Application

### Database Migrations
### Quick Setup with Generator

First, generate migrations for your `Chat`, `Message`, and `ToolCall` models.
{: .d-inline-block }

Available in v1.4.0
{: .label .label-yellow }

The easiest way to get started is using the provided Rails generator:

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

This generator automatically creates:
- All required migrations (Chat, Message, ToolCall tables)
- Model files with `acts_as_chat`, `acts_as_message`, and `acts_as_tool_call` configured
- A RubyLLM initializer in `config/initializers/ruby_llm.rb`

After running the generator:

```bash
rails db:migrate
```

You're ready to go! The generator handles all the setup complexity for you.

#### Generator Options

The generator supports custom model names if needed:

```bash
# Use custom model names
rails generate ruby_llm:install --chat-model-name=Conversation --message-model-name=ChatMessage --tool-call-model-name=FunctionCall
```

This is useful if you already have models with these names or prefer different naming conventions.

### Manual Setup

If you prefer to set up manually or need custom table/model names, you can create the migrations yourself:

```bash
# Generate basic models and migrations
Expand All @@ -70,7 +107,7 @@ rails g model Message chat:references role:string content:text model_id:string i
rails g model ToolCall message:references tool_call_id:string:index name:string arguments:jsonb
```

Adjust the migrations as needed (e.g., `null: false` constraints, `jsonb` type for PostgreSQL).
Then adjust the migrations as needed (e.g., `null: false` constraints, `jsonb` type for PostgreSQL).

```ruby
# db/migrate/YYYYMMDDHHMMSS_create_chats.rb
Expand Down Expand Up @@ -105,17 +142,23 @@ class CreateToolCalls < ActiveRecord::Migration[7.1]
def change
create_table :tool_calls do |t|
t.references :message, null: false, foreign_key: true # Assistant message making the call
t.string :tool_call_id, null: false, index: { unique: true } # Provider's ID for the call
t.string :tool_call_id, null: false # Provider's ID for the call
t.string :name, null: false
t.jsonb :arguments, default: {} # Use jsonb for PostgreSQL
# Use jsonb for PostgreSQL, json for MySQL/SQLite
t.jsonb :arguments, default: {} # Change to t.json for non-PostgreSQL databases
t.timestamps
end

add_index :tool_calls, :tool_call_id, unique: true
end
end
```

Run the migrations: `rails db:migrate`

{: .note }
**Database Compatibility:** The generator automatically detects your database and uses `jsonb` for PostgreSQL or `json` for MySQL/SQLite. If setting up manually, adjust the column type accordingly.

### ActiveStorage Setup for Attachments (Optional)

If you want to use attachments (images, audio, PDFs) with your AI chats, you need to set up ActiveStorage:
Expand Down
25 changes: 11 additions & 14 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,31 +150,28 @@ See the [Installation Guide](https://rubyllm.com/installation) for full details.

Add persistence to your chat models effortlessly:

```bash
# Generate models and migrations (available in v1.4.0)
rails generate ruby_llm:install
```

```ruby
# app/models/chat.rb
# Or add to existing models
class Chat < ApplicationRecord
acts_as_chat # Automatically saves messages & tool calls
# ... your other model logic ...
end

# app/models/message.rb
class Message < ApplicationRecord
acts_as_message
# ...
end

# app/models/tool_call.rb (if using tools)
class ToolCall < ApplicationRecord
acts_as_tool_call
# ...
end

# Now interacting with a Chat record persists the conversation:
chat_record = Chat.create!(model_id: "gpt-4.1-nano")
chat_record.ask("Explain Active Record callbacks.") # User & Assistant messages saved

# Works seamlessly with file attachments - types automatically detected
chat_record.ask("What's in this file?", with: "report.pdf")
chat_record.ask("Analyze these", with: ["image.jpg", "data.csv", "notes.txt"])
# Now chats persist automatically
chat = Chat.create!(model_id: "gpt-4.1-nano")
chat.ask("What's in this file?", with: "report.pdf")
```
Check the [Rails Integration Guide](https://rubyllm.com/guides/rails) for more.

See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for details.
108 changes: 108 additions & 0 deletions lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# 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

**Note:** Do not add `validates :content, presence: true` to your Message model - RubyLLM creates empty assistant messages before API calls for streaming support.

## 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
```

**Database Note:** The migrations use `jsonb` for PostgreSQL and `json` for MySQL/SQLite automatically.

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']
config.gemini_api_key = ENV['GEMINI_API_KEY']
# ... add other providers as needed
end
```

3. **Start using RubyLLM in your code:**
```ruby
# Basic usage
chat = <%= options[:chat_model_name] %>.create!(model_id: 'gpt-4.1-nano')
response = chat.ask("What is Ruby on Rails?")

# With file attachments (requires ActiveStorage setup)
chat.ask("What's in this file?", with: "report.pdf")
chat.ask("Analyze these files", with: ["image.jpg", "data.csv", "notes.txt"])
```

4. **For streaming responses** with Hotwire/Turbo:
```ruby
# app/models/<%= options[:message_model_name].underscore %>.rb
class <%= options[:message_model_name] %> < ApplicationRecord
acts_as_message

# Helper to broadcast chunks during streaming
def broadcast_append_chunk(chunk_content)
broadcast_append_to [ chat, "messages" ],
target: dom_id(self, "content"),
html: chunk_content
end
end

# app/jobs/chat_stream_job.rb
class ChatStreamJob < ApplicationJob
def perform(chat_id, user_content)
chat = <%= options[:chat_model_name] %>.find(chat_id)
chat.ask(user_content) do |chunk|
assistant_message = chat.messages.last
if chunk.content && assistant_message
assistant_message.broadcast_append_chunk(chunk.content)
end
end
end
end

# In your controller
ChatStreamJob.perform_later(@chat.id, params[:content])
```

## Optional: ActiveStorage for Attachments

If you want to use file attachments (PDFs, images, etc.), set up ActiveStorage:

```bash
rails active_storage:install
rails db:migrate
```

Then add to your Message model:
```ruby
class <%= options[:message_model_name] %> < ApplicationRecord
acts_as_message
has_many_attached :attachments
end
```

## Learn More

- See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for detailed examples
- Visit the [RubyLLM Documentation](https://rubyllm.com) for full API reference
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,15 @@
# Migration for creating messages table with references to chats and tool_calls
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