Last updated

Themes System Architecture

Table of Contents


  1. Overview
  2. Core Concepts
  3. System Architecture
  4. Request Flow Architecture
  5. Understanding Schema Components
  6. Implementation Details
  7. Template Development Patterns
  8. Theme Customization Architecture
  9. Company Theme Switching
  10. API Design and Usage
  11. Error Handling
  12. Model Usage Examples
  13. Use Cases
  14. Benefits

Overview

The Themes System provides a flexible, multi-layered architecture for customizing the appearance and behavior of pages across the platform. It separates theme management from template content, enabling companies to customize their user-facing pages through a combination of theme selection, template customization, and variable configuration. The system supports both traditional Liquid templating and modern JSON-based section components.

Core Concepts

Themes vs Application Themes

The system maintains two distinct theme models representing different generations of theming functionality:

Legacy Theme Model (Theme):

  • Simple theme selection system with predefined options
  • Used for basic theme switching in older parts of the system
  • Contains hardcoded theme names like fashion, cosmetics, minimalistic, etc.
  • Primarily used by UserCompany associations for individual user theme preferences

Modern Application Theme Model (ApplicationTheme):

  • Full-featured theme system with template management, versioning, and customization
  • Supports company-specific theme customization and template overrides
  • Includes CSS variable injection, custom stylesheets, and rich media support
  • Primary system for all current theme functionality

Application Themes

Application Themes represent complete theme packages that define the visual appearance and layout structure for a company's pages. Each theme can exist as either a root theme (shared across the platform) or a company-specific theme (customized for individual companies).

Key characteristics:

  • Themes contain collections of templates for different page types
  • Support CSS variable injection for dynamic styling
  • Include versioning and publishing workflows
  • Can be cloned and customized per company
  • Maintain audit trails for change tracking

Theme Hierarchy:

  • Root Themes: Platform-provided base themes (fluid, vox, base) that serve as templates
  • Company Themes: Company-specific themes that clone and customize root themes
  • Template Inheritance: Company themes can override specific templates while inheriting others

Root Theme Definition: A theme becomes a "root theme" when it has company_id: nil in the database. Root themes are platform-wide resources that:

  • Are created and maintained by the platform administrators
  • Serve as the foundation for all company-specific theme customization
  • Cannot be directly modified by individual companies
  • Provide the base templates and styling that companies clone and customize
  • Are accessible to all companies for cloning purposes
  • Include complete sets of templates for all major page types (product, home_page, etc.)

Application Theme Templates

Application Theme Templates are individual page templates within a theme that define the layout and content structure for specific page types. Each template can render different types of content and supports both Liquid and JSON formats.

Key characteristics:

  • Templates belong to specific themes and handle particular content types
  • Support both Liquid templating and JSON-based section systems
  • Include schema definitions for dynamic content configuration
  • Can be assigned to specific content items (products, media, pages)
  • Maintain status tracking (draft/active) and versioning

Template Types:

  • Content Templates: product, medium, enrollment_pack, library - Render specific content items
  • Page Templates: home_page, shop_page, cart_page, category_page, collection_page, join_page - Handle page types
  • Layout Components: navbar, footer, layouts - Provide page structure and navigation
  • Structural Components: sections, components, config - Reusable template parts and configuration

Template Processing System

The system processes templates through a sophisticated pipeline that combines content resolution, variable injection, and rendering:

Liquid Template Processing:

  • Traditional server-side templating using Liquid syntax
  • Variables injected as {{ variable_name }} expressions
  • Control flow through {% tag %} blocks
  • Support for layouts, includes, and custom filters

Custom Liquid Filters: The system provides specialized filters for common theming tasks:

  • {{ product.price | money }} - Formats currency values with proper symbols and localization
  • {{ image.url | img_url: '300x200' }} - Resizes images to specified dimensions via ImageKit
  • {{ 'common.add_to_cart' | t }} - Translates text based on current locale
  • {{ product.created_at | date: '%B %d, %Y' }} - Formats dates with custom patterns
  • {{ content | truncate: 150 }} - Truncates text to specified character length
  • {{ product.tags | join: ', ' }} - Joins array elements with specified separator

JSON Section Processing: JSON templates represent a modern, component-based approach that separates content structure from presentation logic:

  • Schema-Driven Configuration: Templates define sections with JSON schemas that specify three key components:
    • Settings: Individual configuration options that control section appearance and behavior (colors, text, toggles, etc.)
    • Blocks: Repeatable content elements within a section that can be added, removed, and reordered dynamically
    • Presets: Pre-configured combinations of settings and blocks that provide starting templates for common use cases
  • Component-Based Architecture: Each section type corresponds to a reusable Liquid component template
  • Dynamic Content Blocks: Sections can contain configurable blocks that are rendered with context-specific data
  • Visual Page Builder Integration: JSON structure enables drag-and-drop page building interfaces
  • Settings Management: Schema definitions automatically generate form interfaces for template customization

JSON Template Structure:

{
  "layout": "theme",
  "sections": {
    "header": {
      "type": "product_header",
      "settings": {
        "show_vendor": true,
        "show_price": true,
        "title_size": "large"
      }
    },
    "gallery": {
      "type": "product_gallery",
      "settings": {
        "gallery_type": "slideshow",
        "show_thumbnails": true
      },
      "blocks": {
        "block_1": {
          "type": "image_slide",
          "settings": {
            "image": "{{ product.featured_image }}",
            "caption": "Main product image"
          }
        }
      },
      "block_order": ["block_1"]
    }
  },
  "order": ["header", "gallery"]
}

Section Processing Flow:

  1. Section Resolution: Each section type maps to a Liquid template in the sections themeable_type
  2. Settings Injection: Section settings are merged with schema defaults and injected as variables
  3. Block Processing: Dynamic blocks are processed with their individual settings and rendered within sections
  4. Schema Validation: Settings are validated against the section's schema definition
  5. Template Rendering: The resolved section template is rendered with populated variables

Understanding Schema Components: Settings, Blocks, and Presets

Settings: Individual Configuration Options Settings are individual form fields that control specific aspects of a section's appearance or behavior. Each setting has a type, default value, and user-facing label.

{
  "name": "Product Gallery",
  "settings": [
    {
      "type": "select",
      "id": "gallery_layout",
      "label": "Gallery Layout",
      "options": [
        { "value": "grid", "label": "Grid" },
        { "value": "carousel", "label": "Carousel" },
        { "value": "stacked", "label": "Stacked" }
      ],
      "default": "grid"
    },
    {
      "type": "checkbox",
      "id": "show_thumbnails",
      "label": "Show thumbnail navigation",
      "default": true
    },
    {
      "type": "color",
      "id": "border_color",
      "label": "Border color",
      "default": "#e0e0e0"
    },
    {
      "type": "text",
      "id": "heading_text",
      "label": "Section heading",
      "default": "Product Images"
    },
    {
      "type": "range",
      "id": "columns_count",
      "label": "Columns per row",
      "min": 2,
      "max": 6,
      "step": 1,
      "default": 4
    }
  ]
}

Common Setting Types:

  • text: Single-line text input
  • textarea: Multi-line text input
  • select: Dropdown with predefined options
  • checkbox: Boolean toggle
  • color: Color picker
  • range: Slider with min/max values
  • number: Numeric input
  • url: URL input with validation
  • image_picker: Asset selection interface

Blocks: Repeatable Content Elements Blocks are dynamic content items that can be added, removed, and reordered within a section. Each block has its own settings and can represent different content types.

