Skip to content

Commit 0d23da4

Browse files
authored
Fix create_table migrations to prevent foreign key errors (#409) (#411)
## Description This PR fixes issue #409 where the rails generate ruby_llm:install generator creates migrations with foreign key references to tables that haven't been created yet, causing migration failures. ## Problem When running the install generator on a fresh Rails application, the generated migrations contain t.references calls that try to create foreign key constraints to tables that are created in later migrations. This causes PostgreSQL (and other databases) to fail with "relation does not exist" errors because the referenced tables haven't been created yet. ## Solution 1. Removes the t.references lines from the initial table creation migrations 2. Creates a new migration that runs after all tables are created to add the foreign key references. This can be tested with a brand new rails app by adding the gem from this branch: `gem "ruby_llm", git: "https://github.com/matiasmoya/ruby_llm", branch: "409-fix-install-migrations"` ## What this does - Remove t.reference calls from initial table creation migrations - Add new migration generator template for adding references separately - Update install generator to create tables first, then add references ## Type of change - [x] Bug fix ## Scope check - [x] I read the [Contributing Guide](https://github.com/crmne/ruby_llm/blob/main/CONTRIBUTING.md) - [x] This aligns with RubyLLM's focus on **LLM communication** - [x] This isn't application-specific logic that belongs in user code - [x] This benefits most users, not just my specific use case ## Quality check - [x] I ran `overcommit --install` and all hooks pass - [x] I tested my changes thoroughly - [x] For provider changes: Re-recorded VCR cassettes with `bundle exec rake vcr:record[provider_name]` - [x] All tests pass: `bundle exec rspec` - [x] I updated documentation if needed - [x] I didn't modify auto-generated files manually (`models.json`, `aliases.json`) ## API changes - [x] No API changes ## Related issues Fixes #409
1 parent fa10f0c commit 0d23da4

File tree

6 files changed

+63
-19
lines changed

6 files changed

+63
-19
lines changed

lib/generators/ruby_llm/install/install_generator.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ def create_migration_files
3232
migration_template 'create_chats_migration.rb.tt',
3333
"db/migrate/create_#{chat_table_name}.rb"
3434

35-
# Then create messages table (must come before tool_calls due to foreign key)
35+
# Then create messages table
3636
sleep 1 # Ensure different timestamp
3737
migration_template 'create_messages_migration.rb.tt',
3838
"db/migrate/create_#{message_table_name}.rb"
3939

40-
# Then create tool_calls table (references messages)
40+
# Then create tool_calls table
4141
sleep 1 # Ensure different timestamp
4242
migration_template 'create_tool_calls_migration.rb.tt',
4343
"db/migrate/create_#{tool_call_table_name}.rb"
@@ -46,6 +46,12 @@ def create_migration_files
4646
sleep 1 # Ensure different timestamp
4747
migration_template 'create_models_migration.rb.tt',
4848
"db/migrate/create_#{model_table_name}.rb"
49+
50+
# Add references to chats, tool_calls and messages.
51+
sleep 1 # Ensure different timestamp
52+
migration_template 'add_references_to_chats_tool_calls_and_messages_migration.rb.tt',
53+
'db/migrate/add_references_to_' \
54+
"#{chat_table_name}_#{tool_call_table_name}_and_#{message_table_name}.rb"
4955
end
5056

5157
def create_model_files
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class AddReferencesTo<%= "#{chat_model_name.gsub('::', '').pluralize}#{tool_call_model_name.gsub('::', '').pluralize}And#{message_model_name.gsub('::', '').pluralize}" %> < ActiveRecord::Migration<%= migration_version %>
2+
def change
3+
add_reference :<%= chat_table_name %>, :<%= model_table_name.singularize %>, foreign_key: true
4+
add_reference :<%= tool_call_table_name %>, :<%= message_table_name.singularize %>, null: false, foreign_key: true
5+
add_reference :<%= message_table_name %>, :<%= chat_table_name.singularize %>, null: false, foreign_key: true
6+
add_reference :<%= message_table_name %>, :<%= model_table_name.singularize %>, foreign_key: true
7+
add_reference :<%= message_table_name %>, :<%= tool_call_table_name.singularize %>, foreign_key: true
8+
end
9+
end

lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
class Create<%= chat_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
22
def change
33
create_table :<%= chat_table_name %> do |t|
4-
t.references :<%= model_table_name.singularize %>, foreign_key: true
54
t.timestamps
65
end
76
end

lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
class Create<%= message_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
22
def change
33
create_table :<%= message_table_name %> do |t|
4-
t.references :<%= chat_table_name.singularize %>, null: false, foreign_key: true
54
t.string :role, null: false
65
t.text :content
7-
t.references :<%= model_table_name.singularize %>, foreign_key: true
86
t.integer :input_tokens
97
t.integer :output_tokens
10-
t.references :<%= tool_call_table_name.singularize %>, foreign_key: true
118
t.timestamps
129
end
1310

lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
class Create<%= tool_call_model_name.gsub('::', '').pluralize %> < ActiveRecord::Migration<%= migration_version %>
33
def change
44
create_table :<%= tool_call_table_name %> do |t|
5-
t.references :<%= message_table_name.singularize %>, null: false, foreign_key: true
65
t.string :tool_call_id, null: false
76
t.string :name, null: false
87
t.<%= postgresql? ? 'jsonb' : 'json' %> :arguments, default: {}

spec/lib/generators/ruby_llm/install_generator_spec.rb

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
'create_chats_migration.rb.tt',
1616
'create_messages_migration.rb.tt',
1717
'create_tool_calls_migration.rb.tt',
18-
'create_models_migration.rb.tt'
18+
'create_models_migration.rb.tt',
19+
'add_references_to_chats_tool_calls_and_messages_migration.rb.tt'
1920
]
2021
end
2122

