| name | pallastrade-dependencies |
| description | Use when the user wants to swap how a core PallasTrade service computes — cart add, cart recalculate, checkout flow, ability checks, payment processing, search, serializers in the API. Common phrasings include "PallasTrade.dependencies", "PallasTrade::Dependencies", "replace PallasTrade::Cart::AddItem", "swap the cart recalculate service", "custom ability", "override an API serializer", "swap a service", "dependency injection in PallasTrade", "pallastrade:dependencies:list", "pallastrade:dependencies:overrides", "pallastrade:dependencies:validate", "what services can I swap". Covers global vs API-level overrides, the introspection rake tasks, and the full catalog of swappable services. For deciding *whether* to swap a service vs use a decorator or subscriber, see the `pallastrade-customization` skill first. |
PallasTrade Dependencies (Dependency Injection)
Commands below use the PallasTrade CLI form (pallastrade …, Docker). On a classic Rails app without the CLI (typical pre-5.4), use the native mapping in the pallastrade-project skill — bin/rails / bundle exec rake from the app root, paths without the backend/ prefix.
PallasTrade.dependencies is the canonical way to replace a core PallasTrade service with your own implementation — no fork, no monkey-patch, no decorator. You inherit from the PallasTrade default, override the methods you need, and register your class as the dependency. PallasTrade's own code calls your service everywhere it used to call the default.
The core has 70+ injection points (71 in 5.5); the API has 300+ more (303 in 5.5) for serializers, finders, and per-endpoint services. The full set is documented at node_modules/@pallastrade/docs/dist/developer/customization/dependencies.md.
When to reach for this vs other patterns
| Want to... | Use |
|---|
| Replace how a core service computes (cart add, cart recalculate, checkout step, ability checks, search, finder) | Dependency injection (this skill) |
| Replace an API serializer everywhere | Dependency injection — PallasTrade.api.<serializer> = MyApp::Foo |
| React to something happening after a service runs (sync to ERP, notify) | Events subscriber — see pallastrade-events-webhooks |
| Add an association / validation / scope / method to a model | Decorator — see pallastrade-decorators |
| Add a brand-new model + API endpoint | pallastrade:api_resource — see pallastrade-resource |
| Tweak runtime config / preferences | PallasTrade::Config[:key] |
If PallasTrade gives you a swappable service, use it. Decorating PallasTrade::Cart::AddItem would couple you to the parent's step names and break on minor upgrades; subclassing + injection is the supported extension point.
The basic pattern
Step 1: subclass the PallasTrade default
class MyApp::Cart::AddItem < PallasTrade::Cart::AddItem
def call(order:, variant:, quantity: nil, metadata: {}, public_metadata: {}, private_metadata: {}, options: {})
ApplicationRecord.transaction do
run :add_to_line_item
run :handle_stock_reservations
run :update_in_external_system
run PallasTrade.cart_recalculate_service
end
end
private
def update_in_external_system(order:, line_item:, **rest)
success(order: order, line_item: line_item, **rest)
end
end
Inherit from the PallasTrade default. Override call if you need to change the step chain; override individual private steps (add_to_line_item, handle_stock_reservations) if you only need to tweak one piece of behavior.
Every run step must return success(...) or failure(...) — otherwise PallasTrade::ServiceModule::WrongDataPassed is raised after the chain. The value you pass to success is double-splatted (**) into the next step, so it must be a hash whenever another step follows. That's also why the custom step goes before run PallasTrade.cart_recalculate_service here: PallasTrade::Cart::Recalculate ends with success(line_item) (a bare LineItem, not a hash), so no run step can come after it.
Step 2: register the override
In config/initializers/pallastrade.rb. Two syntaxes — both work, the direct form is concise:
PallasTrade.cart_add_item_service = MyApp::Cart::AddItem
PallasTrade.dependencies do |deps|
deps.cart_add_item_service = MyApp::Cart::AddItem
deps.cart_recalculate_service = MyApp::Cart::Recalculate
deps.checkout_advance_service = MyApp::Checkout::Advance
end
Step 3: PallasTrade picks up your service everywhere
You don't have to find and patch callers. PallasTrade's own code calls PallasTrade.cart_add_item_service.call(...) rather than PallasTrade::Cart::AddItem.call(...) — your replacement runs in admin requests, API requests, the dashboard, the storefront, background jobs, everything.
Using dependencies from your own code
When your code needs to call a PallasTrade service that might be overridden by someone else's customization, go through the dependency accessor — never hardcode the default class:
PallasTrade.cart_add_item_service.call(order: order, variant: variant, quantity: 1)
PallasTrade.api.cart_serializer.new(order).serializable_hash
PallasTrade::Cart::AddItem.call(order: order, variant: variant, quantity: 1)
This matters for extensions and shared code — using the accessor means your code composes cleanly with whatever overrides the host app has registered.
Global vs API-level overrides
The two API surfaces are Store API v3 and Admin API v3. They share core services but have separate serializer injection points, so you can customize one surface without touching the other:
- Serializers are per-surface:
PallasTrade.api.<resource>_serializer for the Store API (e.g. PallasTrade.api.product_serializer, PallasTrade.api.cart_serializer) and PallasTrade.api.admin_<resource>_serializer for the Admin API (e.g. PallasTrade.api.admin_product_serializer). Admin serializers extend their Store counterparts, so public-field changes propagate automatically.
- Services have a single core-level injection point —
PallasTrade.<name> (e.g. PallasTrade.cart_add_item_service). All v3 endpoints, Store and Admin alike, call the core dependency directly; there is no per-surface service layer.
PallasTrade.cart_add_item_service = MyApp::CartAddItem
PallasTrade.api.product_serializer = 'MyApp::ProductSerializer'
PallasTrade.api.admin_product_serializer = 'MyApp::Admin::ProductSerializer'
Per-controller overrides
If you only want to swap a serializer for one specific controller (rather than globally or per-surface), use a controller decorator overriding serializer_class:
module PallasTrade::Api::V3::Store
module CartsControllerDecorator
def serializer_class
MyApp::PremiumCartSerializer
end
end
end
PallasTrade::Api::V3::Store::CartsController.prepend PallasTrade::Api::V3::Store::CartsControllerDecorator
Generate the file with bin/rails g pallastrade:controller_decorator PallasTrade::Api::V3::Store::CartsController.
The v3 ResourceController hooks you can override this way are model_class, serializer_class, scope, find_resource, permitted_params, and collection_includes. There are no per-controller <name>_service hooks in API v3 — controllers call the registered dependencies directly, so to swap a service use the global (PallasTrade.<name>) injection points described above. (Per-surface PallasTrade.api.* points exist only for serializers.)
For the decorator syntax + generator, see the pallastrade-decorators skill.
Inspecting + debugging dependencies
PallasTrade ships three rake tasks for working with the dependency graph.
List everything
pallastrade rake pallastrade:dependencies:list
Output looks like:
[CORE]
ability_class PallasTrade::Ability
cart_add_item_service PallasTrade::Cart::AddItem
cart_create_service PallasTrade::Cart::Create
cart_recalculate_service PallasTrade::Cart::Recalculate [OVERRIDDEN]
...
[API]
cart_serializer PallasTrade::Api::V3::CartSerializer
admin_product_serializer PallasTrade::Api::V3::Admin::ProductSerializer
product_serializer MyApp::ProductSerializer [OVERRIDDEN]
...
[OVERRIDDEN] flags every dependency that's been swapped from the default — invaluable for figuring out what an extension changed.
Filter to find a specific service:
pallastrade rake pallastrade:dependencies:list | grep cart
pallastrade rake pallastrade:dependencies:list | grep -i serializer
Show only overrides
pallastrade rake pallastrade:dependencies:overrides
Lists only the dependencies that differ from the default, with the source location (file + line) of the override:
[Core OVERRIDES]
cart_recalculate_service PallasTrade::Cart::Recalculate -> MyApp::Cart::Recalculate (config/initializers/pallastrade.rb:15)
[API OVERRIDES]
admin_product_serializer PallasTrade::Api::V3::Admin::ProductSerializer -> MyApp::Admin::ProductSerializer (config/initializers/pallastrade.rb:20)
Use this when you walk into an inherited project — it answers "what has this app customized?" in one command.
Validate that all dependencies resolve
pallastrade rake pallastrade:dependencies:validate
Loads every registered dependency and confirms it points to a real class. Catches typos and missing constants before runtime:
....F...............
1 invalid dependencies:
[Core] cart_add_item_service: uninitialized constant MyApp::Cart::AddIem
Wire this into CI on any project that overrides dependencies. Typos here are silent at boot and only surface when the affected code path runs in production.
Programmatic introspection
The rake tasks are thin wrappers around a public Ruby API. Use it in console sessions, custom rake tasks, or extension health checks:
PallasTrade::Dependencies.current_values
PallasTrade::Dependencies.overridden?(:cart_add_item_service)
PallasTrade::Dependencies.override_info(:cart_add_item_service)
PallasTrade::Dependencies.validate!
PallasTrade::Api::Dependencies exposes the same surface for the API-level injection points.
The catalog — what's actually swappable
The injection points are grouped by domain. The list is too long to enumerate in full; this is the categorical map. Run pallastrade rake pallastrade:dependencies:list to see the full set for the installed version.
Core (71 injection points in 5.5)
| Category | Examples |
|---|
| Cart | cart_add_item_service, cart_remove_item_service, cart_recalculate_service, cart_create_service, cart_update_service, cart_set_item_quantity_service, cart_compare_line_items_service, cart_change_currency_service, cart_empty_service, cart_destroy_service, cart_associate_service, cart_estimate_shipping_rates_service, cart_remove_out_of_stock_items_service |
| Carts (plural) | carts_complete_service |
| Checkout | checkout_next_service, checkout_advance_service, checkout_update_service, checkout_complete_service, checkout_add_store_credit_service, checkout_remove_store_credit_service, checkout_get_shipping_rates_service, checkout_select_shipping_method_service |
| Order | (order finalization, recalculation, cancellation services) |
| Shipment | (shipment update, ready, ship, cancel services) |
| Gift cards | gift_card_apply_service |
| Coupons | coupon_handler — single handler for apply + remove (PallasTrade::PromotionHandler::Coupon) |
| Tracking numbers | (tracking number generators) |
| Account | (account create/update services) |
| Addresses | (address create/update services) |
| Credit cards | (credit card management) |
| Classifications | (product-taxon association services) |
| Line items | (line item create/update/destroy services) |
| Payments | payment_create_service, payments_handle_webhook_service |
| Finders | (record lookup classes — line_item_by_variant_finder, etc.) |
API (138 injection points)
| Category | Examples |
|---|
| v3 Store serializers | cart_serializer, product_serializer, order_serializer, etc. (unprefixed, one per resource) |
| v3 Admin serializers | admin_product_serializer, admin_order_serializer, etc. |
| v3 event serializers | Serializers for models that don't yet have Store API endpoints |
| Sorters / Paginators / Finders | API-specific sort, pagination, and lookup classes |
| Coupon code handler | Per-API-surface coupon handler |
The PallasTrade.api.<name> accessor reaches into API-level dependencies; the bare PallasTrade.<name> accessor reaches into core dependencies.
Backwards compatibility — old syntax
Older PallasTrade projects used a string-based syntax that's still supported:
PallasTrade::Dependencies.cart_add_item_service = 'MyApp::Cart::AddItem'
result = PallasTrade::Dependencies.cart_add_item_service.constantize
PallasTrade.cart_add_item_service = MyApp::Cart::AddItem
result = PallasTrade.cart_add_item_service
Both can coexist in one initializer. New code should use the direct syntax — it catches misspelled class names immediately rather than at first invocation.
Common pitfalls
Calling the wrong accessor in your own code
PallasTrade.cart_add_item_service resolves to whatever the current dependency is (default OR an override). PallasTrade::Cart::AddItem is always the literal default class. Always use the accessor in code that might run alongside an override — extensions, shared services, decorators. Hardcoding the default class breaks composition.
Forgetting to inherit from the default
A common mistake on first try:
class MyApp::Cart::AddItem
def call(order:, variant:, **)
end
end
class MyApp::Cart::AddItem < PallasTrade::Cart::AddItem
def call(order:, variant:, **)
super
end
end
PallasTrade services prepend PallasTrade::ServiceModule::Base for the run step orchestration. Your replacement must inherit from the PallasTrade default (or prepend that module itself — include will not wire the class-level .call) for the same step-chain behavior to work.
Dropping steps from call
When you override call, every run step in the parent that you don't repeat is dropped:
def call(order:, variant:, **)
ApplicationRecord.transaction do
run :add_to_line_item
run :handle_stock_reservations
run PallasTrade.cart_recalculate_service
end
end
def call(order:, variant:, **)
ApplicationRecord.transaction do
run :add_to_line_item
run PallasTrade.cart_recalculate_service
run :my_custom_step
end
end
Stock reservations stop working. Read the parent's call and preserve every step you don't have a reason to drop.
Assuming you need a second, API-level assignment
You don't. A global override applies everywhere, including the v3 Store and Admin APIs:
PallasTrade.cart_add_item_service = MyApp::Cart::AddItem
The v3 controllers call PallasTrade.cart_add_item_service (the core injection point) directly and resolve it lazily at request time, so an override in config/initializers/pallastrade.rb takes effect for API requests too.
Initializer load order
Dependency overrides go in config/initializers/pallastrade.rb. Multiple extensions setting the same dependency follow alphabetical gem load order — the last assignment wins. If two gems both try to override cart_add_item_service, only the alphabetically-later one's override survives. Use pallastrade:dependencies:overrides to confirm what actually ended up registered.
Where to read further
- Docs:
node_modules/@pallastrade/docs/dist/developer/customization/dependencies.md
- Core injection point list:
PallasTrade::Core::Dependencies::INJECTION_POINTS_WITH_DEFAULTS in the installed pallastrade_core gem, lib/pallastrade/core/dependencies.rb
- API injection point list:
PallasTrade::Api::ApiDependencies::INJECTION_POINTS_WITH_DEFAULTS in the installed pallastrade_api gem, lib/pallastrade/api/dependencies.rb (PallasTrade::Api::Dependencies is an instance of this class)
PallasTrade::ServiceModule::Base — the base class behind the run :step_name orchestration
- For deciding whether to swap a service vs use events vs decorate: the
pallastrade-customization skill
- For installing third-party PallasTrade gems that ship dependency overrides: the
pallastrade-extensions skill