{
  "name": "Testimonials Section",
  "settings": [
    {
      "type": "text",
      "id": "section_title",
      "label": "Section Title",
      "default": "What Our Customers Say"
    }
  ],
  "blocks": [
    {
      "type": "testimonial",
      "name": "Customer Testimonial",
      "limit": 10,
      "settings": [
        {
          "type": "text",
          "id": "customer_name",
          "label": "Customer Name"
        },
        {
          "type": "textarea",
          "id": "testimonial_text",
          "label": "Testimonial"
        },
        {
          "type": "image_picker",
          "id": "customer_photo",
          "label": "Customer Photo"
        },
        {
          "type": "text",
          "id": "customer_title",
          "label": "Customer Title/Company"
        },
        {
          "type": "range",
          "id": "star_rating",
          "label": "Star Rating",
          "min": 1,
          "max": 5,
          "default": 5
        }
      ]
    },
    {
      "type": "divider",
      "name": "Section Divider",
      "settings": [
        {
          "type": "select",
          "id": "divider_style",
          "label": "Divider Style",
          "options": [
            { "value": "line", "label": "Line" },
            { "value": "dots", "label": "Dots" },
            { "value": "stars", "label": "Stars" }
          ],
          "default": "line"
        }
      ]
    }
  ]
}

Block Characteristics:

  • Dynamic Quantity: Users can add/remove blocks as needed
  • Reorderable: Blocks can be dragged to change their sequence
  • Type-Specific: Different block types have different settings schemas
  • Limited: Optional limit property restricts maximum number of blocks

Presets: Pre-Configured Starting Points Presets provide ready-to-use configurations that combine section settings with a predefined set of blocks, giving users a starting point for common use cases.

{
  "name": "Feature Showcase",
  "settings": [
    {
      "type": "text",
      "id": "section_heading",
      "label": "Section Heading",
      "default": "Key Features"
    }
  ],
  "blocks": [
    {
      "type": "feature_item",
      "name": "Feature",
      "settings": [
        {
          "type": "text",
          "id": "feature_title",
          "label": "Feature Title"
        },
        {
          "type": "textarea",
          "id": "feature_description",
          "label": "Feature Description"
        },
        {
          "type": "image_picker",
          "id": "feature_icon",
          "label": "Feature Icon"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Basic Features (3 items)",
      "settings": {
        "section_heading": "Why Choose Us"
      },
      "blocks": [
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Fast Delivery",
            "feature_description": "Get your order in 24 hours or less"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Quality Guarantee",
            "feature_description": "100% satisfaction or your money back"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Expert Support",
            "feature_description": "24/7 customer service from our team"
          }
        }
      ]
    },
    {
      "name": "Product Benefits (5 items)",
      "settings": {
        "section_heading": "Product Benefits"
      },
      "blocks": [
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Eco-Friendly",
            "feature_description": "Made from sustainable materials"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Durable Design",
            "feature_description": "Built to last for years of use"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Easy to Use",
            "feature_description": "Simple setup in under 5 minutes"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Versatile",
            "feature_description": "Works in multiple environments"
          }
        },
        {
          "type": "feature_item",
          "settings": {
            "feature_title": "Cost Effective",
            "feature_description": "Saves money compared to alternatives"
          }
        }
      ]
    }
  ]
}

Preset Benefits:

  • Quick Setup: Users can start with pre-configured content instead of building from scratch
  • Best Practices: Presets demonstrate optimal configurations for common scenarios
  • Inspiration: Show users what's possible with the section
  • Consistency: Ensure common use cases follow established patterns

How They Work Together in Practice:

  1. User Selects Preset: When adding a section, user chooses "Product Benefits (5 items)" preset
  2. System Applies Configuration: Section gets the preset's settings and 5 pre-configured feature blocks
  3. User Customizes Settings: User modifies section heading, colors, layout options through the settings
  4. User Manages Blocks: User can edit individual features, reorder them, add more, or remove some
  5. Final Rendering: Section template renders with the customized settings and blocks

This three-tier system (settings + blocks + presets) provides maximum flexibility while maintaining usability for non-technical users.

