Vue and Rails: a perfect match

Torsten Ruger
rubydesign_web
Published in
7 min readJan 21, 2018
Winning combination

Now that i basically hyped Vue, let’s get down to business and see how to integrate it. One of Vue’s headlines is incrementally adoptable and now we’ll see how true that is. It is just perfect for adding to an existing rails app. And off course rails is still great to get started quickly and churn out db backed data. (PS: yes that previous article was hype. If you don’t think so, you’re not from Finland :-) )

Webpacker

Rails loves JavaScript: yes, after many years of mixed feelings, it is now official. Webpacker has been integrated into the workflow, sort of seamlessly. After hearing that, off i went and did the required steps. Just to find that they are not quite as simple as i had hoped. There are lots of posts about that, but the uphill made me wonder and google again and read again.

I came to the conclusion that Webpacker probably is great for the problem that it solves. The same problem bundler solves for gemfiles. Getting and managing all the right (compatible) versions of many libraries. There is even a bit of the pipeline in there as you can have filters and what not.

But if you just want the one library, ie Vue, it is overkill. And rails has a perfectly integrated asset pipeline, thank you.

Asset pipeline

To keep it simple (KISS), i downgraded to the asset pipeline. Yes, there was even a gem, but it was not maintained. So over to Vue installation page, download the dev version, into the assets and off we go. Like the million times before.

This really is simple. Great. And it works in development like a charm. Vue devtools recognises it and all the getting started examples work just fine. Ready for action.

Alas, it keeps informing us to remember to use the production version in production.

Cdn, the final stop

As the asset pipeline is not really made for different versions, i changed to the simplest of all the solutions. Funny how circumstance has to push one there. Just add the script tag to the latest version, with a little Ruby if to use the production version in production.

Everybody wins: I have the simplest imaginable setup. And thanks to the cdn, the user has the fastest possible delivery.

The catch you ask: well, we can’t have .vue single file components as we don’t have the vue loader, nor pug. But as we will see, in a rails environment, this is not really an issue.

Code

Ok, enough with the talk, let’s see some code. If you haven’t used Vue at all, it’s best just to try the examples from the vue page. We’ll do some tabs instead:

.col-md-2.pill{ “v-bind:class”: “{active_pill: tab == 1}”}
%h6
%a{“@click”: “tab = 1”} Basket

As you can see, this is haml, just because i am too lazy to write closing tags and search for missing closing tags (it’s not the 90’s after all). As the hash after the class/tag get’s expanded to key = “value” in the output, we can just write the vue directives in there. Unfortunately only, that vue uses the -, so we need quotes. The value eg “{active_pill: tab == 1}” usually gets interpreted as js code by vue, though sometimes one can stumble across a missing “:”.

Also the click handler is pretty self explanatory (we assign 1 as current tab), so we can just add as many of the pills as we need, and later in the code write the actual tab like this:

#app
.col-md-12.tab.basket{“v-bind:class”: “{active_tab: tab == 1}”}

I’ll skip the css, but you can see how this is working: We style our pills using the active class, and when clicked, we make the tab visible. Ie the active_tab class will have a “display: block” in there among whatever else. Now the only thing we are missing is the javascript:

:javascript
var app = new Vue({ el: '#app', data: { tab: 1} } )

A one-liner!! But let’s walk though it.

  • we define a tab variable to hold which tab is active, defaulting to 1
  • Pills are active if their tab number (the literal in the haml) is the current tab (eg “{active_tab: tab == 1}”)
  • A click on a pill makes that tab number the current tab (assignment in the click handler, “@click”: “tab = 1”)
  • We change the visibility of the actual tab, according to which tab is current, by assigning a class (css not shown)

And the best part: we describe the result, and let vue figure out what html has to be updated when. Also, we are completely free in our html structure, unlike css based tab solutions.

Components

I didn’t explicitly make the point that the code is in the same file as the markup, but it is worth mentioning. It makes the whole very understandable, and as such we are very close to the Vue concept of components. The main thing missing, the third musketeer (Aramis), is the css. And i happen to think this is a good thing. Css is a global concern in an application. It is best managed in the asset pipeline, at least until that day when we’ll all be writing web-components (as per standard).

The other thing missing to create a component, would be that actual component definition, and that needs a template. For the component definition we just change the “new Vue” to a “Vue.component” and give the name as first argument. For very small components string templates may also be used, but for real markup that gets unreadable.

X-template to the rescue. In haml that starts like this:

%script{‘type’=>”text/x-template”, id: ‘table-component’}

And the lines after become the component template. Still haml though, in other words rails will transform the haml to html and (da da) expand any Ruby we have in there. Smart! For completeness, the (abbreviated) component definition code:

:javascript
Vue.component("table-component" ,
{ template: "#table-component",
props: ['columns' . . . . ],
data: { order: 1, . . . },
methods: ....
} )

Now it get’s funky

So by now we can easily write apps and components. Integrated with rails we use rails’ haml templates effectively, mixing and matching our server and client abilities as we need.

What is missing is the communication between the vue and rails part. We all know the standard ajax way, but that is so after the fact, it’s not the place to start. Our Vue app and components don’t come out of thin air, they are rendered on the server, so let’s give them something on the way.

A much (much!) easier way to get data from the server to the client is to bundle it with the request for the page. As i said, usually our one page wonders will start life as a rails request, with a Vue app in the page: So let’s just write the data we need in there!

:javascript
var app = Vue.new({
el: "#products_table",
data: {
products: #{@products.to_json}
}
})

Easy as pie. Rails will pick up the #{} and execute the Ruby inside it. to_json will create a JavaScript array from our Ruby products and those will get parsed on the client and assigned as Vue data. Done! to_json has many options to massage the data, and then there is always rabl for more complex scenarios. But let’s not forget the simple ones too: any configuration data, language data, app data, user or profile data (non-sensitive) can go this way, very very easily.

And now for the final Ruby treat

JavsScript is getting better all the time, off course. Even most rubyist don’t have an issue writing it and if that is you, just skip ahead to the next chapter. For some (yes, me), the syntax has become not only ingrained (happens after 15 years), but outright beautiful.

Many may have tried opal to convert Ruby to JavaScript. But as opal converts semantics too, it has to wrap every object and method. This can get in the way. Ruby2js on the other hand goes the other way: it just transforms the Ruby syntax into JavaScript syntax. For the most part eye candy and function renaming, yes. But there is some handy conversion of blocks to anonymous functions and (my favorite) Ruby ways of writing loops.

Ruby2js has a concept of filters, and there are special ones to make working with libraries easier. One of those is a Vue filter, that transforms the vue concepts to Ruby ones:

  • Deriving from Vue becomes a Vue.new or Vue.component
  • initialize becomes the data function
  • instance variables become data
  • method definition get sorted to methods or computed, depending on whether they have arguments

It’s definitely not for everyone, but those that love it really do. The author of ruby2js, Sam Ruby, has written quite a chunk of the apache whimsy project with it :-)

Btw: conversion from javascript to ruby (by using ruby2js) has decreased code size by as much as 50%. In other words, there is up to twice as much Javascript generated as there is ruby (for Vue components or apps).

Conclusion

Like any good relationship, this is an all win situation. Let’s see what we have:

  • a really easy way of integrating/installing vue into a rails app
  • great reuse of rails templates for vue templates, either in apps or components
  • super easy way to populate data for a vue app, so it can trickle down to components
  • A neat way to write Vue apps or components in ruby

So now we know how to do it, let’s do it. Like i promised, next up will be a table component that can be sorted, filtered and allows cells to be overwritten, in a total (template and code) of 50 lines. Get ready to be amazed.

--

--