Rails 5.2+Postgres11:能否将小Product表存内存并保留Active Record查询能力?
当然可行!你不需要切换整个应用的数据库,只需要给Product模型单独配置一个内存版的SQLite连接,同时保留其他模型使用PostgreSQL。这样既能把7条数据存在内存里,又能完整使用Active Record的类型推断和所有你提到的查询方法。
这是最推荐的方案,因为它完全保留Active Record的所有功能,不需要自己造轮子。
1. 配置内存SQLite连接
在config/database.yml中添加一个新的数据库配置:
sqlite_memory: adapter: sqlite3 database: ":memory:" pool: 5 timeout: 5000
2. 让Product模型使用这个连接
修改app/models/product.rb,指定模型使用内存SQLite连接:
class Product < ApplicationRecord # 覆盖默认的PostgreSQL连接,改用内存SQLite establish_connection :sqlite_memory end
3. 启动时从PostgreSQL导入数据到内存
因为内存数据库每次重启都会清空,所以需要在应用启动时把PostgreSQL里的Product数据导入进去。可以在config/initializers/products.rb中添加初始化逻辑:
# 仅在指定环境执行(比如开发/测试环境,根据你的需求调整) if Rails.env.development? || Rails.env.test? # 临时切换回PostgreSQL连接,读取原始数据 Product.establish_connection :development # 这里替换成你的PostgreSQL配置名 original_products = Product.all.to_a # 切回内存SQLite连接,创建表结构并导入数据 Product.establish_connection :sqlite_memory # 先创建和PostgreSQL一致的表结构 Product.connection.create_table :products, force: true do |t| # 自动读取原表的列定义,确保结构一致 Product.columns.each do |column| t.send(column.type, column.name, **column.options.except(:name, :type)) end end # 批量插入数据 Product.insert_all(original_products.map(&:attributes)) end
这样每次启动应用时,都会自动把PostgreSQL里的7条Product数据加载到内存SQLite中,之后所有对Product模型的查询都会直接操作内存数据,速度极快,而且完全支持find_by、where、first、last这些Active Record方法,类型推断也和原来一致。
如果完全不想用任何数据库,也可以把数据加载到内存数组中,手动模拟Active Record的查询接口。但这种方法需要自己实现大量方法,而且类型推断需要手动处理,适合简单场景:
class Product include ActiveModel::Model # 这里列出所有Product属性,确保和数据库字段一致 attr_accessor :id, :name, :price, :created_at, :updated_at # 存储所有数据的内存集合 @@products = [] # 从PostgreSQL加载数据到内存 def self.load_from_postgres # 创建临时Active Record模型读取PostgreSQL数据 temp_model = Class.new(ActiveRecord::Base) do self.table_name = "products" establish_connection :development # 你的PostgreSQL配置名 end @@products = temp_model.all.map do |db_product| new(db_product.attributes) end end # 模拟find_by方法 def self.find_by(conditions) @@products.find do |product| conditions.all? { |key, value| product.send(key) == value } end end # 模拟where方法,返回支持链式调用的集合 def self.where(conditions) filtered = @@products.select do |product| conditions.all? { |key, value| product.send(key) == value } end # 封装成支持first/last/to_a的对象 OpenStruct.new( to_a: filtered, first: filtered.first, last: filtered.last, where: ->(new_conditions) { where(new_conditions) } ) end # 模拟first/last方法 def self.first = @@products.first def self.last = @@products.last end
然后在初始化时调用Product.load_from_postgres即可。但这种方法的缺点很明显:你需要自己实现更多Active Record的方法(比如order、limit),而且类型转换(比如日期、数字)需要手动处理,不如方案一省心。
- 内存SQLite的数据在应用重启后会丢失,所以每次启动都需要重新导入,这符合你的需求(不想存在数据库里)。
- 如果
Product表的结构发生变化,方案一的初始化代码会自动同步表结构,因为它读取了原PostgreSQL表的列定义。 - 如果你在生产环境使用,需要确保初始化逻辑只在应用启动时执行一次,避免重复导入数据。
内容的提问来源于stack exchange,提问作者user2012677