User Experience Flow
Preset Components
Block Components
Settings Components
JSON Schema Structure
generates forms for
populates
populates
Select Preset
User adds section
Apply preset settings & blocks
Customize Settings
Add/Remove/Reorder Blocks
Final Rendering
Preset 1: Basic Setup
Default Settings Values
Pre-configured Blocks
Preset 2: Advanced Setup
Default Settings Values
Pre-configured Blocks
Block Type Definition
Block Settings
Block Limit
Another Block Type
Block Settings
Text Input
Select Dropdown
Color Picker
Range Slider
Checkbox Toggle
Section Schema
Settings Array
Blocks Array
Presets Array

Layout Resolution:

  • Templates can specify layout wrappers using {% layout 'theme' %} tags
  • Layouts are separate templates with themeable_type: "layouts"
  • Content injection through {{ content_for_layout }} variable
  • Supports nested layouts and template inheritance

Variable System

The themes system includes a comprehensive variable injection system that populates templates with dynamic content:

Variable Categories:

  • Global Variables: Company information, site settings, localization data
  • Content Variables: Product details, page content, media information
  • Request Variables: URL paths, query parameters, user session data
  • Theme Variables: CSS custom properties, color schemes, typography settings

Variable Processing:

  • Variables are resolved by specialized service classes in app/services/themes/templates/variables/
  • Different content types have dedicated variable providers
  • Caching mechanisms prevent N+1 queries and improve performance
  • Variables support both simple values and complex objects (drops)

System Architecture

Theme-Company Relationships

Companies can have multiple themes but only one active theme at a time:

CompanyintidPKstringnamestringsubdomainApplicationThemeintidPKintcompany_idFKNULL for root themesstringnametextvariablesJSON CSS variablestextcustom_stylesheettextglobal_stylesheetstringversionintstatus0=draft, 1=activeApplicationThemeTemplateintidPKintcompany_idFKintapplication_theme_idFKintparent_idFKfor nested templatesstringnameintthemeable_typeenum: product, medium, page, etctextcontentLiquid or JSON templatestringformatliquid or jsonintstatus0=draft, 1=activebooleandefaultdefault for themeable_typeImageFileResourceFileReferenceProductMediumLibraryEnrollmentPackPageThemeUserCompanyhas many themeshas one current_themehas template overridescontains templateshas cover imageshas assetshas nested templates (parent_id)references fileshas imageslinks tocan have specific templatecan have specific templatecan have specific templatecan have specific templatecan have specific templatelegacy belongs toused by

Template Hierarchy and Inheritance

Templates follow a hierarchical resolution system:

Yes
No
Yes
No
Yes
No
Yes
No
Request for Product Page
URL template_id parameter?
Use ApplicationThemeTemplate.find template_id
Product has specific template assigned?
Use product.application_theme_template
Company has active theme with default product template?
Use company.current_theme.default_product_template
Root theme has default product template?
Use root_theme.default_product_template
Error: No template found
Render Template

File and Asset Management

Theme assets are managed through the broader file resource system:

ApplicationTheme
├── has_many :images (cover images, theme previews)
├── has_many :file_resources (CSS files, JavaScript, fonts)
└── has_many :ordered_images (positioned theme gallery images)

ApplicationThemeTemplate
├── has_many :file_references -> FileResource (template-specific assets)
├── has_many :images (template preview images)
└── content field contains template code (Liquid/JSON)

Request Flow Architecture

UserRouterControllerThemeableTemplateResolverVariableServicePageBuilderLiquidEngineResponsePriority-based template resolutionalt[Liquid Template][JSON Template]GET /products/123PublicController or SharesControllerinclude Themeable concernset_theme_template_and_variables()theme_template_for(:product, product)Check URL paramsCheck content-specific assignmentCheck company theme defaultsCheck root theme fallbackApplicationThemeTemplatetheme_template_variables_for(template, product)Build global variablesBuild content variablesBuild request variablesVariables hashtemplate + variablesrender_template(template, variables)parse(content, variables)Rendered HTMLprocess_sections(json, variables)Rendered HTMLresolve_layout(template)apply_stylesheets()Final HTMLHTTP ResponseRendered PageUserRouterControllerThemeableTemplateResolverVariableServicePageBuilderLiquidEngineResponse

