mcpField
S
store-model-coder

store-model-coder

github.com/majesticlabs-devDevelopment5046 installsUpdated 7w ago
Repository

About store-model-coder

A Claude skill for modeling JSON-backed ActiveRecord attributes as typed classes with validations, nesting, and enum support. It helps Rails developers structure and validate JSON column data.

Key features

  1. 01Typed attributes and defaults
  2. 02Validations and error merging
  3. 03Enums
  4. 04Nested and array models
  5. 05Dirty-tracking guidance

Use cases

  1. 01Model validated configuration in JSON columns
  2. 02Represent nested address or coordinate data
  3. 03Store arrays of typed line items

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/majesticlabs-dev/majestic-marketplace --skill store-model-coder

After installing, type/store-model-coderin Claude Code to confirm it loaded.

Bundled filesreferences/advanced-patterns.md

Details

From the project README

StoreModel: JSON-Backed ActiveRecord Attributes

Wrap JSON-backed database columns with ActiveModel-like classes for type safety, validations, and clean separation of concerns.

When to Use This Skill

  • Configuration objects with validated fields
  • Nested attributes stored in JSON columns
  • JSON data requiring type safety and ActiveModel behavior
  • Separating JSON logic from parent ActiveRecord model

When NOT to Use StoreModel

ScenarioBetter Alternative
Simple key-value settingsActiveRecord::Store or store_accessor
Need database-level queries on JSONRaw jsonb with PostgreSQL operators
Data needs relationships/joinsNormalize into separate tables
Truly simple JSON without validationPlain JSON column access

Setup

# Gemfile
gem "store_model", "~> 3.0"

Basic Usage

Define a StoreModel Class
# 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
Register in ActiveRecord
# app/models/product.rb
class Product < ApplicationRecord
  attribute :configuration, Configuration.to_type
end
Usage
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")

Enums

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"

Validations

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
Merging Errors to Parent
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"]

Nested Models

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

Array of Models

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

Dirty Tracking

StoreModel doesn't automatically detect nested changes. Use one of these approaches:

# 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" }

Controller Pattern

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

Testing

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

Detailed References

For advanced patterns:

  • references/advanced-patterns.md - Nested attributes, custom types, one-of types, parent tracking

Comments

Markdown supported