Blog
0 pixels scrolled
  • Home
  • Work
  • Services
  • About
  • Blog
  • Contact
Tools for Developing Solidus Extensions:
solidus_support
Chris Todorov on January 10, 2023
Learn how solidus_support helps save you time and makes your Solidus extensions more compatible.
Tools for Developing Solidus Extensions:
solidus_support
Chris Todorov on January 10, 2023
Posted in Solidus
← Back to the blog

At Super Good we spend a lot of time working on Rails applications, and more specifically applications which include a Solidus store. Often there is a use case for sharing some functionality between Solidus stores. The best way to do that is by developing a Solidus extension, which usually takes the form of a Rails engine. You can think of an engine as a Rails application which can be embedded in another Rails application.

Developing a Rails engine is slightly different than developing a Rails application. For one, to run tests for an engine you have to generate an actual Rails application to host the engine.

There are two gems that simplify writing Solidus extensions, but at first they can be somewhat confusing to navigate. These gems are solidus_support and solidus_dev_support. They each solve a different problem.

The main goal of the solidus_support gem is to provide extension developers a compatibility layer for their code so it can work against a wider range of Solidus and Rails versions. In this post we will take a closer look at some of the tools it provides.

Migration Base Class

This feature backports versioned migrations to versions of Rails which don’t support them (versions prior to v5.0). If you are writing an extension and want to maintain support for older versions of Rails, this feature may be useful so you don’t have to maintain separate versions of your source. You can use the provided base class the same way as you would the one provided by Rails:

# On Rails 4.2 returns `ActiveRecord::Migration`
class AddBrickAndMortarStores < SolidusSupport::Migration[4.2]
  def self.up
    create_table :brick_and_mortar_stores, force: true do |table|
      table.string :name, null: false
      table.json :hours, null: false
      table.integer :address_id, null: false

      table.timestamps
    end
  end

  def self.down
    drop_table :brick_and_mortar_stores
  end
end

You only need to use this feature if your extension needs to support versions of Rails older than v5.0.

Engine Extensions

Most of the functionality provided by this gem is exposed through the EngineExtensions module. You can include it in your extension’s engine class like this:

module SuperGoodStoreLocator
  class Engine < Rails::Engine
    engine_name 'super_good_store_locator'

    include SolidusSupport::EngineExtensions
  end
end

This enables many quality-of-life features in your extension. But I want to touch on two specifically: automatic override code loading, and a compatibility layer for Solidus’s new Omnes event bus.

Automatic File and Override (Decorator) Loading

In order to make customizations to the parts of Solidus that aren’t configurable, extensions often use overrides. (Older versions of the Solidus documentation called these “decorators”.)

Extensions which rely on decorators get auto-loading when the files are placed in the lib/decorators/* folder. In addition to that, solidus_support provides conditional loading of files based on which Solidus engines are present in a host application. When stores opt to use only individual Solidus components, this utility allows your extension to provide behaviour which extends solidus_core / solidus_api / solidus_backend without having to test whether each one is loaded, simply by placing the files in a folder named after the extension.

For example, if your extension provides functionality specific to solidus_backend, adding your code to the following folders will result in it being loaded only if the host application includes that engine:

  • lib/views/backend
  • lib/controllers/backend
  • lib/decorators/backend

For example, if you’re adding an admin interface for your store locator extension, you might want to add a controller and some views:

lib/controllers/backend/brick_and_mortar_stores_controller.rb
lib/views/backend/brick_and_mortar_stores/_form.html.erb
lib/views/backend/brick_and_mortar_stores/edit.html.erb
lib/views/backend/brick_and_mortar_stores/index.html.erb
lib/views/backend/brick_and_mortar_stores/new.html.erb
lib/views/backend/brick_and_mortar_stores/show.html.erb

And if the engine is included in the Rails application, these files would be loaded. If, for some reason, a store using the extension isn’t using solidus_backend, the controller and views won’t be loaded. This way, we won’t run into dependency failures if the Spree::Admin::BaseController provided by solidus_backend isn’t available.

Omnes Event Bus Compatibility Layer

By default solidus_support loads event subscriber files from the lib/subscribers/* folder in extensions. Something even more useful is the forwards compatibility layer for the new event system which was introduced in Solidus 3.2. The previous implementation relied on ActiveSupport::Notifications. The new event system is built on Omnes. This change requires you to upgrade event subscribers to the new version of the event bus.

You can take advantage of the SolidusSupport::LegacyEventCompat::Subscriber module to make your existing event subscribers work with Omnes without having to rewite them. This means you don’t have to maintain separate versions in order to support both event systems.

module SuperGoodStoreLocator
  module BrickAndMortarStoreSubscriber
    include ::Spree::Event::Subscriber
    include SolidusSupport::LegacyEventCompat::Subscriber

    event_action :store_hours_changed

    def store_hours_changed(event)
      UpdateGoogleMapsInfoJob
        .perform_later(event.payload[:brick_and_mortar_store].hours)
      end
  end
end

If your extension adds custom events and you want that to continue to work in applications running on Solidus 3.2 you can use the provided compatibility layer to do that

SolidusSupport::LegacyEventCompat::Bus
  .publish(:store_hours_changed, brick_and_mortar_store: brick_and_mortar_store)

On applications which still use the legacy event bus, this is equivalent to

Spree::Event
  .fire(:store_hours_changed, brick_and_mortar_store: brick_and_mortar_store)

and for applications that have opted-in to the new default, this uses the new API

Spree::Bus
  .publish(:store_hours_changed, brick_and_mortar_store: brick_and_mortar_store)

Hopefully all this has convinced you of the many benefits of building your Solidus extension on top of the functionality solidus_support provides. The Solidus community has done a great job of ensuring that developers have the right tools to write extensions in a more maintainable and backwards compatible way.

Work ServicesAboutBlogCareersContact


Privacy policyTerms and conditions© 2023 Super Good Software Inc. All rights reserved.