Vue.js and Rails - a 2017 update

Vue on Rails book now available -- Purchase Vue on Rails

I wrote a post about Vue.js and Rails which is a bit outdated now, so I thought it would be about to time to post an update. I would like to talk about two different approaches to take when starting a new project with the intention of using Vue.js and Rails. At the time of this writing, the latest versions of Vue and Rails are 2.4.3 and 5.1.4 respectively. Hopefully, this will give you some guidance when contemplating using Vue in your current or next Rails project.

# Approach #1: Dedicated Client and Server Projects

This approach is ideal for teams with dedicated frontend and backend developers and/or applications that determine a single page application is necessary. I took this approach when building System Tester, which is a chrome extension on the client side and a Ruby gem which you install in your Rails project to add the necessary endpoints.

Starting a project in this manner is simple, simply initialize a new Rails project with the --api flag so it only acts as a JSON API and initialize a vue project using vue-cli and apply your desired settings. You could use two separate repositories, or combine them into one. You would just have to incorporate the client build into the deployment process so that files are copied into the public folder. I've also created a custom template that does this for you when running npm run build.

$ vue init <template-name> <project-name>
$ rails new <project-name> --api

Using this approach gives you option of using vuex for central state management. Using vuex in the second approach may not make a whole lot of sense.

# Approach #2: Using the webpacker gem

Better support for javascript as of Rails 5.1 has been a great improvement along with the release of the webpacker gem. This approach is great for existing projects with the appropriate Rails version or new applications that don't need a single page application and developers who prefer the traditional Rails conventions and views. You could still build a SPA using webpacker, but you can also use the vue.esm.js package to be able to pick and choose where you want to insert your vue components and which pages you need to load in Vue. Getting started is a matter of adding the webpacker gem to your application and run bin/rails webpack:install:vue

# An Example for Approach #2

I've been experimenting with the second approach for building Light Bulb List. The idea for Light Bulb List was to have a way for software developers to publish their ideas and connect them to MailChimp newsletters. Visitors can subscribe to these lists. This is how I implemented the Subscribe button component, which opens up a bootstrap modal to request an email address.

# Inside a Rails view
<% content_tag "div", id: "sb-#{bulb.id}", data: {
    id: bulb.id,
    title: bulb.title,
    url: bulb.url
  } do %><% end %>
<subscribe-button :bid="<%= bulb.id %>"></subscribe-button>

// javascript/packs/SubscribeButton.vue
<template>
  <button
    type="button"
    class="btn"
    :class="{'btn-warning': !subscribed, 'btn-success': subscribed}"
    data-toggle="modal"
    data-target="#subscribeModal"
    :disabled="subscribed"
    @click="open()">
      Subscribe{{ subscribed ? 'd ✓' : '' }}
  </button>
</template>

<script>
import EventBus from './EventBus'
export default {
  name: 'subscribe-button',
  props: {
    bid: Number
  },
  data () {
    return {
      subscribed: false,
      burl: '',
      btitle: '',
    }
  },
  mounted () {
    this.subscribed = this.alreadySubscribed()
    let that = this
    this.btitle = $('#sb-' + this.bid).data('title')
    this.burl = $('#sb-' + this.bid).data('url')
    EventBus.$on('subscribed', () => {
      that.$nextTick(() => {
        that.subscribed = that.alreadySubscribed()
      })
    })
  },
  methods: {
    alreadySubscribed() {
      let subscribed = {}
      let email = ''
      try {
        email = localStorage.getItem('email') || ''
        subscribed = JSON.parse(localStorage.getItem('subscribed') || '{}')
      } catch(e) {
        console.error(e)
      }
      console.debug(subscribed)
      return subscribed[email] != undefined && subscribed[email].includes(this.bid)
    },
    open() {
      EventBus.$emit('sb-modal-open', {id: this.bid, title: this.btitle, url: this.burl})
    }
  }
}
</script>

The modal lives in another component, but to be able to reuse the subscribe button, implementing a global event bus has seemed to be the best approach. Email and the bulbs subscribed to are stored in local storage. The subscribe button component listens for and handles the 'subscribed' event if the visitor subscribed to this particular bulb, which disables the button, modifies the CSS, and changes the text to subscribed. The subscribe button component also emits an event when it is clicked so that the subscribe modal component knows which bulb to use.

So that is a quick update on Vue.js and Rails in 2017. I know it's almost Q4 but better late than never. Stayed tuned because I plan on offering tutorials, resources, and possibly a book about building applications with Vue.js and Rails.