Ruby on Rails integration

How to use Vue with Ruby on Rails - free starter

This guide will provide you with a free template for a Ruby on Rails application, with MySQL database and Vue + Bootstrap 5 front-end.


Prerequisites

Before starting the project make sure to install the following utilities:

  • Node LTS (14.x.x or higher recommended)
  • Ruby (2.7.0 or higher recommended)
  • MySQL (8.x.x or higher recommended)
  • Yarn (1.22.19 or higher recommended) - for optional step only
  • Code editor. We recommend VSCode

Creating a new Ruby on Rails application

Let's create a fresh Ruby on Rails application so that we can go through all the steps together.

Step 1

Creating MySQL database.

In order to create a new database you need to run the following command:

        
            
          mdb database init -db mysql8
      
        
    
  • Create a new user
  • Provide username, password, database name and description.

CLI will display your username, password, database name and connections string. Now you can go to phpMyAdmin where you will be able to handle the administration of the MySQL database.

Note: the password must contain at least one uppercase letter, one lowercase letter, one number, one special symbol and have minimum length of 8.

Important: Do not close your terminal window until you save your credentials somewhere. This is the only time we will show you your database password. If you won't save it you'll loose it.

Step 2

Create a table.

Go to phpMyAdmin, log in, and add a table. For example on purpose of this tutoria with name tasks. Then create a three columns: id (type: int), name (type: text) and description (type: text).

Step 3

Install Rails by applying this command into terminal:

        
            
        gem install rails
      
        
    

Step 4

Navigate to you root directory and create a new project with a rails command and additioanl flags: -d mysql and --api - this creates Ruby on Rails scaffold prepared only for working as a API for our frontend project & integrated with MySQL database. ruby-api is the name (you can change whatever you like).

        
            
        rails new ruby-api -d mysql --api
      
        
    

Note: In case of error bound to install mysql2 gem, check solution from Github thread.

Step 5

Then go to newly created project.

        
            
        cd ruby-api
      
        
    

Step 6

Create a controller and model. Task is name of your file - you can change whatever you like. After that you should have new files in app/controllers and app/models directories - in this particular case all of them will have the name Tasks (framework creates the plural on its own). You can use built in Ruby pre-made function: rails generate scaffold Task.

        
            
        rails generate scaffold Task
      
        
    

Step 7

Delete table schema.

Go to file located in config/db/migrate directory - after scaffold Ruby has generated random numbers in first part but name of the second should be created_tasks. Just delete this file.

Step 8

Install additional library for handling CORS - rack-cors.

        
            
        gem install rack-cors
      
        
    

Step 9

Go to config/initializers directory and find cors.rb file - uncomment code under link from "Read more".

        
            
        Rails.application.config.middleware.insert_before 0,
        Rack::Cors do allow do
          origins "*"

          resource "*",
            headers: :any,
            methods: [:get, :post, :put, :patch, :delete, :options, :head]
        end
      end
      
        
    

Step 10

Then go to root directory and in Gemfile uncomment line with gem "rack-cors".

Step 11

In newly created controller (tasks_controller.rb) change placeholders in POST and PATCH/PUT endpoints: replace task_params with this code: name: params[:name], description: params[:description], id: params[:id].

        
            
        # POST /tasks
        def create
          @task = Task.new(name: params[:name], description: params[:description], id:params[:id])
      
          if @task.save
            render json: @task, status: :created, location: @task
          else
            render json: @task.errors, status: :unprocessable_entity
          end
        end
      
        # PATCH/PUT /tasks/1
        def update
          if @task.update(name: params[:name], description: params[:description], id:params[:id])
            render json: @task
          else
            render json: @task.errors, status: :unprocessable_entity
          end
        end    
      
        
    

Step 12

Open database.yml file (which can you find in config directory) and edit deafult database settings with your credentials.

        
            
        default: &default
          adapter: mysql2
          encoding: utf8mb4
          pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
          username: your_username
          password: your_password
          host: mysql.db.mdbgo.com

        development:
          <<: *default
          database: your_database_name
      
        
    

Step 13

Start your server.

Note: In case of error bound to tzinfo-data library check solution from Stackoverflow thread.

        
            
        rails server
      
        
    

