Technical Documentation: solidusio/solidus
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (`README.md`)
Solidus
A free, open-source e-commerce platform that gives you complete control over your store.
- Visit our website: https://solidus.io/
- Read our Community Guidelines: https://guides.solidus.io/policies/community-guidelines/
- View the project roadmap: https://github.com/orgs/solidusio/projects/7
- Read our guides: https://guides.solidus.io/
- Join our Slack: http://slack.solidus.io/
- Solidus Security: mailing list
Table of Contents
- Supporting Solidus
- Summary
- Demo
- Getting Started
- Installation Options
- Performance
- Developing Solidus
- Contributing
Supporting Solidus
As a community-driven project, Solidus relies on funds and time donated by developers and stakeholders who use Solidus for their businesses. If you'd like to help Solidus keep growing, please consider:
Main Contributor & Director
At present, Nebulab is the main code contributor and director of Solidus, providing technical guidance and coordinating community efforts and activities.
Ambassadors
Support this project by becoming a Solidus Ambassador. Your logo will show up here with a link to your website. Become an Ambassador.
Summary
Solidus is a complete open source e-commerce solution built with Ruby on Rails.
It is a fork of Spree.
See the Solidus class documentation and the Solidus
Guides for information about the functionality that
Solidus provides.
Solidus consists of several gems. When you require the solidus gem in yourGemfile, Bundler will install all of the following gems:
solidus_api(RESTful API)solidus_backend(Admin area)solidus_core(Essential models, mailers, and classes)solidus_sample(Sample data)
All of the gems are designed to work together to provide a fully functional
e-commerce platform. However, you may only want to use thesolidus_core gem
combine it with your own custom frontend, admin interface, and API.
Demo
You can try the live Solidus demo here. The admin section can be accessed here.
Getting started
Begin by making sure you have
Imagemagick installed, which is
required for Paperclip. (You can install it using Homebrew if
you're on a Mac.)
To install Solidus with the current storefront, follow the instructions in
storefront/README.md.
Accessing Solidus Store
Start the Rails server with the command:
bin/rails sThe storefront will be accessible at
http://localhost:3000/ and the admin can be found at
http://localhost:3000/admin/.
For information on how to customize your store, check out the customization guides.
Default Username/Password
As part of running the above installation steps, you will be asked to set an admin email/password combination. The default values are [email protected] and test123, respectively.
Questions?
The best way to ask questions is to join the Solidus Slack and join the #support channel.
Installation options
Instead of a stable build, if you want to use the bleeding edge version of
Solidus, use this line:
gem 'solidus', github: 'solidusio/solidus'Note: The master branch is not guaranteed to ever be in a fully functioning
state. It is too risky to use this branch in production.
By default, the installation generator (solidus:install) will run
migrations as well as adding seed and sample data. This can be disabled using
bin/rails g solidus:install --migrate=false --sample=false --seed=falseYou can always perform any of these steps later by using these commands.
bin/rails railties:install:migrations
bin/rails db:migrate
bin/rails db:seed
bin/rails spree_sample:loadThere are also options and rake tasks provided by
solidus_auth_devise.
Performance
You may notice that your Solidus store runs slowly in development mode. This
can be because in development each CSS and JavaScript is loaded as a separate
include. This can be disabled by adding the following toconfig/environments/development.rb.
config.assets.debug = falseTurbolinks
To gain some extra speed you may enable Turbolinks inside of Solidus admin.
Add gem 'turbolinks', '~> 5.0.0' into your Gemfile (if not already present)
and change vendor/assets/javascripts/spree/backend/all.js as follows:
//= require turbolinks
//
// ... current file content
//
//= require spree/backend/turbolinks-integration.jsCAUTION Please be aware that Turbolinks can break extensions
and/or customizations to the Solidus admin. Use at your own risk.
Developing Solidus
Clone the Git repo
bashgit clone git://github.com/solidusio/solidus.git cd solidus
Without Docker
Install the gem dependencies
bashbin/setupNote: If you're using PostgreSQL, MySQL, or MariaDB, you'll need to install those gems through the DB environment variable.
bash# PostgreSQL export DB=postgresql bin/setup # MySQL or MariaDB (mysql2 adapter) export DB=mysql bin/setup
With Docker
docker-compose up -dWait for all the gems to be installed (progress can be checked through docker-compose logs -f app).
You can provide the ruby version you want your image to use:
docker-compose build --build-arg RUBY_VERSION=3.0 app
docker-compose up -dThe rails version can be customized at runtime through the RAILS_VERSION environment variable:
RAILS_VERSION='~> 5.0' docker-compose up -dRunning tests:
# sqlite
docker-compose exec app bin/rspec
# postgres
docker-compose exec app env DB=postgres bin/rspec
# mysql
docker-compose exec app env DB=mysql bin/rspecAccessing the databases:
# sqlite
docker-compose exec app sqlite3 /path/to/db
# postgres
docker-compose exec app env PGPASSWORD=password psql -U root -h postgres
# mysql
docker-compose exec app mysql -u root -h mysql -ppasswordIn order to be able to access the sandbox application, just make
sure to provide the appropriate --binding option to rails server. By
default, port 3000 is exposed, but you can change it through SANDBOX_PORT
environment variable:
SANDBOX_PORT=4000 docker-compose up -d
docker-compose exec app bin/sandbox
SANDBOX_PORT=4000 docker-compose exec app bin/devSandbox
Solidus is meant to be run within the context of Rails application. You can
easily create a sandbox application inside of your cloned source directory for
testing purposes.
This sandbox includes solidus_auth_devise and generates with seed and sample
data already loaded.
Create the sandbox application
bashbin/sandboxYou can create a sandbox with PostgreSQL, MySQL, or MariaDB by setting the DB environment variable.
bash# PostgreSQL export DB=postgresql bin/sandbox # MySQL or MariaDB (mysql2 adapter) export DB=mysql bin/sandboxDepending on your local environment, it may be necessary for you to set environment variables for your RDBMS, namely:
DB_HOSTDB_USERDB_PASSWORD
If you need to create a Rails 5.2 application for your sandbox, for example
if you are still using Ruby 2.4 which is not supported by Rails 6, you can
use theRAILS_VERSIONenvironment variable.bashexport RAILS_VERSION='~> 5.2.0' bin/setup bin/sandboxYou can start the Rails server and other services from either the Solidus folder or the
sandbox one by running the command:bashbin/dev
Please note: if you run bin/rails server or similar commands, only the Rails server will
start. This might cause the error couldn't find file 'solidus_admin/tailwind.css' when you
try to load all pages.
Tests
Solidus uses RSpec for tests. Refer to its documentation for
more information about the testing library.
CI/CD
We use GitHub Actions to run Solidus' test suites against all supported versions
of Ruby and Rails. Before a pull request can be merged, all of the tests must
pass.
You can see the build statuses on the repository's GitHub Actions
page.
Run all tests
ChromeDriver is required to run
the backend test suites.
To execute all of the test specs, run the bin/build script at the root of the Solidus project:
createuser --superuser --echo postgres # only the first time
bin/buildThe bin/build script runs using PostgreSQL by default, but it can be overridden by setting the DB environment variable to DB=sqlite or DB=mysql (MariaDB uses DB=mysql). For example:
env DB=mysql bin/buildIf the command fails with MySQL or MariaDB related errors you can try creating a user with this command:
# Creates a user with the same name as the current user and no restrictions.
mysql --user="root" --execute="CREATE USER '$USER'@'localhost'; GRANT ALL PRIVILEGES ON * . * TO '$USER'@'localhost';"Run an individual test suite
Each gem contains its own series of tests. To run the tests for the core project:
cd core
bundle exec rspecBy default, rspec runs the tests for SQLite 3. If you would like to run specs
against another database you may specify the database in the command:
env DB=postgresql bundle exec rspecCode coverage reports
If you want to run the SimpleCov code
coverage report:
COVERAGE=true bundle exec rspecExtensions
In addition to core functionality provided in Solidus, there are a number of
ways to add features to your store that are not (or not yet) part of the core
project.
A list can be found at extensions.solidus.io.
If you want to write an extension for Solidus, you can use the
solidus_dev_support gem.
Contributing
Solidus is an open source project and we encourage contributions. Please read
CONTRIBUTING.md before contributing.
Overview (`admin/README.md`)
Solidus Admin
A Rails engine that provides an administrative interface to the Solidus e-commerce platform.
Overview
- Based on ViewComponent and TailwindCSS
- Uses StimulusJS and Turbo for interactivity
- Works as a separate engine with its own routes
- Uses the same models as the main Solidus engine
- Has its own set of controllers
Installation
solidus_admin is included by default in new stores generated with Solidus 4.3 or later, as well as those generated from the main branch.
solidus_admin can be added to existing stores by bundling it in the Gemfile and running the installer generator:
bundle add solidus_admin
bin/rails g solidus_admin:installIf you're using an authentication system other than solidus_auth_devise you'll need to manually configure authentication methods (see api documentation for SolidusAdmin::Configuration).
If you encounter the error couldn't find file 'solidus_admin/tailwind.css' when loading admin pages, you need to manually build the solidus_admin tailwind CSS styles.
This issue typically occurs when you bundle Solidus from a GitHub branch or from the local filesystem, or with the sandbox application.
Please see Customizing tailwind for more information.
Components
See docs/contributing/components.md for more information about components.
Using it alongside `solidus_backend`
solidus_backend is the current admin interface for Solidus. SolidusAdmin is under development, acts as a drop-in replacement for solidus_backend and will eventually replace it. It's designed to gradually take over existing functions.
For now, you can use both solidus_backend and SolidusAdmin in the same application. To do this, mount the SolidusAdmin engine before Spree::Core::Engine.
You can use a route constraint to replace any solidus_backend routes with SolidusAdmin routes.
By default, SolidusAdmin routes are turned off if a cookie named solidus_admin is set to false, or if a query parameter named solidus_admin is set to false. This lets you switch between the two admin interfaces easily.
This constraint is set up in the application's routes file, so you can easily change it:
# config/routes.rb
mount SolidusAdmin::Engine, at: '/admin', constraints: ->(req) {
$redis.get('solidus_admin') == 'true' # or any other logic
}Authentication & Authorization
- Solidus Amidn delegates authentication to
solidus_backendand relies onsolidus_corefor authorization.
Development
Adding components to Solidus Admin
Solidus Admin components can be generated with the solidus_admin:componentgenerator:
# the `solidus_admin/` namespace is added by default
bin/rails admin g solidus_admin:component foo
create app/components/solidus_admin/foo/component.rb
create app/components/solidus_admin/foo/component.html.erb
create app/components/solidus_admin/foo/component.yml
create app/components/solidus_admin/foo/component.js
create spec/components/solidus_admin/foo/component_spec.rbPlease note that when using the component generator from within the admin folder it will generate the component in the library
instead of the sandbox application.
Releasing
- Update the version in
lib/solidus_admin/version.rb - Commit the changes with the message
Release solidus_admin/v1.2.3 cd admin; bundle exec rake release- Manually release on GitHub
Acknowledgements
This project uses icons from Remix Icon, licensed
under the Remix Icon License v1.0.
Overview (`api/README.md`)
solidus_api
API contains the controllers and Jbuilder views implementing the REST API of
Solidus.
Testing
Run the API tests:
bundle exec rspecDocumentation
The API documentation is in the openapi directory. It follows the
OpenAPI specification and it is hosted on Stoplight Docs.
If you want to contribute, you can use Stoplight Studio. Simply
follow these steps:
- Create a new Stoplight Studio project
- Copy-paste the content of
openapi/solidus-api.oas.ymlinto your project - Edit the endpoints and models as needed
- Copy-paste the result back into
openapi/solidus-api.oas.yml - Open a PR!
Note: Only use embedded models in Stoplight Studio, as Stoplight Docs is
not compatible with externally-defined models!
CircleCI automatically syncs our Git repo with Stoplight Docs when a PR is
merged, and automatically publishes a new version on Docs when a new Solidus
version is released.
Related projects
- solidus-sdk: created
by Joel Saupe at Deseret Book, this is a JS SDK
that allows you to use the Solidus API. It even supports plug-ins, so you can
easily extend it with the endpoints provided by your Solidus extensions!
Overview (`backend/README.md`)
solidus\_backend
Backend contains the controllers, views, and assets making up the admin interface of Solidus.
Assets
JavaScript
Can be found in app/assets/javascripts/spree/backend/
Any new files should be in JavaScript (ES5).
Stylesheets
Can be found in app/assets/stylesheets/spree/backend/
The stylesheets are written in SCSS and include all of Bourbon and Bootstrap 4 alpha.
When running the application there is a styleguide available at:
/admin/style_guideTesting
Run the tests
bundle exec rspecRun the JavaScript tests (must have ChromeDriver installed)
bundle exec teaspoonOverview (`core/README.md`)
Summary
Solidus Core provides the essential e-commerce data models upon which the
Solidus system depends.
Core models
Solidus implements over 200 models,
and thus a deep inspection of each in this README would be overkill. Instead,
let's take a quick look at the fundamental models upon which all else depend.
Currently, these models remain in the Spree namespace as part of the legacy of
forking Spree.
NOTE: Documentation is a work in progress
The documentation of Solidus Core is still in progress. Contributions following
this form are welcome and encouraged!
Order sub-system
Spree::Store- Records store specific configuration such as store name and URL.Spree::Order- The customers cart until completed, then acts as
permanent record of the transaction.Spree::LineItem- Variants placed in the order at a particular price.
User sub-system
Spree::LegacyUser- Default implementation of User.Spree::UserClassHandle- Configuration point for User model implementation.- solidus_auth_devise -
An official, more robust implementation of a User class with Devise
integration.
Payment sub-system
Spree::Payment- Manage and process a payment for an order, from a specific
source (e.g.Spree::CreditCard) using a specific payment method (e.gSolidus::Gateway::Braintree).Spree::PaymentMethod- A base class which is used for implementing payment methods.Spree::PaymentMethod::CreditCard- An implementation of aSpree::PaymentMethodfor credit card payments.Spree::CreditCard- Thesourceof aSpree::PaymentusingSpree::PaymentMethod::CreditCardas payment method.
Inventory sub-system
Spree::ReturnAuthorization- Models the return of Inventory Units to
a Stock Location for an Order.Spree::StockLocation- Records the name and addresses from which stock items
are fulfilled in cartons.Spree::InventoryUnit- Tracks the state of line items' fulfillment.Spree::ShippingRate- Records the costs of different shipping methods for a
shipment and which method has been selected to deliver the shipment.Spree::ShippingMethod- Represents a means of having a shipment delivered,
such as FedEx or UPS.
Shipments sub-system
Spree::Shipment- An order's planned shipments including
tracking and cost. Shipments are fulfilled from Stock Locations.
Developer notes
Testing
Run the tests:
bundle exec rspecOverview (`i18n/README.md`)
Solidus Internationalization
This is the Internationalization project for Solidus
Changes in Version 2.0
solidus_i18n Version 2.0+ only contains translation files.
Previous versions of solidus_i18n included extra functionality like locale
selectors and which is now built in to Solidus 2.6+. Configuration forrouting-filter has also been removed and must be configured manually
(See Locale in URL).
Installation
Add the following to your Gemfile:
gem 'solidus_i18n'
gem 'rails-i18n'
gem 'kaminari-i18n'Locale in URL
Older versions of solidus_i18n included the routing-filter gem and configured routes to include the locale in the URL.
This is still supported (maybe even recommended) but requires some additional configuration.
- Add this gem to your
Gemfile, then runbundle install
gem 'routing-filter'- Add
filter :localeto yourconfig/routes.rb
Rails.application.routes.draw do
filter :locale
mount Spree::Core::Engine, at: '/'
end- Configure routing-fitler in
config/initializers/locale_filter.rb(optional)
# Do not include the default locale in the URL
RoutingFilter::Locale.include_default_locale = falseSupported languages
We currently support the following locales
by default. If you need a locale that is not in the list you can add a custom
translation file into your application by following the
Rails translations guide.
Updating Translations
If you want to improve the translations on your language, run the tasks:
bundle exec rake solidus_i18n:update_default
bundle exec i18n-tasks add-missing --nil-value --locale <LOCALE>Substitute it).
This will do a cleanup and prepare <LOCALE>.yml with all the missing keys.
You can then write the translations and open a pull request.
Model Translations
We removed support for translating models into a separate Gem.
Please update your Gemfile if you still need the model translations.
# Gemfile
gem 'solidus_globalize', github: 'solidusio-contrib/solidus_globalize', branch: 'master'Localizing country names
You can translate country names by defining spree.country_names in your own locale files. For example, to have countries in Spanish do:
es:
spree:
country_names:
US: Estados Unidos de Amรฉrica
UK: Reino Unido
CA: Canadรก
# ...Some supported languages already define localized country names. Take a look at this repo's .yml files for your locale to confirm if we already provide translations.
Contributing
Solidus is an open source project and we encourage contributions. Please read
CONTRIBUTING.md before contributing.
Development
Testing the extension
First bundle your dependencies, then run bin/rake. bin/rake will default to building the dummy
app if it does not exist, then it will run specs. The dummy app can be regenerated by usingbin/rake extension:test_app.
bin/rakeTo run Rubocop static code analysis run
bundle exec rubocopWhen testing your application's integration with this extension you may use its factories.
You can load Solidus core factories along with this extension's factories using this statement:
SolidusDevSupport::TestingSupport::Factories.load_for(SolidusI18n::Engine)Running the sandbox
To run this extension in a sandboxed Solidus application, you can run bin/sandbox. The path for
the sandbox app is ./sandbox and bin/rails will forward any Rails commands tosandbox/bin/rails.
Here's an example:
$ bin/rails server
=> Booting Puma
=> Rails 6.0.2.1 application starting in development
* Listening on tcp://127.0.0.1:3000
Use Ctrl-C to stopReleasing new versions
Please refer to the dedicated page in the Solidus wiki.
License
Copyright (c) 2026 Thomas von Deyen, released under the New BSD License.
Overview (`legacy_promotions/README.md`)
Solidus Legacy Promotions
A Rails Engine that contains the classic Spree/Solidus promotion system, extracted from the other Solidus gems.
Installation
If your Gemfile contains the line gem "solidus", this gem is automatically installed. If you require the individual parts of the Solidus suite, you need to add this gem to your Gemfile:
gem "solidus_legacy_promotions"This gem is slated for deprecation, as its name implies. We're working on integrating a new implementation for promotions and shipping it later this year.
Architecture overview
Solidus Legacy Promotions ships with a powerful rule-based promotions system that allows you to grant flexible
discounts to your customers in many different scenarios. You can apply discounts to the entire
order, to a single line item or a set of line items, or to the shipping fees.
In order to achieve this level of flexibility, the promotions system is composed of four concepts:
- Promotion handlers are responsible for activating a promotion at the right step of the
customer experience. - Promotion rules are responsible for checking whether an order is eligible for a promotion.
- Promotion actions are responsible for defining the discount(s) to be applied to eligible
orders. - Adjustments are responsible for storing discount information. Promotion adjustments are
recalculated every time the order is updated, to check if their eligibility persists when the
state of the order changes. It is possible to
[customize how this recalculation behaves][how-to-use-a-custom-promotion-adjuster].
[!NOTE]
Adjustments go beyond promotions and apply to other concepts that modify the order amount.
Taxes are another good example.
Let's take the example of the following promotion:
Apply free shipping on any orders whose total is $100 USD or greater.
Here's the flow Solidus follows to apply such a promotion:
- When the customer enters their shipping information,
theShipping
promotion handler activates the promotion on the order. - When activated, the promotion will perform
some basic eligibility checks (
e.g. usage limit, validity dates) and
then ensure the defined promotion rules are met. - When called,
theItemTotal
promotion rule will ensure the order's total is $100 USD or greater. - Since the order is eligible for the promotion,
theFreeShipping
action is applied to the order's shipment. The action creates an adjustment that cancels the cost
of the shipment. - The customer gets free shipping!
This is the architecture at a glance. As you can see, Solidus already ships with some useful
handlers, rules, and actions out of the box.
However, you're not limited to using the stock functionality. In fact, the promotions system shows
its full potential when you use it to implement your own logic. In the rest of the guide, we'll use
the promotions system to implement the following requirements:
We want to uphold a partnership with a new payment platform by offering a 50% shipping discount
when customers pay with it during the checkout.
In order to do this, we'll have to implement our own handler, rule, and action. Let's get to work!
Implementing a new handler
There's nothing special about promotion handlers: technically, they're just plain old Ruby objects
that are created and called in the right places during the checkout flow.
There is no unified API for promotion handlers, but we can take inspiration from
the existing ones
and use a similar format:
# frozen_string_literal: true
module AmazingStore
module PromotionHandler
class Payment
RULES_TYPE = 'AmazingStore::Promotion::Rules::Payment'
attr_reader :order
def initialize(order)
@order = order
end
def activate
promotions.each do |promotion|
promotion.activate(order: order) if promotion.eligible?(order)
end
end
private
def promotions
::Spree::Promotion.
active.
joins(:promotion_rules).
where('promotion_rules.type' => RULES_TYPE)
end
end
end
endOur promotion handler selects a subset of promotions with a specific rule type that we haven't yet
created. Then, it activates the eligible ones, i.e., those who obey its rules.
Remember that promotion handlers simply apply active promotions to the current order at the correct
stage of the order workflow. While other handlers might pick up our promotions, they won't be able
to activate it if they run before the payment step. With the new handler, we want to ensure that
promotions can be activated after a payment method has been selected for the order.
Let's call our handler as a callback after the checkout flow has transitioned from the :payment
state (see
the section on how to customize state machines):
# frozen_string_literal: true
module AmazingStore
module LoadPaymentPromotionHandler
def self.prepended(base)
base.state_machine.after_transition(from: :payment) do |order|
AmazingStore::PromotionHandler::Payment.new(order).activate
end
end
::Spree::Order.prepend(self)
end
endImplementing a new rule
Now that we have our handler, let's move on and implement the promotion rule that checks whether the
customer is using the promoted payment method.
We'll allow store admins to edit which payment method carries the discount. The best way to do that
is to create a preference for the promotion rule itself:
# frozen_string_literal: true
module AmazingStore
module Promotion
module Rules
class Payment < ::Spree::PromotionRule
DEFAULT_PREFERRED_PAYMENT_TYPE = 'AmazingStore::AmazingPaymentPlatform'
ALLOWED_PAYMENT_TYPES = [
DEFAULT_PREFERRED_PAYMENT_TYPE,
'Spree::PaymentMethod::Check',
'Spree::PaymentMethod::CreditCard'
].freeze
preference :payment_type, :string, default: DEFAULT_PREFERRED_PAYMENT_TYPE
validates :preferred_payment_type, inclusion: {
in: ALLOWED_PAYMENT_TYPES,
allow_blank: true
}, on: :update
def applicable?(promotable)
promotable.is_a?(::Spree::Order)
end
def eligible?(order, _options = {})
order.payments.any? do |payment|
payment.payment_method.type == preferred_payment_type
end
end
end
end
end
end[!CAUTION]
You may have noticed that we allow the payment type to be blank on creation. This is because
promotion rules are initially created without any of their preferences, so that the correct form can
be presented to the admin when configuring the rule. If we enforced the presence of a payment type
since the very beginning, Solidus wouldn't be able to create the promotion rule and admins would get
an error.
Now that we have the implementation of our promotion rule, we also need to give admins a nice UI
where they can manage the rule and enter the promoted payment type. We just need to create the right
partial, where we'll have a local variable promotion_rule available to access the current
promotion rule instance:
<div class="row">
<div class="col-6">
<div class="field">
<%= promotion_rule.class.human_attribute_name(:payment_type) %>
</div>
</div>
<div class="col-6">
<div class="field">
<%= select_tag "#{param_prefix}[preferred_payment_type]", options_for_select(promotion_rule.class::ALLOWED_PAYMENT_TYPES, promotion_rule.preferred_payment_type), class: 'fullwidth' %>
</div>
</div>
</div>The last step is to register our new promotion rule in an initializer:
# ...
Rails.application.config.spree.promotions.rules << 'AmazingStore::Promotion::Rules::Payment'When you create a new promotion in the backend, we should now see the Payment promotion rule. For
a better experience, we can associate a description so that it's rendered along its form:
en:
# ...
activerecord:
attributes:
amazing_store/promotion/rules/payment:
description: Must use the specified payment methodImplementing a new action
Finally, let's implement the promotion action that will grant customers a 50% shipping discount. In
order to do that, we can take inspiration from the
existing FreeShipping
action:
/* Detailed source-code truncated for AI context efficiency. */As you can see, there's quite a bit going on here, but hopefully, the comments help you with the
flow of the action and the purpose of the methods we implemented.
Just like rules, promotion actions can also have preferences and allow admin to define them via the
UI. However, in this case, we don't need any of that. Still, Solidus will expect a partial for the
action, so we should create an empty ERB file.
[!TIP]
You can look at
? theCreateQuantityAdjustments
action and
the corresponding view
for an example of actions with preferences.
Finally, we need to register our action by adding the following to an initializer:
# ...
Rails.application.config.spree.promotions.actions << 'AmazingStore::Promotion::Actions::HalfShipping'Like before, let's add a human-friendly description:
en:
# ...
activerecord:
attributes:
amazing_store/promotion/actions/half_shipping:
description: Applies 50% discount in shippingRestart the server and you should now see your new promotion action!
Let's try it out!
First of all, go to the Promotions section on the backend and click New Promotion. In this case,
it makes sense to check the Apply to all orders option, as our promotion doesn't need a code. Once
the promotion has been created, add the Payment rule and the Half shipping action.
You can now go to the frontend and see how the shipment price is dropped by 50% if you select the
configured payment method.
Overview (`promotions/README.md`)
Solidus Promotions
This gem contains Solidus' recommended promotion system. It is slated to replace the promotion system in the legacy_promotions gem.
The basic architecture is very similar to the legacy promotion system, but with a few decisive tweaks, which are explained in the subsequent sections.
[!IMPORTANT]
If you are upgrading from a previous version of Solidus, it is advised that you update also the promotion system to the new gem. Please consult the Migration Guide.
While the current version of Solidus still installs the legacy promotion system, we advise a migration at the earliest convinience to avoid having to rush the migration in the future.
Architecture
This extension centralizes promotion handling in the order updater significantly improving the performance of cart calculations. A service class, the SolidusPromotions::OrderAdjuster applies the current promotion configuration to the order, adjusting or removing adjustments as necessary.
SolidusPromotions::Promotion objects have benefits, and benefits have conditions. For example, a promotion that is "20% off shirts" would have a benefit of type "AdjustLineItem", and that benefit would have a condition of type "LineItemTaxon" that makes sure only line items with the "shirts" taxon will get the benefit.
Promotion lanes
Promotions get applied by "lane". Promotions within a lane conflict with each other, whereas promotions that do not share a lane will apply sequentially in the order of the lanes. By default these are "pre", "default" and "post", but they can be configured using the SolidusPromotions initializer:
SolidusPromotions.configure do |config|
config.preferred_lanes = {
pre: 0,
default: 1,
grogu: 2,
post: 3
}
endTypes of Benefits
The new Solidus Promotions Systems ships with three benefit types: AdjustLineItem, AdjustShipment and CreateDiscountedItem. To allow more efficient processing of returns and reduce the complexity for bookkeping and support departments fixed discount to all line items in an order can now be given through the AdjustLineItem benefit through the DistributedAmount calculator significantly reducing administration issues with refunds.
`AdjustLineItem`
Benefits of this class will create promotion adjustments on line items. By default, they will create a discount on every line item in the order. If you want to restrict which line items get the discount, add line-item level conditions, such as LineItemProduct.
`AdjustShipment`
Benefits of this class will create promotion adjustments on shipments. By default, they will create a discount on every shipment in the order. If you want to restrict which shipments get a discount, add shipment-level conditions, such as ShippingMethod.
Conditions
Every type of benefit has a list of rules that can be applied to them. When calculating adjustments for an order, benefits will only produce adjustments on line items or shipments if all their respective conditions are true.
Connecting promotions to orders
When there is a join record SolidusPromotions::OrderPromotion, the promotion and the order will be "connected", and the promotion will be applied even if it does not apply_automatically (see below). This is different from Solidus' legacy promotion system here in that promotions are not automatically connected to orders when they produce an adjustment.
If you want to create an OrderPromotion record, the usual way to do this is via a promotion handler:
SolidusPromotions::PromotionHandler::Coupon: Connects orders to promotions if a customer or service agent enters a matching promotion code.SolidusPromotions::PromotionHandler::Page: Connects orders to promotions if a customer visits a page with the correct path. This handler is not integrated in core Solidus, and must be integrated by you.SolidusPromotions::PromotionHandler::Page: Connects orders to promotions if a customer visits a page with the correct path. This handler is not integrated in core Solidus, and must be integrated by you.
Promotion categories
Promotion categories simply allow admins to group promotions. They have no further significance with regards to the functionality of the promotion system.
Promotion recalculation
Solidus allows changing orders up until when they are shipped. SolidusPromotions therefore will recalculate orders up until when they are shipped as well. If your admins change promotions rather than add new ones and carefully manage the start and end dates of promotions, you might want to disable this feature:
SolidusPromotions.configure do |config|
config.recalculate_complete_orders = false
endCoupon Code Normalization
Solidus Promotions provides a configurable coupon code normalizer that controls how coupon codes are processed before saving and lookup. By default, codes are case-insensitive (e.g., "SAVE20" and "save20" are treated as the same).
You can customize this behavior to support case-sensitive codes, remove special characters, apply formatting rules, or implement other normalization strategies based on your business requirements.
See the coupon_code_normalizer_class configuration option for implementation details.
Installation
Add solidus_promotions to your Gemfile:
gem 'solidus_promotions'Once this project is out of the research phase, a proper gem release will follow.
Bundle your dependencies and run the installation generator:
bin/rails generate solidus_promotions:installThis will create the tables for this extension. It will also replace the promotion administration system under/admin/promotions with a new one that needs turbo-rails. It will also create an initializer within which Solidus is configured to use Spree::SimpleOrderContents and this extension's OrderAdjuster classes. Feel free to override with your own implementations!
Usage
Add a promotion using the admin. Add benefits with conditions, and observe promotions getting applied how you'd expect them to.
In the admin screen, you can set a number of attributes on your promotion:
Name: The name of the promotion. The
nameattribute will also be used to generate adjustment labels. In multi-lingual stores, you probably want different promotions per language for this reason.Description: This is purely informative. Some stores use
descriptionin order display information about this promotion to customers, but there is nothing in core Solidus that does it.Start and End: Outside of the time range between
starts_atandexpires_at, the promotion will not be eligible to any order.Usage Limit:
usage_limitcontrols to how many orders this promotion can be applied, independent of promotion code or user. This is most commonly used to limit the risk of losing too much revenue with a particular promotion.Path:
pathis a URL path that connects the promotion to the order upon visitation. Not currently implemented in either Solidus core or this extension.Per Code Usage Limit: How often each code can be used. Useful for limiting how many orders can be placed with a single promotion code.
Apply Automatically: Whether this promotion should apply automatically. This means that the promotion is checked for eligibility every time the customer's order is recalculated. Promotion Codes and automatic applications are incompatible.
Promotion Category: Used to group promotions in the admin view.
Development
When testing your application's integration with this extension you may use its factories.
You can load Solidus core factories along with this extension's factories using this statement:
SolidusDevSupport::TestingSupport::Factories.load_for(SolidusPromotions::Engine)License
Copyright (c) 2024 Martin Meyerhoff, Solidus Team, released under the BSD-3-Clause license.
Overview (`sample/README.md`)
solidus\_sample
Sample contains a set of test data which is used to generate the sandbox can be
used to test stores.
Applications including the solidus_sample gem are provided a rake task to
load the sample data:
bundle exec rake spree_sample:loadTesting
Run the tests:
bundle exec rspecOverview (`storefront/README.md`)
solidus\_storefront
Storefront contains a Rails application template that provides a modern,
minimal, semantic, and easy to extend storefront for
Solidus.
DISCLAIMER: some Solidus extensions (the ones that depend on the legacy
Solidus Frontend) will not work with this storefront because they rely on
defacing views that don't exist here.
Installation
Just run:
rails new store
cd store
bundle add solidus
mkdir -p app/assets/config
cat <<MANIFEST > app/assets/config/manifest.js
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
MANIFEST
bin/rails generate solidus:install --frontend=starterThat will create a new Solidus application with this storefront installed.
Please note that Solidus Auth Devise
will also be added to your application as it's required by this storefront.
Considerations
The installation of the storefront copies the views, assets, routes,
controllers, and specs to your project. You can easily change anything
that we created; this gives you a lot of freedom of customization.
Note that since the storefront is a Rails application template, it
doesn't have the capability to automatically update your storefront code
whenever the template is updated.
It is not possible right now to generate a new Rails app with the template,
i.e. run rails new --template=URL, since the template expects Solidus to be
installed on the app.
In addition, please note that it will add Solidus Auth Devise frontend
components to your app. At the moment, you will need to manually remove the
gem and its frontend components if you don't need them.
Compatibility
Because this component copies files over the host application, and it is
intended to be used only when Solidus is installed for the first time, we can
guarantee the latest code only works with the latest available version of
Solidus and Rails.
Each Solidus version ships with its own version of the storefront: the
installer applies the templates coming from the Solidus branch corresponding
to the version being installed. For example, Solidus v4.5 will use the
templates coming from the v4.5 branch of the Solidus repository, while
prereleases will use the ones coming from the main branch.
Testing
Run the storefront test suite, which generates a sandbox application from the
templates (if it doesn't exist yet) and runs the specs against it:
bin/rspecDevelopment
For information about contributing to this component please refer to
docs/development.md. There you'll find information on
tasks like:
- Running the sandbox
- Docker development
Overview (`lib/demo/README.md`)
Solidus Docker Demo
The Dockerfile in this folder is intended for demoing purposes, meaning that a
user can run an installation of Solidus using a Docker container.
How to build the image
Make sure Docker is installed in your local and run the following command:
docker build -t solidusio/solidus-demo:latest -f lib/demo/Dockerfile .How to run the image
You can either run the image you built locally or run the official image pushed
in Dockerhub.
docker run --rm -it -p 3000:3000 solidusio/solidus-demo:latestHow to push the image
If you want to push the image you can use the following command, just note that
this specific command will push the image to the official Solidus Dockerhub
account.
docker push solidusio/solidus-demo:latestAdmin Docs Components (`admin/docs/components.md`)
Components
Components are the main building blocks of the admin interface. They are implemented as ViewComponents and are rendered directly by controllers.
The following documentation assumes familiarity with ViewComponents. If you are not please refer to the ViewComponent documentation.
There are two types of components:
- UI components are the building blocks of the interface. Typically, they are small components that are used to build more complex components and are generic enough to be reused in various contexts. UI components are located under the
app/components/solidus_admin/uifolder. - Page components are the primary components rendered by the controllers. Generally, they are full-page components rendered directly by the controllers. They are located under the
app/components/solidus_admindirectory following the naming convention of the controller and action names they are used in. For exampleapp/components/solidus_admin/orders/index/component.rbis the component that is rendered by theSolidusAdmin::OrdersController#indexaction.
Generating components
Components can be generated using the solidus_admin:component generator combined with the bin/rails command from the Solidus repository.
$ bin/rails admin g solidus_admin:component foo
create app/components/solidus_admin/foo/component.rb
create app/components/solidus_admin/foo/component.html.erb
create app/components/solidus_admin/foo/component.yml
create app/components/solidus_admin/foo/component.jsUsing bin/rails admin will run the generator from the solidus_admin engine, instead of the sandbox application.
Coding style
For UI components in particular, it's preferable to accept only simple Ruby values in the initializer and use alternative constructors to accept more complex objects. This makes components easier to use and test. For example:
# bad
class SolidusAdmin::UI::OrderStatus::Component < ViewComponent::Base
def initialize(order:)
@order = order
end
end
# good
class SolidusAdmin::UI::OrderStatus::Component < ViewComponent::Base
def initialize(status:)
@status = status
end
def self.for_order(order)
new(status: order.status)
end
endFor style variations within the component we use the term scheme rather than variant to avoid confusion with product variants.
For size variations we use the term size with single letter values, e.g. s, m, l, xl, xxl.
For text content we use the term text rather than name to avoid confusion with the name attribute of the HTML tag.
Component registry
Components are registered in the SolidusAdmin::Config.components registry. This allows replacing components for customization purposes and components deprecation between versions.
To retrieve component classes from the registry, use the component helper within controllers and components that inherit from SolidusAdmin::BaseComponent or include SolidusAdmin::ComponentHelper. For example, component('ui/button') will fetch SolidusAdmin::UI::Button::Component.
When to use UI vs. Page components
Generally new components are built for a specific controller action and are used only within that action. In such cases, it's better to use a Page component and define it under the namespace of the action, e.g. app/components/solidus_admin/orders/index/payment_status/component.rb.
If a component is used by multiple actions of the same controller it can be moved to the controller namespace, e.g. app/components/solidus_admin/orders/payment_status/component.rb.
When a component is used by multiple controllers, you can either duplicate it in multiple places or move it to the ui namespace.
Although it may seem counterintuitive, duplicating the component can often be beneficial. This allows you to modify the component in one place without affecting other components that might be using it. Over time, the two copies may share enough generic code that it can be extracted into a UI component.
UI components should be very generic and reusable. However, they should not try to anticipate all possible use cases. Instead, they should be extracted from existing components that are already used in multiple places. This has proven to be the most effective way to build UI components. We've found that trying to anticipate theoretical use cases often leads to over-engineered code that eventually needs to be adapted to the actual use cases or is never used at all.
Naming conventions
The project uses a naming convention for components that slightly deviates from ViewComponent defaults. This is done to simplify renaming components and moving them around.
All files related to a component have a base name of component, each with its own extension. These files are placed in a folder named after the component class they define.
E.g. app/components/solidus_admin/orders/index/payment_status/component.rb defines the SolidusAdmin::Orders::Index::PaymentStatus::Component class.
With this approach, renaming a component is as simple as renaming the folder and the class name, without the need to change the names of all the files.
Translations
Components can define their own translations in the component.yml file and they're expected to be self contained. This means that translations defined in solidus_core should not be used in components.
Please refer to the ViewComponent documentation for more information.
Previews and Lookbook
For UI components we leverage ViewComponent previews combined with Lookbook to provide a live preview of the component. This approach is highly beneficial for understanding the component's appearance and how it can be modified using different arguments.
Creating previews for page components can be challenging and prone to errors, as they often require a more complex context for rendering. Therefore, we typically don't use previews for page components, except for the most basic ones. However, if a component has a wide range of arguments and we want to cover all combinations, we might create a preview for it.
In order to inspect previews it's enough to visit /lookbook in the browser while the server is running.
Testing
Testing methods for components vary depending on whether they are UI or Page components. UI components are tested in isolation, while Page components, which often require a more complex context, are tested through feature specs.
For UI components, we use previews to achieve maximum coverage. This approach is sufficient for most basic components, but more complex components may require additional specs. This method has proven to minimize maintenance and code churn in the spec code, and it avoids repeating the code needed to render the component with different arguments.
Page components are tested in the context of the controller action they are used in. For example, admin/spec/features/orders_spec.rb covers interactions with the order listing and indirectly tests the SolidusAdmin::Orders::Index::Component class, among others.
We've found this to be the most effective way to test page components, as recreating the context needed for them in isolation can be difficult and prone to errors.
However, this is not a hard rule. If a Page component needs to be tested in isolation, or if a UI component requires a more complex context, you can always write additional specs.
Admin Docs Customizing Components (`admin/docs/customizing_components.md`)
Customizing components
Solidus Admin uses view components to render the views. Components are
a pattern for breaking up the view layer into small, reusable pieces, easy to
reason about and test.
All the components Solidus Admin uses are located in the app/components folder of thesolidus_admin gem. As you can see, they are organized in a particular folder structure:
- All of them are under the
SolidusAdminnamespace. - They are grouped in sidecar directories, where the main component file and
all its related files (assets, i18n files, etc.) live together.
Solidus Admin components are designed to be easily customizable by the host
application. Because of that, if you look at how they are designed, you'll find
a series of patterns are followed consistently:
- Components are always resolved from a global registry in
SolidusAdmin::Config.componentsinstead of being referenced by their constant. - It's possible to override the registry locally inside a component by redefining
the#componentmethod. This is useful when you need to use a component that
is not registered in the global registry or need to address some edge case. - In any case, Solidus Admin components initializers only take keyword
arguments.
A picture is worth a thousand words, so let's depict how this works with an
example:
# app/components/solidus_admin/foo/component.rb
class SolidusAdmin::Foo::Component < SolidusAdmin::BaseComponent
def component(key)
return MyApplication::Bar::Component if key == 'bar'
super
end
erb_template <<~ERB
<div>
<%= render component('bar').new %>
</div>
ERB
end
# render component('foo').newReplacing a component's template
In the most typical case, you'll only need to replace the template used by a
component. You can do that by creating a new component with a maching path in
your application, inheriting from the default one. Then, you can create a new
template for it. For example, to replace the menu item template:
# app/components/my_admin/navigation/item/component.rb
class MyAdmin::Navigation::Item::Component < SolidusAdmin::Layout::Navigation::Item::Component
end<%# app/components/my_admin/navigation/item/component.html.erb %>
<li><%= link_to @item.name, path %></li>Prepending or appending to a component's template
In some situations, you might only need to add some markup before or after a
component. You can easily do that by rendering the Solidus Admin component and
adding your markup before or after it.
<%# app/components/my_admin/menu_item/component.html.erb %>
<h1>MY STORE ADMINISTRATION</h1>
<%= render SolidusAdmin::MenuItem::Component.new %>Replacing a component
You can replace a component by creating a new one with a matching path in your
application.
There are two considerations to keep in mind:
- Be aware that other components might be using the component you're replacing.
They should only be using its#initializemethod, so make sure to keep
compatibility with it when they're called. - Solidus Admin's components always inherit from
SolidusAdmin::BaseComponent.
You can consider doing the same if you need to use one of its helpers.
For example, the following replaces the menu item component:
# app/components/my_admin/menu_item/component.rb
class MyAdmin::MenuItem::Component < SolidusAdmin::BaseComponent
# Here goes your code
endIf you need more control, you can explicitly register your component in the
Solidus Admin container instead of using an implicit path:
# config/initalizers/solidus_admin.rb
SolidusAdmin::Config.components['ui/button'] = "MyApplication::Button::Component"Tweaking a component
If you only need to tweak a component, you can always inherit from it in a
matching path from within your application (or manually add it to the
registry) and override the methods you need to change:
# app/components/my_admin/menu_item/component.rb
class MyAdmin::MenuItem::Component < SolidusAdmin::MenuItem::Component
def sorted_items
super.reverse
end
endBe aware that this approach comes with an important trade-off: You'll need to
keep your component in sync with the original one as it changes on future updates.
For instance, in the example above, the component is overriding a private
method, so there's no guarantee that it will continue to exist in the future
without being deprecated, as we only guarantee public API stability.
Admin Docs Index Pages (`admin/docs/index_pages.md`)
Adding index pages
Index pages are common in the admin interface, and they are used to display a list of records for a specific model.
Since these pages often have a similar appearance, we have a dedicated component to build them. This component helps avoid writing repetitive boilerplate code.
The `index` action
The index action is the standard action used to display index pages. It's a standard GET action that renders the index component.
To support search scopes and filters the controller should include the SolidusAdmin::ControllerHelpers::Search module and callapply_search_to as follows:
class SolidusAdmin::UsersController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search
def index
users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
# ...For pagination support, the index action should also call the paginate method provided by SolidusAdmin::ControllerHelpers::Pagination (included in SolidusAdmin::BaseController). It paginates the collection with Kaminari, 20 records per page by default (pass per_page: to change it), and returns the page to be displayed. Assign it to @page.
def index
users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
@page = paginate(users)
# ...To sort the records, pass ordered_by: with the columns and directions to sort by. The primary key is added as a final sort column when it isn't already included, so records with equal values don't repeat or go missing between pages.
@page = paginate(products, ordered_by: {name: :asc}) # ORDER BY name ASC, id ASCFinally, the index action should render the index component passing the @page instance variable as the page prop.
def index
users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
@page = paginate(users)
render component('users/index').new(page: @page)
endThe `ui/pages/index` component
The ui/pages/index component is an abstract component that provides sensible defaults for index pages. It also offers template methods that can be used to customize the behavior of these pages.
We recommend examining existing index pages and the UI component itself to understand how they work. In this section, we'll cover the most important aspects.
The index component requires only the page argument during initialization, all other parameters are provided through template methods.
class SolidusAdmin::Users::Index < Solidus::Admin::UI::Pages::Index
def model_class
Spree.user_class
end
end
render component('users/index').new(page: @page)Batch Actions
Batch actions are operations that can be performed on multiple records simultaneously. The index page internally uses the ui/table component and depends on the batch_actions method to render the batch actions dropdown.
In the component, batch actions are provided as an array of hashes, with each hash representing a single batch action. Each hash must contain the following keys:
label: the name of the batch action, this will be used as the label of the dropdown itemicon: the remix icon-name to be used as the icon of the dropdown item (see theui/iconcomponent for more information)action: the name of the action to be performed when the batch action is selected. It can be a URL or a pathmethod: the HTTP method to be used when performing the action, such as:delete
The batch_actions method is called in the context of the controller, so you can use any controller method or helper to build the batch actions.
Batch actions will be submitted to the specified action with an id parameter containing the IDs of the selected records. Using id as the
parameter name allows the same action to support both batch and single-record actions for standard routes.
E.g.
# in the component
def batch_actions
[
{
label: "Delete",
icon: "trash",
action: solidus_admin.delete_admin_users_path,
method: :delete
}
]
end# in the controller
def delete
@users = Spree.user_class.where(id: params[:id])
@users.destroy_all
flash[:notice] = "Admin users deleted"
redirect_to solidus_admin.users_path, status: :see_other
endSearch Scopes
Search scopes are used to filter the records displayed on the index page. The index page internally uses the ui/table component and relies on the scopes method to render the search scope buttons.
In the component, search scopes are provided as an array of hashes, with each hash representing a single search scope. Each hash must contain the following keys:
label: the name of the search scope, used as the label of the buttonname: the name of the search scope, sent viaq[scope]parameter when the button is clickeddefault: whether this is the default search scope, used to highlight the button when when the page is loaded
On the controller side, search scopes can be defined with the search_scope helper, provided by SolidusAdmin::ControllerHelpers::Search. This helper takes a name, an optional default keyword argument, and a block. The block will be called with the current scope and should return a new ActiveRecord scope.
E.g.
module SolidusAdmin
class UsersController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search
search_scope(:customers, default: true) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) }
search_scope(:admin) { _1.joins(:role_users).distinct }
search_scope(:with_orders) { _1.joins(:orders).distinct }
search_scope(:without_orders) { _1.left_outer_joins(:orders).where(orders: { id: nil }) }
search_scope(:all)
def index
users = apply_search_to(Spree.user_class.order(id: :desc), param: :q)
# ...Filters
Filters are used to narrow down the records displayed on the index page. The index page internally uses the ui/table/ransack_filter component and depends on the filters method to render the filters dropdown.
In the component, filters are represented as an array of hashes, with each hash representing a single filter. Each hash must contain the following keys:
label: the name of the filter, used as the label in the filter barattribute: the name of the ransack-enabled attribute to be filteredpredicate: the name of the ransack predicate to be used, e.g.eq,in,contoptions: an array of options to be used for the filter, in the standard rails form of[["label", "value"], ...]
On the controller side it's enough to add ransack support to the index action by including SolidusAdmin::ControllerHelpers::Search and callingapply_search_to as explained in the Index action section.
On the controller side, you simply need to add Ransack support to the index action by including the SolidusAdmin::ControllerHelpers::Search module and calling the apply_search_to method, as explained in the Index action section.
Admin Docs Menu Items (`admin/docs/menu_items.md`)
Customizing the Menu
You are allowed to add your custom links to the main navigation. To do so, you can access SolidusAdmin::Config.menu_items in an initializer:
# config/initializers/solidus_admin.rb
SolidusAdmin::Config.menu_items << {
key: :my_custom_link,
route: :my_custom_link_path,
icon: "24-hours-fill",
position: 80
}- The key you provide will be used to translate the link's label under the
solidus_admin.menu_item.#{key}key. - Icon needs to be an icon name from Remixicon.
- Position tells Solidus where to place the link in the main navigation. The
default items are placed with 10 points of difference between them.
For nested links, you can provide a children: option with an array of hashes:
# config/initializers/solidus_admin.rb
SolidusAdmin::Config.configure do |config|
config.menu_items << {
key: :my_custom_link,
route: :my_custom_link_path,
icon: "24-hours-fill",
position: 80,
children: [
{
key: :my_custom_nested_link,
route: :my_custom_nested_link_path,
position: 80
}
]
}
endYour custom link will be rendered in the active state when its base path (i.e, the path without the query string) matches the one for the current url.
Admin Docs Stimulusjs (`admin/docs/stimulusjs.md`)
StimulusJS
This project uses StimulusJS to add interactivity to the admin interface.
Using the `stimulus_id` helper
All JavaScript files are imported using import-maps, eliminating the need for compilation.
Any component.js file is automatically loaded as a StimulusJS controller. The component path is used as the identifier, which is achieved by using parameterize and replacing / with --.
For example, app/components/solidus_admin/foo/component.js is loaded as solidus-admin--foo..
To simplify the use of StimulusJS controllers in components, a stimulus_id helper is provided.
This helper ensures that the controller identifier is correctly used every time.
<div
data-controller="<%= stimulus_id %>"
data-action="click-><%= stimulus_id %>#doSomething"
data-<%= stimulus_id %>-foo-value="123"
>
...
</div>Coding Style
Besides the standard StimulusJS conventions, we have a few additional tricks to make the code more readable and maintainable.
Separating the state from the DOM
Whenever the controller gets beyond trivial we try to separate the state from DOM updates using a render() method.
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "details" ]
connect() {
this.render()
}
show() {
this.open = true
}
render() {
this.detailsTarget.hidden = !this.open
}
}Using values to communicate with the external world
Values are an excellent tool for communicating with the external environment and representing state.
Any change to them will be reflected in the DOM and the change callbacks provided by StimulusJS are a great way to react to state changes.
import { Controller } from "stimulus"
export default class extends Controller {
static values = { open: Boolean }
connect() {
this.render()
}
show() {
this.openValue = true
}
openValueChanged() {
this.render()
}
render() {
this.detailsTarget.hidden = !this.openValue
}
}Leveraging stimulus-use
Solidus Admin leverages stimulus-use, an external library that offers a collection of composable behaviors for Stimulus Controllers. These mixins greatly simplify the work of developers and can be used when adding new components.
For more information, please refer to the project doc. You can also find examples of usage in the Admin codebase (for instance, useClickOutside and useDebounce mixins are used in admin components).
Admin Docs Tailwindcss (`admin/docs/tailwindcss.md`)
Tailwind CSS for the Admin UI
Solidus Admin uses Tailwind CSS for styling. The
benefit of using Tailwind is that it allows you to customize the look and feel
of the admin without having to write any CSS. By leveraging utility classes,
you can easily change the colors, fonts, and spacing in use.
Solidus Admin provides a precompiled CSS file that includes all the necessary
Tailwind classes for the admin to work out of the box.
In case you need to customize the admin's look and feel, or create custom
components, you can do so by running Tailwind's build process in your host
application.
This process presumes that you have a working knowledge of Tailwind CSS.
If you are not familiar with Tailwind, please refer to the Tailwind documentation for more information.
Coding Style
The provided Tailwind CSS configuration is as vanilla as possible, with little to no customizations.
We aim to stay within the boundaries of the default Tailwind CSS classes and avoid custom classes.
Occasionally, we need to add custom styles and in such cases we try to use dynamic square brackets classes.
CSS classes are all kept in the HTML templates of the components in order to simplify the configuration.
The few customizations we have added, are through plugins as they support not only classes but also variants.
Releasing with a precompiled Tailwind CSS
Solidus Admin Tailwind CSS stylesheets are precompiled and included in Solidus gems released to Rubygems.
This means that there's no need to compile the admin Tailwind CSS file in the host application.
The compiled file is not included in the git repository, but it is generated and incorporated in the gem as part of the rake release task (see releasing instructions). This approach completely removes the potential noise created by committing that file to the repository, and the need to keep it updated in pull requests that should affect it.
Customizing TailwindCSS
In order to customize the admin's look and feel, you'll need to set up a local
Tailwind build. This is a two-step process:
Add Tailwind configuration files to your application:
bin/rails solidus_admin:tailwindcss:installThis will create all the necessary files for you to customize TailwindCSS,
including:
- A
config/solidus_admin/tailwind.config.jsconfiguration file
that will automatically import the Solidus Admin's default configuration. - An
app/assets/stylesheets/solidus_admin/application.tailwind.cssfile
in which you can add your own customizations. - Tasks to build the CSS file once or watch for changes and automatically
rebuild the targetapp/assets/builds/solidus_admin/application.cssfile.
In order to manually build the CSS file, run:
bin/rails solidus_admin:tailwindcss:buildOr, to watch for changes and automatically rebuild the CSS file, run:
bin/rails solidus_admin:tailwindcss:watchCaveat: Conflict with sassc-rails
Tailwind uses modern CSS features that are not recognized by the sassc-rails extension that was included by default in the Gemfile for Rails 6. In order to avoid any errors like SassC::SyntaxError, you must remove that gem from your Gemfile.
See https://github.com/rails/tailwindcss-rails#conflict-with-sassc-rails.
Development
The main solidus bin/dev command will automatically compile the Tailwind CSS stylesheets and watch for changes.
Storefront Docs Development (`storefront/docs/development.md`)
Solidus Storefront development information
This document aims to give some extra information for developers that are going
to contribute to our solidus_storefront component.
Running the sandbox
You can run bin/dev to create a sandboxed Solidus application, start the server and the watcher process that will update files in the sandbox whenever something
changes in the templates folder.
$ bin/dev
18:54:04 web.1 | started with pid 39831
18:54:04 watch.1 | started with pid 39832
18:54:04 watch.1 | 18:54:04 - INFO - Guard is now watching at '/Users/elia/Code/Nebulab/solidus_storefront'
18:54:05 web.1 | => Booting Puma
18:54:05 web.1 | => Rails 7.0.4.1 application starting in development
18:54:05 web.1 | => Run `bin/rails server --help` for more startup options
18:54:06 web.1 | Puma starting in single mode...
18:54:06 web.1 | * Puma version: 5.6.5 (ruby 2.7.6-p219) ("Birdie's Version")
18:54:06 web.1 | * Min threads: 5
18:54:06 web.1 | * Max threads: 5
18:54:06 web.1 | * Environment: development
18:54:06 web.1 | * PID: 39833
18:54:06 web.1 | * Listening on http://127.0.0.1:3000
18:54:06 web.1 | * Listening on http://[::1]:3000
18:54:06 web.1 | Use Ctrl-C to stopIf you need to recreate the sandbox from scratch you can run bin/sandbox.
Using bin/rails will forward any Rails commands to sandbox/bin/rails.
Default username and password for admin are: [email protected] and test123.
To run the watcher manually please use bin/guard (see the Guardfile for the
configuration).
Docker development
If you are a docker user you can wake up the project as usual with:
docker-compose up -dWait for all the dependencies to be installed. You can check progress with docker-compose logs -f app.
Then you can dispatch commands to the app container:
docker-compose exec app bin/rails serverWhen running the sandbox application, take into account
that you need to bind to 0.0.0.0. By default, port 3000 is exposed but you
can changed it through SANDBOX_PORT environment variable:
SANDBOX_PORT=4000 docker-compose up -d
docker-compose exec app bin/sandbox
docker-compose exec app bin/rails server --binding 0.0.0.0 --port 4000--- METRICS ---
- Files Extracted: 18
- Estimated Token Budget: ~19711 tokens
- Canonical Reference: https://codewiki.google/github.com/solidusio/solidus