Skip to content

Commit 552732c

Browse files
committed
Improve upgrade generator and add troubleshooting docs
- Fix instance variable usage in migration template - Fix generators when using namespaced models - Simplify upgrade instructions to point to migration guide - Add troubleshooting section for acts_as_model error from issue #400 - Add warning about acts_as_model error directly in generator output - Create initializer if missing during upgrade - Use consistent helper methods across install and upgrade generators
1 parent ecc8afa commit 552732c

File tree

8 files changed

+113
-110
lines changed

8 files changed

+113
-110
lines changed

docs/_advanced/upgrading-to-1.7.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,31 @@ The chat UI works with your existing Chat and Message models and includes:
187187
- Code syntax highlighting
188188
- Responsive design
189189

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

192217
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)

lib/generators/ruby_llm/install/install_generator.rb

Lines changed: 32 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -64,85 +64,38 @@ def parse_model_mappings
6464
end
6565

6666
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
67+
params = []
7768

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
69+
add_association_params(params, :messages, message_table_name, message_model_name, plural: true)
70+
add_association_params(params, :model, model_table_name, model_model_name)
8271

83-
if acts_as_chat_params.any?
84-
"acts_as_chat #{acts_as_chat_params.join(', ')}"
85-
else
86-
'acts_as_chat'
87-
end
72+
"acts_as_chat#{" #{params.join(', ')}" if params.any?}"
8873
end
8974

9075
def acts_as_message_declaration
9176
params = []
9277

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)
78+
add_association_params(params, :chat, chat_table_name, chat_model_name)
79+
add_association_params(params, :tool_calls, tool_call_table_name, tool_call_model_name, plural: true)
80+
add_association_params(params, :model, model_table_name, model_model_name)
9681

97-
params.any? ? "acts_as_message #{params.join(', ')}" : 'acts_as_message'
82+
"acts_as_message#{" #{params.join(', ')}" if params.any?}"
9883
end
9984

100-
private
85+
def acts_as_model_declaration
86+
params = []
10187

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
88+
add_association_params(params, :chats, chat_table_name, chat_model_name, plural: true)
10489

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
90+
"acts_as_model#{" #{params.join(', ')}" if params.any?}"
11091
end
11192

112-
public
113-
11493
def acts_as_tool_call_declaration
115-
acts_as_tool_call_params = []
116-
message_assoc = message_model_name.underscore.to_sym
94+
params = []
11795

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
96+
add_association_params(params, :message, message_table_name, message_model_name)
12497

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
98+
"acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
14699
end
147100

148101
def create_migration_files
@@ -186,12 +139,6 @@ def install_active_storage
186139
rails_command 'active_storage:install'
187140
end
188141

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-
195142
def show_install_info
196143
say "\n ✅ RubyLLM installed!", :green
197144

@@ -223,5 +170,22 @@ def show_install_info
223170
say ' • 🐦 Follow for updates: https://x.com/paolino'
224171
say "\n"
225172
end
173+
174+
private
175+
176+
def add_association_params(params, default_assoc, table_name, model_name, plural: false)
177+
assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
178+
179+
return if assoc == default_assoc
180+
181+
params << "#{default_assoc}: :#{assoc}"
182+
params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
183+
end
184+
185+
def table_name_for(model_name)
186+
# Convert namespaced model names to proper table names
187+
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
188+
model_name.underscore.pluralize.tr('/', '_')
189+
end
226190
end
227191
end

lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ class MigrateToRubyLLMModelReferences < ActiveRecord::Migration<%= migration_ver
33
model_class = <%= model_model_name %>
44
chat_class = <%= chat_model_name %>
55
message_class = <%= message_model_name %>
6+
<% if @model_table_already_existed %>
7+
# Load models from models.json if Model table already existed
8+
say_with_time "Loading models from models.json" do
9+
RubyLLM.models.load_from_json!
10+
model_class.save_to_database
11+
"Loaded #{model_class.count} models"
12+
end
13+
<% end %>
614

715
# Then check for any models in existing data that aren't in models.json
816
say_with_time "Checking for additional models in existing data" do

lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ def parse_model_mappings
5757
end
5858

5959
def create_migration_file
60+
@model_table_already_existed = table_exists?(table_name_for(model_model_name))
61+
6062
# First check if models table exists, if not create it
61-
unless table_exists?(table_name_for(model_model_name))
63+
unless @model_table_already_existed
6264
migration_template 'create_models_migration.rb.tt',
6365
"db/migrate/create_#{table_name_for(model_model_name)}.rb",
6466
migration_version: migration_version,
@@ -73,7 +75,8 @@ def create_migration_file
7375
chat_model_name: chat_model_name,
7476
message_model_name: message_model_name,
7577
tool_call_model_name: tool_call_model_name,
76-
model_model_name: model_model_name
78+
model_model_name: model_model_name,
79+
model_table_already_existed: @model_table_already_existed
7780
end
7881

