---
title: Elm and Rails Sitting in a Tree
teaser: 'I-N-T-E-G-R-A-T-I-N-G: Webpacker makes using Elm in your Rails app pain-free.

  '
tags: rails,elm
author:
- Tom Wey
- Josh Clayton
published_on: 2017-10-09
---

I've been enjoying developing with [Elm] for a while. In case you're not
familiar with Elm, it's a strongly-, statically-typed language that compiles to
JavaScript. The developer experience is fantastic, largely due to the awesome
compiler.

Thanks to [Webpacker], it's now straightforward to integrate Elm into a Rails
app. I recently worked on my first Rails + Webpacker + Elm app and I'll talk
about a few patterns that came out of the project.

[Elm]: http://elm-lang.org
[Webpacker]: https://github.com/rails/webpacker

## Getting Started

From the docs:

> "Webpacker makes it easy to use the JavaScript pre-processor and bundler
> [Webpack] 3.x.x+ to manage application-like JavaScript in Rails"

I won't duplicate the docs here, but there are handy task commands for getting
up and running with [Elm].

[Webpack]: https://webpack.js.org
[Elm]: https://github.com/rails/webpacker#elm

## Elm on Rails

One of the great things about Rails is that it provides clear patterns of how to
organize your code in the project. Webpacker adds the idea of "packs" which are
entry points for webpack. These live under `app/javascript/packs` by default. Our
application isn't an SPA; instead, we have a sprinkling of Elm components
(or widgets) throughout the app, generally one per page. So we have a single
pack per component. Typically these consist of a single line:

```javascript
// app/javascript/packs/my_component.js

import "my_component";
```

where `my_component` is a directory under `app/javascript` containing the code
for the component and an `index.js` file, which webpack will load by default.

The pack is included on a page with Webpacker's `javascript_pack_tag` helper.
Typically our components exist on a single page, so we call the pack tag helper
in the relevant view. To keep control over where on the page the actual
`script` tag is rendered, we make use of Rails' [`content_for` method].

In our view:

```erb
<!-- app/views/products/show.html.erb -->

<% content_for :tail_scripts do %>
  <%= javascript_pack_tag "my_component" %>
<% end %>
```

and in our layout, at the end of our `body` tag:

```erb
<!-- app/views/layouts/application.html.erb --!>

<html>
  <head>
    <!-- head content --!>
  </head>
  <body>
    <!-- content --!>
    <%= yield :tail_scripts %>
  </body>
</html>
```

In the simplest case the `index.js` for `my_component` looks something like
this:

```javascript
// app/javascript/my_component/index.js

import Elm from './elm/Main'

document.addEventListener('DOMContentLoaded', () => {
  const containerId = "my-elm-container";
  const target = document.getElementById(containerId);
  Elm.Main.embed(target);
})
```

This embeds our Elm component in a DOM element, rendered by Rails, on the page.
Our Elm code lives under `app/javascript/my_component/elm/`, and our entry
point within this directory is `Main.elm`.

[`content_for` method]: http://guides.rubyonrails.org/layouts_and_rendering.html#using-the-content-for-method

## Interacting with Elm from the outside

Mostly our Elm components don't act in isolation and require some context, either
from the current page, or higher level global config.

### Flags

A scenario that frequently occurred was the requirement to pass some initial state
(or config) from the server (Rails) to our Elm component. The pattern that we used
to achieve this was to set data attributes on the container which we render in our
Rails view, read these in our component entry JavaScript (i.e. the file at
`app/javascript/my_component/index.js` we showed above), and pass the values to
Elm as flags.

Let's say we wanted to pass in a name of some kind, which we have in an
instance variable `@name` in Rails. In our Rails view this would look like:

```erb
<!-- app/views/products/show.html.erb -->

<%= content_tag :div, nil, id: "my-elm-container", data: { name: @name } %>
```

We can now update our component entry JavaScript to read the data attribute and
pass it into our Elm component as a flag:

```javascript
// app/javascript/my_component/index.js

import Elm from './elm/Main';

document.addEventListener('DOMContentLoaded', () => {
  const containerId = "my-elm-container";
  const target = document.getElementById(containerId);
  const name = target.getAttribute("data-name");
  Elm.Main.embed(target, {
    name: name,
  });
})
```

For more general config (e.g. an API host shared across multiple Elm
components), we could make this available in a `meta` tag within Rails'
application layout and pass values in as needed to our Elm component:

```erb
<!-- app/views/layouts/application.html.erb --!>
<html>
  <head>
    <!-- head content --!>
    <meta name="apiRoot" value="https://api.example.com">
  </head>
  <body>
    <!-- content --!>
  </body>
</html>
```

With an initial value available in a `meta` tag, we now write a function to
retrieve the value:

```javascript
// app/javascript/meta-tag-bootstrap.js

export default function loadMetaTagData(name) {
  const element = document.querySelectorAll(`meta[name=${name}]`)[0];

  if (typeof element !== "undefined") {
    return element.getAttribute("value");
  } else {
    return null;
  }
}
```

Finally, in our JavaScript file that runs the Elm application, we can import
the function and retrieve the value from the `meta` tag based on its `name`
attribute:

```javascript
// app/javascript/my_component/index.js

import Elm from './elm/Main';
import loadMetaTagData from "../meta-tag-bootstrap";

document.addEventListener('DOMContentLoaded', () => {
  const globalConfig = {
    api_root: loadMetaTagData("apiRoot")
  };

  const containerId = "my-elm-container";
  const target = document.getElementById(containerId);
  const name = target.getAttribute("data-name");

  Elm.Main.embed(target, Object.assign(globalConfig, {
    name: name,
  }));
});
```

### Ports

Because we're not working with a single page app, but rather a series of self-
contained Elm components, it was sometimes necessary to communicate changes
within Elm to the outside world, and vice versa. This might be be as simple as
reflecting a change made in Elm elsewhere on the page, or perhaps triggering
some vanilla JavaScript. The answer here is [Elm ports].

[Elm ports]: https://guide.elm-lang.org/interop/javascript.html#ports

#### Data out

In this example, we broadcast a change to a quantity value over a port and
subscribe to it in our JavaScript code.

In our Elm code we have a function which takes our `Model` and an `Int`, and
returns a tuple `( Model, Cmd Msg )`. This becomes the return tuple from our
`update` function.

```elm
-- app/javascript/my_component/elm/Main.elm

updateQuantity : Model -> Int -> ( Model, Cmd Msg )
updateQuantity model quantity =
    ( { model | quantity = quantity }, quantityChanged quantity )
```

Where `quantityChanged` is defined as a port which sends an integer out to
JavaScript:

```elm
-- app/javascript/my_component/elm/Main.elm

port quantityChanged : Int -> Cmd msg
```

Note that the module where we define our port(s) must be specified as a `port
module`.

Then in our JavaScript we subscribe to the port and receive the sent value:

```javascript
// app/javascript/my_component/index.js

// ...
  const component = Elm.Main.embed(target, Object.assign(globalConfig, {
    name: name,
  }));

  component.ports.quantityChanged.subscribe((newQuantity) => {
    console.log(`Quantity changed to: ${newQuantity}`);
  });
// ...
```

#### Data in

For completeness lets say we also want to be able to set an absolute quantity
from JavaScript. In this case let's add a reset button outside of our Elm code,
which when clicked sends in a quantity of 0.

Our inbound port receives an `Int` and is defined like this:

```elm
-- app/javascript/my_component/elm/Main.elm

port setQuantity : (Int -> msg) -> Sub msg
```

In order to recieve messages from this port we subscribe to it and wire it up
with our app:

```elm
-- app/javascript/my_component/elm/Main.elm

subscriptions : Model -> Sub Msg
subscriptions model =
    setQuantity SetQuantity

main : Program Flags Model Msg
main =
    Html.programWithFlags
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }
```

The subscription maps our port to a `Msg` which we will handle in our `update`
function, just like any other `Msg`. The `Msg` is defined as `SetQuantity Int` and
the relevant branch in our `update` looks like this:

```elm
-- app/javascript/my_component/elm/Main.elm

SetQuantity quantity ->
    updateQuantity model <| max 0 quantity
```

This contains a guard to ensure that our quantity isn't ever < 0. There are
more elegant ways to handle this, for example a custom `Quantity` type, which
are outside of the scope of this article. See [our post on avoiding primitives
in Elm] for more on leveraging Elm's type system.

In our JavaScript code we can send a value to Elm over the port like this:

```javascript
component.ports.setQuantity.send(0);
```

[our post on avoiding primitives in Elm]: https://thoughtbot.com/blog/lessons-learned-avoiding-primitives-in-elm

#### Sending more complex data over ports

If you find yourself needing to send more complex data types over ports you'll
need to encode as JSON on the way in and decode on the way out. Elm has support
for [decoding] and [encoding] JSON in the standard library. For a more in depth
look see our posts on [Decoding JSON Structures with Elm] and [Bridging Elm and
JavaScript with Ports].

[decoding]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode
[encoding]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Encode
[Decoding JSON Structures with Elm]: https://thoughtbot.com/blog/decoding-json-structures-with-elm
[Bridging Elm and JavaScript with Ports]: https://thoughtbot.com/blog/bridging-elm-and-javascript-with-ports

## Wrapping Up

That just about wraps up the basics of how we integrated Elm components into
our Rails app. It feels like a great choice by the Rails team to embrace
Webpacker, and means that we've gained a bunch of flexibility around the
choices we can make for front-end development with Rails.

If you want to play around with any of the examples, a full example Rails app
is available [on GitHub].

[on GitHub]: https://github.com/tjmw/rails-elm-webpacker-example
