DEV Community

Felice Forby
Felice Forby

Posted on

QR Code Reader on Rails

Intro and getting started

I recently had to implement a QR code reader in Rails at work and in this article, I'll take you through how I did it by creating a demo featuring two different ways of handling scanned QR codes: One simply scans a QR code and displays the scanned data on the screen and the other sends the QR data to a Rails controller for processing.

Let's get started right away! We'll do so by creating a new Rails project:

rails new qr-code-reader-demo
Enter fullscreen mode Exit fullscreen mode

Next, we'll install a JavaScript library that will allow us to scan QR codes with a video camera on a phone or computer.

Install the QR code reader: ZXing-js

After research different QR code scanning libraries, I decided to use the ZXing JS library.

Why ZXing?

Ideally, it would have been nice to use a ruby gem. Although there are a few QR code reader gems available, they are no longer maintained (no updates for a couple of years).

The next option was using a JavaScript library. There are several JS libraries that handle QR code scanning, but ZXing is actively maintained and is a popular choice among developers. ZXing-js is actually a port of an "open-source, multi-format 1D/2D barcode image processing library implemented in Java."

Let's get it installed into Rails.

Simply use npm to install the package and then update yarn:

# Install with npm
npm i @zxing/library --save

# Update yarn packages
yarn install --check-files
Enter fullscreen mode Exit fullscreen mode

Basic QR code reader: Scan and display on the screen

First, we'll create a super simple scanner that just takes the data decoded from the QR code and displays it on the page.

Let's set up a route and a controller. You can, of course, name the route and controller whatever you want, but I'm going to make the path /basic-qr-code-reader and route it the #index action of a controller called the BasicQrCodesController:

# config/routes.rb

Rails.application.routes.draw do
  get 'basic-qr-code-reader', to: 'basic_qr_codes#index'
end
Enter fullscreen mode Exit fullscreen mode
# app/controllers/basic_qr_codes_controller.rb

class BasicQrCodesController < ApplicationController
  def index
  end
end
Enter fullscreen mode Exit fullscreen mode

The controller won't be doing anything special except for rendering a view, so it's just a barebones action.

Here is the basic JavaScript code—borrowing from one of ZXing's demos—that will get the job done. We'll put it into a file under app/javascript/packs/:

// app/javascript/packs/basic-qr-code-reader.js

import { BrowserQRCodeReader } from '@zxing/library';

const codeReader = new BrowserQRCodeReader();

codeReader
  .decodeFromInputVideoDevice(undefined, 'video')
  .then((result) => {
    // process the result
    console.log(result.text)

    document.getElementById('result').textContent = result.text
  })
  .catch(err => console.error(err));
Enter fullscreen mode Exit fullscreen mode

