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

#always(value = nil, &block) ⇒ Object



118
119
120
# File 'lib/sinatra/inertia/helpers.rb', line 118

def always(value = nil, &block)
  Sinatra::Inertia.always(value, &block)
end

#clear_history!Object



186
187
188
# File 'lib/sinatra/inertia/helpers.rb', line 186

def clear_history!
  inertia_clear_history!
end

#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.



114
115
116
# File 'lib/sinatra/inertia/helpers.rb', line 114

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).



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/sinatra/inertia/helpers.rb', line 141

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



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

def current_inertia_version
  v = if settings.respond_to?(:page_version)
        settings.page_version
      elsif settings.respond_to?(:inertia_version)
        settings.inertia_version
      end
  v.respond_to?(:call) ? v.call.to_s : v.to_s
end

#defer(group: 'default', &block) ⇒ Object



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

def defer(group: 'default', &block)
  Sinatra::Inertia.defer(group: group, &block)
end

#encrypt_history!(flag = true) ⇒ Object



194
195
196
# File 'lib/sinatra/inertia/helpers.rb', line 194

def encrypt_history!(flag = true)
  inertia_encrypt_history!(flag)
end

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

Render an Inertia response.

render 'Todos/Index', 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 = current_page_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



182
183
184
# File 'lib/sinatra/inertia/helpers.rb', line 182

def inertia_clear_history!
  @inertia_clear_history = true
end

#inertia_encrypt_history!(flag = true) ⇒ Object



190
191
192
# File 'lib/sinatra/inertia/helpers.rb', line 190

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.



169
170
171
172
173
174
175
176
# File 'lib/sinatra/inertia/helpers.rb', line 169

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

#inertia_errors_payloadObject



198
199
200
201
202
203
# File 'lib/sinatra/inertia/helpers.rb', line 198

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)


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

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

#lazy(&block) ⇒ Object



130
131
132
# File 'lib/sinatra/inertia/helpers.rb', line 130

def lazy(&block)
  Sinatra::Inertia.lazy(&block)
end

#merge(value = nil, &block) ⇒ Object



134
135
136
# File 'lib/sinatra/inertia/helpers.rb', line 134

def merge(value = nil, &block)
  Sinatra::Inertia.merge(value, &block)
end

#optional(&block) ⇒ Object



126
127
128
# File 'lib/sinatra/inertia/helpers.rb', line 126

def optional(&block)
  Sinatra::Inertia.optional(&block)
end

#page_errors(payload = nil) ⇒ Object



178
179
180
# File 'lib/sinatra/inertia/helpers.rb', line 178

def page_errors(payload = nil)
  inertia_errors(payload)
end

#page_request?Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/sinatra/inertia/helpers.rb', line 103

def page_request?
  inertia_request?
end

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

Natural page-rendering API: ‘render ’Component’, props_hash`. ‘render inertia: ’Component’, props: …‘ remains available for existing apps, while non-page render calls still delegate to Sinatra. We must preserve Sinatra’s ‘render(engine, data = nil, options = {}, locals = {}, &block)` signature for the non-inertia path, so we forward *args/**kwargs.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/sinatra/inertia/helpers.rb', line 80

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])
  elsif first.is_a?(String) && args.length <= 2 && (args.length == 1 || args[1].is_a?(Hash))
    layout = kwargs.delete(:layout)
    props = {}
    props.merge!(args[1]) if args[1].is_a?(Hash)
    explicit_props = kwargs.delete(:props)
    props.merge!(explicit_props) if explicit_props.is_a?(Hash)
    props.merge!(kwargs)
    inertia(first, props: props, layout: layout)
  else
    super(*args, **kwargs, &block)
  end
end

#sweep_inertia_session!Object



205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/sinatra/inertia/helpers.rb', line 205

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