Step 1: Request Routing and Controller Setup

Request Entry Points:

  • PublicController - Handles company homepage, join pages, privacy policy
  • SharesController - Handles content pages (products, media, libraries, custom pages)

Controller Processing: Both controllers include the Themeable concern which provides theme resolution functionality:

  • set_theme_template_and_variables() - Main orchestration method
  • theme_template_for() - Template resolution logic
  • theme_template_variables_for() - Variable population

Step 2: Theme and Template Resolution

Theme Resolution:

# In Themeable concern
def current_theme_for_company
  @current_theme ||= @company&.current_theme || default_system_theme
end

def theme_template_for(content_type, content_item = nil)
  # 1. Check URL parameters for template override
  return find_template_by_id(params[:template_id]) if params[:template_id]

  # 2. Check content-specific template assignment
  return content_item.application_theme_template if content_item&.application_theme_template

  # 3. Find default template for content type in company's active theme
  ApplicationThemeTemplate.default_for_company_themes(
    company: @company,
    themeable_type: content_type
  ).first
end

Template Selection Logic:

  • Templates are resolved based on themeable_type (product, medium, home_page, etc.)
  • Default templates are marked with default: true within each theme
  • Only active templates are considered for rendering
  • Falls back to root theme defaults if company theme lacks specific templates

Step 3: Variable Population and Context Building

Variable Service Orchestration:

# Variables are created by specialized service classes
def theme_template_variables_for(template, content_item = nil)
  Themes::Templates::Variable.new(
    template: template,
    request: request,
    company: @company,
    record: content_item,
    params: params
  ).call
end

Variable Categories and Sources:

  • Base Variables: URLs, paths, request info, company details
  • Content Variables: Product data, page content, media info (based on content_item)
  • Global Variables: Site settings, localization, social media links
  • Theme Variables: CSS custom properties, layout settings from theme configuration

Step 4: Template Rendering Pipeline

Rendering Orchestration:

# PageBuilder handles the core rendering logic
def render_template(template, variables, layout = nil)
  if template.json?
    render_json_template(template, variables)
  else
    render_liquid_template(template, variables)
  end
end

Liquid Template Processing:

  • Templates processed through LiquidTemplate.parse(template.content, variables)
  • Variables accessible as {{ variable_name }} in templates
  • Support for control flow: {% if condition %}, {% for item in collection %}
  • Custom filters available for formatting, localization, and image processing
  • Layout injection via {{ content_for_layout }} if layout specified

JSON Template Processing: JSON templates use a sophisticated section-based rendering system that separates content structure from presentation:

  • Section-by-Section Rendering: The JSON structure defines discrete sections that are processed and rendered individually
  • Schema-Based Configuration: Each section type has a corresponding schema that defines available settings, blocks, and presets
  • Dynamic Block Management: Sections can contain configurable blocks with their own settings and rendering logic
  • Settings Inheritance: Block settings inherit defaults from their section schema and can be overridden per instance
  • Fluid Attributes: Blocks receive special fluid_attributes for frontend editing integration (section IDs, block types, etc.)

Processing Steps:

  1. JSON Parsing: Template content is parsed as JSON structure
  2. Section Iteration: Each section in the sections object is processed according to the order array
  3. Schema Loading: Section schema is loaded from corresponding sections themeable_type templates
  4. Settings Merging: Section settings are merged with schema defaults
  5. Block Processing: Dynamic blocks are processed with individual settings and fluid attributes
  6. Template Resolution: Each section type maps to a Liquid template that renders the final HTML
  7. Variable Injection: Processed settings become available as {{ section.settings.setting_name }} variables

