QR Code Reader on Rails
Tags: rails, ruby
Published: 2020-02-21 00:06:40

# 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
```

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](https://github.com/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
```


# 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`:

```ruby
# config/routes.rb

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

```ruby
# app/controllers/basic_qr_codes_controller.rb

class BasicQrCodesController < ApplicationController
  def index
  end
end
```

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](https://zxing-js.github.io/library/examples/qr-camera/)—that will get the job done. We'll put it into a file under `app/javascript/packs/`:

```javascript
// 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));
```

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`:

```javascript
document.getElementById('result').textContent = result.text
```

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`:

```erb
<%# 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") %>
```

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:

```ruby
# 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
```

Run `rails db:migrate` to set up the table. 

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

```ruby
# app/models/qr_code.rb

class QrCode < ApplicationRecord
end
```

## 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:

```ruby
resources :qr_codes, only: [:new, :create, :show]
```

The `routes.rb` should look like this now:

```ruby
# 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
```

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

```ruby
# app/controllers/qr_codes_controller.rb

class QrCodesController < ApplicationController
  def new
  end

  def create
  end

  def show
  end
end
```

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:

```javascript
// 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);
  });
```

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:

```javascript
let qrDataFromReader = result.text;
```

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

```javascript
// 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));
```

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

```javascript
Rails.ajax({
  url: "/qr_codes",
  type: "post",
  data: formData
});
```

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:

```erb
<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") %>
```

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:

```ruby
# 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
```

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.


```ruby
# 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
```

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`:

```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>
```

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:


```ruby
# 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
```

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:

```erb
<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>
```

And that's it! You just made a QR coder reader on Rails! I have the full code on [Github](https://github.com/morinoko/qr-code-reader-demo) 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