smithery/majesticlabs-dev

store-model-coder

Wrap JSON-backed database columns with ActiveModel-like classes using store_model.

Installation

$ npx skills add smithery/majesticlabs-dev --skill store-model-coder

Summary

  • Wrap JSON-backed database columns with ActiveModel-like classes using store_model.
  • Use when creating configuration objects, managing nested JSON attributes, or adding validations to JSON data.
  • Triggers on JSON columns, store_model, typed JSON, configuration objects, or nested attributes.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/majesticlabs-dev · top by installs.

npx skills add smithery/majesticlabs-dev

Browse all from smithery/majesticlabs-dev

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,220 B
  • docs SUMMARY.md 313 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

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

Scenario Better Alternative
Simple key-value settings ActiveRecord::Store or store_accessor
Need database-level queries on JSON Raw jsonb with PostgreSQL operators
Data needs relationships/joins Normalize into separate tables
Truly simple JSON without validation Plain 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