| name | rails-wasm-author-constraints |
| description | Use this skill whenever checking if a Rails feature or gem works in WASM, or understanding
what's real vs. conceptual in lessons. Trigger on: 'does X work in WASM', 'can I use this
gem', 'gem compatibility', 'WASM limitation', 'what works', 'supported features', 'PGLite',
'threading', 'Net::HTTP', 'ActionCable', 'background jobs', 'can I teach X', 'bundle
install in WASM', 'conceptual vs real', or any Rails capability question — even without
mentioning WASM. Authoritative compatibility matrix: which features work, which are shimmed,
which are impossible, plus gem tiers, PGLite behavior, boot timing, and conceptual-vs-real
operations. General Rails knowledge is insufficient here. Do NOT use for file organization
(use rails-file-management) or frontmatter (use tutorial-lesson-config).
|
Rails WASM Author Constraints
What works, what doesn't, and how to design lessons around the WASM environment.
Quick Reference: What Can I Teach?
| Rails Feature | Works? | Notes |
|---|
| ActiveRecord (CRUD, queries, scopes) | Yes | Full support via PGLite |
| Migrations & schema management | Yes | Standard rails db:migrate |
| Controllers & routing | Yes | Full support |
| Views (ERB templates) | Yes | Full support |
| Form helpers & validations | Yes | Full support |
| Model associations | Yes | has_many, belongs_to, etc. |
| Scaffolding & generators | Yes | rails generate scaffold, etc. |
| Asset pipeline (Propshaft) | Yes | Importmaps, Stimulus, Turbo |
| Rails console | Yes | Interactive IRB via custom bridge |
| Authentication (basic) | Yes | has_secure_password, session-based |
| ActionMailer (define mailers) | Partial | Can define/configure but delivery is a no-op |
| Active Storage (upload) | Partial | Upload works but image processing is a no-op |
| Background jobs | No | Single-threaded; Solid Queue won't process |
| ActionCable / WebSockets | No | No IO.select, no real socket support |
| External HTTP requests | No | No outbound networking from Ruby — sockets are unimplemented at the WASI level |
| Threads / parallel processing | No | Thread.new uses fibers (cooperative, single-threaded) |
| System commands from Ruby | No | system(), backticks, Open3 are non-functional |
Hard Limitations
These are impossible to work around in the WASM environment. Do not write lessons that depend on them:
No Outbound Networking
Socket operations (TCPSocket, UDPSocket, and all socket classes) fail because the underlying WASI syscalls are unimplemented. Net::HTTP and open-uri will raise errors when attempting connections. You cannot:
- Call external APIs from Ruby
- Download files from the internet
- Connect to external databases or services
Workaround for lessons: If you want to teach API consumption, focus on the controller/model patterns and mock the responses. Show the code structure without executing real HTTP calls.
No Process Spawning
system(), backticks, exec, fork, and Open3 are non-functional. The rails new generator works because it's been patched, but arbitrary shell commands from tutorial code will not work.
No Threading
Thread.new is shimmed to use Fiber.new (cooperative, single-threaded). Code that relies on parallel execution behaves differently. Background job processing and concurrent operations don't work as expected.
No IO.select
The poll_oneoff WASI syscall is unimplemented. This breaks gems like nio4r, Puma, and ActionCable's EventMachine adapter.
No chmod/fchmod
POSIX permission calls are stubbed. Avoid FileUtils.chmod in tutorial code. The rails new generator is pre-patched to handle this.
Gem Compatibility
Adding Gems
Authors can add gems to their tutorial by editing ruby-wasm/Gemfile and running bin/build-wasm to rebuild the WASM binary. This bakes all gems into the binary at build time.
Compatibility Tiers
| Tier | Description | Examples |
|---|
| Works | Pure Ruby gems, no native extensions | devise, friendly_id, pagy, pundit, draper, kaminari |
| Shimmed | Has native extensions but already patched | nokogiri (stub), io-console (stub), bcrypt |
| Needs testing | May work if extension compiles for WASM | Test with bin/build-wasm |
| Won't work | Requires unsupported syscalls or networking | pg (native), mysql2, redis, sidekiq, puma |
Pre-Shimmed Gems
These gems are already handled by the WASM runtime:
| Gem | Behavior |
|---|
nokogiri | 165-line minimal stub; CSS selectors return []; sufficient for sanitization but not real HTML parsing |
io-console | Stubbed; winsize returns [80, 24]; raw yields without change |
nio4r | .so stripped; loads as empty shim |
date, psych, bigdecimal | .so stripped; Ruby falls back to pure-Ruby stdlib |
sqlite3 (native) | Replaced by PGLite adapter |
Gem Build Workflow
vim ruby-wasm/Gemfile
bin/build-wasm
npm run dev
Database: PGLite
The database is PGLite — an in-browser PostgreSQL implementation compiled to WASM.
What Works
- Standard ActiveRecord operations:
create, find, where, update, destroy
- Migrations:
rails db:migrate, rails db:rollback
- Seeds:
rails db:seed
- PostgreSQL-compatible SQL syntax
- Multiple databases (development, test)
- Associations, joins, aggregations
- Indexes and constraints
What to Know
| Behavior | Detail |
|---|
| Data does not survive page reloads | WebContainer filesystem is in-memory; refreshing the browser resets everything |
| Database adapter | pglite (auto-configured by wasmify-rails; authors don't need to set this up) |
| Setup command | node scripts/rails.js db:prepare in prepareCommands |
| Location | pgdata/<dbname>/ in WebContainer filesystem |
| Performance | Slower than native PostgreSQL; acceptable for tutorial-sized datasets |
Lesson Design Implications
- Always include
['node scripts/rails.js db:prepare', 'Prepare development database'] in prepareCommands for lessons that use the database
- Provide seeds in templates so users start with data
- Don't rely on data from a previous lesson persisting — each lesson should set up its own state via migrations + seeds
Boot Timing
The WASM runtime takes time to load. Set expectations for tutorial users:
| Phase | Typical Duration | What Happens |
|---|
npm install | 10-30s | Downloads ~80MB WASM binary + dependencies |
| WASM compile | 2-5s | Browser compiles the binary |
| Rails bootstrap | 2-5s | Loads Rails framework from embedded VFS |
| Command execution | Varies | User's command runs |
Total first-load time: 15-40 seconds depending on network and browser.
Author Tips for Boot Experience
- Include a note in early lessons: "The Ruby runtime takes a moment to load — this is normal!"
- Use
prepareCommands with descriptive labels so users see progress
- The
['output', 'Setup Logs'] terminal panel shows boot details
- Subsequent lesson navigation is faster if the WebContainer is already booted
Auto-Authentication
The runtime includes an auto-login patch: if tmp/authenticated-user.txt exists in the Rails app root, the first HTTP request auto-authenticates the user found by User.find_by(email_address: <file contents>). The file should contain a single email address (e.g., admin@example.com).
To use: Place workspace/tmp/authenticated-user.txt in _files/ or a template, containing the email of a seeded user. The first request calls start_new_session_for(user), creating a session cookie for all subsequent requests. Runs once per VM lifetime (the $__pre_authenticated global flag prevents repeat attempts).
This requires the Rails 8 Authentication concern and a User model with an email_address column.
Integration Testing
Rails integration tests work in the WASM runtime with important limitations.
What Works
ActionDispatch::IntegrationTest with get, post, patch, delete
assert_response, assert_redirected_to
assert_difference, assert_no_difference
- Fixtures (
fixtures :all) for test data
- Cookie-based session management for authentication
What Does NOT Work
Nokogiri-dependent assertions are unavailable. The Nokogiri gem is a minimal stub in WASM — CSS selectors return [] and HTML parsing is non-functional. This means these Rails test helpers will not work:
assert_select — relies on Nokogiri CSS selectors
css_select — same
assert_dom — same
- Any assertion that parses response HTML into a DOM
Best Practices
Use response.body string matching instead of DOM assertions:
assert_select "h1", text: "Tickets"
assert_select ".alert--error", text: /blank/
assert_select "form"
assert_includes response.body, "Tickets"
assert_includes response.body, "blank"
assert_includes response.body, "<form"
Define an assert_text helper in test_helper.rb:
class ActionDispatch::IntegrationTest
def assert_text(text)
assert_includes response.body, text
end
end
Use cookie-based sign_in without making HTTP requests:
class ActionDispatch::IntegrationTest
private
def sign_in(user)
Current.session = user.sessions.create!
ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar|
cookie_jar.signed[:session_id] = Current.session.id
cookies[:session_id] = cookie_jar[:session_id]
end
end
end
Disable test parallelization — we don't have threads or processes:
module ActiveSupport
class TestCase
fixtures :all
end
end
Filesystem Boundaries
Ruby code can only access /workspace (the WASI preopen). Attempting to access paths outside this boundary raises Errno::ENOENT. Tutorial code should never navigate above /workspace with Dir.chdir("..") or absolute paths outside the preopen.
Conceptual vs. Real Operations
Some tutorial operations are conceptual — they teach the correct pattern but the actual execution requires something different in the WASM environment:
| Operation in lesson content | Reality | What the author must do |
|---|
"Add gem 'devise' to your Gemfile" | Gems are baked into the WASM binary at build time | The gem must already be in ruby-wasm/Gemfile and rebuilt with bin/build-wasm before the tutorial is published |
"Run bundle install" | bundle install is a no-op in WASM — all gems come from the binary | Include it for pedagogical completeness; it will appear to succeed |
"Run rails server" | The Rails server runs through node scripts/rails.js server | Use node scripts/rails.js server in frontmatter mainCommand; in lesson content, show rails server since that's what the terminal wrapper understands |
"Edit database.yml" | Database adapter is PGLite, auto-configured by wasmify-rails | Pre-configure in the template; showing a database.yml edit is fine for teaching but won't change the runtime behavior |