Skip to content

Ruact::Serializable

Ruact::Serializable is a mixin that lets you declare exactly which attributes of a Ruby object are safe to send to the browser as React props.


When you pass a Ruby object as a prop to a client component, ruact needs to serialize it to JSON. By default it calls as_json, which may expose internal fields, timestamps, or sensitive data you did not intend to send.

Ruact::Serializable gives you an explicit allowlist via ruact_props:

class Post < ApplicationRecord
include Ruact::Serializable
ruact_props :id, :title, :body
# :admin_notes, :internal_score, etc. are never sent to the browser
end

class Post < ApplicationRecord
include Ruact::Serializable
ruact_props :id, :title, :body, :created_at
end

Use in an ERB template:

<PostCard post={@post} />

The browser receives only { id: 1, title: "Hello", body: "...", created_at: "..." } — nothing else.


Ruact::Serializable is not limited to ActiveRecord models. Include it in any Ruby class:

class UserPresenter
include Ruact::Serializable
attr_reader :id, :display_name, :avatar_url, :role
ruact_props :id, :display_name, :avatar_url, :role
def initialize(user)
@id = user.id
@display_name = user.full_name
@avatar_url = user.avatar.url
@role = user.admin? ? "admin" : "member"
end
end
<UserCard user={UserPresenter.new(@user)} />

ruact_props validates loudly — timing depends on the class

Section titled “ruact_props validates loudly — timing depends on the class”

If you declare a prop that does not correspond to a defined method, ruact raises a clear ArgumentError naming the offending name:

class UserPresenter
include Ruact::Serializable
attr_reader :id, :name
ruact_props :id, :name, :nonexistent_method
# => ArgumentError: ruact_props: method `nonexistent_method` is not defined on UserPresenter
end

The loud check always happens; only when it fires depends on the class:

  • Plain Ruby objects (POROs) are checked at class load time — a typo or missing accessor raises the moment the class is loaded, at startup rather than at render time.
  • ActiveRecord models define their attribute reader methods lazily (on first access), so at the moment ruact_props :title runs the reader for a real column does not exist yet. To avoid rejecting a valid model at boot — and to avoid a database connection at class load — ruact defers the check for an AR model to the first serialize. A valid model boots; a bogus prop name still raises the same clean ArgumentError the first time that model is rendered.

Either way the guarantee is the same: a typo’d or missing prop fails loudly, never silently.


Props can be nested. If a declared prop returns another object that includes Ruact::Serializable, it is serialized recursively using its own ruact_props declaration:

class Author < ApplicationRecord
include Ruact::Serializable
ruact_props :id, :name
end
class Post < ApplicationRecord
include Ruact::Serializable
ruact_props :id, :title, :author
belongs_to :author
end
<PostCard post={@post} />
# Sends: { id: 1, title: "Hello", author: { id: 5, name: "Alice" } }

When config.strict_serialization = true, any object passed as a prop that does not include Ruact::Serializable raises Ruact::SerializationError.

This is the recommended production setting. It prevents passing raw ActiveRecord objects (which would call as_json) and ensures all prop surfaces are explicitly declared.

config/initializers/ruact.rb
Ruact.configure do |config|
config.strict_serialization = Rails.env.production?
end

ruact_props as_json
Explicit allowlist ✅ Yes ❌ No — serializes all attributes
Works on POROs and ActiveRecord models ✅ Yes ✅ Yes
Validated loudly for typos/missing accessors ✅ Yes (POROs at load, AR at first serialize) ❌ No
Enforced in strict mode ✅ Yes ❌ Raises in strict mode
Nested serialization ✅ Recursive ✅ Recursive (via as_json chain)

Class method — declares the attributes that are safe to serialize.

ruact_props :id, :name, :email
  • attrs — one or more Symbol names. Each must correspond to a reader method on the class (an attr_reader, a plain method, or an ActiveRecord column).
  • Raises ArgumentError for an undefined name — at class load for a PORO, at first serialize for an ActiveRecord model (whose readers are defined lazily). Either way it fails loudly.
  • Returns the list of declared props.

Instance method — returns a Hash of the declared props and their current values.

post = Post.find(1)
post.ruact_serialize # => { id: 1, title: "Hello", body: "..." }

This is what ruact calls internally when serializing the object. You generally do not need to call it directly.