Receber por email
← Voltar para o catálogo

spree-extensions

Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies

34estrelas
Atualizado há 5 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add OrcaQubits/agentic-commerce-skills-plugins

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/spree-extensions/svg)](https://www.skillteca.com.br/skills/spree-extensions)

Alerta por categoria

Receba novas skills de Design e Frontend toda segunda

1 email curto, só com as skills novas de Design e Frontend. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Spree Extensions & Decorators

Before writing code

Fetch live docs:

  1. Fetch https://spreecommerce.org/docs/developer/customization/decorators for the modern prepend pattern.
  2. Fetch https://spreecommerce.org/docs/developer/customization/v4/deface to confirm Deface deprecation status.
  3. Inspect any official Spree extension gem (spree_stripe, spree_klaviyo) for current engine scaffolding.
  4. Check the v5.2 announcement for the generator suite added then.
  5. Verify the current Spree::Dependencies registry by reading lib/spree/dependencies.rb in the live gem.

Conceptual Architecture

What an Extension Is

A Spree extension is a Rails engine packaged as a gem. It can ship:

  • Models / migrations / decorators
  • Controllers / routes
  • Views / assets / JavaScript
  • Subscribers / job classes
  • Initializers wiring into Spree::Dependencies and event bus
  • Admin navigation entries

Used the same way as spree_stripe, spree_klaviyo, spree_paypal_checkout.

Four Customization Surfaces (in preference order)

  1. Events + Webhooks — react to Spree::Event publications via Spree::Subscriber or external HTTP receivers. Stable across upgrades.
  2. Dependencies — swap services via Spree::Dependencies.foo_service = MyService. Spree publishes a well-defined registry of replaceable services.
  3. Admin Navigation + Partials — declarative Spree.admin.navigation API + partial injection slots. No code modification.
  4. Decorators (last resort) — prepend to extend a core class. Most fragile; ties you to internals.

Reach for the highest-numbered tool only when lower-numbered tools can't express what you need.

Generating a New Extension

gem install spree_cmd   # or use the bundled generator
bin/rails g spree:extension spree_my_extension

(Verify the current generator name and flags — spree_cmd may have been replaced by the in-spree generator suite added in v5.2.)

Produces:

spree_my_extension/
├── app/
│   ├── controllers/
│   ├── models/
│   ├── views/
│   ├── subscribers/        # event subscribers
│   ├── decorators/         # _decorator.rb files
│   └── assets/
├── config/
│   ├── routes.rb
│   ├── locales/
│   └── initializers/spree_my_extension.rb
├── db/migrate/             # migrations
├── lib/
│   ├── spree_my_extension/
│   │   ├── engine.rb
│   │   └── version.rb
│   └── spree_my_extension.rb
├── spec/                    # RSpec test suite
├── spree_my_extension.gemspec
└── Gemfile

The Engine File

# lib/spree_my_extension/engine.rb
module SpreeMyExtension
  class Engine < ::Rails::Engine
    require 'spree/core'

    isolate_namespace SpreeMyExtension if defined?(SpreeMyExtension)

    engine_name 'spree_my_extension'

    initializer 'spree_my_extension.environment', before: :load_config_initializers do |app|
      # Boot-time wiring (registries, dependencies)
    end

    def self.activate
      cache_klasses = "#{config.root}/app/**/*_decorator*.rb"
      Dir.glob(cache_klasses) do |c|
        Rails.configuration.cache_classes ? require(c) : load(c)
      end
    end

    config.to_prepare(&method(:activate).to_proc)
  end
end

The to_prepare hook ensures decorators reload in development.

The Decorator Pattern

Modern Spree decorators use Module#prepend, not class_eval reopening. File naming convention: app/models/spree/product_decorator.rb (suffix _decorator.rb).

# app/models/spree/product_decorator.rb
module SpreeMyExtension::ProductDecorator
  def self.prepended(base)
    base.has_many :promotional_videos, class_name: 'SpreeMyExtension::PromotionalVideo'
    base.validates :seo_title, length: { maximum: 70 }, allow_nil: true
    base.scope :featured, -> { where(featured: true) }
  end

  def display_name
    seo_title.presence || super  # `super` walks up the prepend chain
  end
end

Spree::Product.prepend(SpreeMyExtension::ProductDecorator) unless Spree::Product.include?(SpreeMyExtension::ProductDecorator)

Why prepend not include: prepend inserts the module above the class in the method lookup chain — super walks back to the original implementation. This is what allows overriding methods.

Decorator Generators (v5.2+)

bin/rails g spree:model_decorator Spree::Product
bin/rails g spree:controller_decorator Spree::Admin::ProductsController

Generates the boilerplate file with the right naming convention.

Spree::Dependencies — Swappable Services

# config/initializers/spree.rb
Spree::Dependencies.cart_add_item_service = MyApp::CartAddItemService
Spree::Dependencies.shipping_rate_estimator = MyApp::CustomEstimator
Spree::Dependencies.order_updater_class = MyApp::OrderUpdater

Common replaceable services (verify against lib/spree/dependencies.rb):

  • cart_add_item_service, cart_remove_item_service, cart_update_service
  • order_updater_class, order_recalculator, order_canceller
  • shipping_rate_estimator, stock_splitter, package_factory
  • tax_calculator
  • payment_create_service, payment_capture_service
  • pricing_options_factory

Always implement the same public contract as the service you replace.

When to Decorate vs. Use Dependencies

| Need | Approach | |------|----------| | Add a column to Product | Migration (or Metafield, no decorator) | | Add a validation to Product | Decorator | | Add a method to Product | Decorator | | Change cart-add behavior globally | Spree::Dependencies.cart_add_item_service = ... | | React to an order completing | Subscriber | | Change how shipping rates are computed | Replace shipping_rate_estimator | | Add an admin menu item | Spree.admin.navigation.register(...) | | Override an admin view | Partial injection slots; avoid Deface in v5 |

Deface (Deprecated in v5)

Deface used CSS-selector view overrides on ERB:

# DEPRECATED — only works on legacy v4 ERB frontend
Deface::Override.new(
  virtual_path: 'spree/admin/products/index',
  name: 'add_button',
  insert_top: "table.product-list",
  text: '<th>Custom Column</th>'
)

In v5+:

  • Admin views: use partial injection slots (store_products_nav_partials, etc.)
  • Storefront views: customize the Next.js storefront directly

Versioning Extensions

Pin the gemspec to Spree version ranges:

# spree_my_extension.gemspec
spec.add_dependency 'spree', '~> 5.4'

Test against multiple Spree minor versions in CI before releasing.

Implementation Guidance

Adding a Field to a Spree Model

First, try Metafields (no migration, upgrade-safe):

product.metafields.create!(namespace: 'my_app', key: 'editor_pick', value: 'true')

If you need indexed / queryable data, write a migration:

# db/migrate/20260512_add_editor_pick_to_spree_products.rb
class AddEditorPickToSpreeProducts < ActiveRecord::Migration[7.1]
  def change
    add_column :spree_products, :editor_pick, :boolean, default: false, null: false
    add_index :spree_products, :editor_pick
  end
end

Plus a thin decorator if you need scopes/validations:

module MyApp::ProductDecorator
  def self.prepended(base)
    base.scope :editor_picks, -> { where(editor_pick: true) }
  end
end
Spree::Product.prepend(MyApp::ProductDecorator)

Replacing a Service Object

# app/services/my_app/cart_add_item_service.rb
module MyApp
  class CartAddItemService < Spree::Cart::AddItem
    def call(order:, variant:, quantity: 1, options: {})
      # custom logic
      result = super
      # post-processing
      result
    end
  end
end

# config/initializers/spree.rb
Spree::Dependencies.cart_add_item_service = MyApp::CartAddItemService

Always extend rather than rewrite — call super to preserve core behavior.

Avoiding Common Decorator Pitfalls

  • Don't decorate to call observers — emit/subscribe to events instead.
  • Don't decorate controllers to add filters — use Rails middleware or before_action in a sibling controller.
Compartilhar X LinkedIn