Class: StyleCapsule::StylesheetRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/style_capsule/stylesheet_registry.rb,
sig/style_capsule.rbs

Overview

At runtime inherits Object when ActiveSupport::CurrentAttributes is unavailable.

Constant Summary collapse

DEFAULT_NAMESPACE =

Default namespace for backward compatibility

Returns:

  • (Symbol)
:default

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.inline_cacheObject (readonly)

Returns the value of attribute inline_cache.



95
96
97
# File 'lib/style_capsule/stylesheet_registry.rb', line 95

def inline_cache
  @inline_cache
end

.manifestObject (readonly)

Returns the value of attribute manifest.



95
96
97
# File 'lib/style_capsule/stylesheet_registry.rb', line 95

def manifest
  @manifest
end

Class Method Details

.any?(namespace: nil) ⇒ Boolean

Check if there are any registered stylesheets

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- checks manifest and request-scoped registries

Parameters:

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace to check (nil checks all)

  • namespace: (Symbol, String, nil) (defaults to: nil)

Returns:

  • (Boolean)


625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/style_capsule/stylesheet_registry.rb', line 625

def self.any?(namespace: nil)
  if namespace.nil?
    # Check process-wide manifest
    has_files = !@manifest.empty? && @manifest.values.any? { |files| !files.empty? }
    # Check request-scoped file paths
    request_files = request_file_stylesheets
    has_request_files = !request_files.empty? && request_files.values.any? { |files| !files.empty? }
    # Check request-scoped inline CSS
    inline = request_inline_stylesheets
    has_inline = !inline.empty? && inline.values.any? { |stylesheets| !stylesheets.empty? }
    has_files || has_request_files || has_inline
  else
    ns = normalize_namespace(namespace)
    # Check process-wide manifest
    has_files = @manifest[ns] && !@manifest[ns].empty?
    # Check request-scoped file paths
    has_request_files = request_file_stylesheets[ns] && !request_file_stylesheets[ns].empty?
    # Check request-scoped inline CSS
    has_inline = request_inline_stylesheets[ns] && !request_inline_stylesheets[ns].empty?
    !!(has_files || has_request_files || has_inline)
  end
end

.cache_inline_css(cache_key, css_content, cache_strategy:, cache_ttl: nil, cache_proc: nil, capsule_id: nil, namespace: nil) ⇒ void

This method returns an undefined value.

Cache inline CSS with expiration

Parameters:

  • cache_key (String)

    Cache key

  • css_content (String)

    CSS content to cache

  • cache_strategy (Symbol)

    Cache strategy

  • cache_ttl (Integer, ActiveSupport::Duration, nil) (defaults to: nil)

    Time-to-live in seconds. Supports ActiveSupport::Duration (e.g., 1.hour, 30.minutes)

  • cache_proc (Proc, nil) (defaults to: nil)

    Custom cache proc

  • capsule_id (String, nil) (defaults to: nil)

    Capsule ID

  • namespace (Symbol) (defaults to: nil)

    Namespace

  • cache_strategy: (Symbol)
  • cache_ttl: (Integer, ActiveSupport::Duration, nil) (defaults to: nil)
  • cache_proc: (Proc, nil) (defaults to: nil)
  • capsule_id: (String, nil) (defaults to: nil)
  • namespace: (Symbol, nil) (defaults to: nil)


386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/style_capsule/stylesheet_registry.rb', line 386

def self.cache_inline_css(cache_key, css_content, cache_strategy:, cache_ttl: nil, cache_proc: nil, capsule_id: nil, namespace: nil)
  expires_at = nil

  case cache_strategy
  when :time
    # Handle ActiveSupport::Duration (e.g., 1.hour) or integer seconds
    ttl_seconds = if cache_ttl.respond_to?(:to_i)
      cache_ttl.to_i
    else
      cache_ttl
    end
    expires_at = ttl_seconds ? current_time + ttl_seconds : nil
  when :proc
    if cache_proc
      _key, _should_cache, proc_expires = cache_proc.call(css_content, capsule_id, namespace)
      expires_at = proc_expires
    end
  end

  @inline_cache[cache_key] = {
    css_content: css_content,
    cached_at: current_time,
    expires_at: expires_at
  }
end

.cached_inline(cache_key, cache_strategy:, cache_ttl: nil, cache_proc: nil, css_content: nil, capsule_id: nil, namespace: nil) ⇒ String?

Get cached inline CSS if available and not expired

Automatically performs lazy cleanup of expired entries if it's been more than 5 minutes since the last cleanup to prevent memory leaks in long-running processes.

Parameters:

  • cache_key (String)

    Cache key to look up

  • cache_strategy (Symbol)

    Cache strategy

  • cache_ttl (Integer, ActiveSupport::Duration, nil) (defaults to: nil)

    Time-to-live in seconds. Supports ActiveSupport::Duration (e.g., 1.hour, 30.minutes)

  • cache_proc (Proc, nil) (defaults to: nil)

    Custom cache proc

  • css_content (String) (defaults to: nil)

    Original CSS content (for proc strategy)

  • capsule_id (String, nil) (defaults to: nil)

    Capsule ID (for proc strategy)

  • namespace (Symbol) (defaults to: nil)

    Namespace (for proc strategy)

  • cache_strategy: (Symbol)
  • cache_ttl: (Integer, ActiveSupport::Duration, nil) (defaults to: nil)
  • cache_proc: (Proc, nil) (defaults to: nil)
  • css_content: (String, nil) (defaults to: nil)
  • capsule_id: (String, nil) (defaults to: nil)
  • namespace: (Symbol, nil) (defaults to: nil)

Returns:

  • (String, nil)

    Cached CSS content or nil if not cached/expired



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/style_capsule/stylesheet_registry.rb', line 352

def self.cached_inline(cache_key, cache_strategy:, cache_ttl: nil, cache_proc: nil, css_content: nil, capsule_id: nil, namespace: nil)
  # Lazy cleanup: remove expired entries if it's been a while (every 5 minutes)
  # This prevents memory leaks in long-running processes without impacting performance
  cleanup_expired_cache_if_needed

  cached_entry = @inline_cache[cache_key]
  return nil unless cached_entry

  # Check expiration based on strategy
  case cache_strategy
  when :time
    return nil if cache_ttl && cached_entry[:expires_at] && current_time > cached_entry[:expires_at]
  when :proc
    return nil unless cache_proc
    # Re-validation: the proc is invoked on every read with the *current* request's CSS.
    # Return [_, true, _] from the proc to keep using the cached entry; false invalidates it.
    # (This is closer to a conditional cache than a blind key/value store.)
    _key, should_use, _expires = cache_proc.call(css_content, capsule_id, namespace)
    return nil unless should_use
  end

  cached_entry[:css_content]
end

.cleanup_expired_cacheInteger

Clean up expired entries from the inline CSS cache

Removes all cache entries that have expired (where expires_at is set and Time.current > expires_at). This prevents memory leaks in long-running processes.

This method is called automatically by cached_inline (lazy cleanup every 5 minutes), but can also be called manually for explicit cleanup (e.g., from a background job or scheduled task).

Examples:

Manual cleanup

StyleCapsule::StylesheetRegistry.cleanup_expired_cache

Scheduled cleanup (e.g., in a background job)

# In a scheduled job or initializer
StyleCapsule::StylesheetRegistry.cleanup_expired_cache

Returns:

  • (Integer)

    Number of expired entries removed



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/style_capsule/stylesheet_registry.rb', line 439

def self.cleanup_expired_cache
  return 0 if @inline_cache.empty?

  now = current_time
  expired_keys = []

  @inline_cache.each do |cache_key, entry|
    # Remove entries that have an expires_at time and it's in the past
    if entry[:expires_at] && now > entry[:expires_at]
      expired_keys << cache_key
    end
  end

  expired_keys.each { |key| @inline_cache.delete(key) }
  @last_cleanup_time = now

  expired_keys.size
end

.clear(namespace: nil) ⇒ void

This method returns an undefined value.

Clear request-scoped inline CSS and render-time file registrations (does not clear process-wide manifest)

Parameters:

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace to clear (nil clears all)

  • namespace: (Symbol, String, nil) (defaults to: nil)


522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/style_capsule/stylesheet_registry.rb', line 522

def self.clear(namespace: nil)
  if namespace.nil?
    self.inline_stylesheets = {}
    self.request_file_stylesheets = {}
  else
    ns = normalize_namespace(namespace)
    inline_registry = inline_stylesheets
    inline_registry.delete(ns)
    self.inline_stylesheets = inline_registry

    file_registry = request_file_stylesheets
    file_registry.delete(ns)
    self.request_file_stylesheets = file_registry
  end
end

.clear_inline_cache(cache_key = nil) ⇒ void

This method returns an undefined value.

Clear inline CSS cache

Parameters:

  • cache_key (String, nil) (defaults to: nil)

    Specific cache key to clear (nil clears all)



416
417
418
419
420
421
422
# File 'lib/style_capsule/stylesheet_registry.rb', line 416

def self.clear_inline_cache(cache_key = nil)
  if cache_key
    @inline_cache.delete(cache_key)
  else
    @inline_cache.clear
  end
end

.clear_manifest(namespace: nil) ⇒ void

This method returns an undefined value.

Clear process-wide manifest (useful for testing or development reloading)

Parameters:

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace to clear (nil clears all)

  • namespace: (Symbol, String, nil) (defaults to: nil)


542
543
544
545
546
547
548
549
# File 'lib/style_capsule/stylesheet_registry.rb', line 542

def self.clear_manifest(namespace: nil)
  if namespace.nil?
    @manifest = {}
  else
    ns = normalize_namespace(namespace)
    @manifest.delete(ns)
  end
end

.current_timeObject

Get current time (ActiveSupport::Time.current or Time.now fallback)



98
99
100
101
102
103
104
105
106
107
# File 'lib/style_capsule/stylesheet_registry.rb', line 98

def current_time
  if defined?(Time) && Time.respond_to?(:current)
    Time.current
  else
    # rubocop:disable Rails/TimeZone
    # Time.now is intentional fallback for non-Rails usage when Time.current is unavailable
    Time.now
    # rubocop:enable Rails/TimeZone
  end
end

.inject_pending_head_stylesheets(html, view_context = nil) ⇒ String

Inject pending request-scoped stylesheets into an HTML document before </head>.

Used by HeadInjectionMiddleware after the body has rendered and components have called register_stylesheet or register_inline.

Parameters:

  • html (String)

    Full HTML response body

  • view_context (ActionView::Base, nil) (defaults to: nil)

    View context for stylesheet_link_tag

Returns:

  • (String)

    HTML with pending stylesheet tags injected, or the original HTML



706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/style_capsule/stylesheet_registry.rb', line 706

def self.inject_pending_head_stylesheets(html, view_context = nil)
  pending_stylesheets = pending_request_stylesheets
  return html if pending_stylesheets.empty?

  closing_head_index = html.match(%r{</head>}i)&.begin(0)
  return html unless closing_head_index

  tags = render_stylesheet_tags(pending_stylesheets, view_context)
  return html if tags.empty?

  injected = html.dup
  injected.insert(closing_head_index, "#{tags}\n")
  clear
  injected
end

.inline_stylesheetsObject

Get inline stylesheets (thread-local fallback if not using CurrentAttributes)



116
117
118
119
120
121
122
123
124
125
# File 'lib/style_capsule/stylesheet_registry.rb', line 116