Creating MDB Vue application

Note: Don't forget to go back to your root folder before next step. Folders ruby-api and mdb5-free-vue should be in the same directory.

Step 1

Create a new vite project with our MDB starter. Run the command below and select MDB5 Free Vue starter.

        
            
          mdb init
      
        
    

Your folder structure should look like this

        
          ruby/
          ├── ruby-api
          └── mdb5-free-vue
        
      

Step 2

Let's make some changes to the created vue app. First we need to install axios inside our mdb5-free-vue directory.

        
            
          npm install axios
      
        
    

Remove style.css file (don't forget to delete it from main.ts file) and remove HelloWorld file from components directory.

Step 3

Let's create a .env file inside a mdb5-free-vue directory. We have to add VITE_ before the name of your variable, because it's the only way to expose them to Vite-processed code. Don't forget to change URL to the one you created earlier.

        
            
          VITE_API = "LINK_TO_YOUR_BACKEND_APP"
      
        
    

Step 4

Add new content to Home.vue file inside the views directory.

Since our starter database contains some sample models and routes, let's use them. We will create an application that will show a list of tasks. We also intend to create a functonality for adding new tasks, changing their content and removing them.

We have already prepared the code for you, so go ahead and copy and paste it into App.vue.

Ok, so what's actually going on there. We use MDB components, MDBBtn, MDBModal, MDBListGroup, MDBInputs and few other. The modal will be responsible to show inputs that will allow you to add, edit and send tasks to the database. The Manage tasks button, gives possibilty to modify or remove tasks. At the end, the list group will display our data.

        
            
        <template>
          <MDBContainer class="mt-5">
            <MDBRow class="pt-5">
              <MDBCol class="text-center">
                <MDBBtn color="primary" @click="taskModal = true">Add task</MDBBtn>
              </MDBCol>
            </MDBRow>
            <MDBRow class="mt-3 p-5" style="min-height: 40vh">
              <MDBCol class="d-flex justify-content-center align-items-center">
                <div v-if="taskList.length === 0">
                  Nothing to display. Add a few tasks.
                </div>
                <MDBListGroup v-else class="list-group-light" style="min-width: 22rem">
                  <MDBListGroupItem
                    class="d-flex justify-content-between align-items-center gap-5"
                    v-for="task in taskList"
                    :key="task.id"
                  >
                    <div>
                      <div class="fw-bold">
                        {{ task.name }}
                      </div>
                      <div class="text-muted">{{ task.description }}</div>
                    </div>
                    <div>
                      <MDBIcon
                        class="text-primary me-3"
                        title="edit"
                        icon="pen"
                        style="cursor: pointer"
                        @click="() => editModal(task)"
                      />
                      <MDBIcon
                        class="text-danger"
                        title="delete"
                        icon="trash"
                        style="cursor: pointer"
                        @click="() => deleteTask(task.id)"
                      />
                    </div>
                  </MDBListGroupItem>
                </MDBListGroup>
              </MDBCol>
            </MDBRow>
          </MDBContainer>
          <MDBModal
            id="addNewTaskModal"
            tabindex="-1"
            labelledby="addNewTaskModalLabel"
            v-model="taskModal"
          >
            <MDBModalHeader>
              <MDBModalTitle id="exampleModalLabel">{{
                isEdited.edited ? "Edit task" : "Add task"
              }}</MDBModalTitle>
            </MDBModalHeader>
            <MDBModalBody>
              <form>
                <div class="my-4">
                  <MDBInput
                    label="Name"
                    type="text"
                    v-model="newTaskName"
                    counter
                    :maxlength="60"
                  />
                </div>
                <div class="my-4">
                  <MDBInput
                    label="Description"
                    type="text"
                    v-model="newTaskDesc"
                    counter
                    :maxlength="255"
                  />
                </div>
              </form>
            </MDBModalBody>
            <MDBModalFooter>
              <MDBBtn
                color="secondary"
                @click="
                  {
                    resetInputs();
                    taskModal = false;
                  }
                "
                >Close</MDBBtn
              >
              <MDBBtn
                color="primary"
                @click="handleSaveChanges"
                :disabled="!canSendData"
                >{{ isEdited.edited ? "Save changes" : "Add task" }}</MDBBtn
              >
            </MDBModalFooter>
          </MDBModal>
        </template>
      
        
    
        
            
        <script setup lang="ts">
          import { ref, onMounted, computed } from "vue";
          import {
            MDBContainer,
            MDBRow,
            MDBCol,
            MDBListGroup,
            MDBListGroupItem,
            MDBBtn,
            MDBModal,
            MDBModalTitle,
            MDBModalHeader,
            MDBModalBody,
            MDBModalFooter,
            MDBInput,
            MDBIcon,
          } from "mdb-vue-ui-kit";
          import axios from "axios";
          
          interface SingleTask {
            id: number;
            name: string;
            description: string;
          }
          
          const taskList = ref<SingleTask[]>([]);
          const taskModal = ref(false);
          const newTaskName = ref("");
          const newTaskDesc = ref("");
          const isEdited = ref({ edited: false, value: -1 });
          const API_URL = ref("");
          
          const canSendData = computed(() => {
            if (newTaskName.value.trim() === "" || newTaskDesc.value.trim() === "") {
              return false;
            }
            return true;
          });
          
          const resetInputs = () => {
            newTaskName.value = "";
            newTaskDesc.value = "";
            isEdited.value = { edited: false, value: -1 };
          };
          
          const handleSaveChanges = async () => {
            if (!canSendData.value) {
              return;
            }
          
            isEdited.value.edited
              ? updateTask(isEdited.value.value, newTaskName.value, newTaskDesc.value)
              : createTask(
                newTaskName.value,
                newTaskDesc.value,
                Math.ceil(Math.random() * 1000000)
              );
            resetInputs();
            taskModal.value = false;
          };
          
          const editModal = (task: SingleTask) => {
            newTaskName.value = task.name;
            newTaskDesc.value = task.description;
            isEdited.value = { edited: true, value: task.id };
          
            taskModal.value = true;
          };
          
          const getTaskList = () => {
            axios
              .get(`${API_URL.value}tasks/`)
              .then((res) => (taskList.value = res.data));
          };
          
          const createTask = (name: string, description: string, id: number) => {
            const data = { name, description, id };
            axios.post(`${API_URL.value}tasks/`, data).then(() => {
              getTaskList();
            });
          };
          
          const deleteTask = (id: number) => {
            axios.delete(`${API_URL.value}tasks/${id}`).then(() => {
              getTaskList();
            });
          };
          
          const updateTask = (id: number, name: string, description: string) => {
            const data = { name, description };
            axios.put(`${API_URL.value}tasks/${id}`, data).then(() => {
              getTaskList();
            });
          };
          
          onMounted(() => {
            API_URL.value = import.meta.env.VITE_API;
            getTaskList();
          });
        </script>
      
        
    

If you haven't done it already, run npm start in your terminal. The app should be fully functional and should work correctly with backend.


Creating a new Ruby on Rails application with JavaScript inside

Let's create a fresh Ruby on Rails application where JavaScript is already implemented so that we can go through all the steps together.

Step 1

Install Rails by applying this command into terminal:

        
            
        gem install rails
      
        
    

Step 2

Navigate to you root directory and create a new project with a rails command. my_new_app is the name (you can change whatever you like).

Note: Despite of the fact that Ruby on Rails offers new tool - importmap - for easier and faster managing your JavaScript code (without additional module bundler), it is not working properly with Vue at the moment. More about importamp you can read in Ruby on Rails documentation.

Note: Following importmap issues, in this tutorial new Ruby on Rails application created with Vue is working on Webpack.

        
            
        rails new my_new_app --javascript=webpack
      
        
    

Step 3

Then go to newly created project.

        
            
        cd my_new_app
      
        
    

Configuring a new Ruby on Rails application

Let's configure a fresh Ruby on Rails application so that we can go through all the steps together.

Note: The Ruby on Rails documentation makes it clear that it's better to use Yarn over NPM. So for the rest of this tutorial, we'll be using yarn add instead of npm install.

Step 1

Start by installing Webpack and Webpack CLI.

        
            
        yarn add -D webpack webpack-cli
      
        
    

Step 2

Then install Webpack's dependencies - loaders for CSS files and Vue components (css-loader, style-loader and vue-loader).

Note: More about Webpack loaders can read on Webpack documentation.

        
            
        yarn add -D css-loader style-loader vue-loader
      
        
    

Step 3

Sometimes in spite of fact that we started Ruby on Rails project with JavaScript and Webpack template, dependencies for them are not installing. So you got to do it by hand: you need a jsbundling-rails library.

        
            
        bin/bundle add jsbundling-rails
        bin/rails javascript:install:webpack
      
        
    

Step 4

Edit Webpack config in webpack.config.js file by adding module object with rules for vue-loader, css-loader adn style-loader and initiate new VueLoaderPlugin() in plugins array. Remeber to import it!

        
            
        const { VueLoaderPlugin } = require("vue-loader");
        ...
        module: {
          rules: [
            { test: /\.vue$/, loader: "vue-loader" },
            {
              test: /\.css$/i,
              use: ["style-loader", "css-loader"],
            },
          ],
        },
        plugins: [
          ...
          new VueLoaderPlugin(),
      ],
      
        
    

Step 5

If you are using Windows, edit Procfile.dev by adding before bin/rails server -p 3000 commend ruby. Then it should looks like this:

        
            
        web: ruby bin/rails server -p 3000
        js: yarn build --watch      
      
        
    

Installing MDB Vue & configuring frontend

Step 1

Install MDB Vue library.

        
            
        yarn add mdb-vue-ui-kit
      
        
    

Step 2

Install Vue.

        
            
        yarn add vue
      
        
    

Step 3

Uncomment root route in routes.rb file (you can found it in config directory. You can change name of this by whatever you like - all you got to remember that creating controller after that should has identical name!

        
            
        Rails.application.routes.draw do
          root "articles#index"
        end
      
        
    

Step 4

Create a controller for root route. After this command you are going to create a new file in app/views/articles directory: index.html.erb.

        
            
        bin/rails generate controller Articles index --skip-routes
      
        
    

Step 5

Edit newly created file in app/views/articles directory: index.html.erb by adding root div for Vue application.

        
            
        <div id="app"></div>
      
        
    

Step 6

Create App.vue file and put it into app/javascript directory (you can change it but remember to assign new import paths).

        
            
        <template>
          <MDBCarousel v-model="carousel" :items="items" />
        </template>    
      
        
    
        
            
        <script setup lang="ts">
          import { ref } from "vue";
          import { MDBCarousel } from "mdb-vue-ui-kit";
            
          const carousel1 = ref(0);
          const items1 = [
              {
                  src: "https://mdbootstrap.com/img/Photos/Slides/img%20(15).webp",
                  alt: "...",
                  label: "First slide label",
                  caption: "Nulla vitae elit libero, a pharetra augue mollis interdum.",
              },
              {
                  src: "https://mdbootstrap.com/img/Photos/Slides/img%20(22).webp",
                  alt: "...",
                  label: "Second slide label",
                  caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
              },
              {
                  src: "https://mdbootstrap.com/img/Photos/Slides/img%20(23).webp",
                  alt: "...",
                  label: "Third slide label",
                  caption:
                      "Praesent commodo cursus magna, vel scelerisque nisl consectetur.",
              },
          ];
        </script>
      
        
    

Step 7

Edit application.ts file (found in app/javascript directory) by creating a Vue app instance and imporing MDB's CSS file - after this it should looks like this:

        
            
        import "mdb-vue-ui-kit/css/mdb.min.css";
        import { createApp } from "vue";
        import App from "./App.vue";
        
        createApp(App).mount("#app");      
      
        
    

Step 8

Then edit application.html.erb file (found in app/views/layouts directory) by adding in head CDN links for Roboto and Awesome fonts.

        
            
        <link
          href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          rel="stylesheet"
        />
        <link
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
          rel="stylesheet"
        />
      
        
    

Step 9

For start your application (both backend and frontend) use bin/dev command.

Note: In case of error bound to tzinfo-data library check solution from Stackoverflow thread.

        
            
        bin/dev
      
        
    

Optimization

If you want to further optimize your application please visit: