> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Extending Core Models

> Learn how to extend Spree's core models to connect Brands with Products

In this tutorial, we'll connect our custom Brand model with Spree's core Product model. This is a common pattern when building features that need to integrate with existing Spree functionality.

<Info>
  This guide assumes you've completed the [Model](/developer/tutorial/model), [Admin](/developer/tutorial/admin), and [File Uploads](/developer/tutorial/file-uploads) tutorials.
</Info>

## What We're Building

By the end of this tutorial, you'll have:

* Products associated with Brands
* A brand selector in the Product admin form
* Understanding of how to safely extend Spree core models

## Choosing the Right Approach

Before extending Spree models, consider which approach fits your needs best:

| What you need                             | Recommended approach                                                                                   |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Add association (belongs\_to, has\_many)  | **Decorator** (this tutorial)                                                                          |
| Add validation or scope                   | **Decorator**                                                                                          |
| Add new instance/class method             | **Decorator**                                                                                          |
| React to model changes (after save, etc.) | [Events subscriber](/developer/core-concepts/events)                                                   |
| Sync with external service on changes     | [Events](/developer/core-concepts/events) or [Webhooks](/developer/core-concepts/webhooks)             |
| Add searchable/filterable field           | [Ransack configuration](/developer/core-concepts/search-filtering#extending-ransackable-configuration) |
| Add admin UI elements                     | [Admin Partials](/developer/admin/extending-ui)                                                        |

<Info>
  This tutorial uses **decorators** because we're adding a structural association between models. For behavioral changes like callbacks, prefer [Events](/developer/core-concepts/events) instead - they're easier to test and maintain.
</Info>

## Understanding Decorators

When working with Spree, you'll often need to add functionality to existing models like `Spree::Product` or `Spree::Order`. However, you shouldn't modify these files directly because:

1. **Upgrades** - Your changes would be lost when updating Spree
2. **Maintainability** - It's hard to track what you've customized
3. **Conflicts** - Direct modifications can conflict with Spree's code

Instead, we use **decorators** - a Ruby pattern that lets you add or modify behavior of existing classes without changing their original source code.

### How Decorators Work

In Ruby, classes are "open" - you can add methods to them at any time. Decorators leverage this by:

1. Creating a module with your new methods
2. Using `Module#prepend` to inject your module into the class's inheritance chain
3. Your methods run first, and can call `super` to invoke the original method

```ruby theme={"theme":"night-owl"}
# This is the basic pattern
module Spree
  module ProductDecorator
    # Add a new method
    def my_new_method
      "Hello from decorator!"
    end

    # Override an existing method
    def existing_method
      # Do something before
      result = super  # Call the original method
      # Do something after
      result
    end
  end

  Product.prepend(ProductDecorator)
end
```

The key line is `Product.prepend(ProductDecorator)` - this inserts your module at the beginning of the method lookup chain, so your methods are found first.

## Step 1: Create the Migration

First, add a `brand_id` column to the products table:

```bash theme={"theme":"night-owl"}
bin/rails g migration AddBrandIdToSpreeProducts brand_id:integer:index
```

Edit the migration to add an index (but no foreign key constraint, keeping it optional):

```ruby db/migrate/XXXXXXXXXXXXXX_add_brand_id_to_spree_products.rb theme={"theme":"night-owl"}
class AddBrandIdToSpreeProducts < ActiveRecord::Migration[8.0]
  def change
    add_column :spree_products, :brand_id, :integer
    add_index :spree_products, :brand_id
  end
end
```

Run the migration:

```bash theme={"theme":"night-owl"}
bin/rails db:migrate
```

<Info>
  We intentionally don't add a foreign key constraint. This keeps the association optional and avoids issues if brands are deleted. Spree follows this pattern for flexibility.
</Info>

## Step 2: Generate the Product Decorator

Spree provides a generator to create decorator files with the correct structure:

```bash theme={"theme":"night-owl"}
bin/rails g spree:model_decorator Spree::Product
```

This creates `app/models/spree/product_decorator.rb`:

```ruby app/models/spree/product_decorator.rb theme={"theme":"night-owl"}
module Spree
  module ProductDecorator
    def self.prepended(base)
      # Class-level configurations go here
    end
  end

  Product.prepend(ProductDecorator)
end
```

## Step 3: Add the Brand Association to Product

Update the decorator to add the `belongs_to` association:

```ruby app/models/spree/product_decorator.rb {4} theme={"theme":"night-owl"}
module Spree
  module ProductDecorator
    def self.prepended(base)
      base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
    end
  end

  Product.prepend(ProductDecorator)
end
```

### Understanding the Code

* `self.prepended(base)` - This callback runs when the module is prepended to a class. The `base` parameter is the class being decorated (`Spree::Product`)
* `base.belongs_to` - We call class methods on `base` to add associations, validations, scopes, etc.
* `optional: true` - Products don't require a brand (the `brand_id` can be `NULL`)

## Step 4: Add Products Association to Brand

Now update your Brand model to define the reverse association:

```ruby app/models/spree/brand.rb {5} theme={"theme":"night-owl"}
module Spree
  class Brand < Spree::Base
    # ... existing code ...

    has_many :products, class_name: 'Spree::Product', dependent: :nullify

    # ... rest of your model ...
  end
end
```

<Info>
  We use `dependent: :nullify` instead of `dependent: :destroy`. When a brand is deleted, products will have their `brand_id` set to `NULL` rather than being deleted. This is safer for e-commerce data.
</Info>

## Step 5: Permit the Brand Parameter

For the admin form to save the brand association, we need to permit the `brand_id` parameter.

Add to your Spree initializer:

```ruby config/initializers/spree.rb {3} theme={"theme":"night-owl"}
# .. other code ..

Spree::PermittedAttributes.product_attributes << :brand_id
```

## Step 6: Add Brand Selector to Product Admin Form

Create a partial to inject the brand selector into the product form. Spree's admin product form has injection points for customization.

Create the partial:

```erb app/views/spree/admin/products/_brand_field.html.erb theme={"theme":"night-owl"}
<div class="card mb-6">
  <div class="card-header">
    <h5 class="card-title"><%= Spree.t(:brand) %></h5>
  </div>
  <div class="card-body">
  <%= f.spree_select :brand_id,
      Spree::Brand.order(:name).pluck(:name, :id),
      { include_blank: true, label: Spree.t(:brand) } %>
</div>
```

Register this partial to appear in the product form. Add to your initializer:

<Tabs>
  <Tab title="Spree 5.2+">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.after_initialize do
      Spree.admin.partials.product_form_sidebar << 'spree/admin/products/brand_field'
    end
    ```
  </Tab>

  <Tab title="Spree 5.1 and below">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.spree_admin.product_form_sidebar_partials << 'spree/admin/products/brand_field'
    ```
  </Tab>
</Tabs>

## Step 7: Add Translation

Add the translation for the brand label:

```yaml config/locales/en.yml theme={"theme":"night-owl"}
en:
  spree:
    brand: Brand
```

## Testing the Association

Verify everything works in the Rails console:

```ruby theme={"theme":"night-owl"}
# Create a brand and product
brand = Spree::Brand.create!(name: 'Nike')
product = Spree::Product.first
product.update!(brand: brand)

# Test associations
product.brand        # => #<Spree::Brand id: 1, name: "Nike"...>
brand.products       # => [#<Spree::Product...>]
brand.products.count # => 1
```

## Decorator Best Practices

<CardGroup cols={2}>
  <Card title="Use prepended callback" icon="code">
    Always use `self.prepended(base)` for class-level additions like associations, validations, and scopes.
  </Card>

  <Card title="Keep decorators focused" icon="crosshairs">
    Each decorator should have a single responsibility. Create multiple decorators if needed.
  </Card>

  <Card title="Call super when overriding" icon="arrow-up">
    When overriding methods, call `super` to preserve original behavior unless you intentionally want to replace it.
  </Card>

  <Card title="Test decorated behavior" icon="flask-vial">
    Write tests specifically for your decorated functionality to catch regressions.
  </Card>
</CardGroup>

### Common Decorator Patterns

#### Adding Validations

```ruby theme={"theme":"night-owl"}
def self.prepended(base)
  base.validates :custom_field, presence: true
end
```

#### Adding Scopes

```ruby theme={"theme":"night-owl"}
def self.prepended(base)
  base.scope :featured, -> { where(featured: true) }
end
```

#### Adding Callbacks

<Warning>
  For callbacks that trigger side effects (syncing to external services, sending notifications, etc.), use [Events subscribers](/developer/core-concepts/events) instead of decorator callbacks. Events are easier to test and won't break during Spree upgrades.
</Warning>

**Decorator approach** (use only for simple, internal logic):

```ruby theme={"theme":"night-owl"}
def self.prepended(base)
  base.before_save :normalize_name
end

def normalize_name
  self.name = name.strip.titleize if name.present?
end
```

**Events approach** (recommended for side effects):

```ruby app/subscribers/my_app/product_sync_subscriber.rb theme={"theme":"night-owl"}
module MyApp
  class ProductSyncSubscriber < Spree::Subscriber
    subscribes_to 'product.updated'

    def handle(event)
      product = Spree::Product.find_by(id: event.payload['id'])
      return unless product

      ExternalSyncService.sync(product)
    end
  end
end
```

#### Adding Class Methods

```ruby theme={"theme":"night-owl"}
def self.prepended(base)
  base.extend ClassMethods
end

module ClassMethods
  def my_class_method
    # Class method logic
  end
end
```

## Complete Files

### Product Decorator

```ruby app/models/spree/product_decorator.rb theme={"theme":"night-owl"}
module Spree
  module ProductDecorator
    def self.prepended(base)
      base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
    end
  end

  Product.prepend(ProductDecorator)
end
```

### Brand Model (Updated)

```ruby app/models/spree/brand.rb theme={"theme":"night-owl"}
module Spree
  class Brand < Spree::Base
    has_many :products, class_name: 'Spree::Product', dependent: :nullify

    has_one_attached :logo
    has_rich_text :description

    validates :name, presence: true
  end
end
```

<Info>
  SEO features like slugs, meta titles, and FriendlyId are covered in the [Slugs](/developer/core-concepts/slugs) documentation.
</Info>

### Spree Initializer Additions

```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
# Permit brand_id in product params
Spree::PermittedAttributes.product_attributes << :brand_id
```

<Tabs>
  <Tab title="Spree 5.2+">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.after_initialize do
      # Add brand field to product form
      Spree.admin.partials.product_form << 'spree/admin/products/brand_field'
    end
    ```
  </Tab>

  <Tab title="Spree 5.1 and below">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    # Add brand field to product form
    Rails.application.config.spree_admin.product_form_partials << 'spree/admin/products/brand_field'
    ```
  </Tab>
</Tabs>

## Next Step

Now that Brands are connected to Products, let's expose them through the Store API:

<Card title="6. Store API" icon="code" href="/developer/tutorial/store-api">
  Create API endpoints for brands and extend the Product API response
</Card>

## Related Documentation

* [Model Tutorial](/developer/tutorial/model) - Creating the Brand model
* [Admin Tutorial](/developer/tutorial/admin) - Building the admin interface
* [Store API Tutorial](/developer/tutorial/store-api) - Exposing brands through the API
* [Events](/developer/core-concepts/events) - Subscribe to model changes without decorators
* [Webhooks](/developer/core-concepts/webhooks) - HTTP callbacks for external integrations
* [Decorators](/developer/customization/decorators) - Full decorator reference
* [Customization Overview](/developer/customization/quickstart) - More customization patterns
* [Products](/developer/core-concepts/products) - Product model documentation
