Back to Spree

Model

docs/developer/tutorial/model.mdx

5.5.04.0 KB
Original Source

In this step we'll create the Spree::Brand model with everything it needs: a name column, a rich text description, and an uploadable logo.

Step 1: Generate the Model

Spree ships a spree:model generator that produces a model and migration following all Spree conventions — Spree:: namespacing, prefixed IDs, presence validations, and API filtering allowlists. It understands Rails attribute types including the virtual ones, so rich text and file attachments are part of the same command:

<CodeGroup>
bash
spree generate model Brand name:string:index description:rich_text logo:attachment
bash
bin/rails g spree:model Brand name:string:index description:rich_text logo:attachment
</CodeGroup> <Info> Throughout this tutorial every command is shown in two forms. **Spree CLI** is for projects created with [create-spree-app](/developer/create-spree-app/quickstart), where the app runs in Docker and `spree <command>` routes into the container. **Without Spree CLI** is for apps running Spree backend directly on your machine. </Info>

This creates two files:

  • app/models/spree/brand.rb — the model
  • db/migrate/XXXXXXXXXXXXXX_create_spree_brands.rb — the migration

Now apply the migration:

<CodeGroup>
bash
spree migrate
bash
bin/rails db:migrate
</CodeGroup>

This creates the spree_brands table with an indexed name column. The description and logo attributes don't add columns — rich text lives in Action Text's action_text_rich_texts table, and uploads live in Active Storage's tables.

Step 2: Understand the Generated Model

Open app/models/spree/brand.rb:

ruby
module Spree
  class Brand < Spree.base_class
    has_prefix_id :brand

    has_rich_text :description
    has_one_attached :logo

    validates :name, presence: true

    self.whitelisted_ransackable_attributes = %w[name]
    self.whitelisted_ransackable_associations = %w[]
    self.whitelisted_ransackable_scopes = %w[]
  end
end

Line by line:

  • Spree.base_class — inherits all Spree model functionality (multi-store scoping helpers, preferences, and more). The class is namespaced under Spree::, so it's available as Spree::Brand.
  • has_prefix_id :brand — records get Stripe-style public IDs like brand_k5nR8xLq. APIs never expose raw database IDs.
  • has_rich_text :description — formatted content via Action Text.
  • has_one_attached :logo — file uploads via Active Storage, with image processing and direct-to-storage uploads.
  • validates :name, presence: true — generated automatically for required columns.
  • whitelisted_ransackable_attributes — controls which attributes API clients may filter and sort by. Only allowlisted attributes are queryable, so adding name here is what later makes ?q[name_cont]=nike work.

Step 3: Try It in the Console

<CodeGroup>
bash
spree console
bash
bin/rails console
</CodeGroup>
ruby
brand = Spree::Brand.create!(name: "Wilson")
brand.prefixed_id           # => "brand_k5nR8xLq"

brand.update!(description: "<h1>Hello</h1><p>World</p>")
brand.description.to_s             # => rendered rich text HTML
brand.description.to_plain_text    # => "Hello World"

Spree::Brand.find_by_prefix_id!("brand_k5nR8xLq")  # lookup by public ID

We'll upload the logo through the admin UI in the next step — the model side is already done.

<Tip> The generator accepts more attribute types and options — references with auto-resolved class names (`user:belongs_to`), unique indexes (`slug:string:uniq`), soft delete (`--paranoid`), and custom fields support (`--metafields`). Run it with `--help` to see everything. </Tip>

Next Step

The model is complete. Now let's give admins a UI to manage brands: Admin Dashboard.