docs/developer/customization/dependencies.mdx
With Dependencies, you can easily replace parts of Spree core with your custom code. You can replace Services, CanCanCan Abilities (used for Permissions), and API Serializers (used for generating JSON API responses).
This will change every aspect of the application (both APIs, Admin Panel, and Storefront).
In your config/initializers/spree.rb file, you can set the following:
Spree.cart_add_item_service = MyAddToCartService
or using the block syntax:
Spree.dependencies do |dependencies|
dependencies.cart_add_item_service = MyAddToCartService
end
Now let's create your custom service.
mkdir -p app/services && touch app/services/my_add_to_cart_service.rb
And add the following code to it:
class MyAddToCartService < Spree::Cart::AddItem
def call(order:, variant:, quantity: nil, metadata: {}, options: {})
ApplicationRecord.transaction do
run :add_to_line_item
run Spree.cart_recalculate_service
run :update_in_external_system
end
end
private
def update_in_external_system(new_order_line_item)
# Your custom logic here
end
end
This code will:
Spree::Cart::AddItemcall method to add your custom logicrun :add_to_line_item to add the item to the cartrun Spree.cart_recalculate_service to recalculate the cart (returns the resolved class)run :update_in_external_system to execute your custom logic, eg. updating Order in an external system such as ERPWhen you need to use a dependency in your code, you can access it directly via the Spree module:
# Returns the resolved class (not a string)
Spree.cart_add_item_service.call(order: order, variant: variant, quantity: 1)
# For API dependencies, use the Spree.api accessor
Spree.api.storefront_cart_serializer.new(order).serializable_hash
If you need to replace serializers or Services in a specific API endpoint you can create a code decorator:
mkdir -p app/controllers/spree && touch app/controllers/spree/cart_controller_decorator.rb
and add the following code to it:
module Spree
module CartControllerDecorator
def resource_serializer
MyNewAwesomeCartSerializer
end
def add_item_service
MyNewAwesomeAddItemToCart
end
end
CartController.prepend(CartControllerDecorator)
end
This will change the serializer in this API endpoint to MyNewAwesomeCartSerializer and also it will swap the default add_item_service to MyNewAwesomeAddItemToCart.
Different API endpoints can have different dependency injection points. You can review their source code to see what you can replace.
Storefront API and Platform API have separate Dependencies injection points so you can easily customize one without touching the other.
In your Spree initializer (config/initializers/spree.rb) please add:
Spree.api.storefront_cart_serializer = MyNewAwesomeCartSerializer
Spree.api.storefront_cart_add_item_service = MyNewAwesomeAddItemToCart
This will swap the default Cart serializer and Add Item to Cart service for your custom ones within all Storefront API endpoints that use those classes.
You can mix and match both global and API-level customizations:
Spree.cart_add_item_service = MyNewAwesomeAddItemToCart
Spree.api.storefront_cart_add_item_service = AnotherAddItemToCart
The second line will have precedence over the first one, and the Storefront API will use AnotherAddItemToCart and the rest of the application will use MyNewAwesomeAddItemToCart.
Spree provides rake tasks to help you debug and inspect dependencies:
bin/rake spree:dependencies:list
This will output all dependencies with their current values:
[CORE]
cart_add_item_service Spree::Cart::AddItem
cart_create_service Spree::Cart::Create
cart_recalculate_service Spree::Cart::Recalculate [OVERRIDDEN]
...
[API]
storefront_cart_serializer Spree::V2::Storefront::CartSerializer
storefront_cart_add_item_service MyApp::CartAddItem [OVERRIDDEN]
...
You can use grep to filter results:
bin/rake spree:dependencies:list | grep cart
bin/rake spree:dependencies:overrides
This shows only the dependencies that have been customized, along with their original and current values:
[Core OVERRIDES]
cart_recalculate_service Spree::Cart::Recalculate -> MyApp::CartRecalculate (config/initializers/spree.rb:15)
[API OVERRIDES]
storefront_cart_add_item_service Spree::Cart::AddItem -> MyApp::CartAddItem (config/initializers/spree.rb:20)
bin/rake spree:dependencies:validate
This validates that all dependencies can be resolved to valid classes. If any dependency points to a non-existent class, it will report an error:
..........F.........
1 invalid dependencies:
[Core] cart_add_item_service: uninitialized constant NonExistentClass
You can also inspect dependencies programmatically:
# Check all current values
Spree::Dependencies.current_values
# => [{name: :cart_add_item_service, current: MyApp::CartAddItem, default: 'Spree::Cart::AddItem', overridden: true}, ...]
# Check if a specific dependency is overridden
Spree::Dependencies.overridden?(:cart_add_item_service)
# => true
# Get override information (where it was set)
Spree::Dependencies.override_info(:cart_add_item_service)
# => {value: MyApp::CartAddItem, source: "config/initializers/spree.rb:15", set_at: 2024-01-15 10:30:00}
# Validate all dependencies resolve to valid classes
Spree::Dependencies.validate!
# => true (or raises Spree::DependencyError)
The legacy string-based syntax is still supported for backwards compatibility:
# Legacy syntax (still works)
Spree::Dependencies.cart_add_item_service = 'MyAddToCartService'
result = Spree::Dependencies.cart_add_item_service.constantize
# New syntax (recommended)
Spree.cart_add_item_service = MyAddToCartService
result = Spree.cart_add_item_service
Both syntaxes can coexist, but the new syntax is recommended as it's more concise and provides better error messages at assignment time.
Default values can be easily checked by:
bin/rake spree:dependencies:list