Skip to content

Commit 40b5e31

Browse files
committed
Merge branch 'main' into community-main
2 parents 63d4c2b + fa10f0c commit 40b5e31

File tree

14 files changed

+220
-268
lines changed

14 files changed

+220
-268
lines changed

.github/workflows/cicd.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ jobs:
5050

5151
steps:
5252
- uses: actions/checkout@v4
53-
with:
54-
lfs: true
5553

5654
- name: Set up Ruby
5755
uses: ruby/setup-ruby@v1
@@ -100,8 +98,6 @@ jobs:
10098

10199
steps:
102100
- uses: actions/checkout@v4
103-
with:
104-
lfs: true
105101

106102
- name: Set up Ruby
107103
uses: ruby/setup-ruby@v1

.github/workflows/docs.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ jobs:
1818
steps:
1919
- name: Checkout
2020
uses: actions/checkout@v4
21-
with:
22-
lfs: true
2321

2422
- name: Setup Ruby for models guide generation (root Gemfile)
2523
uses: ruby/setup-ruby@v1

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="https://chatwithwork.com/logotype-dark.svg"><img src="https://chatwithwork.com/logotype.svg" alt="Chat with Work" height="30" align="absmiddle"></picture>](https://chatwithwork.com)*Claude Code for your documents*
1111

12-
[![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=8)](https://badge.fury.io/rb/ruby_llm)
12+
[![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=9)](https://badge.fury.io/rb/ruby_llm)
1313
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
1414
[![Gem Downloads](https://img.shields.io/gem/dt/ruby_llm)](https://rubygems.org/gems/ruby_llm)
1515
[![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg?a=2)](https://codecov.io/gh/crmne/ruby_llm)

docs/_advanced/upgrading-to-1.7.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ rails db:migrate
3434
That's it! The generator:
3535
- Creates the models table if needed
3636
- Automatically adds `config.use_new_acts_as = true` to your initializer
37+
- Automatically updates your existing models' `acts_as` declarations to the new version
3738
- Migrates your existing data to use foreign keys
39+
- Loads the models in the db
3840
- Preserves all your data (old string columns renamed to `model_id_string`)
3941

4042
### Custom Model Names
@@ -187,6 +189,31 @@ The chat UI works with your existing Chat and Message models and includes:
187189
- Code syntax highlighting
188190
- Responsive design
189191

192+
## Troubleshooting
193+
194+
### "undefined local variable or method 'acts_as_model'" error during migration
195+
196+
If you get this error when running `rails db:migrate`, add the configuration to `config/application.rb` **before** your Application class:
197+
198+
```ruby
199+
# config/application.rb
200+
require_relative "boot"
201+
require "rails/all"
202+
203+
# Configure RubyLLM before Rails::Application is inherited
204+
RubyLLM.configure do |config|
205+
config.use_new_acts_as = true
206+
end
207+
208+
module YourApp
209+
class Application < Rails::Application
210+
# ...
211+
end
212+
end
213+
```
214+
215+
This ensures RubyLLM is configured before ActiveRecord loads your models.
216+
190217
## New Applications
191218

192219
Fresh installs get the model registry automatically:

gemfiles/rails_7.1.gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ GEM
9898
rake
9999
thor (>= 0.14.0)
100100
ast (2.4.3)
101-
async (2.31.0)
101+
async (2.32.0)
102102
console (~> 1.29)
103103
fiber-annotation
104104
io-event (~> 1.11)

gemfiles/rails_7.2.gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ GEM
9292
rake
9393
thor (>= 0.14.0)
9494
ast (2.4.3)
95-
async (2.31.0)
95+
async (2.32.0)
9696
console (~> 1.29)
9797
fiber-annotation
9898
io-event (~> 1.11)

gemfiles/rails_8.0.gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ GEM
9292
rake
9393
thor (>= 0.14.0)
9494
ast (2.4.3)
95-
async (2.31.0)
95+
async (2.32.0)
9696
console (~> 1.29)
9797
fiber-annotation
9898
io-event (~> 1.11)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# frozen_string_literal: true
2+
3+
module RubyLLM
4+
# Shared helpers for RubyLLM generators
5+
module GeneratorHelpers
6+
def parse_model_mappings
7+
@model_names = {
8+
chat: 'Chat',
9+
message: 'Message',
10+
tool_call: 'ToolCall',
11+
model: 'Model'
12+
}
13+
14+
model_mappings.each do |mapping|
15+
if mapping.include?(':')
16+
key, value = mapping.split(':', 2)
17+
@model_names[key.to_sym] = value.classify
18+
end
19+
end
20+
21+
@model_names
22+
end
23+
24+
%i[chat message tool_call model].each do |type|
25+
define_method("#{type}_model_name") do
26+
@model_names ||= parse_model_mappings
27+
@model_names[type]
28+
end
29+
30+
define_method("#{type}_table_name") do
31+
table_name_for(send("#{type}_model_name"))
32+
end
33+
end
34+
35+
def acts_as_chat_declaration
36+
params = []
37+
38+
add_association_params(params, :messages, message_table_name, message_model_name, plural: true)
39+
add_association_params(params, :model, model_table_name, model_model_name)
40+
41+
"acts_as_chat#{" #{params.join(', ')}" if params.any?}"
42+
end
43+
44+
def acts_as_message_declaration
45+
params = []
46+
47+
add_association_params(params, :chat, chat_table_name, chat_model_name)
48+
add_association_params(params, :tool_calls, tool_call_table_name, tool_call_model_name, plural: true)
49+
add_association_params(params, :model, model_table_name, model_model_name)
50+
51+
"acts_as_message#{" #{params.join(', ')}" if params.any?}"
52+
end
53+
54+
def acts_as_model_declaration
55+
params = []
56+
57+
add_association_params(params, :chats, chat_table_name, chat_model_name, plural: true)
58+
59+
"acts_as_model#{" #{params.join(', ')}" if params.any?}"
60+
end
61+
62+
def acts_as_tool_call_declaration
63+
params = []
64+
65+
add_association_params(params, :message, message_table_name, message_model_name)
66+
67+
"acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
68+
end
69+
70+
def create_namespace_modules
71+
namespaces = []
72+
73+
[chat_model_name, message_model_name, tool_call_model_name, model_model_name].each do |model_name|
74+
if model_name.include?('::')
75+
namespace = model_name.split('::').first
76+
namespaces << namespace unless namespaces.include?(namespace)
77+
end
78+
end
79+
80+
namespaces.each do |namespace|
81+
module_path = "app/models/#{namespace.underscore}.rb"
82+
next if File.exist?(Rails.root.join(module_path))
83+
84+
create_file module_path do
85+
<<~RUBY
86+
module #{namespace}
87+
def self.table_name_prefix
88+
"#{namespace.underscore}_"
89+
end
90+
end
91+
RUBY
92+
end
93+
end
94+
end
95+
96+
def migration_version
97+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
98+
end
99+
100+
def postgresql?
101+
::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
102+
rescue StandardError
103+
false
104+
end
105+
106+
def table_exists?(table_name)
107+
::ActiveRecord::Base.connection.table_exists?(table_name)
108+
rescue StandardError
109+
false
110+
end
111+
112+
private
113+
114+
def add_association_params(params, default_assoc, table_name, model_name, plural: false)
115+
assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
116+
117+
return if assoc == default_assoc
118+
119+
params << "#{default_assoc}: :#{assoc}"
120+
params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
121+
end
122+
123+
def table_name_for(model_name)
124+
# Convert namespaced model names to proper table names
125+
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
126+
model_name.underscore.pluralize.tr('/', '_')
127+
end
128+
end
129+
end

lib/generators/ruby_llm/install/install_generator.rb

Lines changed: 4 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
require 'rails/generators'
44
require 'rails/generators/active_record'
5+
require_relative '../generator_helpers'
56

67
module RubyLLM
78
# Generator for RubyLLM Rails models and migrations
89
class InstallGenerator < Rails::Generators::Base
910
include Rails::Generators::Migration
11+
include RubyLLM::GeneratorHelpers
1012

1113
namespace 'ruby_llm:install'
1214

@@ -24,127 +26,6 @@ def self.next_migration_number(dirname)
2426
::ActiveRecord::Generators::Base.next_migration_number(dirname)
2527
end
2628

27-
def migration_version
28-
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
29-
end
30-
31-
def postgresql?
32-
::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
33-
rescue StandardError
34-
false
35-
end
36-
37-
def parse_model_mappings
38-
@model_names = {
39-
chat: 'Chat',
40-
message: 'Message',
41-
tool_call: 'ToolCall',
42-
model: 'Model'
43-
}
44-
45-
model_mappings.each do |mapping|
46-
if mapping.include?(':')
47-
key, value = mapping.split(':', 2)
48-
@model_names[key.to_sym] = value.classify
49-
end
50-
end
51-
52-
@model_names
53-
end
54-
55-
%i[chat message tool_call model].each do |type|
56-
define_method("#{type}_model_name") do
57-
@model_names ||= parse_model_mappings
58-
@model_names[type]
59-
end
60-
61-
define_method("#{type}_table_name") do
62-
table_name_for(send("#{type}_model_name"))
63-
end
64-
end
65-
66-
def acts_as_chat_declaration
67-
acts_as_chat_params = []
68-
messages_assoc = message_model_name.tableize.to_sym
69-
model_assoc = model_model_name.underscore.to_sym
70-
71-
if messages_assoc != :messages
72-
acts_as_chat_params << "messages: :#{messages_assoc}"
73-
if message_model_name != messages_assoc.to_s.classify
74-
acts_as_chat_params << "message_class: '#{message_model_name}'"
75-
end
76-
end
77-
78-
if model_assoc != :model
79-
acts_as_chat_params << "model: :#{model_assoc}"
80-
acts_as_chat_params << "model_class: '#{model_model_name}'" if model_model_name != model_assoc.to_s.classify
81-
end
82-
83-
if acts_as_chat_params.any?
84-
"acts_as_chat #{acts_as_chat_params.join(', ')}"
85-
else
86-
'acts_as_chat'
87-
end
88-
end
89-
90-
def acts_as_message_declaration
91-
params = []
92-
93-
add_message_association_params(params, :chat, chat_model_name)
94-
add_message_association_params(params, :tool_calls, tool_call_model_name, tableize: true)
95-
add_message_association_params(params, :model, model_model_name)
96-
97-
params.any? ? "acts_as_message #{params.join(', ')}" : 'acts_as_message'
98-
end
99-
100-
private
101-
102-
def add_message_association_params(params, default_assoc, model_name, tableize: false)
103-
assoc = tableize ? model_name.tableize.to_sym : model_name.underscore.to_sym
104-
105-
return if assoc == default_assoc
106-
107-
params << "#{default_assoc}: :#{assoc}"
108-
expected_class = assoc.to_s.classify
109-
params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != expected_class
110-
end
111-
112-
public
113-
114-
def acts_as_tool_call_declaration
115-
acts_as_tool_call_params = []
116-
message_assoc = message_model_name.underscore.to_sym
117-
118-
if message_assoc != :message
119-
acts_as_tool_call_params << "message: :#{message_assoc}"
120-
if message_model_name != message_assoc.to_s.classify
121-
acts_as_tool_call_params << "message_class: '#{message_model_name}'"
122-
end
123-
end
124-
125-
if acts_as_tool_call_params.any?
126-
"acts_as_tool_call #{acts_as_tool_call_params.join(', ')}"
127-
else
128-
'acts_as_tool_call'
129-
end
130-
end
131-
132-
def acts_as_model_declaration
133-
acts_as_model_params = []
134-
chats_assoc = chat_model_name.tableize.to_sym
135-
136-
if chats_assoc != :chats
137-
acts_as_model_params << "chats: :#{chats_assoc}"
138-
acts_as_model_params << "chat_class: '#{chat_model_name}'" if chat_model_name != chats_assoc.to_s.classify
139-
end
140-
141-
if acts_as_model_params.any?
142-
"acts_as_model #{acts_as_model_params.join(', ')}"
143-
else
144-
'acts_as_model'
145-
end
146-
end
147-
14829
def create_migration_files
14930
# Create migrations with timestamps to ensure proper order
15031
# First create chats table
@@ -168,6 +49,8 @@ def create_migration_files
16849
end
16950

17051
def create_model_files
52+
create_namespace_modules
53+
17154
template 'chat_model.rb.tt', "app/models/#{chat_model_name.underscore}.rb"
17255
template 'message_model.rb.tt', "app/models/#{message_model_name.underscore}.rb"
17356
template 'tool_call_model.rb.tt', "app/models/#{tool_call_model_name.underscore}.rb"
@@ -186,12 +69,6 @@ def install_active_storage
18669
rails_command 'active_storage:install'
18770
end
18871

189-
def table_name_for(model_name)
190-
# Convert namespaced model names to proper table names
191-
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
192-
model_name.underscore.pluralize.tr('/', '_')
193-
end
194-
19572
def show_install_info
19673
say "\n ✅ RubyLLM installed!", :green
19774

0 commit comments

Comments
 (0)