Here, codeReader is an instance of the ZXing's BrowserQRCodeReader class. In our case, we want to use a camera on some kind of device, so we'll use the decodeFromInputVideoDevice() method, which takes a device ID and a video element id as arguments. Passing in undefined chooses the default camera (if there are multiple cameras, you could allow the user to choose which one they want to use, for example. Check ZXing's documentation for more info).

decodeFromInputVideoDevice() will return a promise with the data (a string) that was decoded from the QR code.

In our HTML view, we need to make sure we have a video element with the given id (in our case it's just video). The JavaScript will decode the QR code from there.

Also, since we plan on displaying the result on the page, we need a place to put it. Examining the JavaScript above, you can see that the result is going to be injected into the HTML via an element that has an id of result:

document.getElementById('result').textContent = result.text
Enter fullscreen mode Exit fullscreen mode

Here is a basic view that includes all of the necessary elements as well as our QR code reader JavaScript inserted with a Rails javascript_pack_tag:

<%# app/views/basic_qr_codes/index.html.erb%>

<h1>QR Code Reader Demo</h1>

<div>
  <video id="video" width="500" height="400"></video>
</div>

<label>Result:</label>
<pre><code id="result"></code></pre>

<%= javascript_pack_tag("basic-qr-code-reader") %>
Enter fullscreen mode Exit fullscreen mode

And, that's it for the basic version! Start up the rails server with rails s and navigate to http://localhost:3000/basic-qr-code-reader. Try scanning a QR code and it should appear right there on the screen.

QR Code Reader on Rails: Processing scan data in Rails

The basic QR code scanner above is fun to play around with but it doesn't let us doing anything with the data we decoded.

In this section, we're going to get the JavaScript to handover the code data to Rails so we can process it and go further.

For this demo, we'll be set up a simple ActiveRecord model and controller that lets us use the data to create QR code objects and save them to the database. We'll also have a page that shows a history of our most recent scans.

Setting up a QR code model

Let's start off by creating a model called QrCode and database table where we can save the data.

Run rails g migration CreateQrCodes data:string and then open up the generated file to check it's contents:

# Migration for QrCodes

class CreateQrCodes < ActiveRecord::Migration[6.0]
  def change
    create_table :qr_codes do |t|
      t.string :data

      t.timestamps
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Run rails db:migrate to set up the table.

For the model, we just need the bare minimum for now:

# app/models/qr_code.rb

class QrCode < ApplicationRecord
end
Enter fullscreen mode Exit fullscreen mode

Initial routes and controller set up

To get things moving, we need to add some routes and a couple of controller actions.

We'll keep the flow basic: Open the scanner camera and get the data (new action), use the data to create a QrCode object (create action), and display the newly scanned QR code data (show action).

Add the necessary routes to routes.rb with the following line:

resources :qr_codes, only: [:new, :create, :show]
Enter fullscreen mode Exit fullscreen mode

The routes.rb should look like this now:

# config/routes.rb

Rails.application.routes.draw do
  get 'basic-qr-code-reader', to: 'basic_qr_codes#index'

  resources :qr_codes, only: [:new, :create, :show]
end
Enter fullscreen mode Exit fullscreen mode

We'll go ahead and stub out the controller actions, too. Create a file called qr_codes_controller.rb in the app/controllers directory:

# app/controllers/qr_codes_controller.rb

class QrCodesController < ApplicationController
  def new
  end

  def create
  end

  def show
  end
end
Enter fullscreen mode Exit fullscreen mode

Okay, that takes care of the wiring. Now on to the JavaScript.

JavaScript: Send the QR code scan data to Rails

We need to do a few extra things in the QR code reader JavaScript in order to get it to communicate with our controller. (We'll put the new version of the QR reader JavaScript in another pack file, app/javascript/packs/rails-qr-code-reader.js, for the purpose of this demonstration.)

Here's the code and I'll explain below:

// app/javascript/packs/rails-qr-code-reader.js

import { BrowserQRCodeReader } from '@zxing/library';
import Rails from '@rails/ujs'; // Use to make an ajax post request to Rails

const codeReader = new BrowserQRCodeReader();

codeReader
  .decodeFromInputVideoDevice(undefined, 'video')
  .then((result) => {
    let qrDataFromReader = result.text;

    // Prepare a post request so it can be sent to the Rails controller
    let formData = new FormData();

    let qrCodeParams = {
      qr_data: qrDataFromReader
    };

    formData.append("qr_code_json_data", JSON.stringify(qrCodeParams));

    // Send QR code data as JSON to the 
    // qr_codes#create action using Rails ujs
    Rails.ajax({
      url: "/qr_codes",
      type: "post",
      data: formData
    });

  })
  .catch(error => {
    console.error(error);
  });
Enter fullscreen mode Exit fullscreen mode

The plan is to send the QR code data to the create action of the QrCodes controller, which we'll do by making a POST request with ajax. To make that easier, we'll use Rails' ujs library. It needs to be imported into the JavaScript with import Rails from '@rails/ujs'; at the top of the file.

After getting the data from the code reader, we'll save it into variable:

let qrDataFromReader = result.text;
Enter fullscreen mode Exit fullscreen mode

Next, we'll prepare the data with by creating a new JavaScript FormData object, parameterizing the data, and converting it to JSON:

// Create a new FormData object
let formData = new FormData();

// Prepare the data params
let qrCodeParams = {
  qr_data: qrDataFromReader
}; 

// Add the params to the FormData object, making sure to convert it to JSON
formData.append("qr_code_json_data", JSON.stringify(qrCodeParams));
Enter fullscreen mode Exit fullscreen mode

Lastly, we just need to use the .ajax method of Rails ujs to send the data to the controller!

Rails.ajax({
  url: "/qr_codes",
  type: "post",
  data: formData
});
Enter fullscreen mode Exit fullscreen mode

Note that the url is the POST path for the QrCodesController.

Set up a view for the QR code reader in app/views/qr_codes/new.html.erb and make sure the JavaScript pack gets included:

<h1>QR Code Reader Demo</h1>

<h2>QR Code Reader on Rails</h2>

<div>
  <video id="video" width="500" height="400"></video>
</div>

<%= javascript_pack_tag("rails-qr-code-reader") %>
Enter fullscreen mode Exit fullscreen mode

Now that the scanner is ready, next we'll catch the scan data in the controller and process it.

Processing the QR code data

The data will arrive in the QrCodes controller create action. Because it comes in as JSON, we need to parse the JSON so we can use it. Let's add a before action that does that for us:

# app/controllers/qr_codes_controller.rb

class QrCodesController < ApplicationController
  before_action :set_qr_data, only: :create

  def index
  end

  def new
  end

  def create
  end

  def show
  end

  private

  def set_qr_data
    qr_code_params = JSON.parse(params[:qr_code_json_data]).with_indifferent_access

    @qr_data = qr_code_params[:qr_data]
  end
end
Enter fullscreen mode Exit fullscreen mode

Here, JSON.parse gets the data into a usable form and .with_indifferent_access allows us to access the data using symbols (qr_code_params[:qr_data]) instead of quotes (qr_code_params['qr_data']). Not necessary, but I prefer symbols.

The params are available with params[:qr_code_json_data] because we named them that in the JavaScript above with this line: formData.append("qr_code_json_data", JSON.stringify(qrCodeParams));.

Inside of the QR code params, the qr data is available through the qr_data key, also because of the way we set up the data earlier in the JavaScript (let qrCodeParams = { qr_data: qrDataFromReader };).

After the everything is parsed, assign the data to an instance variable like so @qr_data = qr_code_params[:qr_data] and now we can treat the data as usual and use it to create new QrCode objects!

Let's do so in the create action and then redirect to a show page where we can see the new database entry.

# app/controllers/qr_codes_controller.rb

class QrCodesController < ApplicationController
  before_action :set_qr_data, only: :create

  def index
  end

  def new
  end

  def create
    qr_code = QrCode.create(content: @qr_data)

    redirect_to qr_code_path(qr_code)
  end

  def show
    @qr_code = QrCode.find(params[:id])
  end

  private

  def set_qr_data
    qr_code_params = JSON.parse(params[:qr_code_json_data]).with_indifferent_access

    @qr_data = qr_code_params[:qr_data]
  end
end
Enter fullscreen mode Exit fullscreen mode

We just need to set up a basic show view where we can display the data, which we'll put in a new view file at app/views/qr_codes/new.html.erb:

<h1>QR Code Show Page</h1>

<p>QR data: <%= @qr_code.content %></p>
<p>Scanned on <%= @qr_code.created_at.strftime('%Y-%m-%d') %></p>
Enter fullscreen mode Exit fullscreen mode

Nothing special here, but it lets us see the scanned data and the day it was scanned on.

It might also be nice to have an index page that lists all the recent scans, so let's set up the index action in the controller. We'll keep it simple and just show the 20 most recent scans:

# app/controllers/qr_codes_controller.rb

class QrCodesController < ApplicationController
  before_action :set_qr_data, only: :create

  def index
    @qr_codes = QrCode.order(created_at: :desc).first(20)
  end

  def new
  end

  def create
    qr_code = QrCode.create(content: @qr_data)

    redirect_to qr_code_path(qr_code)
  end

  def show
    @qr_code = QrCode.find(params[:id])
  end

  private

  def set_qr_data
    qr_code_params = JSON.parse(params[:qr_code_json_data]).with_indifferent_access

    @qr_data = qr_code_params[:qr_data]
  end
end
Enter fullscreen mode Exit fullscreen mode

We also need the view for the index (app/views/qr_codes/index.html.erb), which we'll set up to be a simple list of scans that links to each scan's show page. It also has a button that links to the QR code scanner:

<h1>Recent QR Code Scans</h1>

<%= link_to 'New Scan', new_rails_qr_code_path, class: 'button' %>

<p><strong>20 most recent scans:</strong></p>
<ul>
  <% @qr_codes.each.with_index(1) do |qr_code, index| %>
    <li><%= "Scan #{index}: " %><%= link_to qr_code.content, rails_qr_code_path(qr_code) %></li>
  <% end %>
</ul>
Enter fullscreen mode Exit fullscreen mode

And that's it! You just made a QR coder reader on Rails! I have the full code on Github if you'd like to explore.

This is a somewhat contrived and simple example, but there are plenty of applications for scanning QR codes and using their data. For example, for the project I did at work, the scanned QR codes contained the manufacturer serial numbers of certain machines. The serial codes were used to find specific models based on the serial number (which was done in the controller) and take the user to that model's information page for troubleshooting.

Anyway, have fun with it and enjoy :D

Discussion (1)

Collapse
gathuku profile image
Moses Gathuku

Hey @morinoko , Thanks for this owesome blog . Do you have some ideas on how to use this library with a hand help qr code scanner(connected through a keyboard to a cumputer).