def inline_stylesheets
  if using_current_attributes?
    # When using CurrentAttributes, access the instance attribute
    # CurrentAttributes automatically provides access to instance attributes
    inst = instance
    inst&.inline_stylesheets || {}
  else
    Thread.current[:style_capsule_inline_stylesheets] ||= {}
  end
end

.inline_stylesheets=(value) ⇒ Object

Set inline stylesheets (thread-local fallback if not using CurrentAttributes)



128
129
130
131
132
133
134
135
136
# File 'lib/style_capsule/stylesheet_registry.rb', line 128

def inline_stylesheets=(value)
  if using_current_attributes?
    # When using CurrentAttributes, set via the instance
    inst = instance
    inst.inline_stylesheets = value if inst
  else
    Thread.current[:style_capsule_inline_stylesheets] = value
  end
end

.instanceObject

Get instance (for CurrentAttributes compatibility)



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/style_capsule/stylesheet_registry.rb', line 159

def instance
  if using_current_attributes?
    # Call the CurrentAttributes instance method from parent class
    super
  else
    # Return a simple object that responds to request-scoped attributes
    # This is mainly for compatibility with code that might call instance.inline_stylesheets
    registry_class = self
    @_standalone_instance ||= begin
      obj = Object.new
      obj.define_singleton_method(:inline_stylesheets) { registry_class.inline_stylesheets }
      obj.define_singleton_method(:inline_stylesheets=) { |v| registry_class.inline_stylesheets = v }
      obj.define_singleton_method(:request_file_stylesheets) { registry_class.request_file_stylesheets }
      obj.define_singleton_method(:request_file_stylesheets=) { |v| registry_class.request_file_stylesheets = v }
      obj
    end
  end
end

.manifest_filesHash<Symbol, Array<Hash>>

Get all registered file paths from process-wide manifest (organized by namespace)

Returns:

  • (Hash<Symbol, Array<Hash>>)

    Hash of namespace => array of file registrations



479
480
481
# File 'lib/style_capsule/stylesheet_registry.rb', line 479

def self.manifest_files
  @manifest.transform_values { |h| h.values }
end

.normalize_namespace(namespace) ⇒ Symbol

Normalize namespace (nil/blank becomes DEFAULT_NAMESPACE)

Parameters:

  • namespace (Symbol, String, nil)

    Namespace identifier

Returns:

  • (Symbol)

    Normalized namespace



185
186
187
188
# File 'lib/style_capsule/stylesheet_registry.rb', line 185

def self.normalize_namespace(namespace)
  return DEFAULT_NAMESPACE if namespace.nil? || namespace.to_s.strip.empty?
  namespace.to_sym
end

.pending_head_stylesheets?Boolean

Whether request-scoped stylesheets remain to inject into <head>

Returns:

  • (Boolean)


514
515
516
# File 'lib/style_capsule/stylesheet_registry.rb', line 514

def self.pending_head_stylesheets?
  !pending_request_stylesheets.empty?
end

.register(file_path, namespace: nil, **options) ⇒ void

This method returns an undefined value.

Register a stylesheet file path during rendering (request-scoped).

Render-time registrations are stored per request and emitted by render_head_stylesheets when the layout head runs before the body, or by HeadInjectionMiddleware when components register stylesheets later in the response.

Files registered here are served through Rails asset pipeline (via stylesheet_link_tag).

Parameters:

  • file_path (String)

    Path to stylesheet (relative to app/assets/stylesheets)

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace for separation (nil/blank uses default)

  • options (Hash)

    Options for stylesheet_link_tag

  • namespace: (Symbol, String, nil) (defaults to: nil)


202
203
204
# File 'lib/style_capsule/stylesheet_registry.rb', line 202

def self.register(file_path, namespace: nil, **options)
  register_request_file(file_path, namespace: namespace, **options)
end

.register_eager(file_path, namespace: nil, **options) ⇒ void

