Class: Ruact::Configuration
- Inherits:
-
Object
- Object
- Ruact::Configuration
- Defined in:
- lib/ruact/configuration.rb
Overview
Holds gem-wide configuration. Instantiated once via Ruact.config.
Configure via Ruact.configure { |c| c.attr = value } in an initializer.
Frozen after Ruact.configure returns (Story 7.3) — direct post-boot
mutation (Ruact.config.attr = value outside the block) raises
Ruact::ConfigurationError with the offending attribute, the caller's
file:line, and the suggested fix. Re-calling Ruact.configure after boot
replaces the configuration atomically and emits a [ruact] warning.
Constant Summary collapse
- ATTRIBUTES =
The set of public attributes; new attributes added here automatically inherit the freeze contract via the
define_methodwriter below. %i[ manifest_path strict_serialization suspense_timeout vite_dev_server dev_error_payload_enabled max_upload_bytes query_route_prefix query_parent_controller signed_global_id_default_purpose signed_global_id_default_expires_in shadcn_compatible_versions layout ].freeze
Instance Attribute Summary collapse
-
#dev_error_payload_enabled ⇒ Boolean?
readonly
Story 8.4 — When true, server-action failures respond with a verbose JSON payload (action name, error class, message, split backtrace, contextual suggestion, validation errors).
-
#layout ⇒ Boolean, String
readonly
Which document wrapper a ruact page's HTML response is rendered into.
-
#manifest_path ⇒ String?
readonly
Path to react-client-manifest.json.
-
#max_upload_bytes ⇒ Integer?
readonly
Story 8.5 — upper bound (in bytes) on the
Content-Lengthofmultipart/form-dataandapplication/x-www-form-urlencodedrequests dispatched to aRuact::Servermutation route. -
#query_parent_controller ⇒ String
readonly
Story 9.4 — class NAME of the controller the gem's internal query dispatch controller inherits from (default
"ApplicationController"— the Deviseparent_controllerpattern). -
#query_route_prefix ⇒ String
readonly
Story 9.4 — URL prefix under which the
ruact_queriesrouting macro draws one named GET route per public query method (default"/q"→GET /q/<jsIdentifier>). -
#shadcn_compatible_versions ⇒ Array<Integer>
readonly
Story 10.5 — the shadcn/ui MAJOR versions the
ruact:scaffoldgenerator is regression-tested against. -
#signed_global_id_default_expires_in ⇒ ActiveSupport::Duration?
readonly
Story 13.2 (FR96) — the default
expires_in:Ruact.signed_global_iduses when the call omitsexpires_in:. -
#signed_global_id_default_purpose ⇒ Symbol, ...
readonly
Story 13.2 (FR96) — the default
for:purposeRuact.signed_global_id/Ruact.locate_signeduse when the call omitsfor:. -
#strict_serialization ⇒ Boolean
readonly
When true, objects without explicit ruact_props declaration raise Ruact::SerializationError.
-
#suspense_timeout ⇒ Float
readonly
Seconds before a deferred Suspense chunk times out.
-
#vite_dev_server ⇒ String
readonly
Base URL of the Vite dev server.
Instance Method Summary collapse
-
#initialize(template: nil) ⇒ Configuration
constructor
Build a fresh Configuration.
Constructor Details
#initialize(template: nil) ⇒ Configuration
Build a fresh Configuration. When template is given, dup every public
attribute from it so the draft is mutable — used by Ruact.configure for
atomic-replacement cloning. The dup is required because the template is
always a published (frozen) Configuration with deep-frozen attribute
values, and AC1 requires the DSL inside the configure block to behave
identically regardless of whether this is the first call or a later one
(including idiomatic in-place mutation of inherited values).
dup is safe for every supported attribute type: Strings produce an
unfrozen copy; nil/true/false/Numerics/Symbols dup to themselves (they
are inherently immutable, so the dup is a no-op).
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/ruact/configuration.rb', line 206 def initialize(template: nil) if template ATTRIBUTES.each do |attr| value = template.public_send(attr) # Procs are immutable from the outside. Duping creates a different Proc # instance, breaking identity comparisons across re-configurations. # Procs are inherently re-entrant safe (no mutable internal state # surface) so the dup is unnecessary; the freeze at seal! time is enough. cloned = value.is_a?(Proc) ? value : value.dup instance_variable_set("@#{attr}", cloned) end else @manifest_path = nil @strict_serialization = begin Rails.env.production? rescue StandardError false end @suspense_timeout = 5.0 @vite_dev_server = "http://localhost:5173" @dev_error_payload_enabled = nil @max_upload_bytes = 10 * 1024 * 1024 @query_route_prefix = "/q" @query_parent_controller = "ApplicationController" @signed_global_id_default_purpose = nil @signed_global_id_default_expires_in = nil @shadcn_compatible_versions = [1, 2] @layout = false end end |
Instance Attribute Details
#dev_error_payload_enabled ⇒ Boolean? (readonly)
Returns Story 8.4 — When true, server-action failures
respond with a verbose JSON payload (action name, error class,
message, split backtrace, contextual suggestion, validation errors).
When false, the wire body carries only the four baseline fields
(_ruact_server_action_error, action_name, error_class,
message) so React components can render their own UI without
accidental backtrace leakage. Default nil — the error-rendering
layer resolves nil to Rails.env.development? || Rails.env.test?,
keeping the Configuration trivially constructible in non-Rails specs.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#layout ⇒ Boolean, String (readonly)
A ruact view is rendered in its own pass (it produces the component
tree), so content_for declared inside the view does NOT reach the
layout. Set document metadata from the controller instead.
Returns Which document wrapper a ruact page's HTML
response is rendered into. The Flight response shape
(text/x-component) is never affected — this is only about the
full-document render a browser gets on a normal navigation.
false(default) — render the gem's built-in minimal shell.true— render through the controller's normal Rails layout.- a String — render through that named layout (e.g.
"ruact").
The layout path exists because the document <head> belongs to the
host app: stylesheet_link_tag, favicons, fonts, analytics and any
<head>-writing gem only reach the page when Rails' own layout owns
the document. The built-in shell carries no stylesheet slot, so under
the false default a ruact page renders with no app CSS at all —
which is why rails generate ruact:install writes config.layout = true into the generated initializer and adds <%= ruact_js_assets %>
to your layout in the same run.
This setting is deliberately explicit — there is no auto-detection. ruact used to try to infer whether your layout was ready by inspecting it. Deciding that reliably means answering "does this template call this method?", which cannot be done by pattern-matching a template language: three review rounds each found another shape that fooled it (a mention in a comment, a commented-out call, a trim-mode comment), and each wrong answer governed how every page in the app rendered. One explicit line is worth more than a clever guess here.
A layout is ready when it calls <%= ruact_js_assets %> (which emits
the React root's bootstrap entry tags and the per-render Flight
payload) next to a <div id="root"></div>. If it does not, ruact says
so loudly in development rather than serving a blank page, and
rails ruact:doctor reports it.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#manifest_path ⇒ String? (readonly)
Returns Path to react-client-manifest.json. Defaults to Rails.root.join("public/react-client-manifest.json") when nil.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#max_upload_bytes ⇒ Integer? (readonly)
This is a controller-level "fail fast at the boundary" knob, not
a stream-safety guarantee — Rack's multipart parser will still buffer
bodies up to its own limits before the guard rejects. For very large
uploads route through Active Storage Direct Upload or a presigned S3
URL; see website/docs/api/server-actions.md "File uploads" section.
Returns Story 8.5 — upper bound (in bytes) on the
Content-Length of multipart/form-data and
application/x-www-form-urlencoded requests dispatched to a
Ruact::Server mutation route. When the inbound Content-Length
exceeds this value, the server concern raises
Ruact::UploadTooLargeError BEFORE Rack's multipart parser runs,
producing a 413 with the Story 8.4 structured error body.
Default: 10 * 1024 * 1024 (10 MB). Set to nil to disable the
gem-side guard — typical when a reverse proxy (client_max_body_size)
or host middleware already owns the operational cap. Chunked-transfer
requests (no Content-Length header) bypass the guard regardless of
this setting; the action body is responsible for any belt-and-suspenders
check via params[:file].size / params[:file].byte_size in that case.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#query_parent_controller ⇒ String (readonly)
Returns Story 9.4 — class NAME of the controller the gem's
internal query dispatch controller inherits from (default
"ApplicationController" — the Devise parent_controller pattern).
Kept as a String and constantized lazily at route-draw time, NOT at
configure time: ApplicationController does not exist when the gem
loads. The host's REAL callback chain (authenticate_user!, tenant
scoping, Pundit) runs before any query class is instantiated (FR89).
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#query_route_prefix ⇒ String (readonly)
Returns Story 9.4 — URL prefix under which the ruact_queries
routing macro draws one named GET route per public query method
(default "/q" → GET /q/<jsIdentifier>). Must be a String starting
with / and without a trailing slash (the macro joins prefix and
identifier with /). Changing the prefix is configuration, never code.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#shadcn_compatible_versions ⇒ Array<Integer> (readonly)
Returns Story 10.5 — the shadcn/ui MAJOR versions the
ruact:scaffold generator is regression-tested against. When the
generator detects an installed shadcn major (best-effort, from the
host package.json) that is NOT in this list, it emits a warning
(never a hard stop) that the generated components may import from
outdated @/components/ui/* paths. Must be a non-empty Array of
Integers. Default [1, 2] (the majors tested at gem-release time).
A dev who has manually verified a newer major adds it here to
suppress the warning — the documented "override" path.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#signed_global_id_default_expires_in ⇒ ActiveSupport::Duration? (readonly)
Returns Story 13.2 (FR96) — the default
expires_in: Ruact.signed_global_id uses when the call omits
expires_in:. Must be an ActiveSupport::Duration (e.g. 15.minutes)
— globalid calls #from_now on it. Default nil — when neither the
call nor this config supplies an expiry, the helper raises
Ruact::Error rather than mint a non-expiring token. To deliberately
mint a non-expiring token, pass an explicit expires_in: nil at the
call site (a reviewed per-call choice), never via this default.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#signed_global_id_default_purpose ⇒ Symbol, ... (readonly)
Returns Story 13.2 (FR96) — the default for:
purpose Ruact.signed_global_id / Ruact.locate_signed use when the
call omits for:. A purpose scopes a signed reference to one
use-site so a token minted for editing a post cannot be replayed
against, say, a delete endpoint. Default nil — when neither the
call nor this config supplies a purpose, the helper raises
Ruact::Error rather than sign an unscoped token (the "developer
forgot" path is a loud error, never a silent insecure default).
Prefer a per-call for: when use-sites differ; set this only for an
app-wide default purpose.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#strict_serialization ⇒ Boolean (readonly)
Returns When true, objects without explicit ruact_props declaration raise Ruact::SerializationError. Defaults to false in development, true in production.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#suspense_timeout ⇒ Float (readonly)
Returns Seconds before a deferred Suspense chunk times out. Default: 5.0.
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |
#vite_dev_server ⇒ String (readonly)
Returns Base URL of the Vite dev server. Default: "http://localhost:5173".
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ruact/configuration.rb', line 180 ATTRIBUTES.each do |attr| attr_reader attr define_method("#{attr}=") do |value| if frozen? location = caller_locations(1, 1).first raise Ruact::ConfigurationError, (attr, location) end validate_attribute_value!(attr, value) instance_variable_set("@#{attr}", value) end end |