class RubyLLM::ActiveRecord::FileStore
Keeps a persisted chat’s files in ruby_llm_chat_files, with their content in Active Storage, so they live on whatever service Active Storage uses: a disk now, a bucket later. A persisted chat uses one when ChatFiles#with_file_store is given no store.
chat.with_file_store chat.file_store.list
The store writes files for the chat’s owner, the record acts_as_chat owner: names or one set with ChatMethods#with_owner, and sees only their files and the files without an owner, so in a chat several people share, one person’s tool results stay theirs:
chat.with_owner(Current.user).with_file_store
What the model last read of a file is kept in the row’s read_digest column, so it holds across processes, and with_lock locks the chat’s row. Converted text is kept in the row’s text column. Encrypt it, like anything else the model reads, with RubyLLM::ActiveRecord::ChatFile.encrypts :text.
Attributes
The chat record the files belong to.
Public Class Methods
Source
# File lib/ruby_llm/active_record/file_store.rb, line 39 def initialize(chat, owner: nil) unless chat.is_a?(ChatFiles) raise ArgumentError, "#{self.class} needs an acts_as_chat record, not #{chat.class}" end unless ChatFile.respond_to?(:reflect_on_attachment) && ChatFile.reflect_on_attachment(:file) raise ConfigurationError, "#{self.class} needs Active Storage. " \ 'Load it with require "active_storage/engine" in config/application.rb, ' \ 'then run bin/rails active_storage:install and bin/rails db:migrate.' end super() @chat = chat @owner = owner end
Creates a store for the files of the chat record, writing them for owner when given, and otherwise for the chat’s owner.
Raises ArgumentError when chat is not an acts_as_chat record, and ConfigurationError when Active Storage is not loaded.
Public Instance Methods
Source
# File lib/ruby_llm/active_record/file_store.rb, line 71 def [](path) row(path)&.to_stored_file end
Returns the StoredFile at path, or nil.
Source
# File lib/ruby_llm/active_record/file_store.rb, line 96 def delete(path) path = path(path) with_lock do record = owned.find_by(path:) refuse_folder(path) if record.nil? && rows_in_folder(path).any? { |row| row.path != path } record&.destroy record&.to_stored_file end end
Deletes the file at path and returns its StoredFile, or nil. A store with an owner deletes only that owner’s files.
Source
# File lib/ruby_llm/active_record/file_store.rb, line 109 def delete_folder(path) path = path(path) with_lock do refuse_file(path) if row(path) rows_in_folder(path, owned).each(&:destroy).map(&:to_stored_file) end end
Deletes everything inside the folder path and returns the deleted StoredFile objects. A store with an owner deletes only that owner’s files.
Source
# File lib/ruby_llm/active_record/file_store.rb, line 65 def list(prefix = nil) scope = prefix.present? ? inside(path(prefix)) : visible preferred(scope.includes(:owner).order(:path, :id).to_a).map(&:to_stored_file) end
Returns the files inside the folder prefix, or every file.
Source
# File lib/ruby_llm/active_record/file_store.rb, line 57 def owner return @owner unless @owner.nil? owner = @chat.to_llm.owner owner if owner.is_a?(::ActiveRecord::Base) end
Returns the owner the store writes files for: the one it was created with, or else the chat’s owner when that is a record. Returns nil when there is neither.
Source
# File lib/ruby_llm/active_record/file_store.rb, line 76 def read(path) record = row(path) || raise(FileNotFoundError, "No file at #{path(path)}") record.file.download.b end
Returns the content of the file at path.
Source
# File lib/ruby_llm/active_record/file_store.rb, line 135 def with_lock super do ChatFile.uncached do ChatFile.transaction do lock_chat yield end end end end
Runs the block in a transaction that locks the chat’s row, past the query cache, so other processes wait for it.
RubyLLM::FileStore#with_lock
# File lib/ruby_llm/active_record/file_store.rb, line 82 def write(path, content, content_type: nil) path = path(path) rewindable(content) do |io| type = content_type || RubyLLM::FileStore.content_type_for(path, io) digest = RubyLLM::FileStore.digest(io) io.rewind blob = ::ActiveStorage::Blob.create_and_upload!(io:, filename: File.basename(path), content_type: type, identify: false) save(path, blob.byte_size, type, digest) { |record| record.file.attach(blob) } end end
Writes content to path and returns its StoredFile.