Module: Sinatra::Inertia::Helpers

Defined in:
lib/sinatra/inertia/helpers.rb

Overview

Sinatra helpers exposed to route handlers. Mounted by the ‘Sinatra::Inertia` extension.

Instance Method Summary collapse

Instance Method Details

#csrf_tokenObject

CSRF token for the current request. Mounted by CSRFMiddleware (‘set :inertia_csrf_protection, true` by default). Pair this with `inertia_share { { csrfToken: csrf_token } }` so the React/Vue client picks it up automatically — but note that when `Sinatra::Inertia::CSRFMiddleware` is active, the cookie + header exchange is already handled by the Inertia client; this helper is mainly for hidden-field forms or non-XHR submissions.



100
101
102
# File 'lib/sinatra/inertia/helpers.rb', line 100

def csrf_token
  request.env['sinatra.inertia.csrf_token']
end

#current_inertia_sharedObject


Shared props — runtime accessors (the ‘inertia_share` class DSL is in extension.rb, this is the per-request resolver).



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/sinatra/inertia/helpers.rb', line 107

def current_inertia_shared
  blocks = settings.inertia_share_blocks || []
  merged = {}
  blocks.each do |b|
    v = instance_exec(&b)
    if v.is_a?(Hash)
      merged = deep_merge(merged, v)
    end
  end
  merged
end

#current_inertia_versionObject


Asset version



121
122
123
124
# File 'lib/sinatra/inertia/helpers.rb', line 121

def current_inertia_version
  v = settings.respond_to?(:inertia_version) ? settings.inertia_version : nil
  v.respond_to?(:call) ? v.call.to_s : v.to_s
end

#inertia(component, props: {}, layout: nil) ⇒ Object

Render an Inertia response.

inertia 'Todos/Index', props: { todos: -> { Todo.all } }

Layout selection: the configured layout (default ‘:layout`) is rendered for full HTML responses. The view receives `@page_json` (an HTML-escaped JSON string ready to drop into a `data-page` attribute) and `@page` (the underlying Hash, useful for SSR or custom rendering).



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/sinatra/inertia/helpers.rb', line 20

def inertia(component, props: {}, layout: nil)
  layout = settings.respond_to?(:inertia_layout) ? settings.inertia_layout : :layout if layout.nil?

  version = current_inertia_version
  shared = current_inertia_shared
  encrypt = if !@inertia_encrypt_history_override.nil?
              @inertia_encrypt_history_override == true
            elsif settings.respond_to?(:inertia_encrypt_history)
              settings.inertia_encrypt_history == true
            else
              false
            end
  clear = @inertia_clear_history == true

  # Set the protocol response headers BEFORE we touch any async
  # `to_h` resolution. Under Opal, `Response#to_h` is an `async`
  # function (it `await`s any Proc-returned JS Promise), so the
  # rest of this method runs after a JS-level suspend. Setting
  # `content_type` / `X-Inertia` before the suspend guarantees
  # Sinatra's dispatch sees them when it finalises the response,
  # regardless of how the underlying runtime schedules the
  # awaited continuation.
  if inertia_request?
    content_type 'application/json; charset=utf-8'
    headers 'X-Inertia' => 'true', 'Vary' => 'X-Inertia'
  end

  # Read errors *before* sweeping so the response carries them, then
  # sweep immediately so the next request sees a clean slate. The
  # sweep must happen before any further session writes that the
  # framework might serialise on commit.
  errors_payload = inertia_errors_payload
  sweep_inertia_session!

  response_obj = Sinatra::Inertia::Response.new(
    component: component,
    props: props,
    request: request,
    version: version,
    url: request.fullpath,
    encrypt_history: encrypt,
    clear_history: clear,
    shared: shared,
    errors: errors_payload
  )
  page_hash = response_obj.to_h
  page_json = page_hash.to_json

  return page_json if inertia_request?

  @page = page_hash
  @page_json = ::Rack::Utils.escape_html(page_json)
  erb layout, layout: false
end

#inertia_clear_history!Object



140
141
142
# File 'lib/sinatra/inertia/helpers.rb', line 140

def inertia_clear_history!
  @inertia_clear_history = true
end

#inertia_encrypt_history!(flag = true) ⇒ Object



144
145
146
# File 'lib/sinatra/inertia/helpers.rb', line 144

def inertia_encrypt_history!(flag = true)
  @inertia_encrypt_history_override = flag
end

#inertia_errors(payload = nil) ⇒ Object


Errors / flash session sweep (per Inertia validation pattern). Consumers call ‘inertia_errors(field: ’message’)‘ before redirecting to a form route; the next request renders the form with errors and sweeps them out of the session.



131
132
133
134
135
136
137
138
# File 'lib/sinatra/inertia/helpers.rb', line 131

def inertia_errors(payload = nil)
  if payload.nil?
    (session[:_inertia_errors] || {}).dup
  else
    session[:_inertia_errors] = payload
    payload
  end
end

#inertia_errors_payloadObject



148
149
150
151
152
153
# File 'lib/sinatra/inertia/helpers.rb', line 148

def inertia_errors_payload
  errors = session[:_inertia_errors]
  return nil if errors.nil?
  return nil if errors.respond_to?(:empty?) && errors.empty?
  errors
end

#inertia_request?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/sinatra/inertia/helpers.rb', line 89

def inertia_request?
  request.env['HTTP_X_INERTIA'] == 'true'
end

#render(*args, **kwargs, &block) ⇒ Object

Rails-flavored alias: ‘render inertia: ’Component’, props: …‘ We must preserve Sinatra’s ‘render(engine, data = nil, options = {}, locals = {}, &block)` signature for the non-inertia path, so we forward *args/**kwargs.



78
79
80
81
82
83
84
85
86
87
# File 'lib/sinatra/inertia/helpers.rb', line 78

def render(*args, **kwargs, &block)
  first = args.first
  if args.length == 1 && first.is_a?(Hash) && first.key?(:inertia)
    inertia(first[:inertia], props: first[:props] || {}, layout: first[:layout])
  elsif kwargs.key?(:inertia) && args.empty?
    inertia(kwargs[:inertia], props: kwargs[:props] || {}, layout: kwargs[:layout])
  else
    super(*args, **kwargs, &block)
  end
end

#sweep_inertia_session!Object



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/sinatra/inertia/helpers.rb', line 155

def sweep_inertia_session!
  # Rack::Session::Cookie tracks writes by hash mutation. On some
  # session backends (e.g. the JSON-coder cookie store homura uses
  # under Cloudflare Workers) `delete` is a no-op for the *backing
  # cookie* — the change isn't serialised back. Force a write by
  # assigning nil instead, which the JSON encoder still emits as
  # `null` and makes `inertia_errors_payload` treat the field as
  # absent on the next visit.
  if session.respond_to?(:[]=)
    session[:_inertia_errors] = nil
  end
end