store-model-coder について
JSON データベース列を型付き ActiveRecord 属性クラスとして扱う Claude スキルです。Rails 開発者向けに、検証、入れ子構造、列挙型を扱います。
主な機能
- 01型付き属性とデフォルト値
- 02バリデーションとエラー統合
- 03列挙型
- 04入れ子モデルとモデル配列
- 05変更追跡のガイダンス
活用シーン
- 01JSON 列の設定データをモデル化・検証
- 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 属性
JSON 対応のデータベース列を ActiveModel のようなクラスでラップし、型安全性、バリデーション、関心事の適切な分離を実現します。
このスキルを使用する場面
- フィールドを検証する設定オブジェクト
- 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 対応