Class: Ruact::Railtie
- Inherits:
-
Rails::Railtie
- Object
- Rails::Railtie
- Ruact::Railtie
- Defined in:
- lib/ruact/railtie.rb
Class Method Summary collapse
-
.check_manifest!(manifest_path) ⇒ Object
Checks whether the manifest exists and either warns (dev) or raises (prod).
-
.check_vite! ⇒ Object
Checks whether the Vite dev server is accessible and warns if not (AC#4).
-
.controller_paths_for(engine) ⇒ Array<String>
Existing controller directory paths.
-
.detect_streaming_mode! ⇒ Object
Detects the web server at boot, stores the streaming mode, and logs the result (AC#1–3).
-
.force_load_dir(dir, glob: "**/*_controller.rb") ⇒ Integer
Number of files loaded.
-
.force_load_server_function_hosts! ⇒ Integer
(also: force_load_controllers!)
Story 8.1 review-batch 3 (2026-05-14) — force-loads every controller file under ‘Rails.application.config.paths` so the `ruact_action` registrations populate the registry on a clean boot.
-
.safe_engine_instance(engine_class) ⇒ Object
Some engine subclasses are abstract (no ‘.instance` defined yet) or fail at `.instance` if their config block raises.
-
.server_actions_paths_for(engine) ⇒ Object
Story 8.3 — locates ‘app/server_actions/` for the host application or a mounted engine.
-
.write_server_functions_snapshot! ⇒ Boolean
Writes the server-functions JSON snapshot to tmp/cache/ruact/ on every config.to_prepare.
Class Method Details
.check_manifest!(manifest_path) ⇒ Object
Checks whether the manifest exists and either warns (dev) or raises (prod). Extracted as a class method for direct testability without a full Rails app.
305 306 307 308 309 310 311 312 313 314 |
# File 'lib/ruact/railtie.rb', line 305 def self.check_manifest!(manifest_path) if Rails.env.production? raise ManifestError, "react-client-manifest.json not found — run vite build before deploying" else Rails.logger.warn "[ruact] react-client-manifest.json not found at " \ "#{manifest_path} — RSC rendering will be unavailable. " \ "Run 'npm run build' or start the Vite dev server." end end |
.check_vite! ⇒ Object
Checks whether the Vite dev server is accessible and warns if not (AC#4). Extracted as a class method for direct testability without a full Rails app.
161 162 163 164 165 166 167 |
# File 'lib/ruact/railtie.rb', line 161 def self.check_vite! require "socket" TCPSocket.new("localhost", 5173).close rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH Rails.logger.warn "[ruact] Vite dev server not detected at localhost:5173 " \ "— run npm run dev for HMR" end |
.controller_paths_for(engine) ⇒ Array<String>
Returns existing controller directory paths.
238 239 240 241 242 243 244 245 |
# File 'lib/ruact/railtie.rb', line 238 def self.controller_paths_for(engine) return [] unless engine.respond_to?(:config) && engine.config.respond_to?(:paths) paths = engine.config.paths["app/controllers"] return [] unless paths.respond_to?(:existent) paths.existent end |
.detect_streaming_mode! ⇒ Object
Detects the web server at boot, stores the streaming mode, and logs the result (AC#1–3). Detection is constant-based (zero I/O): Puma → enabled, Unicorn/Passenger → buffered, unknown → buffered (safe mode).
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/ruact/railtie.rb', line 140 def self.detect_streaming_mode! mode, label = if defined?(::Puma::Server) [:enabled, "Puma detected"] elsif defined?(::Falcon::Server) [:enabled, "Falcon detected"] elsif defined?(::Unicorn) [:buffered, "Unicorn detected"] elsif defined?(::PhusionPassenger) [:buffered, "Passenger detected"] else [:buffered, "server unknown — defaulting to safe mode"] end Ruact.streaming_mode = mode verb = mode == :enabled ? "enabled" : "buffered" Rails.logger.info "[ruact] streaming: #{verb} (#{label})" mode end |
.force_load_dir(dir, glob: "**/*_controller.rb") ⇒ Integer
Returns number of files loaded.
251 252 253 254 255 |
# File 'lib/ruact/railtie.rb', line 251 def self.force_load_dir(dir, glob: "**/*_controller.rb") Dir.glob("#{dir}/#{glob}").each do |file| require_dependency(file) end.length end |
.force_load_server_function_hosts! ⇒ Integer Also known as: force_load_controllers!
Story 8.1 review-batch 3 (2026-05-14) — force-loads every controller file under ‘Rails.application.config.paths` so the `ruact_action` registrations populate the registry on a clean boot.
Without this, Rails’ dev-mode lazy autoload only loads a controller when it’s first referenced (typically the first request that routes to it). That means the codegen snapshot in ‘to_prepare` would miss any controller not yet touched.
Implementation: glob the ‘app/controllers` directories listed in the Rails paths configuration and `require_dependency` each `*_controller.rb` file. `require_dependency` works in both Zeitwerk (Rails 7+) and the classic autoloader. On Zeitwerk it is implemented as `Rails.autoloaders.main.load_file(path)` under the hood.
Errors are surfaced as ‘Ruact::Error` with a controller hint so the developer sees a meaningful boot failure instead of a silent skip.
Re-run-6 (2026-05-15) — also walks every mounted ‘Rails::Engine` (and `Rails::Railtie` with `paths`) so engine- owned controllers that declare `ruact_action` populate the registry at boot. Without this an engine’s ‘ruact_action` declarations would only register on first request that touches the engine controller —codegen + endpoint dispatch would lag behind boot.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/ruact/railtie.rb', line 195 def self.force_load_server_function_hosts! loaded = 0 controller_paths_for(Rails.application).each do |dir| loaded += force_load_dir(dir, glob: "**/*_controller.rb") end server_actions_paths_for(Rails.application).each do |dir| loaded += force_load_dir(dir, glob: "**/*.rb") end if defined?(Rails::Engine) Rails::Engine.subclasses.each do |engine_class| # Skip the host app itself; `Rails.application.class` is a # `Rails::Engine` subclass and was already covered above. next if engine_class == Rails.application.class engine = safe_engine_instance(engine_class) next unless engine controller_paths_for(engine).each { |dir| loaded += force_load_dir(dir, glob: "**/*_controller.rb") } server_actions_paths_for(engine).each { |dir| loaded += force_load_dir(dir, glob: "**/*.rb") } end end loaded rescue LoadError, NameError => e raise Ruact::Error, "ruact: failed to force-load a server-function host while populating " \ "Ruact.action_registry: #{e.class}: #{e.}. The gem " \ "force-loads `app/controllers/**/*_controller.rb` and " \ "`app/server_actions/**/*.rb` at `config.to_prepare` so " \ "registries are complete on first boot." end |
.safe_engine_instance(engine_class) ⇒ Object
Some engine subclasses are abstract (no ‘.instance` defined yet) or fail at `.instance` if their config block raises. Swallow those quietly —the gem’s responsibility is to load whatever it can; engines whose ‘.instance` blows up will surface on first request anyway.
282 283 284 285 286 |
# File 'lib/ruact/railtie.rb', line 282 def self.safe_engine_instance(engine_class) engine_class.instance rescue StandardError nil end |
.server_actions_paths_for(engine) ⇒ Object
Story 8.3 — locates ‘app/server_actions/` for the host application or a mounted engine. Uses the Rails `paths` enumerator (populated by the Railtie initializer) when present, falling back to a direct `engine.root.join` lookup for engines that haven’t registered the path. Returns an empty array when the directory doesn’t exist —silent no-op for hosts that don’t use standalone actions.
263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/ruact/railtie.rb', line 263 def self.server_actions_paths_for(engine) if engine.respond_to?(:config) && engine.config.respond_to?(:paths) paths = engine.config.paths["app/server_actions"] if paths.respond_to?(:existent) existent = paths.existent return existent unless existent.empty? end end return [] unless engine.respond_to?(:root) && engine.root candidate = engine.root.join("app/server_actions") candidate.directory? ? [candidate.to_s] : [] end |
.write_server_functions_snapshot! ⇒ Boolean
Writes the server-functions JSON snapshot to tmp/cache/ruact/ on every config.to_prepare. The write is short-circuited when the registry payload is unchanged (Story 8.0a — pitfall #1: dev mode fires to_prepare per request; a naive rewrite would burn IOPS and confuse the Vite plugin’s chokidar watcher).
295 296 297 298 299 300 301 |
# File 'lib/ruact/railtie.rb', line 295 def self.write_server_functions_snapshot! Ruact::ServerFunctions::Snapshot.generate!( action_registry: Ruact.action_registry, query_registry: Ruact.query_registry, path: Rails.root.join("tmp/cache/ruact/server-functions.json") ) end |