Step 5: Layout Resolution and Content Wrapping

Layout Processing:

def resolve_theme_layout(template)
  return nil unless template.templateable_type?

  layout_name = template.layout_name || 'theme'
  theme = template.theme

  theme&.application_theme_templates&.find_by(
    name: layout_name,
    themeable_type: 'layouts'
  )&.published
end

Layout Integration:

  • Templates can specify layouts via {% layout 'theme' %} tags or JSON configuration
  • Layout templates wrap content using {{ content_for_layout }} variable
  • Supports nested layouts and template inheritance
  • Layout resolution respects theme hierarchy (company themes inherit root layouts)

Step 6: Stylesheet and Asset Integration

CSS Processing:

# ApplicationTheme methods for stylesheet processing
def global_stylesheet_with_variables
  cache_key = "theme_stylesheet:#{id}:global:#{cache_key_suffix}"
  Rails.cache.fetch(cache_key, expires_in: 1.hour) do
    stylesheet_with_variables(global_stylesheet)
  end
end

def stylesheet_with_variables(stylesheet)
  variables = safe_parse_json(self.variables)
  variables.each { |key, value| stylesheet.gsub!("$#{key}", value) }
  stylesheet
end

Asset Integration:

  • Global stylesheets with CSS variable substitution
  • Custom stylesheets for theme-specific overrides
  • File resources (fonts, images, scripts) linked to themes and templates
  • Caching with variable-based cache invalidation

Implementation Details

Data Model Structure

-- ApplicationTheme: Main theme container
CREATE TABLE application_themes (
    id SERIAL PRIMARY KEY,
    company_id INTEGER REFERENCES companies(id), -- NULL for root themes
    name VARCHAR NOT NULL,
    description TEXT,
    variables TEXT, -- JSON string with CSS variables
    custom_stylesheet TEXT,
    global_stylesheet TEXT,
    version VARCHAR DEFAULT '1.0',
    status INTEGER DEFAULT 0, -- 0: draft, 1: active
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Only one active theme per company
CREATE UNIQUE INDEX idx_app_themes_company_active
ON application_themes (company_id)
WHERE status = 1;

-- ApplicationThemeTemplate: Individual templates within themes
CREATE TABLE application_theme_templates (
    id SERIAL PRIMARY KEY,
    company_id INTEGER REFERENCES companies(id),
    application_theme_id INTEGER REFERENCES application_themes(id),
    parent_id INTEGER REFERENCES application_theme_templates(id), -- For nested templates
    name VARCHAR NOT NULL,
    themeable_type INTEGER NOT NULL, -- Enum: product, medium, home_page, etc.
    content TEXT, -- Template code (Liquid or JSON)
    head TEXT, -- HTML head content
    stylesheet TEXT, -- Template-specific CSS
    format VARCHAR DEFAULT 'liquid', -- 'liquid' or 'json'
    status INTEGER DEFAULT 0, -- 0: draft, 1: active
    applicable INTEGER DEFAULT 0, -- 0: everything, 1: specific
    "default" BOOLEAN DEFAULT FALSE, -- Default template for this themeable_type
    fluid BOOLEAN DEFAULT FALSE, -- System template flag
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Index for finding default templates by company and type
CREATE INDEX idx_app_theme_templates_theme_company_type_default
ON application_theme_templates (application_theme_id, company_id, themeable_type, "default")
WHERE status = 1;

-- Legacy Theme model (maintained for backwards compatibility)
CREATE TABLE themes (
    id SERIAL PRIMARY KEY,
    company_id INTEGER REFERENCES companies(id),
    application_theme_template_id INTEGER REFERENCES application_theme_templates(id),
    name VARCHAR NOT NULL,
    public BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

Key Schema Features:

  • Company Isolation: Templates and themes are scoped by company_id
  • Root Theme Support: Root themes have company_id: NULL and are shared across the platform
  • Template Hierarchy: Templates can have parent-child relationships via parent_id
  • Status Management: Draft/active status for both themes and templates
  • Default Template Resolution: Index optimized for finding default templates by type
  • Format Flexibility: Templates can be either Liquid or JSON format

Themeable Types Enumeration

# ApplicationThemeTemplate themeable_type enum
enum :themeable_type, {
  product: 0,           # Individual product pages
  medium: 1,            # Media post pages
  enrollment_pack: 2,   # Course/enrollment pages
  shop_page: 3,         # Shop/catalog pages
  navbar: 4,            # Navigation header
  library: 5,           # Library/playlist pages
  page: 6,              # Custom pages
  components: 7,        # Reusable components
  library_navbar: 8,    # Library-specific navigation
  sections: 9,          # Template sections
  locales: 10,          # Localization templates
  footer: 11,           # Page footer
  layouts: 12,          # Page wrapper layouts
  category_page: 13,    # Category browsing
  collection_page: 14,  # Collection browsing
  cart_page: 15,        # Shopping cart
  config: 16,           # Theme configuration
  home_page: 17,        # Homepage layout
  mysite: 18,           # MySite pages
  join_page: 19         # Registration pages
}

Caching Architecture

Multi-Level Caching Strategy:

  1. Stylesheet Caching (1 hour expiration):

    cache_key = "theme_stylesheet:#{theme_id}:global:#{updated_at.to_i}:#{variables_hash}"
    Rails.cache.fetch(cache_key, expires_in: 1.hour) do
      process_stylesheet_with_variables(stylesheet)
    end
    
  2. Global Embeds Caching (1 hour / 5 seconds in development):

    cache_key = "global_embeds:#{company_id}:#{global_embeds_updated_at}"
    Rails.cache.fetch(cache_key, expires_in: cache_duration) do
      fetch_and_process_global_embeds
    end
    
  3. Variable Optimization:

    • Language and country data memoized to prevent N+1 queries
    • Base URL resolution cached per request
    • Template resolution cached within request scope

Cache Invalidation:

  • Stylesheet cache invalidated when theme variables or CSS content changes
  • Global embeds cache invalidated when company embed settings change
  • Theme template cache invalidated on publish/unpublish actions

Publishing and Versioning System

Publishing Workflow:

# ApplicationTheme publishing
def publish
  update!(status: :active)
  application_theme_templates.each(&:publish) # Publish all templates
end

# ApplicationThemeTemplate publishing
def make_default
  ApplicationRecord.transaction do
    # Set this as the active default template
    active!
    update!(default: true)

    # Deactivate other default templates of same type
    application_theme.application_theme_templates
      .where(themeable_type: themeable_type)
      .where.not(id: id)
      .update_all(default: false)
  end
end

Version Management:

  • Themes use semantic versioning (major.minor format)
  • Minor versions automatically increment on template changes
  • Major versions increment on significant theme updates
  • Audit trail maintains complete change history
  • Auto-upgrade system for compatible theme versions

Template Development Patterns

Liquid Template Structure

Basic Template Format:


  Product template with layout, filters, and sections


<article class="product-page">
  <header class="product-header">
    <h1>{{ product.name }}</h1>
    <div class="product-price">
      {{ product.price | money }}
    </div>
    <div class="product-meta">
      <span class="product-date">{{ product.created_at | date: '%B %d, %Y' }}</span>
        <div class="product-tags">{{ product.tags | join: ', ' }}</div>
    </div>
  </header>

  <div class="product-content">
    <div class="product-images">
        <img src="{{ image.url | img_url: '400x300' }}"
             alt="{{ image.alt }}" />
    </div>

    <div class="product-description">
      {{ product.description | truncate: 250 }}
    </div>
  </div>

    <div class="product-purchase">
      <button class="btn-purchase" data-product-id="{{ product.id }}">
        {{ 'products.add_to_cart' | t }}
      </button>
    </div>
</article>