This method returns an undefined value.

Register a stylesheet file path eagerly (process-wide manifest).

Use at class load or boot time when the stylesheet should always be available in render_head_stylesheets without waiting for a component render.

Parameters:

  • file_path (String)

    Path to stylesheet (relative to app/assets/stylesheets)

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace for separation (nil/blank uses default)

  • options (Hash)

    Options for stylesheet_link_tag

  • namespace: (Symbol, String, nil) (defaults to: nil)


215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/style_capsule/stylesheet_registry.rb', line 215

def self.register_eager(file_path, namespace: nil, **options)
  ns = normalize_namespace(namespace)
  path = AssetPath.validate_logical_path!(file_path)

  Instrumentation.instrument_registration(
    namespace: ns,
    file_path: path,
    inline_size: nil,
    cache_strategy: :none
  ) do
    @manifest[ns] ||= {}
    @manifest[ns][path] = {file_path: path, options: options}
  end
end

.register_inline(css_content, namespace: nil, capsule_id: nil, cache_key: nil, cache_strategy: :none, cache_ttl: nil, cache_proc: nil, component_class: nil, stylesheet_link_options: nil) ⇒ void

This method returns an undefined value.

Register inline CSS for head rendering

Inline CSS can be cached based on cache configuration. Supports:

  • No caching (default): stored per-request
  • Time-based caching: cache expires after TTL
  • Custom proc caching: use proc to determine cache key and validity
  • File-based caching: writes CSS to files for HTTP caching

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- coordinates file, inline, and cache registration

Parameters:

  • css_content (String)

    CSS content (should already be scoped)

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace for separation (nil/blank uses default)

  • capsule_id (String, nil) (defaults to: nil)

    Optional capsule ID for reference

  • cache_key (String, nil) (defaults to: nil)

    Optional cache key (for cache lookup)

  • cache_strategy (Symbol, nil) (defaults to: :none)

    Cache strategy: :none, :time, :proc, :file (default: :none)

  • cache_ttl (Integer, ActiveSupport::Duration, nil) (defaults to: nil)

    Time-to-live in seconds (for :time strategy). Supports ActiveSupport::Duration (e.g., 1.hour, 30.minutes)

  • cache_proc (Proc, nil) (defaults to: nil)

    Custom cache proc (for :proc strategy) Proc receives: (css_content, capsule_id, namespace) and should return [cache_key, should_cache, expires_at]

  • component_class (Class, nil) (defaults to: nil)

    Component class (for :file strategy)

  • stylesheet_link_options (Hash, nil) (defaults to: nil)

    Options for stylesheet_link_tag (for :file strategy)

  • namespace: (Symbol, String, nil) (defaults to: nil)
  • capsule_id: (String, nil) (defaults to: nil)
  • cache_key: (String, nil) (defaults to: nil)
  • cache_strategy: (Symbol) (defaults to: :none)
  • cache_ttl: (Integer, ActiveSupport::Duration, nil) (defaults to: nil)
  • cache_proc: (Proc, nil) (defaults to: nil)
  • component_class: (Class, nil) (defaults to: nil)
  • stylesheet_link_options: (Hash[untyped, untyped], nil) (defaults to: nil)


269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/style_capsule/stylesheet_registry.rb', line 269

