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 4 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
24 changes: 24 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# RubyLLM Development Guidelines

## Commands
- Run all tests: `bundle exec rspec`
- Run single test: `bundle exec rspec spec/path/to/file_spec.rb`
- Run specific test: `bundle exec rspec spec/path/to/file_spec.rb:LINE_NUMBER`
- Code style check: `bundle exec rubocop`
- Auto-fix style issues: `bundle exec rubocop -A`
- Record VCR cassettes: `bundle exec rake vcr:record[provider]` (where provider is openai, anthropic, gemini, etc.)

## Style Guidelines
- Follow Standard Ruby style (https://github.com/testdouble/standard)
- Use frozen_string_literal comment at top of files
- Add YARD documentation comments for public methods
- Use proper error handling with specific error classes
- Follow consistent model naming conventions across providers
- Keep normalized model IDs (separate model from provider)
- Use consistent parameter naming across providers

## Testing Practices
- Tests automatically create VCR cassettes based on their descriptions
- Use specific, descriptive test descriptions
- Check VCR cassettes for sensitive information
- Write tests for all new features and bug fixes
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ bundle exec rspec

# Run a specific test file
bundle exec rspec spec/ruby_llm/chat_spec.rb

# Run generator template tests
bundle exec rspec spec/lib/generators/ruby_llm/template_files_spec.rb
```

### Recording VCR Cassettes
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,37 @@ chat.with_model('gemini-2.0-flash').ask "What's your favorite algorithm?"

## Rails integration that makes sense

### Option 1: Create new models with the generator

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: Add to your existing models

Or, add RubyLLM to your existing ActiveRecord models:

```ruby
# app/models/chat.rb
class Chat < ApplicationRecord
acts_as_chat

# Works great with Turbo
broadcasts_to ->(chat) { "chat_#{chat.id}" }
end
Expand Down
29 changes: 26 additions & 3 deletions docs/guides/rails.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,32 @@ RubyLLM provides seamless integration with Rails through ActiveRecord models. Th

## Setup

### 1. Create Migrations
### Using the Generator (Recommended)

First, create the necessary tables in your database:
The easiest way to set up RubyLLM with Rails is to use the built-in generator:

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

This will automatically:
1. Create the necessary migrations for chats, messages, and tool calls
2. Create model files with appropriate `acts_as_*` methods
3. Set up proper relationships between models

After running the generator, simply run the migrations:

```bash
rails db:migrate
```

### Manual Setup

If you prefer to set up manually or need to customize the implementation, follow these steps:

#### 1. Create Migrations

Create the necessary tables in your database:

```ruby
# db/migrate/YYYYMMDDHHMMSS_create_chats.rb
Expand Down Expand Up @@ -65,7 +88,7 @@ Run the migrations:
rails db:migrate
```

### 2. Set Up Models
#### 2. Set Up Models

Create the model classes:

Expand Down
5 changes: 5 additions & 0 deletions lib/generators/ruby_llm/install/templates/chat_model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class Chat < ApplicationRecord
acts_as_chat
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class CreateChats < ActiveRecord::Migration<%= migration_version %>
def change
create_table :chats do |t|
t.string :model_id
t.timestamps
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

# This migration must be run AFTER create_chats and create_tool_calls migrations
# to ensure proper foreign key references
class CreateMessages < ActiveRecord::Migration<%= migration_version %>
def change
create_table :messages do |t|
t.references :chat, 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 :tool_call, foreign_key: true
t.timestamps
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

# Migration for creating tool_calls table with database-specific JSON handling
class CreateToolCalls < ActiveRecord::Migration<%= migration_version %>
def change
create_table :tool_calls do |t|
# No reference to message to avoid circular references
# Messages will reference tool_calls, not the other way around
t.string :tool_call_id, null: false
t.string :name, null: false

# Use the appropriate JSON column type for the database
if postgresql?
t.jsonb :arguments, default: {}
else
t.json :arguments, default: {}
end

t.timestamps
end

add_index :tool_calls, :tool_call_id
end
end
16 changes: 16 additions & 0 deletions lib/generators/ruby_llm/install/templates/initializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# 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
5 changes: 5 additions & 0 deletions lib/generators/ruby_llm/install/templates/message_model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class Message < ApplicationRecord
acts_as_message
end
5 changes: 5 additions & 0 deletions lib/generators/ruby_llm/install/templates/tool_call_model.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class ToolCall < ApplicationRecord
acts_as_tool_call
end
63 changes: 63 additions & 0 deletions lib/generators/ruby_llm/install_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require "rails/generators"
require "rails/generators/active_record"

module RubyLLM
# Generator for RubyLLM Rails models and migrations
class InstallGenerator < Rails::Generators::Base
include Rails::Generators::Migration

source_root File.expand_path("install/templates", __dir__)

desc "Creates model files for Chat, Message, and ToolCall, and creates migrations for RubyLLM Rails integration"

def self.next_migration_number(dirname)
ActiveRecord::Generators::Base.next_migration_number(dirname)
end

def migration_version
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end

def postgresql?
ActiveRecord::Base.connection.adapter_name.downcase.include?("postgresql")
rescue
false
end

def create_migration_files
# Create migrations in the correct order with sequential timestamps
# to ensure proper foreign key references:
# 1. First create chats (no dependencies)
# 2. Then create tool_calls (will be referenced by messages)
# 3. Finally create messages (depends on both chats and tool_calls)

# Use a fixed timestamp for testing and to ensure they're sequential
@migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S")
migration_template "create_chats_migration.rb", "db/migrate/create_chats.rb"

# Increment timestamp for the next migration
@migration_number = (@migration_number.to_i + 1).to_s
migration_template "create_tool_calls_migration.rb", "db/migrate/create_tool_calls.rb"

# Increment timestamp again for the final migration
@migration_number = (@migration_number.to_i + 2).to_s
migration_template "create_messages_migration.rb", "db/migrate/create_messages.rb"
end

def create_model_files
template "chat_model.rb", "app/models/chat.rb"
template "message_model.rb", "app/models/message.rb"
template "tool_call_model.rb", "app/models/tool_call.rb"
end

def create_initializer
template "initializer.rb", "config/initializers/ruby_llm.rb"
end

def show_readme
readme "README.md"
end
end
end
1 change: 1 addition & 0 deletions lib/ruby_llm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
loader.ignore("#{__dir__}/tasks")
loader.ignore("#{__dir__}/ruby_llm/railtie")
loader.ignore("#{__dir__}/ruby_llm/active_record")
loader.ignore("#{__dir__}/generators")
loader.setup

# A delightful Ruby interface to modern AI language models.
Expand Down
10 changes: 10 additions & 0 deletions lib/ruby_llm/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,15 @@ class Railtie < Rails::Railtie
include RubyLLM::ActiveRecord::ActsAs
end
end

# Include rake tasks if applicable

Choose a reason for hiding this comment

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

We should include the ruby_llm rake tasks in a Rails application:

    rake_tasks do
      path = File.expand_path(__dir__)
      Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
    end

Choose a reason for hiding this comment

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

I added an issue for the rake task not show up in a Rails application: #136

This is making it harder for me to use the latest Gemini models.

rake_tasks do
# Task definitions go here if needed
end

# Register generators
generators do
require 'generators/ruby_llm/install_generator'
end
end
end
Loading