0.0
The project is in a healthy, maintained state
ActionSchema provides a flexible, Rails-friendly approach to rendering and parsing structured data in your controllers.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

Runtime

 Project Readme

Gem Version

ActionSchema

ActionSchema provides a flexible, Rails-friendly approach to rendering and parsing (coming soon) structured data in your controllers.

Status: Experimental 🚧

ActionSchema is currently in early development. Its API is subject to significant changes. If you do choose to use it, please:

  • Lock the version in your Gemfile (e.g. gem "action_schema", "= 0.1.1")
  • Be prepared to update your code as the library evolves
  • Refer to the CHANGELOG for updates
  • Report any issues you encounter to help improve the library

Installation

Add the gem to your Gemfile:

bundle add action_schema -v '= 0.1.1'

Usage

Defining Schemas

Inline Schemas

The simplest way to define a schema is inline in your controller action:

class UsersController < ApplicationController
  def index
    users = User.all
    render json: schema_for(users, -> {
      fields :id, :email
      computed :full_name, ->(user) { "#{user.first_name} #{user.last_name}" }
    })
  end
end

This approach is ideal for quick, one-off schemas. Think of it as a step up from as_json with added flexibility and readability.

Controller Schemas

For reusable schemas, define them in your controller. This keeps your code DRY and allows schemas to be shared across multiple actions:

class UsersController < ApplicationController
  schema :index do
    fields :id, :email
    computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
  end

  def index
    users = User.all
    render json: schema_for(users, :index)
  end
end

Tip: Controller schemas are inherited by subclasses. This makes it easy to define a common schemas in a base controller.

Class Schemas

For global schemas, use schema classes:

class UserSchema < ActionSchema::Base
  fields :id, :email
  computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }
end

You can use these in your controllers like so:

class UsersController < ApplicationController
  def index
    users = User.all
    render json: schema_for(users, UserSchema)
  end
end

Schema Definition DSL

Fields

Regular fields are defined using the field and fields methods:

field :id
field :name
field :created_at
field :updated_at

or

fields :id, :name, :created_at, :updated_at

Fields can be conditionally rendered using the if and unless options:

field :email, if: ->(user) { user.email.present? }
field :phone, unless: ->(user) { user.phone.nil? }

Omitting Fields

You can omit fields using the omit method:

omit :created_at, :updated_at

This can be useful when your schema inherits fields from a superclass, but you don't need all of them in a particular action.

Computed Fields

Computed fields are defined using the computed method:

computed(:full_name) { |user| "#{user.first_name} #{user.last_name}" }

Associations

Association schemas are specified using the association method, and like other schemas, these can be defined inline, at the controller level, or in a schema class.

Inline
schema do
  fields :id, :email
  association :posts do
    fields :id, :title
  end
end
Named Schema
schema :index do
  fields :id, :email
  association :posts, :post
end

schema :post do
  fields :id, :title
end
Class Schema
class UserSchema < ActionSchema::Base
  fields :id, :email
  association :posts, PostSchema
end

class PostSchema < ActionSchema::Base
  fields :id, :title
end

Contexts

Computed fields can access contextual data:

computed(:is_current_user) { |user, context| user == context[:current_user] }

In your controller, you can pass the context to #schema_for:

render json: schema_for(users, context: { current_user: current_user })

Alternatively, you can define the context at the controller level:

class UsersController < ApplicationController
  schema_context({ current_user: :current_user })
end

Controller-level contexts are inherited, and are merged with contexts defined in the subclass. Action-level contexts inherit from the controller-level context, and are similarly merged. More specific contexts take precedence over less specific ones.

Hooks

You can define hooks to run before or after rendering. The before_render hook takes a closure that receives the record or collection to be rendered. The after_render hook takes a closure that receives the rendered data. Both hooks allow you to replace the data with a new value using the transform method:

schema :default do
  fields :id, :email

  before_render do |user|
    user.email.downcase!
  end

  after_render do |data|
    transform({ user: data })
  end
end

Tip: Keep in mind that hooks are run whether the rendered object is a single record or a collection. Be sure to handle both cases as necessary.

Rationale

The main reason that ActionSchema exists is that I find serialization in Rails to be a bit of a pain. While Rails' built-in serialization methods (as_json) are fine for simple cases, they quickly fall apart when you need to handle anything more complex. On the other hand, many serialization libraries feel like overkill, requiring too much boilerplate for tasks that should be straightforward.

I believe that serialization is fundamentally the controller's responsibility. After all, you can't effectively optimize your queries if you don't know what data will be used. Integrating serialization into the controller, close to the query, makes it easier to reason about and ensures a tighter integration between your data and its representation.

That said, I understand that not everyone shares this view. While ActionSchema provides a seamless API for defining schemas entirely within controllers, it also supports defining reusable schema classes outside of controllers for those who prefer a more decoupled approach. My goal is to strike a balance: to make serialization simple when you need it to be, while remaining flexible enough to adapt to a variety of use cases and scenarios.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/jtnegrotto/action_schema.

License

The gem is available as open source under the terms of the MIT License.