@@ -31,10 +32,6 @@
3132
it 'defines chats table' do
3233
expect(chat_migration).to include('create_table :<%= chat_table_name %>')
3334
end
34-
35-
it 'includes model reference' do
36-
expect(chat_migration).to include('t.references :<%= model_table_name.singularize %>')
37-
end
3835
end
3936

4037
describe 'messages migration' do
@@ -44,10 +41,6 @@
4441
expect(message_migration).to include('create_table :<%= message_table_name %>')
4542
end
4643

47-
it 'includes chat reference' do
48-
expect(message_migration).to include('t.references :<%= chat_table_name.singularize %>, null: false, foreign_key: true') # rubocop:disable Layout/LineLength
49-
end
50-
5144
it 'includes role field' do
5245
expect(message_migration).to include('t.string :role')
5346
end
@@ -72,6 +65,39 @@
7265
expect(tool_call_migration).to include('t.string :name')
7366
end
7467
end
68+
69+
describe 'add references migration' do
70+
let(:add_references_migration) do
71+
File.read(File.join(template_dir, 'add_references_to_chats_tool_calls_and_messages_migration.rb.tt'))
72+
end
73+
74+
it 'adds model reference to chats' do
75+
expect(add_references_migration).to include('add_reference :<%= chat_table_name %>, ' \
76+
':<%= model_table_name.singularize %>, foreign_key: true')
77+
end
78+
79+
it 'adds message reference to tool_calls' do
80+
expect(add_references_migration).to include('add_reference :<%= tool_call_table_name %>, ' \
81+
':<%= message_table_name.singularize %>, ' \
82+
'null: false, foreign_key: true')
83+
end
84+
85+
it 'adds chat reference to messages' do
86+
expect(add_references_migration).to include('add_reference :<%= message_table_name %>, ' \
87+
':<%= chat_table_name.singularize %>, ' \
88+
'null: false, foreign_key: true')
89+
end
90+
91+
it 'adds model reference to messages' do
92+
expect(add_references_migration).to include('add_reference :<%= message_table_name %>, ' \
93+
':<%= model_table_name.singularize %>, foreign_key: true')
94+
end
95+
96+
it 'adds tool_call reference to messages' do
97+
expect(add_references_migration).to include('add_reference :<%= message_table_name %>, ' \
98+
':<%= tool_call_table_name.singularize %>, foreign_key: true')
99+
end
100+
end
75101
end
76102

77103
describe 'JSON handling in migrations' do
@@ -257,10 +283,18 @@
257283
expect(models_position).to be > tool_calls_position if models_position
258284
end
259285

260-
it 'has comments explaining the order' do
286+
it 'adds references after creating all tables' do
261287
migration_section = generator_content[/def create_migration_files.*?\n end/m]
262-
expect(migration_section).to include('must come before tool_calls due to foreign key')
263-
expect(migration_section).to include('references messages')
288+
289+
add_references_position = migration_section.index(
290+
'add_references_to_chats_tool_calls_and_messages_migration.rb.tt'
291+
)
292+
models_position = migration_section.index('model_table_name')
293+
294+
expect(add_references_position).not_to be_nil
295+
expect(models_position).not_to be_nil
296+
297+
expect(add_references_position).to be > models_position
264298
end
265299
end
266300

0 commit comments

Comments
 (0)