def self.register_inline(css_content, namespace: nil, capsule_id: nil, cache_key: nil, cache_strategy: :none, cache_ttl: nil, cache_proc: nil, component_class: nil, stylesheet_link_options: nil)
  ns = normalize_namespace(namespace)

  # Handle file-based caching (writes to file and registers as file path)
  if cache_strategy == :file && component_class && capsule_id
    # Check if file already exists (from precompilation via assets:precompile or previous write)
    # Pre-built files are already available through Rails asset pipeline and just need to be registered
    existing_path = CssFileWriter.file_path_for(
      component_class: component_class,
      capsule_id: capsule_id
    )

    if existing_path
      # File exists (pre-built or previously written), register it as a file path
      # The manifest deduplicates by logical path if the same file is registered multiple times
      # This file will be served through Rails asset pipeline (stylesheet_link_tag)
      link_options = stylesheet_link_options || {}
      register(existing_path, namespace: namespace, **link_options)
      return
    end

    # File doesn't exist, write it dynamically (development mode or first render)
    # After writing, register it as a file path so it's served as an asset
    file_path = CssFileWriter.write_css(
      css_content: css_content,
      component_class: component_class,
      capsule_id: capsule_id
    )

    if file_path
      # Register as file path instead of inline CSS
      # This ensures the file is served through Rails asset pipeline
      link_options = stylesheet_link_options || {}
      register(file_path, namespace: namespace, **link_options)
      return
    end
  end

  # Check cache if strategy is enabled
  cached_css = nil
  if cache_strategy != :none && cache_key && cache_strategy != :file
    cached_css = cached_inline(cache_key, cache_strategy: cache_strategy, cache_ttl: cache_ttl, cache_proc: cache_proc, css_content: css_content, capsule_id: capsule_id, namespace: ns)
  end

  # Use cached CSS if available, otherwise use provided CSS
  final_css = cached_css || css_content

  Instrumentation.instrument_registration(
    namespace: ns,
    file_path: nil,
    inline_size: final_css.bytesize,
    cache_strategy: cache_strategy
  ) do
    registry = inline_stylesheets
    registry[ns] ||= []
    registry[ns] << {
      type: :inline,
      css_content: final_css,
      capsule_id: capsule_id
    }
    self.inline_stylesheets = registry
  end

  # Cache the CSS if strategy is enabled and not already cached
  if cache_strategy != :none && cache_key && !cached_css && cache_strategy != :file
    cache_inline_css(cache_key, css_content, cache_strategy: cache_strategy, cache_ttl: cache_ttl, cache_proc: cache_proc, capsule_id: capsule_id, namespace: ns)
  end
end

.render_head_stylesheets(view_context = nil, namespace: nil) ⇒ String

Render registered stylesheets as HTML

This should be called in the layout's section. Combines eager manifest files with request-scoped file paths and inline CSS registered before this call. Stylesheets registered later in the response are injected into <head> by HeadInjectionMiddleware when enabled.

Automatically clears request-scoped inline CSS and file paths after rendering for the selected namespace(s). The eager manifest persists.

rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity -- renders mixed inline and file registrations

Parameters:

  • view_context (ActionView::Base, nil) (defaults to: nil)

    The view context (for helpers like content_tag, stylesheet_link_tag) In ERB: pass self (the view context) In Phlex: pass view_context method If nil, falls back to basic HTML generation

  • namespace (Symbol, String, nil) (defaults to: nil)

    Optional namespace to render (nil/blank renders all namespaces)

  • namespace: (Symbol, String, nil) (defaults to: nil)

Returns:

  • (String)

    HTML-safe string with stylesheet tags



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# File 'lib/style_capsule/stylesheet_registry.rb', line 568

