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



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

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

#clear_history!Object



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

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.



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

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



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

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

  merged
end

#current_inertia_versionObject


Asset version



166
167
168
169
170
171
172
173
174
# File 'lib/sinatra/inertia/helpers.rb', line 166

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



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

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

#encrypt_history!(flag = true) ⇒ Object



206
207
208
# File 'lib/sinatra/inertia/helpers.rb', line 206

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



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
74
75
# File 'lib/sinatra/inertia/helpers.rb', line 21

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



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

def inertia_clear_history!
  @inertia_clear_history = true
end

#inertia_encrypt_history!(flag = true) ⇒ Object



202
203
204
# File 'lib/sinatra/inertia/helpers.rb', line 202

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.



181
182
183
184
185
186
187
188
# File 'lib/sinatra/inertia/helpers.rb', line 181

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

#inertia_errors_payloadObject



210
211
212
213
214
215
# File 'lib/sinatra/inertia/helpers.rb', line 210

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)


111
112
113
# File 'lib/sinatra/inertia/helpers.rb', line 111

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

#lazy(&block) ⇒ Object



142
143
144
# File 'lib/sinatra/inertia/helpers.rb', line 142

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

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



146
147
148
# File 'lib/sinatra/inertia/helpers.rb', line 146

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

#optional(&block) ⇒ Object



138
139
140
# File 'lib/sinatra/inertia/helpers.rb', line 138

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

#page_errors(payload = nil) ⇒ Object



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

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

#page_request?Boolean

Returns:

  • (Boolean)


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

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.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/sinatra/inertia/helpers.rb', line 82

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



217
218
219
220
221
222
223
224
225
226
# File 'lib/sinatra/inertia/helpers.rb', line 217

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.
  session[:_inertia_errors] = nil if session.respond_to?(:[]=)
end