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.
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
:
# config/routes.rb
Rails.application.routes.draw do
get 'basic-qr-code-reader', to: 'basic_qr_codes#index'
end
# 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—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));
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
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") %>
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
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
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]
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
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
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);
});
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;
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));
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
});
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") %>
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
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
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>
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
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>
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
Top comments (4)
Hi @morinoko, incredible work... i'm trying to implement this scanner to follow the assistance to classroom... i downloaded the code, it works fine on pc... but on cellphone the camera not start...
Can u give a hand with that... i am not an expert, neither a professional programmer...
Greetings from Argentina!!!
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).
Hey there! Sorry just saw your comment. Unfortunately I don’t know how to use it with a handheld scanner…
Hey There this is great, any ide what would be needed to enable this on mobile?