Skip to content

Commit 6d8b113

Browse files
kieranklaassenclaudejamstercrmne
authored
Rails Generator for RubyLLM Models (#75)
This PR adds a Rails generator for RubyLLM models, making it easier to integrate RubyLLM with Rails applications. ### Changes in this PR - Added generator that creates Chat, Message, and ToolCall models with migrations - Updated Rails integration guide with generator instructions - Added support for customizing model names to avoid namespace collisions - Added cross-database support for JSON columns ### Recent updates to address PR comments 1. **Renamed README.md.tt to INSTALL_INFO.md.tt** to avoid confusion with the project README, and updated the reference in the install_generator.rb file. 2. **Fixed migration order** by using timestamps in the migration filenames in the install_generator.rb file. This ensures that migrations run in the correct order (chats first, then tool_calls, then messages). 3. **Updated railtie.rb** to include rake tasks from the lib/ruby_llm/tasks directory, which addresses the issue mentioned in the comments about rake tasks not showing up in Rails applications. 4. **Reverted several changes in docs/guides/rails.md** as requested in the comments, including fixing formatting issues and removing unnecessary content. 5. **Removed the comment about migration order** from the create_messages_migration.rb.tt file since we're now enforcing it with timestamps. These changes should address all the issues mentioned in the PR comments. --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jason Amster <jayamster@gmail.com> Co-authored-by: Carmine Paolino <carmine@paolino.me>
1 parent 03ae231 commit 6d8b113

File tree

15 files changed

+572
-33
lines changed

15 files changed

+572
-33
lines changed

README.md

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,34 +123,31 @@ See the [Installation Guide](https://rubyllm.com/installation) for full details.
123123

124124
Add persistence to your chat models effortlessly:
125125

126+
```bash
127+
# Generate models and migrations (available in v1.4.0)
128+
rails generate ruby_llm:install
129+
```
130+
126131
```ruby
127-
# app/models/chat.rb
132+
# Or add to existing models
128133
class Chat < ApplicationRecord
129134
acts_as_chat # Automatically saves messages & tool calls
130-
# ... your other model logic ...
131135
end
132136

133-
# app/models/message.rb
134137
class Message < ApplicationRecord
135138
acts_as_message
136-
# ...
137139
end
138140

139-
# app/models/tool_call.rb (if using tools)
140141
class ToolCall < ApplicationRecord
141142
acts_as_tool_call
142-
# ...
143143
end
144144

145-
# Now interacting with a Chat record persists the conversation:
146-
chat_record = Chat.create!(model_id: "gpt-4.1-nano")
147-
chat_record.ask("Explain Active Record callbacks.") # User & Assistant messages saved
148-
149-
# Works seamlessly with file attachments - types automatically detected
150-
chat_record.ask("What's in this file?", with: "report.pdf")
151-
chat_record.ask("Analyze these", with: ["image.jpg", "data.csv", "notes.txt"])
145+
# Now chats persist automatically
146+
chat = Chat.create!(model_id: "gpt-4.1-nano")
147+
chat.ask("What's in this file?", with: "report.pdf")
152148
```
153-
Check the [Rails Integration Guide](https://rubyllm.com/guides/rails) for more.
149+
150+
See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for details.
154151

155152
## Learn More
156153

docs/guides/rails.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,46 @@ This approach has one important consequence: **you cannot use `validates :conten
5959

6060
## Setting Up Your Rails Application
6161

62-
### Database Migrations
62+
### Quick Setup with Generator
6363

64-
First, generate migrations for your `Chat`, `Message`, and `ToolCall` models.
64+
{: .d-inline-block }
65+
66+
Available in v1.4.0
67+
{: .label .label-yellow }
68+
69+
The easiest way to get started is using the provided Rails generator:
70+
71+
```bash
72+
rails generate ruby_llm:install
73+
```
74+
75+
This generator automatically creates:
76+
- All required migrations (Chat, Message, ToolCall tables)
77+
- Model files with `acts_as_chat`, `acts_as_message`, and `acts_as_tool_call` configured
78+
- A RubyLLM initializer in `config/initializers/ruby_llm.rb`
79+
80+
After running the generator:
81+
82+
```bash
83+
rails db:migrate
84+
```
85+
86+
You're ready to go! The generator handles all the setup complexity for you.
87+
88+
#### Generator Options
89+
90+
The generator supports custom model names if needed:
91+
92+
```bash
93+
# Use custom model names
94+
rails generate ruby_llm:install --chat-model-name=Conversation --message-model-name=ChatMessage --tool-call-model-name=FunctionCall
95+
```
96+
97+
This is useful if you already have models with these names or prefer different naming conventions.
98+
99+
### Manual Setup
100+
101+
If you prefer to set up manually or need custom table/model names, you can create the migrations yourself:
65102

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

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

75112
```ruby
76113
# db/migrate/YYYYMMDDHHMMSS_create_chats.rb
@@ -105,17 +142,23 @@ class CreateToolCalls < ActiveRecord::Migration[7.1]
105142
def change
106143
create_table :tool_calls do |t|
107144
t.references :message, null: false, foreign_key: true # Assistant message making the call
108-
t.string :tool_call_id, null: false, index: { unique: true } # Provider's ID for the call
145+
t.string :tool_call_id, null: false # Provider's ID for the call
109146
t.string :name, null: false
110-
t.jsonb :arguments, default: {} # Use jsonb for PostgreSQL
147+
# Use jsonb for PostgreSQL, json for MySQL/SQLite
148+
t.jsonb :arguments, default: {} # Change to t.json for non-PostgreSQL databases
111149
t.timestamps
112150
end
151+
152+
add_index :tool_calls, :tool_call_id, unique: true
113153
end
114154
end
115155
```
116156

117157
Run the migrations: `rails db:migrate`
118158

159+
{: .note }
160+
**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.
161+
119162
### ActiveStorage Setup for Attachments (Optional)
120163

121164
If you want to use attachments (images, audio, PDFs) with your AI chats, you need to set up ActiveStorage:

docs/index.md

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -150,31 +150,28 @@ See the [Installation Guide](https://rubyllm.com/installation) for full details.
150150

151151
Add persistence to your chat models effortlessly:
152152

153+
```bash
154+
# Generate models and migrations (available in v1.4.0)
155+
rails generate ruby_llm:install
156+
```
157+
153158
```ruby
154-
# app/models/chat.rb
159+
# Or add to existing models
155160
class Chat < ApplicationRecord
156161
acts_as_chat # Automatically saves messages & tool calls
157-
# ... your other model logic ...
158162
end
159163

160-
# app/models/message.rb
161164
class Message < ApplicationRecord
162165
acts_as_message
163-
# ...
164166
end
165167

166-
# app/models/tool_call.rb (if using tools)
167168
class ToolCall < ApplicationRecord
168169
acts_as_tool_call
169-
# ...
170170
end
171171

172-
# Now interacting with a Chat record persists the conversation:
173-
chat_record = Chat.create!(model_id: "gpt-4.1-nano")
174-
chat_record.ask("Explain Active Record callbacks.") # User & Assistant messages saved
175-
176-
# Works seamlessly with file attachments - types automatically detected
177-
chat_record.ask("What's in this file?", with: "report.pdf")
178-
chat_record.ask("Analyze these", with: ["image.jpg", "data.csv", "notes.txt"])
172+
# Now chats persist automatically
173+
chat = Chat.create!(model_id: "gpt-4.1-nano")
174+
chat.ask("What's in this file?", with: "report.pdf")
179175
```
180-
Check the [Rails Integration Guide](https://rubyllm.com/guides/rails) for more.
176+
177+
See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for details.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# RubyLLM Rails Setup Complete!
2+
3+
Thanks for installing RubyLLM in your Rails application. Here's what was created:
4+
5+
## Models
6+
7+
- `<%= options[:chat_model_name] %>` - Stores chat sessions and their associated model ID
8+
- `<%= options[:message_model_name] %>` - Stores individual messages in a chat
9+
- `<%= options[:tool_call_model_name] %>` - Stores tool calls made by language models
10+
11+
**Note:** Do not add `validates :content, presence: true` to your Message model - RubyLLM creates empty assistant messages before API calls for streaming support.
12+
13+
## Configuration Options
14+
15+
The generator supports the following options to customize model names:
16+
17+
```bash
18+
rails generate ruby_llm:install \
19+
--chat-model-name=Conversation \
20+
--message-model-name=ChatMessage \
21+
--tool-call-model-name=FunctionCall
22+
```
23+
24+
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.
25+
26+
## Next Steps
27+
28+
1. **Run migrations:**
29+
```bash
30+
rails db:migrate
31+
```
32+
33+
**Database Note:** The migrations use `jsonb` for PostgreSQL and `json` for MySQL/SQLite automatically.
34+
35+
2. **Set your API keys** in `config/initializers/ruby_llm.rb` or using environment variables:
36+
```ruby
37+
# config/initializers/ruby_llm.rb
38+
RubyLLM.configure do |config|
39+
config.openai_api_key = ENV['OPENAI_API_KEY']
40+
config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
41+
config.gemini_api_key = ENV['GEMINI_API_KEY']
42+
# ... add other providers as needed
43+
end
44+
```
45+
46+
3. **Start using RubyLLM in your code:**
47+
```ruby
48+
# Basic usage
49+
chat = <%= options[:chat_model_name] %>.create!(model_id: 'gpt-4.1-nano')
50+
response = chat.ask("What is Ruby on Rails?")
51+
52+
# With file attachments (requires ActiveStorage setup)
53+
chat.ask("What's in this file?", with: "report.pdf")
54+
chat.ask("Analyze these files", with: ["image.jpg", "data.csv", "notes.txt"])
55+
```
56+
57+
4. **For streaming responses** with Hotwire/Turbo:
58+
```ruby
59+
# app/models/<%= options[:message_model_name].underscore %>.rb
60+
class <%= options[:message_model_name] %> < ApplicationRecord
61+
acts_as_message
62+
63+
# Helper to broadcast chunks during streaming
64+
def broadcast_append_chunk(chunk_content)
65+
broadcast_append_to [ chat, "messages" ],
66+
target: dom_id(self, "content"),
67+
html: chunk_content
68+
end
69+
end
70+
71+
# app/jobs/chat_stream_job.rb
72+
class ChatStreamJob < ApplicationJob
73+
def perform(chat_id, user_content)
74+
chat = <%= options[:chat_model_name] %>.find(chat_id)
75+
chat.ask(user_content) do |chunk|
76+
assistant_message = chat.messages.last
77+
if chunk.content && assistant_message
78+
assistant_message.broadcast_append_chunk(chunk.content)
79+
end
80+
end
81+
end
82+
end
83+
84+
# In your controller
85+
ChatStreamJob.perform_later(@chat.id, params[:content])
86+
```
87+
88+
## Optional: ActiveStorage for Attachments
89+
90+
If you want to use file attachments (PDFs, images, etc.), set up ActiveStorage:
91+
92+
```bash
93+
rails active_storage:install
94+
rails db:migrate
95+
```
96+
97+
Then add to your Message model:
98+
```ruby
99+
class <%= options[:message_model_name] %> < ApplicationRecord
100+
acts_as_message
101+
has_many_attached :attachments
102+
end
103+
```
104+
105+
## Learn More
106+
107+
- See the [Rails Integration Guide](https://rubyllm.com/guides/rails) for detailed examples
108+
- Visit the [RubyLLM Documentation](https://rubyllm.com) for full API reference
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class <%= options[:chat_model_name] %> < ApplicationRecord
2+
<%= acts_as_chat_declaration %>
3+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Create<%= options[:chat_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
2+
def change
3+
create_table :<%= options[:chat_model_name].tableize %> do |t|
4+
t.string :model_id
5+
t.timestamps
6+
end
7+
end
8+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Migration for creating messages table with references to chats and tool_calls
2+
class Create<%= options[:message_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
3+
def change
4+
create_table :<%= options[:message_model_name].tableize %> do |t|
5+
t.references :<%= options[:chat_model_name].tableize.singularize %>, null: false, foreign_key: true
6+
t.string :role
7+
t.text :content
8+
t.string :model_id
9+
t.integer :input_tokens
10+
t.integer :output_tokens
11+
t.references :<%= options[:tool_call_model_name].tableize.singularize %>
12+
t.timestamps
13+
end
14+
end
15+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<%#- # Migration for creating tool_calls table with database-specific JSON handling -%>
2+
class Create<%= options[:tool_call_model_name].pluralize %> < ActiveRecord::Migration<%= migration_version %>
3+
def change
4+
create_table :<%= options[:tool_call_model_name].tableize %> do |t|
5+
t.references :<%= options[:message_model_name].tableize.singularize %>, null: false, foreign_key: true
6+
t.string :tool_call_id, null: false
7+
t.string :name, null: false
8+
t.<%= postgresql? ? 'jsonb' : 'json' %> :arguments, default: {}
9+
t.timestamps
10+
end
11+
12+
add_index :<%= options[:tool_call_model_name].tableize %>, :tool_call_id
13+
end
14+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# RubyLLM configuration
2+
RubyLLM.configure do |config|
3+
# Set your API keys here or use environment variables
4+
# config.openai_api_key = ENV["OPENAI_API_KEY"]
5+
# config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
6+
# config.gemini_api_key = ENV["GEMINI_API_KEY"]
7+
# config.deepseek_api_key = ENV["DEEPSEEK_API_KEY"]
8+
9+
# Uncomment to set a default model
10+
# config.default_model = "gpt-4o-mini"
11+
12+
# Uncomment to set default options
13+
# config.default_options = { temperature: 0.7 }
14+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class <%= options[:message_model_name] %> < ApplicationRecord
2+
<%= acts_as_message_declaration %>
3+
end

0 commit comments

Comments
 (0)