# Optimism

[![GitHub stars](https://img.shields.io/github/stars/leastbad/optimism?style=social)](https://github.com/leastbad/optimism) [![GitHub forks](https://img.shields.io/github/forks/leastbad/optimism?style=social)](https://github.com/leastbad/optimism) [![Twitter follow](https://img.shields.io/twitter/follow/theleastbad?style=social)](https://twitter.com/theleastbad) [![Discord](https://img.shields.io/discord/681373845323513862)](https://discord.gg/GnweR3)

The missing drop-in solution for realtime remote form validation in Rails.

{% hint style="success" %}
Optimism v0.4.1 now supports Ruby 3.0.0, thanks to [Julian Rubisch](https://twitter.com/julian_rubisch) and Rails 6.1, thanks to [Josh LeBlanc](https://github.com/joshleblanc). 🎉
{% endhint %}

## Why have optimism?

[Optimism](https://github.com/leastbad/optimism) is an MIT-licensed [Ruby on Rails](https://rubyonrails.org/) gem that makes it easy to give your users instant constructive feedback if they enter invalid data into your application. Instead of dumping a list of errors at the top of your interface, Optimism provides specific instructions directly beside or below individual input elements.

You can try a 👉 [live demo](https://optimism-demo.herokuapp.com) 👈 right now.

![](/files/-M0nqHVibCutsEm8oqIJ)

## Is optimism for you?

Are you trying to use remote forms but feeling frustrated by their inflexibility? You need to mainline a dose of Optimism, stat!

If you care about reducing churn and giving your users the best experience possible, Optimism is a great way to achieve your UX goals without having to waste time writing repetitive and brittle validation code. Properly constrained and highly opinionated, you'll be able to keep your validation logic on the server where it belongs without sacrificing the immediate response of a reactive Single Page App. Whether you're working on a complex multi-element form with a traditional Submit button or a dynamic search that delivers results as you type, Optimism chops, grinds, slices and dices your validation concerns away.

Optimism is safe and approved for all diets, religions and political appetites. Many developers find that Optimism is highly addictive and lowers stress when applied regularly.

## How does optimism work?

Rails applications receive requests to update database records based on a list of proposed changes that come from a ~~dog~~ user submitting a form in their browser. If all proposed changes can be made without breaking any business rules, ActiveRecord can update the email address and age of the ~~dog~~ user. Optimism kicks in when `user_params = {email: 7, age: "bark_ruffalo@gmail.com"}`.

When a model validation error prevents an update from succeeding, Optimism builds a list of issues that must be resolved. This list is broadcast to the browser over a websocket connection, and the live document is changed to show the necessary validation hints. No page refreshes are required and the entire process happens faster than you can blink.

![](/files/-M0npokR8Evq9_DofeT2)

## Key features and advantages

* [x] Easy to learn, quick to implement
* [x] Automatically handles multi-level nested forms
* [x] Plays well with existing tools such as [StimulusReflex](https://github.com/hopsoft/stimulus_reflex), [Turbolinks](https://github.com/turbolinks/turbolinks) and even [jQuery](https://jquery.com/)
* [x] Contextual user feedback in a few milliseconds
* [x] Supports form-based and in-line edit scenarios equally well
* [x] Optional support for emitting DOM events
* [x] Highly configurable via an optional initializer file
* [x] CSS framework agnostic with Bootstrap and vanilla samples provided
* [x] Lightweight, coming in at \~100 LOC

## Try it now

The project repository lives on Github at <https://github.com/leastbad/optimism> and this documentation is available at <https://optimism.leastbad.com>

There's a **live demo** that you can try right now at [https://optimism-demo.herokuapp.com](https://optimism-demo.herokuapp.com/)

Even better, the source code for the demo is [available on Github](https://github.com/leastbad/optimism-demo). The project README lists every step required to build the demo application from scratch in about five minutes.

Excited? Great! Let's [setup Optimism](https://optimism.leastbad.com/setup) in your Rails application now. And if you're having any trouble at all, [drop by our Discord server](https://discord.gg/wKzsAYJ) for help.


# Setup

Form validations in five minutes or it's free

## Setup

Optimism has a relatively small number of dependencies, but it does assume that it's being run in a Rails project with ActionCable configured for the environments you intend to operate in. You can easily install Optimism to new and existing Rails projects.

```bash
bundle add optimism
yarn add cable_ready
rake optimism:install
```

The terminal commands above will ensure that Optimism is installed. It creates the client-side websocket channel infrastructure required to process the list of commands from the server. All of this is possible because it's built on the shoulders of the incredible [CableReady](https://cableready.stimulusreflex.com/) gem, which gives developers the ability to tweak many different aspects of the current page from the server.

**Anyhow\... that's it: you're ready to start integrating Optimism into your user interfaces**.

![](/files/-M0pcFZJu2g-mm1siEx6)

## Authentication

{% hint style="info" %}
If you're just experimenting with Optimism or trying to bootstrap a proof-of-concept application on your local workstation, you can actually skip this section until you're planning to deploy.
{% endhint %}

Out of the box, ActionCable doesn't give Optimism the ability to distinguish between multiple concurrent users looking at the same page.

**If you deploy to a host with more than one person accessing your app, you'll find that you're sharing a session and seeing other people's updates.** That isn't what most developers have in mind!

When the time comes, it's easy to configure your application to support authenticating users by their Rails session or current\_user scope. Just check out the Authentication page and choose your own adventure.

{% content-ref url="/pages/-M7p-wVVRANXYS9-sxcI" %}
[Authentication](/authentication)
{% endcontent-ref %}

## Logging

In the default *debug* log level, ActionCable emits particularly verbose log messages. You can optionally discard everything but exceptions by switching to the *warn* log level, as is common in development environments:

{% code title="config/environments/development.rb" %}

```ruby
# :debug, :info, :warn, :error, :fatal, :unknown
config.log_level = :warn
```

{% endcode %}

## Troubleshooting

{% hint style="info" %}
If *something* goes wrong, it's often because of the **spring** gem. You can test this by temporarily setting the `DISABLE_SPRING=1` environment variable and restarting your server.

To remove **spring** forever, here is the process we recommend:

1. `pkill -f spring`
2. `bundle remove spring spring-watcher-listen --install`
3. `bin/spring binstub -remove -all`
   {% endhint %}


# Quick Start

Let's start with the simplest scenario possible: you have a form with multiple input elements, and when the user clicks on the Submit button you want to display any validation error messages beside the elements that have issues. When the user resolves these issues and clicks the Submit button, the form is processed normal and the page navigates to whatever comes next.

## Model

Validations are covered in-depth by the [official documentation](https://guides.rubyonrails.org/active_record_validations.html#validation-helpers). Optimism doesn't require anything special.

{% hint style="info" %}
Optimism is designed for ActiveRecord models that have validations defined, although it should work with any Ruby class that implements [Active Model](https://guides.rubyonrails.org/active_model_basics.html) and has an `errors` accessor.
{% endhint %}

## View

Here's sample form partial for a Post model. It has two attributes - **name** and **body** and was generated with a Rails scaffold command.

{% code title="app/views/posts/\_form.html.erb BEFORE Optimism" %}

```rust
<%= form_with(model: post, local: true) do |form| %>
  <% if post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
        <% post.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

{% endcode %}

And here is that same form partial, configured to work with Optimism:

{% code title="app/views/posts/\_form.html.erb AFTER Optimism" %}

```rust
<%= form_with(model: post) do |form| %>
  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
    <%= form.error_for :name %>
  </div>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
    <%= form.error_for :body %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

{% endcode %}

Eagle-eyed readers will see that setting up a bare-bones Optimism integration requires removing two things and adding one thing to each attribute:

1. Remove `local: true` from the `form_with` on the first line
2. Remove the error messages block from lines 2-12 entirely
3. Add an `error_for` helper for each attribute

The `error_for` helper creates an empty `span` tag with an id such as *posts\_body\_error*, and this is where the error messages for the body attribute will appear.

{% hint style="success" %}
Even though `form_with` is remote-by-default, many developers were confused and frustrated by the lack of opinionated validation handling out of the box for remote forms. Since scaffolds are for new users to get comfortable, remote forms are disabled. This is the primary reason that Optimism was created: we want our tasty remote forms without any heartburn.
{% endhint %}

## Controller

The last step is to slightly modify the **create** and **update** actions in our PostsController. The other actions have been removed for brevity:

{% code title="app/controllers/posts\_controller.rb" %}

```rust
  def create
    @post = Post.new(post_params)
    respond_to do |format|
      if @post.save
        format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @post }
      else
        format.html { broadcast_errors @post, post_params }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { broadcast_errors @post, post_params }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end
```

{% endcode %}

The only meaningful change required (as seen on lines 8 and 20 in this example) is to replace `render :new` and `render :edit` with a call to `broadcast_errors` which has two mandatory parameters: the model instance and the list of attributes to validate. Usually this is the whitelisted params hash, but you can pass a subset as small as one attribute to be validated.

That's all there is to it. You now have live - if *unstyled* - form validations being delivered over websockets.

![](/files/-M0sxGmvNuaC3lo4Cs9r)


# Typical Usage

Now that you have seen what Optimism can do, let's flex our muscles a bit and see how far this goes.

{% tabs %}
{% tab title="Vanilla Rails Scaffold" %}
{% code title="app/views/posts/\_form.html.erb" %}

```rust
<%= form_with(model: post, id: "posts_form") do |form| %>
  <%= form.container_for :name, class: "field" do %>
    <%= form.label :name %>
    <%= form.text_field :name %>
    <%= form.error_for :name, class: "danger hide" %>
  <% end %>

  <%= form.container_for :body, class: "field" do %>
    <%= form.label :body %>
    <%= form.text_area :body %>
    <%= form.error_for :body, class: "danger hide" %>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

{% endcode %}
{% endtab %}

{% tab title="Bootstrap" %}
{% code title="app/views/posts/\_form.html.erb" %}

```rust
<%= form_with(model: post, id: "posts_form") do |form| %>
  <%= form.container_for :name, class: "input-group" do %>
    <%= form.label :name %>
    <%= form.text_field :name, class: "form-control" %>
    <%= form.error_for :name, class: "small align-bottom text-danger d-none" %>
  <% end %>

  <%= form.container_for :body, class: "input-group" do %>
    <%= form.label :body %>
    <%= form.text_area :body, class: "form-control" %>
    <%= form.error_for :body, class: "small align-bottom text-danger d-none" %>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

{% endcode %}
{% endtab %}
{% endtabs %}

Here we introduce the `container_for` helper, which wraps a form element in a `div` that has an id similar to the `error_for` helper, except you're looking at *posts\_body\_container*. More importantly, the container receives a CSS class called *error* that can be used to change the visual characteristics of the error message as well as the input element itself. **It is this interplay between cascading style and the order in which the styles are declared that makes this approach work.** Consider the following CSS:

{% tabs %}
{% tab title="Vanilla Rails Scaffold" %}

```css
.danger {
  color: red;
}

.hide {
  display: none;
}

.field.error > .hide {
  display: block;
}

.field.error > input,
.field.error > textarea {
  background-color: rgba(255, 239, 213, 0.7);
}
```

{% endtab %}

{% tab title="Bootstrap" %}

```css
.input-group.error > .d-none {
  display: inline !important;
}

.input-group.error > input,
.input-group.error > textarea {
  background-color: rgba(255, 239, 213, 0.7);
}
```

{% endtab %}
{% endtabs %}

When there is a validation error on an input element, the error message is injected into the span and an *error* CSS class is added to the container for that element. This results in a cascade where **the class responsible for hiding the error message is now flipped to display it instead**. In this example, we also show how we can change the visual characteristics of the input elements themselves, in this case making the background color a sickly and distressing translucent peach. It really helps sell the urgency.

If you assign an id to the form itself that matches the expected format - in this case, `posts_form` - Optimism will also place an *invalid* class on the form if there are *any* errors at all. This gives the developer the flexibility to demonstrate an error state at the form level by tweaking how the form is displayed.

{% hint style="info" %}
Unfortunately, we can't automatically generate the id for the form during its own declaration. Luckily, the format is pretty easy: **resources\_form**.
{% endhint %}

## Gotchas

Optimism is a fundamentally simple library, which means that inevitably, there's going to be scenarios that require some extra consideration.

#### Radio buttons and checkboxes

Thanks to a quirk of the way certain OG form input elements work, it's possible for an unchecked element to not be included in a form's parameters. For example, if you have a set of radio buttons with no default selection, your `params` hash will not include data for it.

Make sure to use the `checkbox` helpers that Rails provides, which implement a clever hack to make sure that unchecked checkboxes produce a `false` value in your `params`.

![](/files/-M0xpJIq0iX9bcU-JDKP)


# Authentication

Practice safe optimism

If you're just trying to bootstrap a proof-of-concept application on your local workstation, you don't technically have to worry about giving ActionCable the ability to distinguish between multiple concurrent users. However, **the moment you deploy to a host with more than one person accessing your app, you'll find that you're sharing a session and seeing other people's updates**. That isn't what most developers have in mind.

## Encrypted Session Cookies

You can use your default Rails encrypted cookie-based sessions to isolate your users into their own sessions. This works great even if your application doesn't have a login system.

{% code title="app/controllers/application\_controller.rb" %}

```ruby
class ApplicationController < ActionController::Base
  before_action :set_action_cable_identifier

  private

  def set_action_cable_identifier
    cookies.encrypted[:session_id] = session.id.to_s
  end
end
```

{% endcode %}

{% code title="app/channels/application\_cable/connection.rb" %}

```ruby
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :session_id

    def connect
      self.session_id = cookies.encrypted[:session_id]
    end
  end
end
```

{% endcode %}

We need to instruct ActionCable to stream updates on a per-session basis:

{% code title="app/channels/optimism\_channel.rb" %}

```ruby
class OptimismChannel < ApplicationCable::Channel
  def subscribed
    stream_for session_id
  end
end
```

{% endcode %}

Finally, we need to give Optimism the ability to broadcast updates to the correct channel subscription. We will override Optimism's default "OptimismChannel" with a lambda that returns the subscription identifier. You might already have other values in your initializer, or it might not yet exist at all:

{% code title="config/initializers/optimism.rb" %}

```ruby
Optimism.configure do |config|
  config.channel_proc = ->(context) { OptimismChannel.broadcasting_for(context.session.id) }
end
```

{% endcode %}

## User-based Authentication

Many Rails apps use the current\_user convention or more recently, the [Current](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html) object to provide a global user context. This gives access to the user scope from *almost* all parts of your application.

{% code title="app/controllers/application\_controller.rb  " %}

```ruby
class ApplicationController < ActionController::Base
  before_action :set_action_cable_identifier

  private

  def set_action_cable_identifier
    cookies.encrypted[:user_id] = current_user&.id
  end
end
```

{% endcode %}

{% code title="app/channels/application\_cable/connection.rb " %}

```ruby
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      user_id = cookies.encrypted[:user_id]
      return reject_unauthorized_connection if user_id.nil?
      user = User.find_by(id: user_id)
      return reject_unauthorized_connection if user.nil?
      self.current_user = user
    end
  end
end
```

{% endcode %}

We need to instruct ActionCable to stream updates on a per-session basis:

{% code title="app/channels/optimism\_channel.rb" %}

```ruby
class OptimismChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end
```

{% endcode %}

Finally, we need to give Optimism the ability to broadcast updates to the correct channel subscription. We will override Optimism's default "OptimismChannel" with a lambda that returns the subscription identifier. You might already have other values in your initializer, or it might not yet exist at all:

{% code title="config/initializers/optimism.rb" %}

```ruby
Optimism.configure do |config|
  config.channel_proc = ->(context) { OptimismChannel.broadcasting_for(context.current_user) }
end
```

{% endcode %}

## Devise-based Authentication

If you're using the versatile [Devise](https://github.com/plataformatec/devise) authentication library, your configuration is *identical* to the User-Based Authentication above, **except** for how ActionCable looks up a user:

{% code title="app/channels/application\_cable/connection.rb" %}

```ruby
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    protected

    def find_verified_user
      if current_user = env["warden"].user
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end
```

{% endcode %}


# Reference

## ActionController Mixins

### **broadcast\_errors**(model, attributes)

**model**: an instance of an Active Record model, or a class inheriting from Active Model\
**attributes**: one or many attributes in the form of a ActionController::Parameters, Hash, HashWithIndifferentAccess, Symbol, String or Array (of Strings or Symbols)

Call this method in a resource controller's `create` or `update` actions when a model validation fails. A list of instructions for the client browser will be prepared and dispatched via a persistent websocket connection.

The two most common use cases are form-based (Parameters) or in-line editing of a single attribute (Symbol or String).

#### Example

```ruby
if @post.update(post_params)
  # Eat. Pray. Love.
else
  broadcast_errors @post, post_params
end
```

## Form Builder Helpers

### container\_for(attribute, \*\*options, \&block)

**attribute**: Symbol identifying the the model attribute to use\
**options**: an implicit Hash allowing you to pass in class names, data attributes and other HTML attributes\
**block**: all ERB content passed to this helper inside the do..end will be rendered as content

Call this helper to create a `div` that will wrap your input element along with any labels and error messages. It will have an id attribute that will allow Optimism to route any validation errors to the correct place. When a validation failure occurs, this `div` will have an *error* class added to it, allowing a style cascade to change the visual appearance of the input element and the error message.

#### Example

```rust
  <%= form.container_for :name, class: "field" do %>
    <%= form.text_field :name %>
  <% end %>
```

### container\_id\_for(attribute)

**attribute**: Symbol identifying the the model attribute to use

Returns the id required for a container to wrap your input elements and receive CSS updates. Use it if you are forced to create your own container markup from scratch; generally it is easiest to make use of **container\_for** if possible.

#### Example

```rust
<blockquote id="<%= form.container_id_for :name %>"></blockquote>
```

### error\_for(attribute, \*\*options)

**attribute**: Symbol identifying the the model attribute to use\
**options**: an implicit Hash allowing you to pass in class names, data attributes and other HTML attributes

Call this helper to create a `span` that you place adjacent to your your input elements. It will have an id attribute that will allow Optimism to route any validation errors to the correct place. When a validation failure occurs, this `span` will have the error message injected into it. It is typically hidden unless there is a message present.

#### Example

```rust
<%= form.error_for :name, class: "d-none text-danger small form-group" %>
```

### error\_id\_for(attribute)

**attribute**: Symbol identifying the the model attribute to use

Returns the id required for a container to receive validation error text. Use it if you are forced to create your own error message markup from scratch; generally it is easiest to make use of **error\_for** if possible.

```rust
<blockquote id="<%= form.error_id_for :name %>"></blockquote>
```

![](/files/-M13TFRwvV96j-Dl7FFh)

## Initializer

Optimism is configurable via an optional initializer file. As with all initializers, changes only take effect after your Rails server has been restarted. Here is a sample initializer file that contains all of the default values for the configuration of the library. All changes apply globally to all instances of Optimism.

{% code title="config/initializers/optimism.rb" %}

```ruby
Optimism.configure do |config|
  config.channel_proc = ->(context) { "OptimismChannel" }
  config.form_class = "invalid"
  config.error_class = "error"
  config.disable_submit = false
  config.suffix = ""
  config.emit_events = false
  config.add_css = true
  config.inject_inline = true
  config.container_selector = "#RESOURCE_ATTRIBUTE_container"
  config.error_selector = "#RESOURCE_ATTRIBUTE_error"
  config.form_selector = "#RESOURCE_form"
  config.submit_selector = "#RESOURCE_submit"
end
```

{% endcode %}

**channel**: The ActionCable channel created by the `rake optimism:install` setup task. Good enough to get you up and running in development, you will need to pull your desired identifier from the context Optimism is running in and let the `broadcasting_for` method on `OptimismChannel` call the shots. Find out more on the [authentication](https://optimism.leastbad.com/authentication) page.

**form\_class**: The CSS class that will be applied to the form if the id has been properly set eg. `posts_form` (following the simple pattern **resources\_form**). If form\_class is set to false or nil, no CSS class will be applied.

**error\_class**: The CSS class the will be applied to a container when a validation fails. Use this class to cascade to the input elements and change their appearance.

**disable\_submit**: If set to true and your Submit button is named properly eg. `posts_submit` (following the simple pattern **resources\_submit**), your Submit button will be disabled if there are validation errors. It will also be re-enabled if the validation errors are corrected. Only use this if you are working with in-line validations or else your users will lose the ability to Submit your form more than once.

**suffix**: Likely the most important setting of all, this string will be appended to all validation error messages. While most validation errors on the web today do not have a trailing period, this is a matter of developer preference and when you're working on Rails, we care about your trails.

**emit\_events**: Optimism is so flexible that you can opt to have it fire DOM events in addition to (or instead of) text content and CSS updates. Scroll down for more information on the events that will be sent.

**add\_css**: Flag to control whether containers will receive CSS updates when a form is invalid.

**inject\_inline**: Flag to control whether validation error messages will be displayed inside *error\_for* spans.

**container\_selector**: This is the pattern from which container id CSS selectors will be constructed. You probably shouldn't change this.

**error\_selector**: This is the pattern from which error\_for span id CSS selectors will be constructed. You probably shouldn't change this.

**form\_selector**: This is the pattern from which form id CSS selectors will be constructed. You probably shouldn't change this.

**submit\_selector**: This is the pattern from which Submit button id CSS selectors will be constructed. You probably shouldn't change this.

## Events

If you set the `emit_events` property to true in your initializer, Optimism will emit DOM events in response to validation errors. This can happen in addition to or instead of CSS and text updates. This is a great alternative for complicated integrations where you have legacy components which need to be notified of error conditions on the backend.

In practical terms, DOM events give you tooling options and creative flexibility that are difficult to achieve with textual error messages and CSS error classes. It's simply a fact that no library can anticipate every design pattern or UI innovation. Today you might connect DOM events to a [toast notification library](https://www.jqueryscript.net/blog/Best-Toast-Notification-jQuery-Plugins.html#vanilla), but tomorrow there could be a mass proliferation of embedded ocular computers with sub-vocalization control interfaces, and we aren't going to tell you how those devices should punish users for bad input.

### Form-level events

Event: **optimism:form:invalid**\
Detail: resource

Event: **optimism:form:valid**\
Detail: resource

One of these events will fire, depending on whether the model is valid. Resource is the pluralized class name of the Active Record model, eg. `posts`

### Attribute-level events

Event: **optimism:attribute:invalid**\
Detail: resource, attribute, text

Event: **optimism:attribute:valid**\
Detail: resource, attribute

One of these events will fire **for each attribute**, depending on whether that attribute is valid. Resource is the pluralized class name of the Active Record model, eg. `posts`. Attribute is hopefully self-explanatory. Text is the text content of the validation error message.

![](/files/-M13TjYJnOilX8u3NGDK)


# Advanced Usage

Until now, the majority of this documentation has focused on the primary out-of-the-box functionality of Rails remote forms, which are just like 1996 forms except they are submitted using 1999's XMLHttpRequest object. This page deals with contemporary interfaces, tilting towards the future.

Generally speaking, traditional forms are a way to submit a bucket of associated key/value pairs intended to update a single instance of a resource. With the advent of JS-powered interfaces, there are now many patterns which require re-thinking how to update individual attributes while ensuring a consistent state between the client and server. Unfortunately, the aging HTML form is poorly equipped to deal with anything that doesn't conform to a dated, white bread possibility space.

Optimism is a server-side solution. It would be impractical to try and provide client-side solutions to these problems; even if we succeed in covering every framework and design approach, we don't want to limit creative freedom or made integration difficult just because we didn't anticipate something.

That said, we believe Optimism provides a lot of flexibility for people who can build or adapt their client-side solutions to work with our simple API; in many cases, it just comes down to sending less data. When combined with other tools, comprehensive solutions are possible with very little custom code.

## Reactive validations

Whether hotter/colder updates about the availability of our desired username or having the background color of a text input change color when we type too many characters, people have come to expect real-time feedback from input elements. If you have to submit a form to find out that your password needs to have special characters in it, well, that page sucks and many users are sophisticated enough to feel legitimate frustration when developers get this wrong.

The key to real-time validation updates with Rails remote forms is that you can submit to the same resource end-point with the same form structure, just constrained to only the current attribute. That means your client code has to be able to pick up the current form's HTTP **\_method** and **authenticity\_token** fields and pack them into a POST along with the desired attribute, making sure to grab the full Rails name key for the attribute. This is especially vital when dealing with nested forms, for example Comments embedded in a Post might look like '**post\[comments\_attributes]\[0]\[body]**'. You'll also need to remember to **set the X-Requested-With header to 'XMLHttpRequest'**.

In theory, if you follow the thread of the previous paragraph Optimism should "just work" because the post\_params will only have the attribute currently being modified in it. However, if your application works differently, the most important thing to remember is that the 2nd parameter to the [broadcast\_errors](https://optimism.leastbad.com/reference#broadcast_errors-model-attributes) function can also be a single String or Symbol. This means that you can use Optimism regardless of whether there's a form on the active page, even if it doesn't look like a form. You could give live feedback for a typeahead search in a header that uses an input element outside of the context of a form.

Optimism works by looking for elements with IDs that match expected patterns. If you're only emitting events then even that constraint is off the table. Advanced users can think of Optimism as a message bus for communicating validation errors in real-time over websockets, which is very liberating.

![](/files/-M1AD_GcyH_NAFD77IYx)

## In-line edit UIs

After the advances in UI design by thinkers such as Larry Tesler and Jef Raskin during the 1980s led to concepts like [modeless computing](https://en.wikipedia.org/wiki/Mode_\(user_interface\)), it could be argued that the humble HTML form was a primitive modeless concept designed to work over a stateless protocol.

Then REST API came along and brought the computing world right back to the 1970s, with distinct modes for listing, viewing and editing data. REST brought much needed structure, a discoverable interface and good URL conventions. It is also fundamentally inflexible and has little provision for real-time dynamic interfaces, much less bi-directional data binding. That leaves us developers to figure out how to find a general solution to editing data without leaving the view (show) state.

Unlike reactive validations, where the server is a single source of truth, actually changing a single data value from an arbitrary place in your application is a surprisingly difficult challenge, before even considering all of the different client libraries and design approaches one might take.

Someone tackling an in-line edit interface in Rails need to tackle two problems: syncronization and partial updates.

Syncronization is the easiest to explain and the hardest to solve. Simply: what if the value of data changes on the server after the page has been rendered on client browser? Libraries like [StimulusReflex](https://github.com/hopsoft/stimulus_reflex) go to great pains to not replace the contents of an input element while you're typing into it. Simply put, there's an opinionated decision made that what you're typing should be the source of truth until that element no longer has focus. You then have to figure out how to decide which value wins: the one on the server that the user never saw, or this new input from the user? There is no right answer, and usually the last update wins. Interfaces such as Optimism and libraries like StimulusReflex give us the opportunity to consider a hybrid approach where the form could update to show that data has changed on the server, either showing the new server value alongside the input element, or even locking the input element entirely.

Partial updates is an umbrella concept for a group of related concerns that emerge when updating a subset of attributes on a model that has rules in place covering many attributes. Some of those attributes might not be included in the subset you're updating. For example, if you're trying to provide an in-line edit UI for a model that has multiple required attributes, when you enter data for the first attribute the model will not be created because it fails validations. This raises the question of where those individual updates go before you have enough data captured to create a valid instance of the model. Rails does not address this gracefully out of the box, beyond implying that a new model instance should be created in one shot while some additional flexibility is possible with updates.

It's impossible to be half-pregnant. In Rails, it's impossible to half-save a new record.

For example, this means that it's really tricky to implement a git-style commit concept, where you might make multiple changes to a dataset before ultimately commiting it to a permanent datastore. This was less practical in the era of stateless HTTP page reloads, but now that we have websockets and powerful UI libraries, it sucks that we don't have better answers to these patterns.

Another casualty is the prospect of undo/redo capable UIs. Today, undo/redo is generally confined to text editing operations, while applications like Photoshop have had powerful history navigators for decades. Frankly, it's upsetting that so much energy goes into making React useful when we still haven't solved making undo work across multiple components on a single page.

Partial updates aren't a Rails-specific problem, but Rails validation infrastucture does make this design goal harder to achieve. All general schemes have constraints and limitations, and if DHH had made different decisions, we would have different problems to deal with.

There is also progress in the ActiveRecord API. You can now query individual attributes to see if they are valid or dirty (changed). These building blocks offer paths to more comprehensive solutions in the future.

What Optimism can offer today is a bridge while other aspects of this domain are figured out for the next generation of web developers. It has the ability to add a CSS class representing invalid state to the form. It can even be [configured](https://optimism.leastbad.com/reference#initializer) to disable the Submit button if the model is in an invalid state. While not a complete solution, we hope this proves a useful piece of the picture.

![](/files/-M1ANgsH-mkR_7LiZj9v)

##


