Skip to content

Instantly share code, notes, and snippets.

@xlanex6
Forked from przbadu/_vue-rails.md
Created May 24, 2018 12:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xlanex6/5e3a0c83cc620597fd033ad454b4505d to your computer and use it in GitHub Desktop.
Save xlanex6/5e3a0c83cc620597fd033ad454b4505d to your computer and use it in GitHub Desktop.
Vue js and Rails integration

Setup Rails and Vuejs

  1. Generate new rails app using --webpack flag
rails new myApp --webpack=vue

Note:

  1. You can use --webpack=angular for angular application and --webpack=react for react.
  2. you can also pass -J flag like rails new myApp -J --webpack=vue to ignore turbolink, but strongly recommend you don't do this, I will see how to make vue js compitable with turbolinks.
  1. Keep all of the vue js related codes in app/javascript/pack/ directory. Anything that needs to be compiled by webpack should go inside this directory.

  2. app/assets/javascript is still there for your js and coffescript code that are independed of webpack modules.

  3. vue-turbolinks npm module to fix turbolink problems

import TurbolinksAdapter from 'vue-turbolinks';

document.addEventListener('turbolinks:load', () => {
  // This code will setup headers of X-CSRF-Token that it grabs from rails generated token in meta tag.
  axios.defaults.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

  new Vue({
    ...
    mixin: [TurbolinksAdapter],
    ...
  })
})
yarn add vue-turbolink
  1. axios npm module for making server request (alternative to vue-resource or $.ajax)
yarn add axios

Usage

axios.post('/users', {
  firstName: 'John',
  lastName: 'Doe',
})
 .then(res => console.log(res))
 .catch(err => console.log(err));

Example application below:

<!-- app/views/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>DemoTurbolinks</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application' %>
</head>
<body>
<%= yield %>
</body>
</html>
// app/javascript/packs/application.js
import Employee from './employee';
// app/javascript/packs/employee.js
import Vue from 'vue/dist/vue.esm';
import TurbolinksAdapter from 'vue-turbolinks';
import axios from 'axios';
export default document.addEventListener('turbolinks:load', () => {
axios.defaults.headers.common['X-CSRF-Token'] = document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content');
const element = document.getElementById('employees');
if (element !== null) {
Vue.component('employee-row', {
template: '#employee-row',
mixin: [TurbolinksAdapter],
props: {
employee: Object,
},
data() {
return {
editMode: false,
errors: {},
employeeRow: this.employee,
};
},
computed: {
changeEmployeeRow: {
get() {
this.employeeRow;
},
set(data) {
this.employeeRow = data;
},
},
},
methods: {
// toggle the employee status which also updates
// the employee in the database
toggleManagerStatus() {
this.employee.manager = !this.employee.manager;
this.updateEmployee();
},
// ajax call to update an employee
updateEmployee() {
axios
.put(`/employees/${this.employee.id}.json`, {
employee: this.employee,
})
.then(response => {
// Turbolinks.visit(`/employees`);
this.errors = {};
this.employeeRow = response.data;
this.editMode = false;
})
.catch(error => {
this.errors = error.response && error.response.data;
});
},
fireEmployee() {
axios
.delete(`/employees/${this.employee.id}.json`)
.then(response => Turbolinks.visit(`/employees`))
.catch(error => console.log(error));
},
},
});
const employees = new Vue({
el: element,
mixin: [TurbolinksAdapter],
data() {
return {
employees: [],
employee: {
name: '',
email: '',
manager: false,
},
errors: {},
};
},
created() {
axios
.get('/employees.json')
.then(response => (this.employees = response.data))
.catch(error => console.log(error));
},
methods: {
hireEmployee() {
axios
.post('/employees.json', {
employee: this.employee,
})
.then(response => {
this.errors = {};
this.employees.push(response.data);
})
.catch(error => {
this.errors = error.response.data;
});
},
},
});
}
});
# app/controllers/employees_controller.rb
class EmployeesController < ApplicationController
before_action :set_employee, only: [:show, :edit, :update, :destroy]
# GET /employees
# GET /employees.json
def index
@employees = Employee.all
end
# GET /employees/1
# GET /employees/1.json
def show
end
# GET /employees/new
def new
@employee = Employee.new
end
# GET /employees/1/edit
def edit
end
# POST /employees
# POST /employees.json
def create
@employee = Employee.new(employee_params)
respond_to do |format|
if @employee.save
format.html { redirect_to @employee, notice: 'Employee was successfully created.' }
format.json { render :show, status: :created, location: @employee }
else
format.html { render :new }
format.json { render json: @employee.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /employees/1
# PATCH/PUT /employees/1.json
def update
respond_to do |format|
if @employee.update(employee_params)
format.html { redirect_to @employee, notice: 'Employee was successfully updated.' }
format.json { render :show, status: :ok, location: @employee }
else
format.html { render :edit }
format.json { render json: @employee.errors, status: :unprocessable_entity }
end
end
end
# DELETE /employees/1
# DELETE /employees/1.json
def destroy
@employee.destroy
respond_to do |format|
format.html { redirect_to employees_url, notice: 'Employee was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_employee
@employee = Employee.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def employee_params
params.require(:employee).permit(:name, :email, :manager)
end
end
<!-- app/views/employees/index.html.erb -->
<p id="notice"><%= notice %></p>
<h1>Employees</h1>
<%= link_to 'New Employee', new_employee_path %>
<br>
<div id="employees">
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Manager?</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="text" v-model="employee.name"/> <br/>
<span style="color:red">{{errors.name}}</span>
</td>
<td>
<input type="email" v-model="employee.email"/> <br/>
<span style="color:red">{{errors.email}}</span>
</td>
<td>
<input type="checkbox" v-model="employee.manager"/>
</td>
<td>
<button @click="hireEmployee">Save</button>
</td>
</tr>
</tbody>
<tbody
is="employee-row"
v-for="employee in employees"
:employee="employee">
</tbody>
</table>
</div>
<script type="text/x-template" id="employee-row">
<tr>
<td>
<div v-if="editMode">
<input type="text" v-model="employeeRow.name"/> <br/>
<span style="color:red">{{errors.name}}</span>
</div>
<div v-else>{{ employeeRow.name }}</div>
</td>
<td>
<div v-if="editMode">
<input type="email" v-model="employeeRow.email"/> <br/>
<span style="color:red">{{errors.email}}</span>
</div>
<div v-else>{{ employeeRow.email }}</div>
</td>
<td>
<div v-if="editMode">
<input type="checkbox" v-model="employeeRow.manager"/>
</div>
<div v-else>{{ employeeRow.manager ? '&#10004;' : '' }}</div>
</td>
<td>
<button v-if="editMode" @click="updateEmployee">Save</button>
<button v-else @click="editMode = true">Edit</button>
<button v-if="!editMode" @click="toggleManagerStatus">
{{ employeeRow.manager ? 'Demote' : 'Promote' }}
</button>
<button v-if="!editMode" @click="fireEmployee" style="color:red">Fire</button>
</td>
</tr>
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment