关于 store-model-coder
面向 Rails 开发者的 Claude Skill,将 JSON 数据库列建模为带类型的 ActiveRecord 属性类,支持验证、嵌套结构和枚举。
主要功能
- 01带类型的属性与默认值
- 02验证及错误合并
- 03枚举
- 04嵌套模型与模型数组
- 05脏数据跟踪指引
适用场景
- 01为 JSON 列中的配置建模并验证
- 02表示嵌套地址或坐标数据
- 03存储类型化行项目数组
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ majesticlabs-dev/ majestic-marketplace --skill store-model-coder安装后在 Claude Code 中输入/store-model-coder确认已加载。
附带文件references/advanced-patterns.md
详细介绍
StoreModel:JSON 后端 ActiveRecord 属性
使用类似 ActiveModel 的类封装 JSON 后端数据库列,实现类型安全、验证和清晰的职责分离。
适用场景
- 带有已验证字段的配置对象
- 存储在 JSON 列中的嵌套属性
- 需要类型安全和 ActiveModel 行为的 JSON 数据
- 将 JSON 逻辑与父级 ActiveRecord 模型分离
不适合使用 StoreModel 的场景
| 场景 | 更好的替代方案 |
|---|---|
| 简单的键值设置 | ActiveRecord::Store 或 store_accessor |
| 需要对 JSON 执行数据库级查询 | 使用 PostgreSQL 运算符操作原始 jsonb |
| 数据需要关联或联接 | 规范化并拆分到单独的表中 |
| 无需验证的简单 JSON | 直接访问普通 JSON 列 |
设置
# Gemfile
gem "store_model", "~> 3.0"
基本用法
定义 StoreModel 类
# app/models/configuration.rb
class Configuration
include StoreModel::Model
attribute :model, :string
attribute :color, :string
attribute :max_speed, :integer, default: 100
validates :model, presence: true
validates :max_speed, numericality: { greater_than: 0 }
end
在 ActiveRecord 中注册
# app/models/product.rb
class Product < ApplicationRecord
attribute :configuration, Configuration.to_type
end
用法
product = Product.new
product.configuration = { model: "rocket", color: "red" }
product.configuration.model # => "rocket"
product.configuration.color = "blue"
product.save!
# Also accepts StoreModel instances
product.configuration = Configuration.new(model: "shuttle")
枚举
class Configuration
include StoreModel::Model
attribute :model, :string
enum :status, %i[draft active archived], default: :draft
# With custom values
enum :priority, { low: 0, medium: 1, high: 2 }, default: :medium
end
config = Configuration.new
config.status # => "draft"
config.active? # => false
config.active! # Sets status to :active
config.status # => "active"
验证
class Address
include StoreModel::Model
attribute :street, :string
attribute :city, :string
attribute :zip, :string
attribute :country, :string, default: "US"
validates :street, :city, :zip, presence: true
validates :zip, format: { with: /\A\d{5}(-\d{4})?\z/ }, if: -> { country == "US" }
end
将错误合并到父级
class User < ApplicationRecord
attribute :address, Address.to_type
validates :address, store_model: { merge_errors: true }
end
user = User.new(address: { street: "", city: "" })
user.valid?
user.errors.full_messages
# => ["Address street can't be blank", "Address city can't be blank", "Address zip can't be blank"]
嵌套模型
class Coordinate
include StoreModel::Model
attribute :latitude, :float
attribute :longitude, :float
validates :latitude, :longitude, presence: true
end
class Location
include StoreModel::Model
attribute :name, :string
attribute :coordinate, Coordinate.to_type
validates :name, presence: true
validates :coordinate, store_model: { merge_errors: true }
end
模型数组
class LineItem
include StoreModel::Model
attribute :name, :string
attribute :quantity, :integer, default: 1
attribute :price_cents, :integer
validates :name, :price_cents, presence: true
end
class Order < ApplicationRecord
attribute :line_items, LineItem.to_array_type
validates :line_items, store_model: { merge_array_errors: true }
end
order = Order.new
order.line_items = [
{ name: "Widget", quantity: 2, price_cents: 1000 },
{ name: "Gadget", quantity: 1, price_cents: 2500 }
]
order.line_items.first.name # => "Widget"
order.line_items.sum(&:price_cents) # => 3500
脏数据跟踪
StoreModel 不会自动检测嵌套更改。可使用以下任一方法:
# Option 1: Reassign the entire object
product.configuration = product.configuration.dup.tap { |c| c.color = "green" }
# Option 2: Mark as changed explicitly
product.configuration.color = "green"
product.configuration_will_change!
product.save!
# Option 3: Use attribute assignment
product.configuration = { **product.configuration.attributes, color: "green" }
控制器模式
class ProductsController < ApplicationController
def create
@product = Product.new(product_params)
if @product.save
redirect_to @product
else
render :new, status: :unprocessable_entity
end
end
private
def product_params
params.require(:product).permit(
:name,
configuration: [:model, :color, :max_speed, :status]
)
end
end
测试
RSpec.describe Configuration do
describe "validations" do
it "requires model" do
config = described_class.new(model: nil)
expect(config).not_to be_valid
expect(config.errors[:model]).to include("can't be blank")
end
end
describe "enums" do
it "defaults to draft status" do
config = described_class.new
expect(config).to be_draft
end
it "transitions status" do
config = described_class.new
config.active!
expect(config).to be_active
end
end
end
RSpec.describe Product do
describe "configuration" do
it "accepts hash" do
product = described_class.new(configuration: { model: "rocket" })
expect(product.configuration.model).to eq("rocket")
end
it "merges validation errors" do
product = described_class.new(configuration: { model: nil })
expect(product).not_to be_valid
expect(product.errors[:configuration]).to be_present
end
end
end
详细参考
高级模式:
references/advanced-patterns.md- 嵌套属性、自定义类型、one-of 类型、父级跟踪
评论
支持 Markdown