Skip to content

Commit c024641

Browse files
trevorturkclaude
andcommitted
Fix ActiveRecord dependency check for Zeitwerk eager loading
Wrap all ActiveRecord module definitions with `if defined?(ActiveRecord::Base)` to prevent Zeitwerk errors when ActiveRecord is not present. - Wrap active_record/*.rb files with ActiveRecord::Base check - Add active_record directory to Zeitwerk ignore list - Load method modules before acts_as in railtie initializer - Remove duplicate require of acts_as from lib/ruby_llm.rb Fixes Zeitwerk::NameError when eager loading without ActiveRecord. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 46bc870 commit c024641

7 files changed

Lines changed: 1037 additions & 1023 deletions

File tree

lib/ruby_llm.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
loader.ignore("#{__dir__}/tasks")
3131
loader.ignore("#{__dir__}/generators")
3232
loader.ignore("#{__dir__}/ruby_llm/railtie.rb")
33+
loader.ignore("#{__dir__}/ruby_llm/active_record")
3334
loader.setup
3435

3536
# A delightful Ruby interface to modern AI language models.
@@ -103,5 +104,4 @@ def logger
103104

104105
if defined?(Rails::Railtie)
105106
require 'ruby_llm/railtie'
106-
require 'ruby_llm/active_record/acts_as'
107107
end

lib/ruby_llm/active_record/acts_as.rb

Lines changed: 162 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,171 +1,173 @@
11
# frozen_string_literal: true
22

3-
module RubyLLM
4-
module ActiveRecord
5-
# Adds chat and message persistence capabilities to ActiveRecord models.
6-
module ActsAs
7-
extend ActiveSupport::Concern
8-
9-
# When ActsAs is included, ensure models are loaded from database
10-
def self.included(base)
11-
super
12-
# Monkey-patch Models to use database when ActsAs is active
13-
RubyLLM::Models.class_eval do
14-
def self.load_models
15-
read_from_database
16-
rescue StandardError => e
17-
RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
18-
read_from_json
19-
end
20-
21-
def self.read_from_database
22-
model_class = RubyLLM.config.model_registry_class
23-
model_class = model_class.constantize if model_class.is_a?(String)
24-
model_class.all.map(&:to_llm)
25-
end
26-
27-
def load_from_database!
28-
@models = self.class.read_from_database
3+
if defined?(ActiveRecord::Base)
4+
module RubyLLM
5+
module ActiveRecord
6+
# Adds chat and message persistence capabilities to ActiveRecord models.
7+
module ActsAs
8+
extend ActiveSupport::Concern
9+
10+
# When ActsAs is included, ensure models are loaded from database
11+
def self.included(base)
12+
super
13+
# Monkey-patch Models to use database when ActsAs is active
14+
RubyLLM::Models.class_eval do
15+
def self.load_models
16+
read_from_database
17+
rescue StandardError => e
18+
RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
19+
read_from_json
20+
end
21+
22+
def self.read_from_database
23+
model_class = RubyLLM.config.model_registry_class
24+
model_class = model_class.constantize if model_class.is_a?(String)
25+
model_class.all.map(&:to_llm)
26+
end
27+
28+
def load_from_database!
29+
@models = self.class.read_from_database
30+
end
2931
end
3032
end
31-
end
32-
33-
class_methods do # rubocop:disable Metrics/BlockLength
34-
def acts_as_chat(messages: :messages, message_class: nil, messages_foreign_key: nil, # rubocop:disable Metrics/ParameterLists
35-
model: :model, model_class: nil, model_foreign_key: nil)
36-
include RubyLLM::ActiveRecord::ChatMethods
37-
38-
class_attribute :messages_association_name, :model_association_name, :message_class, :model_class
39-
40-
self.messages_association_name = messages
41-
self.model_association_name = model
42-
self.message_class = (message_class || messages.to_s.classify).to_s
43-
self.model_class = (model_class || model.to_s.classify).to_s
44-
45-
has_many messages,
46-
-> { order(created_at: :asc) },
47-
class_name: self.message_class,
48-
foreign_key: messages_foreign_key,
49-
dependent: :destroy
50-
51-
belongs_to model,
52-
class_name: self.model_class,
53-
foreign_key: model_foreign_key,
54-
optional: true
55-
56-
delegate :add_message, to: :to_llm
57-
58-
define_method :messages_association do
59-
send(messages_association_name)
60-
end
61-
62-
define_method :model_association do
63-
send(model_association_name)
64-
end
65-
66-
define_method :'model_association=' do |value|
67-
send("#{model_association_name}=", value)
33+
34+
class_methods do # rubocop:disable Metrics/BlockLength
35+
def acts_as_chat(messages: :messages, message_class: nil, messages_foreign_key: nil, # rubocop:disable Metrics/ParameterLists
36+
model: :model, model_class: nil, model_foreign_key: nil)
37+
include RubyLLM::ActiveRecord::ChatMethods
38+
39+
class_attribute :messages_association_name, :model_association_name, :message_class, :model_class
40+
41+
self.messages_association_name = messages
42+
self.model_association_name = model
43+
self.message_class = (message_class || messages.to_s.classify).to_s
44+
self.model_class = (model_class || model.to_s.classify).to_s
45+
46+
has_many messages,
47+
-> { order(created_at: :asc) },
48+
class_name: self.message_class,
49+
foreign_key: messages_foreign_key,
50+
dependent: :destroy
51+
52+
belongs_to model,
53+
class_name: self.model_class,
54+
foreign_key: model_foreign_key,
55+
optional: true
56+
57+
delegate :add_message, to: :to_llm
58+
59+
define_method :messages_association do
60+
send(messages_association_name)
61+
end
62+
63+
define_method :model_association do
64+
send(model_association_name)
65+
end
66+
67+
define_method :'model_association=' do |value|
68+
send("#{model_association_name}=", value)
69+
end
6870
end
69-
end
70-
71-
def acts_as_model(chats: :chats, chat_class: nil, chats_foreign_key: nil)
72-
include RubyLLM::ActiveRecord::ModelMethods
73-
74-
class_attribute :chats_association_name, :chat_class
75-
76-
self.chats_association_name = chats
77-
self.chat_class = (chat_class || chats.to_s.classify).to_s
78-
79-
validates :model_id, presence: true, uniqueness: { scope: :provider }
80-
validates :provider, presence: true
81-
validates :name, presence: true
82-
83-
has_many chats, class_name: self.chat_class, foreign_key: chats_foreign_key
84-
85-
define_method :chats_association do
86-
send(chats_association_name)
71+
72+
def acts_as_model(chats: :chats, chat_class: nil, chats_foreign_key: nil)
73+
include RubyLLM::ActiveRecord::ModelMethods
74+
75+
class_attribute :chats_association_name, :chat_class
76+
77+
self.chats_association_name = chats
78+
self.chat_class = (chat_class || chats.to_s.classify).to_s
79+
80+
validates :model_id, presence: true, uniqueness: { scope: :provider }
81+
validates :provider, presence: true
82+
validates :name, presence: true
83+
84+
has_many chats, class_name: self.chat_class, foreign_key: chats_foreign_key
85+
86+
define_method :chats_association do
87+
send(chats_association_name)
88+
end
8789
end
88-
end
89-
90-
def acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_chat: false, # rubocop:disable Metrics/ParameterLists
91-
tool_calls: :tool_calls, tool_call_class: nil, tool_calls_foreign_key: nil,
92-
model: :model, model_class: nil, model_foreign_key: nil)
93-
include RubyLLM::ActiveRecord::MessageMethods
94-
95-
class_attribute :chat_association_name, :tool_calls_association_name, :model_association_name,
96-
:chat_class, :tool_call_class, :model_class
97-
98-
self.chat_association_name = chat
99-
self.tool_calls_association_name = tool_calls
100-
self.model_association_name = model
101-
self.chat_class = (chat_class || chat.to_s.classify).to_s
102-
self.tool_call_class = (tool_call_class || tool_calls.to_s.classify).to_s
103-
self.model_class = (model_class || model.to_s.classify).to_s
104-
105-
belongs_to chat,
106-
class_name: self.chat_class,
107-
foreign_key: chat_foreign_key,
108-
touch: touch_chat
109-
110-
has_many tool_calls,
111-
class_name: self.tool_call_class,
112-
foreign_key: tool_calls_foreign_key,
113-
dependent: :destroy
114-
115-
belongs_to :parent_tool_call,
90+
91+
def acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_chat: false, # rubocop:disable Metrics/ParameterLists
92+
tool_calls: :tool_calls, tool_call_class: nil, tool_calls_foreign_key: nil,
93+
model: :model, model_class: nil, model_foreign_key: nil)
94+
include RubyLLM::ActiveRecord::MessageMethods
95+
96+
class_attribute :chat_association_name, :tool_calls_association_name, :model_association_name,
97+
:chat_class, :tool_call_class, :model_class
98+
99+
self.chat_association_name = chat
100+
self.tool_calls_association_name = tool_calls
101+
self.model_association_name = model
102+
self.chat_class = (chat_class || chat.to_s.classify).to_s
103+
self.tool_call_class = (tool_call_class || tool_calls.to_s.classify).to_s
104+
self.model_class = (model_class || model.to_s.classify).to_s
105+
106+
belongs_to chat,
107+
class_name: self.chat_class,
108+
foreign_key: chat_foreign_key,
109+
touch: touch_chat
110+
111+
has_many tool_calls,
116112
class_name: self.tool_call_class,
117-
foreign_key: ActiveSupport::Inflector.foreign_key(tool_calls.to_s.singularize),
118-
optional: true
119-
120-
has_many :tool_results,
121-
through: tool_calls,
122-
source: :result,
123-
class_name: name
124-
125-
belongs_to model,
126-
class_name: self.model_class,
127-
foreign_key: model_foreign_key,
128-
optional: true
129-
130-
delegate :tool_call?, :tool_result?, to: :to_llm
131-
132-
define_method :chat_association do
133-
send(chat_association_name)
134-
end
135-
136-
define_method :tool_calls_association do
137-
send(tool_calls_association_name)
138-
end
139-
140-
define_method :model_association do
141-
send(model_association_name)
142-
end
143-
end
144-
145-
def acts_as_tool_call(message: :message, message_class: nil, message_foreign_key: nil, # rubocop:disable Metrics/ParameterLists
146-
result: :result, result_class: nil, result_foreign_key: nil)
147-
class_attribute :message_association_name, :result_association_name, :message_class, :result_class
148-
149-
self.message_association_name = message
150-
self.result_association_name = result
151-
self.message_class = (message_class || message.to_s.classify).to_s
152-
self.result_class = (result_class || self.message_class).to_s
153-
154-
belongs_to message,
155-
class_name: self.message_class,
156-
foreign_key: message_foreign_key
157-
158-
has_one result,
159-
class_name: self.result_class,
160-
foreign_key: result_foreign_key,
161-
dependent: :nullify
162-
163-
define_method :message_association do
164-
send(message_association_name)
113+
foreign_key: tool_calls_foreign_key,
114+
dependent: :destroy
115+
116+
belongs_to :parent_tool_call,
117+
class_name: self.tool_call_class,
118+
foreign_key: ActiveSupport::Inflector.foreign_key(tool_calls.to_s.singularize),
119+
optional: true
120+
121+
has_many :tool_results,
122+
through: tool_calls,
123+
source: :result,
124+
class_name: name
125+
126+
belongs_to model,
127+
class_name: self.model_class,
128+
foreign_key: model_foreign_key,
129+
optional: true
130+
131+
delegate :tool_call?, :tool_result?, to: :to_llm
132+
133+
define_method :chat_association do
134+
send(chat_association_name)
135+
end
136+
137+
define_method :tool_calls_association do
138+
send(tool_calls_association_name)
139+
end
140+
141+
define_method :model_association do
142+
send(model_association_name)
143+
end
165144
end
166-
167-
define_method :result_association do
168-
send(result_association_name)
145+
146+
def acts_as_tool_call(message: :message, message_class: nil, message_foreign_key: nil, # rubocop:disable Metrics/ParameterLists
147+
result: :result, result_class: nil, result_foreign_key: nil)
148+
class_attribute :message_association_name, :result_association_name, :message_class, :result_class
149+
150+
self.message_association_name = message
151+
self.result_association_name = result
152+
self.message_class = (message_class || message.to_s.classify).to_s
153+
self.result_class = (result_class || self.message_class).to_s
154+
155+
belongs_to message,
156+
class_name: self.message_class,
157+
foreign_key: message_foreign_key
158+
159+
has_one result,
160+
class_name: self.result_class,
161+
foreign_key: result_foreign_key,
162+
dependent: :nullify
163+
164+
define_method :message_association do
165+
send(message_association_name)
166+
end
167+
168+
define_method :result_association do
169+
send(result_association_name)
170+
end
169171
end
170172
end
171173
end

0 commit comments

Comments
 (0)