Rails integration

ReactorSDK works well in plain Ruby, but Rails is a natural fit for the gem because Launch workflows usually need secret management, background jobs, model synchronization, audit logging, and service-object orchestration. The SDK itself stays stateless enough that you can choose the right integration boundary for your app.

Single-tenant initializer

For applications that talk to exactly one Adobe org, a Rails initializer is the simplest integration pattern.

config/initializers/reactor_sdk.rb

# Create one shared client for a single Adobe org.
REACTOR_CLIENT = ReactorSDK::Client.new(
  # Client ID loaded from Rails encrypted credentials.
  client_id:     Rails.application.credentials.adobe[:client_id],
  # Client secret loaded from Rails encrypted credentials.
  client_secret: Rails.application.credentials.adobe[:client_secret],
  # IMS organization ID loaded from Rails encrypted credentials.
  org_id:        Rails.application.credentials.adobe[:org_id],
  # Reuse the Rails logger for request visibility.
  logger:        Rails.logger
)

This is an application convenience only. The SDK itself does not depend on a global singleton internally.

Multi-tenant client factory

When your application stores multiple Adobe credential sets, build a client per credential record at the service or job boundary.

Factory pattern

# Build a client per credential record so each Adobe org is isolated.
class ReactorClientFactory
  # Accept one credential record and an optional logger.
  def self.build(credential_record, logger: Rails.logger)
    ReactorSDK::Client.new(
      # Client ID for this specific Adobe org.
      client_id:     credential_record.client_id,
      # Client secret for this specific Adobe org.
      client_secret: credential_record.client_secret,
      # IMS organization ID for this specific Adobe org.
      org_id:        credential_record.org_id,
      # Logger injected from the caller.
      logger:        logger
    )
  end
end

This preserves per-client token caches and rate limit state instead of forcing every organization through one shared client.

Service objects and jobs

Most Reactor workflows are easier to test and reason about when you keep them in explicit services and jobs.

Property sync service

# Synchronize Adobe properties into a local Rails model.
class PropertySyncService
  # org = local application record that owns the synced properties.
  # client = ReactorSDK client already configured for that org.
  def initialize(org:, client: REACTOR_CLIENT)
    @org = org
    @client = client
  end

  # Pull remote properties and upsert them locally.
  def call
    # Use the first visible Adobe company for this org.
    company = @client.companies.list.first
    # Load all properties for that company.
    properties = @client.properties.list_for_company(company.id)

    # Persist each Adobe property into the local database.
    properties.each do |property|
      upsert_property(property)
    end
  end

  private

  # Create or update one local Property record from the Adobe resource.
  def upsert_property(adobe_property)
    Property.find_or_initialize_by(
      # Associate the record with the local organization.
      org: @org,
      # Use the Adobe property ID as the stable external identifier.
      adobe_property_id: adobe_property.id
    ).tap do |record|
      # Copy the Adobe display name.
      record.name = adobe_property.name
      # Copy the Adobe platform such as web or mobile.
      record.platform = adobe_property.platform
      # Persist the record or raise if validation fails.
      record.save!
    end
  end
end

Background job boundary

# Execute the sync asynchronously for one stored credential record.
class SyncAdobePropertyJob < ApplicationJob
  # credential_id = local database ID for the Adobe credential record.
  def perform(credential_id)
    # Load the credential record from the database.
    credential = AdobeCredential.find(credential_id)
    # Build a Reactor client scoped to that credential.
    client = ReactorClientFactory.build(credential)
    # Run the sync service using the scoped client.
    PropertySyncService.new(org: credential.org, client: client).call
  end
end

Publish workflow service

Launch publication is a good candidate for a dedicated service object because it mixes relationship updates, build polling, and state transitions.

Library publish service

# Publish one library through environment assignment, build polling, and transitions.
class LibraryPublishService
  # Seconds to wait between build status checks.
  POLL_INTERVAL = 30
  # Maximum number of seconds to wait before timing out.
  MAX_WAIT = 600

  # library_id = Adobe library ID.
  # environment_id = Adobe environment ID used for the build.
  # client = ReactorSDK client configured for the correct Adobe org.
  def initialize(library_id:, environment_id:, client: REACTOR_CLIENT)
    @library_id = library_id
    @environment_id = environment_id
    @client = client
  end

  # Run the full publication workflow.
  def call
    # Attach the library to the target environment.
    @client.libraries.assign_environment(@library_id, @environment_id)
    # Start a new build.
    build = @client.libraries.build(@library_id)

    # Keep track of the total wait time.
    elapsed = 0
    # Poll until the build succeeds, fails, or times out.
    loop do
      # Refresh the current build status from Reactor.
      build = @client.builds.find(build.id)
      # Stop polling once the build reaches a terminal state.
      break if build.succeeded? || build.failed?
      # Abort if the build has taken too long.
      raise "Build timed out" if elapsed >= MAX_WAIT
      # Wait before the next poll.
      sleep POLL_INTERVAL
      # Increase the elapsed counter.
      elapsed += POLL_INTERVAL
    end

    # Stop the workflow if Adobe marked the build as failed.
    raise "Build failed" if build.failed?

    # Move the library through Adobe's publication states in order.
    @client.libraries.transition(@library_id, state: "submitted")
    @client.libraries.transition(@library_id, state: "approved")
    @client.libraries.transition(@library_id, state: "published")
  end
end

Was this page helpful?