ActiveRecordAdapter¶
The ActiveRecordAdapter allows you to store prompts in a relational database using ActiveRecord, perfect for Rails applications and scenarios requiring database-backed prompt storage.
Overview¶
Store prompts in your application's database alongside other application data. This adapter provides full CRUD operations, query capabilities, and integration with ActiveRecord models.
Setup¶
Migration¶
Create the prompts table:
# Generate migration
rails generate migration CreatePrompts
# In the migration file:
class CreatePrompts < ActiveRecord::Migration[7.0]
def change
create_table :prompts do |t|
t.string :prompt_id, null: false, index: { unique: true }
t.text :content, null: false
t.text :description
t.json :metadata, default: {}
t.timestamps
end
end
end
Model¶
# app/models/prompt.rb
class Prompt < ApplicationRecord
validates :prompt_id, presence: true, uniqueness: true
validates :content, presence: true
# Optional: Add scopes for organization
scope :by_category, ->(category) { where("metadata->>'category' = ?", category) }
scope :recent, -> { order(updated_at: :desc) }
end
Configuration¶
# Configure PromptManager to use ActiveRecord
PromptManager.configure do |config|
config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
model_class: Prompt,
id_column: :prompt_id,
content_column: :content
)
end
Basic Usage¶
Creating Prompts¶
# Create via PromptManager
prompt = PromptManager::Prompt.new(id: 'welcome_email')
prompt.save(
content: 'Welcome to our service, [USER_NAME]!',
metadata: {
category: 'email',
author: 'marketing_team',
version: '1.0'
}
)
# Or create via ActiveRecord
Prompt.create!(
prompt_id: 'goodbye_email',
content: 'Thanks for using our service, [USER_NAME]!',
description: 'Farewell email template',
metadata: { category: 'email', priority: 'low' }
)
Reading Prompts¶
# Via PromptManager (recommended)
prompt = PromptManager::Prompt.new(id: 'welcome_email')
result = prompt.render(user_name: 'Alice')
# Via ActiveRecord
prompt_record = Prompt.find_by(prompt_id: 'welcome_email')
puts prompt_record.content
Updating Prompts¶
# Via PromptManager
prompt = PromptManager::Prompt.new(id: 'welcome_email')
prompt.save('Updated content: Welcome [USER_NAME] to our platform!')
# Via ActiveRecord
prompt_record = Prompt.find_by(prompt_id: 'welcome_email')
prompt_record.update!(
content: 'Updated content...',
metadata: prompt_record.metadata.merge(version: '1.1')
)
Advanced Features¶
Query Interface¶
# Configure with query support
PromptManager.configure do |config|
config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
model_class: Prompt,
id_column: :prompt_id,
content_column: :content,
enable_queries: true
)
end
# Search prompts
results = PromptManager.storage.search(
category: 'email',
content_contains: 'welcome'
)
# List by metadata
email_prompts = PromptManager.storage.where(
"metadata->>'category' = ?", 'email'
)
Versioning¶
# Add versioning to your model
class Prompt < ApplicationRecord
has_many :prompt_versions, dependent: :destroy
before_update :create_version
private
def create_version
if content_changed?
prompt_versions.create!(
content: content_was,
version_number: (prompt_versions.maximum(:version_number) || 0) + 1,
created_at: updated_at_was
)
end
end
end
# Version model
class PromptVersion < ApplicationRecord
belongs_to :prompt
end
# Migration for versions
class CreatePromptVersions < ActiveRecord::Migration[7.0]
def change
create_table :prompt_versions do |t|
t.references :prompt, null: false, foreign_key: true
t.text :content, null: false
t.integer :version_number, null: false
t.timestamps
end
add_index :prompt_versions, [:prompt_id, :version_number], unique: true
end
end
Multi-tenancy¶
# Add tenant support
class Prompt < ApplicationRecord
belongs_to :tenant
validates :prompt_id, uniqueness: { scope: :tenant_id }
scope :for_tenant, ->(tenant) { where(tenant: tenant) }
end
# Configure with tenant scope
PromptManager.configure do |config|
config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
model_class: Prompt,
id_column: :prompt_id,
content_column: :content,
scope: -> { Prompt.for_tenant(Current.tenant) }
)
end
Performance Optimization¶
Indexing¶
# Add performance indexes
class AddPromptIndexes < ActiveRecord::Migration[7.0]
def change
add_index :prompts, :prompt_id, unique: true
add_index :prompts, :updated_at
add_index :prompts, "((metadata->>'category'))", name: 'index_prompts_on_category'
add_index :prompts, :content, type: :gin # For full-text search (PostgreSQL)
end
end
Caching¶
# Configure caching
PromptManager.configure do |config|
config.storage = PromptManager::Storage::ActiveRecordAdapter.new(
model_class: Prompt,
id_column: :prompt_id,
content_column: :content,
cache_queries: true,
cache_ttl: 300 # 5 minutes
)
end
# Add caching to your model
class Prompt < ApplicationRecord
after_update :clear_cache
after_destroy :clear_cache
private
def clear_cache
Rails.cache.delete("prompt:#{prompt_id}")
end
end
Connection Pooling¶
# For high-traffic applications
class PromptReadOnlyRecord < ApplicationRecord
self.abstract_class = true
connects_to database: { reading: :prompt_replica }
end
class Prompt < PromptReadOnlyRecord
# Read operations use replica database
end
Integration Examples¶
Rails Controller¶
class PromptsController < ApplicationController
def show
prompt = PromptManager::Prompt.new(id: params[:id])
@content = prompt.render(params.permit(:user_name, :product_name))
rescue PromptManager::PromptNotFoundError
render json: { error: 'Prompt not found' }, status: 404
end
def create
prompt = PromptManager::Prompt.new(id: prompt_params[:id])
prompt.save(prompt_params[:content])
render json: { message: 'Prompt created successfully' }
rescue => e
render json: { error: e.message }, status: 422
end
private
def prompt_params
params.require(:prompt).permit(:id, :content, :description, metadata: {})
end
end
Background Jobs¶
class ProcessPromptJob < ApplicationJob
def perform(prompt_id, parameters)
prompt = PromptManager::Prompt.new(id: prompt_id)
result = prompt.render(parameters)
# Process the result
NotificationService.send_message(result)
rescue PromptManager::PromptNotFoundError => e
logger.error "Prompt not found: #{prompt_id}"
# Handle gracefully
end
end
# Usage
ProcessPromptJob.perform_later('daily_report', user_id: user.id)
Best Practices¶
- Use Transactions: Wrap prompt operations in database transactions
- Validate Content: Add model validations for prompt content
- Index Strategically: Index frequently queried fields
- Cache Wisely: Cache frequently accessed prompts
- Monitor Performance: Track database query performance
- Backup Regularly: Include prompts in your database backup strategy
- Version Control: Consider versioning for important prompts
- Secure Access: Use database-level permissions and encryption
Migration from FileSystem¶
# Migrate from filesystem to database
class MigratePromptsToDatabase
def self.perform(prompts_dir)
Dir.glob(File.join(prompts_dir, '**/*.txt')).each do |file_path|
relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(prompts_dir))
prompt_id = relative_path.sub_ext('').to_s
content = File.read(file_path)
Prompt.create!(
prompt_id: prompt_id,
content: content,
description: "Migrated from #{file_path}",
metadata: {
original_file: file_path,
migrated_at: Time.current
}
)
end
end
end
# Run migration
MigratePromptsToDatabase.perform('/path/to/prompts')