def self.render_head_stylesheets(view_context = nil, namespace: nil)
  if namespace.nil? || namespace.to_s.strip.empty?
    request_files_snapshot = request_file_stylesheets.transform_values { |files| files.keys.dup }
    request_inline_snapshot = request_inline_stylesheets.transform_values(&:dup)

    all_stylesheets = merged_file_registrations_all_namespaces

    request_inline_stylesheets.each do |_ns, inline|
      all_stylesheets.concat(inline)
    end

    return safe_string("") if all_stylesheets.empty?

    rendered = all_stylesheets.map do |stylesheet|
      if stylesheet[:type] == :inline
        render_inline_stylesheet(stylesheet, view_context)
      else
        render_file_stylesheet(stylesheet, view_context)
      end
    end.join("\n").then { |s| safe_string(s) }

    clear_snapshotted_request_registrations(request_files_snapshot, request_inline_snapshot)
    rendered

  else
    # Render specific namespace
    ns = normalize_namespace(namespace)
    snapshotted_file_paths = request_file_stylesheets[ns]&.keys&.dup || []
    snapshotted_inline = request_inline_stylesheets[ns]&.dup || []
    stylesheets = stylesheets_for(namespace: ns).dup

    return safe_string("") if stylesheets.empty?

    rendered = stylesheets.map do |stylesheet|
      if stylesheet[:type] == :inline
        render_inline_stylesheet(stylesheet, view_context)
      else
        render_file_stylesheet(stylesheet, view_context)
      end
    end.join("\n").then { |s| safe_string(s) }

    clear_snapshotted_request_registrations_for_namespace(
      ns,
      file_paths: snapshotted_file_paths,
      snapshotted_inline_stylesheets: snapshotted_inline
    )
    rendered

  end
end

.request_file_stylesheetsObject

Get request-scoped file stylesheets (thread-local fallback if not using CurrentAttributes)



139
140
141
142
143
144
145
146
# File 'lib/style_capsule/stylesheet_registry.rb', line 139

def request_file_stylesheets
  if using_current_attributes?
    inst = instance
    inst&.request_file_stylesheets || {}
  else
    Thread.current[:style_capsule_request_file_stylesheets] ||= {}
  end
end

.request_file_stylesheets=(value) ⇒ Object

Set request-scoped file stylesheets (thread-local fallback if not using CurrentAttributes)



149
150
151
152
153
154
155
156
# File 'lib/style_capsule/stylesheet_registry.rb', line 149

def request_file_stylesheets=(value)
  if using_current_attributes?
    inst = instance
    inst.request_file_stylesheets = value if inst
  else
    Thread.current[:style_capsule_request_file_stylesheets] = value
  end
end

.request_inline_stylesheetsHash<Symbol, Array<Hash>>

Get all registered inline stylesheets for current request (organized by namespace)

Returns:

  • (Hash<Symbol, Array<Hash>>)

    Hash of namespace => array of inline stylesheet registrations



486
487
488
# File 'lib/style_capsule/stylesheet_registry.rb', line 486

def self.request_inline_stylesheets
  inline_stylesheets
end

.request_stylesheet_filesHash<Symbol, Hash<String, Hash>>

Get all request-scoped file stylesheets for the current request (organized by namespace)

Returns:

  • (Hash<Symbol, Hash<String, Hash>>)

    Hash of namespace => logical path => registration



493
494
495
# File 'lib/style_capsule/stylesheet_registry.rb', line 493

def self.request_stylesheet_files
  request_file_stylesheets
end

.stylesheets_for(namespace: nil) ⇒ Array<Hash>

Get all stylesheets (files + inline) for a specific namespace

Parameters:

  • namespace (Symbol, String, nil) (defaults to: nil)

    Namespace identifier (nil/blank uses default)

  • namespace: (Symbol, String, nil) (defaults to: nil)

Returns:

  • (Array<Hash>)

    Array of stylesheet registrations for the namespace



501
502
503
504
505
506
507
508
509
# File 'lib/style_capsule/stylesheet_registry.rb', line 501

def self.stylesheets_for(namespace: nil)
  ns = normalize_namespace(namespace)
  result = merged_file_registrations_for_namespace(ns)

  inline = request_inline_stylesheets[ns] || []
  result.concat(inline)

  result
end

.using_current_attributes?Boolean

Check if we're using ActiveSupport::CurrentAttributes This method can be stubbed in tests to test fallback paths

Returns:

  • (Boolean)


111
112
113
# File 'lib/style_capsule/stylesheet_registry.rb', line 111

def using_current_attributes?
  defined?(ActiveSupport::CurrentAttributes) && self < ActiveSupport::CurrentAttributes
end