7982
def create_model_file
@@ -94,51 +97,51 @@ class #{model_model_name} < ApplicationRecord
9497
end
9598

9699
def acts_as_model_declaration
97-
acts_as_model_params = []
98-
chats_assoc = chat_model_name.tableize.to_sym
100+
params = []
99101

100-
if chats_assoc != :chats
101-
acts_as_model_params << "chats: :#{chats_assoc}"
102-
acts_as_model_params << "chat_class: '#{chat_model_name}'" if chat_model_name != chats_assoc.to_s.classify
103-
end
102+
add_association_params(params, :chats, chat_table_name, chat_model_name, plural: true)
104103

105-
if acts_as_model_params.any?
106-
"acts_as_model #{acts_as_model_params.join(', ')}"
107-
else
108-
'acts_as_model'
109-
end
104+
"acts_as_model#{" #{params.join(', ')}" if params.any?}"
110105
end
111106

112107
def update_initializer
113-
initializer_content = File.read('config/initializers/ruby_llm.rb')
114-
115-
unless initializer_content.include?('config.use_new_acts_as')
116-
inject_into_file 'config/initializers/ruby_llm.rb', before: /^end/ do
117-
lines = ["\n # Enable the new Rails-like API", ' config.use_new_acts_as = true']
118-
lines << " config.model_registry_class = \"#{model_model_name}\"" if model_model_name != 'Model'
119-
lines << "\n"
120-
lines.join("\n")
121-
end
108+
initializer_path = 'config/initializers/ruby_llm.rb'
109+
110+
unless File.exist?(initializer_path)
111+
say_status :warning, 'No initializer found. Creating one...', :yellow
112+
template '../install/templates/initializer.rb.tt', initializer_path
113+
return
114+
end
115+
116+
initializer_content = File.read(initializer_path)
117+
118+
return if initializer_content.include?('config.use_new_acts_as')
119+
120+
inject_into_file initializer_path, before: /^end/ do
121+
lines = ["\n # Enable the new Rails-like API", ' config.use_new_acts_as = true']
122+
lines << " config.model_registry_class = \"#{model_model_name}\"" if model_model_name != 'Model'
123+
lines << "\n"
124+
lines.join("\n")
122125
end
123-
rescue Errno::ENOENT
124-
say_status :error, 'config/initializers/ruby_llm.rb not found', :red
125126
end
126127

127128
def show_next_steps
128-
say_status :success, 'Migration created!', :green
129+
say_status :success, 'Upgrade prepared!', :green
129130
say <<~INSTRUCTIONS
130131
131132
Next steps:
132-
1. Review the migration: db/migrate/*_migrate_to_ruby_llm_model_references.rb
133+
1. Review the generated migrations
133134
2. Run: rails db:migrate
134-
3. Update config/initializers/ruby_llm.rb as shown above
135-
4. Test your application thoroughly
135+
3. Update your code to use the new API
136+
137+
⚠️ If you get "undefined method 'acts_as_model'" during migration:
138+
Add this to config/application.rb BEFORE your Application class:
139+
140+
RubyLLM.configure do |config|
141+
config.use_new_acts_as = true
142+
end
136143
137-
The migration will:
138-
- Create the Models table if it doesn't exist
139-
- Load all models from models.json
140-
- Migrate your existing data to use foreign keys
141-
- Preserve all existing data (string columns renamed to model_id_string)
144+
📚 See the full migration guide: https://rubyllm.com/upgrading-to-1-7/
142145
143146
INSTRUCTIONS
144147
end
@@ -149,6 +152,15 @@ def migration_version
149152
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
150153
end
151154

155+
def add_association_params(params, default_assoc, table_name, model_name, plural: false)
156+
assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
157+
158+
return if assoc == default_assoc
159+
160+
params << "#{default_assoc}: :#{assoc}"
161+
params << "#{default_assoc}_class: '#{model_name}'" if model_name != assoc.to_s.classify
162+
end
163+
152164
def table_name_for(model_name)
153165
# Convert namespaced model names to proper table names
154166
# e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")

spec/dummy/config/application.rb

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@
99
require 'action_controller/railtie'
1010

1111
Bundler.require(*Rails.groups)
12-
require 'ruby_llm'
13-
14-
# Configure RubyLLM to use Model registry for tests
15-
RubyLLM.configure do |config|
16-
config.model_registry_class = 'Model'
17-
end
1812

1913
module Dummy
2014
class Application < Rails::Application

0 commit comments

Comments
 (0)