Class: Rhales::View

Inherits:
Object
  • Object
show all
Defined in:
lib/rhales/view.rb

Overview

Complete RSFC view implementation

Single public interface for RSFC template rendering that handles:

  • Context creation (with pluggable context classes)

  • Template loading and parsing

  • Template rendering with Rhales

  • Data hydration and injection

## Context and Data Boundaries

Views implement a two-phase security model:

### Server Templates: Full Context Access Templates have complete access to all server-side data:

  • All props passed to View.new

  • Data from .rue file’s <data> section (processed server-side)

  • Runtime data (CSRF tokens, nonces, request metadata)

  • Computed data (authentication status, theme classes)

  • User objects, configuration, internal APIs

### Client Data: Explicit Allowlist Only data declared in <data> sections reahas_role?ches the browser:

  • Creates a REST API-like boundary

  • Server-side variable interpolation processes secrets safely

  • JSON serialization validates data structure

  • No accidental exposure of sensitive server data

Example:

# Server template has full access:
{{user.admin?}} {{csrf_token}} {{internal_config}}

# Client only gets declared data:
{ "user_name": "{{user.name}}", "theme": "{{user.theme}}" }

See docs/CONTEXT_AND_DATA_BOUNDARIES.md for complete details.

Subclasses can override context_class to use different context implementations.

Defined Under Namespace

Classes: RenderError, TemplateNotFoundError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(req, sess = nil, cust = nil, locale_override = nil, props: {}, config: nil) ⇒ View

Returns a new instance of View.



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/rhales/view.rb', line 59

def initialize(req, sess = nil, cust = nil, locale_override = nil, props: {}, config: nil)
  @req           = req
  @sess          = sess
  @cust          = cust
  @locale        = locale_override
  @props         = props
  @config        = config || Rhales.configuration

  # Create context using the specified context class
  @rsfc_context = create_context
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



57
58
59
# File 'lib/rhales/view.rb', line 57

def config
  @config
end

#custObject (readonly)

Returns the value of attribute cust.



57
58
59
# File 'lib/rhales/view.rb', line 57

def cust
  @cust
end

#localeObject (readonly)

Returns the value of attribute locale.



57
58
59
# File 'lib/rhales/view.rb', line 57

def locale
  @locale
end

#propsObject (readonly)

Returns the value of attribute props.



57
58
59
# File 'lib/rhales/view.rb', line 57

def props
  @props
end

#reqObject (readonly)

Returns the value of attribute req.



57
58
59
# File 'lib/rhales/view.rb', line 57

def req
  @req
end

#rsfc_contextObject (readonly)

Returns the value of attribute rsfc_context.



57
58
59
# File 'lib/rhales/view.rb', line 57

def rsfc_context
  @rsfc_context
end

#sessObject (readonly)

Returns the value of attribute sess.



57
58
59
# File 'lib/rhales/view.rb', line 57

def sess
  @sess
end

Class Method Details

.default_template_nameObject

Get default template name based on class name



391
392
393
394
395
396
397
398
# File 'lib/rhales/view.rb', line 391

def default_template_name
  # Convert ClassName to class_name
  name.split('::').last
    .gsub(/([A-Z])/, '_\1')
    .downcase
    .sub(/^_/, '')
    .sub(/_view$/, '')
end

.render_with_data(req, sess, cust, locale, template_name: nil, config: nil, **props) ⇒ Object

Render template with props



401
402
403
404
# File 'lib/rhales/view.rb', line 401

def render_with_data(req, sess, cust, locale, template_name: nil, config: nil, **props)
  view = new(req, sess, cust, locale, props: props, config: config)
  view.render(template_name)
end

.with_data(req, sess, cust, locale, config: nil, **props) ⇒ Object

Create view instance with props



407
408
409
# File 'lib/rhales/view.rb', line 407

def with_data(req, sess, cust, locale, config: nil, **props)
  new(req, sess, cust, locale, props: props, config: config)
end

Instance Method Details

#data_hash(template_name = nil) ⇒ Object

Get processed data as hash (for API endpoints or testing)



119
120
121
122
123
124
125
126
# File 'lib/rhales/view.rb', line 119

def data_hash(template_name = nil)
  template_name ||= self.class.default_template_name

  # Build composition and aggregate data
  composition = build_view_composition(template_name)
  aggregator  = HydrationDataAggregator.new(@rsfc_context)
  aggregator.aggregate(composition)
end

#render(template_name = nil) ⇒ Object

Render RSFC template with hydration using two-pass architecture



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rhales/view.rb', line 72

def render(template_name = nil)
  template_name ||= self.class.default_template_name

  # Phase 1: Build view composition and aggregate data
  composition           = build_view_composition(template_name)
  aggregator            = HydrationDataAggregator.new(@rsfc_context)
  merged_hydration_data = aggregator.aggregate(composition)

  # Phase 2: Render HTML with pre-computed data
  # Render template content
  template_html = render_template_with_composition(composition, template_name)

  # Generate hydration HTML with merged data
  hydration_html = generate_hydration_from_merged_data(merged_hydration_data)

  # Set CSP header if enabled
  set_csp_header_if_enabled

  # Combine template and hydration
  inject_hydration_into_template(template_html, hydration_html)
rescue StandardError => ex
  raise RenderError, "Failed to render template '#{template_name}': #{ex.message}"
end

#render_hydration_only(template_name = nil) ⇒ Object

Generate only the data hydration HTML



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rhales/view.rb', line 106

def render_hydration_only(template_name = nil)
  template_name ||= self.class.default_template_name

  # Build composition and aggregate data
  composition           = build_view_composition(template_name)
  aggregator            = HydrationDataAggregator.new(@rsfc_context)
  merged_hydration_data = aggregator.aggregate(composition)

  # Generate hydration HTML
  generate_hydration_from_merged_data(merged_hydration_data)
end

#render_template_only(template_name = nil) ⇒ Object

Render only the template section (without data hydration)



97
98
99
100
101
102
103
# File 'lib/rhales/view.rb', line 97

def render_template_only(template_name = nil)
  template_name ||= self.class.default_template_name

  # Build composition for consistent behavior
  composition = build_view_composition(template_name)
  render_template_with_composition(composition, template_name)
end