Decoding and Interacting with Barcodes

#67 Decoding and Interacting with Barcodes
2/12/2017

Summary

Using the QuaggaJS Library, learn how to scan and decode barcodes with your Ruby on Rails application. Using provided callbacks, interact with controller actions to provide a seamless experience.
4
rails javascript view ajax

Summary

application.css#barcode-scanner canvas.drawingBuffer, #barcode-scanner video.drawingBuffer {
  display: none;
}

#barcode-scanner canvas, #barcode-scanner video {
  width: 100%;
  height: auto;
}
application.js//= require quagga
//= require_tree .

function order_by_occurrence(arr) {
  var counts = {};
  arr.forEach(function(value){
      if(!counts[value]) {
          counts[value] = 0;
      }
      counts[value]++;
  });

  return Object.keys(counts).sort(function(curKey,nextKey) {
      return counts[curKey] < counts[nextKey];
  });
}

function load_quagga(){
  if ($('#barcode-scanner').length > 0 && navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {

    var last_result = [];
    if (Quagga.initialized == undefined) {
      Quagga.onDetected(function(result) {
        var last_code = result.codeResult.code;
        last_result.push(last_code);
        if (last_result.length > 20) {
          code = order_by_occurrence(last_result)[0];
          last_result = [];
          Quagga.stop();
          $.ajax({
            type: "POST",
            url: '/products/get_barcode',
            data: { upc: code }
          });
        }
      });
    }

    Quagga.init({
      inputStream : {
        name : "Live",
        type : "LiveStream",
        numOfWorkers: navigator.hardwareConcurrency,
        target: document.querySelector('#barcode-scanner')  
      },
      decoder: {
          readers : ['ean_reader','ean_8_reader','code_39_reader','code_39_vin_reader','codabar_reader','upc_reader','upc_e_reader']
      }
    },function(err) {
        if (err) { console.log(err); return }
        Quagga.initialized = true;
        Quagga.start();
    });

  }
};
$(document).on('turbolinks:load', load_quagga);
visitors/index.html.erb<div id='barcode-scanner'></div>
routes.rbRails.application.routes.draw do
  resources :products do
    post :get_barcode, on: :collection
  end
  root 'visitors#index'

# rake routes
#               Prefix Verb   URI Pattern                     Controller#Action
# get_barcode_products POST   /products/get_barcode(.:format) products#get_barcode
#             products GET    /products(.:format)             products#index
#                      POST   /products(.:format)             products#create
#          new_product GET    /products/new(.:format)         products#new
#         edit_product GET    /products/:id/edit(.:format)    products#edit
#              product GET    /products/:id(.:format)         products#show
#                      PATCH  /products/:id(.:format)         products#update
#                      PUT    /products/:id(.:format)         products#update
#                      DELETE /products/:id(.:format)         products#destroy
#                 root GET    /                               visitors#index
end
products_controller.rb  # GET /products/new
  def new
    @product = Product.new
    @product.upc = params[:upc]
  end

  ...

  # POST /products/get_barcode
  def get_barcode
    @product = Product.find_or_initialize_by(upc: params[:upc])
    unless @product.new_record?
      redirect_to @product
    else
      redirect_to new_product_path(upc: params[:upc])
    end
  end
00000000000000000000000000000000?d=mm&f=y&s=64
Nickiam said about 1 month ago:

Really cool episode! Thanks!

Photo
Michael Law said about 1 month ago:

Hey, I was having trouble once the barcode is captured/scanned. In the console it has the error, ""POST http://localhost:3000/products/get_barcode 404 (Not Found)". I have checked my routes and run rake routes, and they are coming up correctly. Any idea why that may be?

635114?v=3&s=64
kobaltz said about 1 month ago:

Can you post the JS and Routes file?

Photo
Michael Law said about 1 month ago:

I copied and pasted the code as you have it below, except my routes have 'orders' where you have 'products', and I'm only trying to find already existing orders.

The only difference is that my 'orders' routes are actually nested.

In my JS file, I have everything else exactly the same except the url in the ajax request.

$.ajax({
  type: "POST",
  url: '/orders/get_barcode',
  data: { upc: code }
});

Since my orders route is nested, when I run rake routes I get

get_barcode_ticket_orders ... POST ... /tickets/:ticket_id/orders/get_barcode(.:format) ... orders#get_barcode


In my routes file:

resources :tickets do
    resources :orders do
      post :get_barcode, on: :collection
    end
  end

635114?v=3&s=64
kobaltz said about 1 month ago:

With a nested routes like that,

get_barcode_ticket_orders POST   /tickets/:ticket_id/orders/get_barcode(.:format) orders#get_barcode

You would need to also pass the parameter ticket_id into the data hash.  Is the ticket_id available at this point? If so, you could pass it into a data attribute and access it in the JS. Otherwise, you may want to reconsider the routes to look something like this (or similar):

resources :tickets do
  resources :orders
end
post '/orders/get_barcode',  as: :get_barcode, controller: :orders, action: :get_barcode
# get_barcode POST   /orders/get_barcode(.:format)         orders#get_barcode

Photo
Michael Law said about 1 month ago:

I was just about to ask you if it was throwing the error because I'm not passing the ticket_id. I'm not the best with jQuery so I didn't know if the client required that, although as I type it this makes sense that it would.

What do you mean by "pass it into the data attribute"?

Are you saying that the client is throwing the original error I posted because it's trying to find the order, but doesn't have the ticket_id?

635114?v=3&s=64
kobaltz said about 1 month ago:

Since you don't have the ticket_id, it's not a valid route.

In your HTML, if the ticket_id is available and you're already displaying it somewhere then you can create a data attribute like

<%= tag :div, id: 'some_name', data: { 'ticket-id': @ticket.id } %>

You can access the value of the ticket's id in jQuery like

var ticket_id = $('#some_name').data('ticket-id');

And then in the AJAX POST

data: { ticket_id: ticket_id, upc: code }


Photo
Michael Law said about 1 month ago:

Hm. That's a problem because at this point the ticket_id is not accessible for me to put it in a div. Are there any other ways you can think of to do this? You don't have to write out the code, you can just let me know your ideas.

635114?v=3&s=64
kobaltz said about 1 month ago:

Looks like you should remove the collection on the orders and instead do something like this

post '/orders/get_barcode',  as: :get_barcode, controller: :orders, action: :get_barcode

Since it sounds like you have a barcode which references a record in the Order model, you're not really going to be able to do a nested resource and have the collection on the Orders. Instead, you can create a manual path (as shown above) which goes to your orders#get_barcode action.

In the get_barcode action, you can look up the order and then get the ticket association.

It might look something like

def get_barcode
  @ticket = Order.includes(:ticket).find_by(barcode: params[:upc]).ticket
end

This is assuming that a Ticket has_many Orders and an Order belongs_to a Ticket.

The previous example will generate two queries and could be a bit slower. You could get fancy with your query and also do something like this which will create an inner join

@ticket = Ticket.joins(:orders).where(orders: {barcode: params[:upc]})

Photo
Michael Law said about 1 month ago:

I wasn't able to figure it out, and even utilizing the hints you gave me didn't change much. All the errors are popping up in the console on the client. I'm not a jQuery guy, so I'll have to look into another solution for accomplishing this. Thanks for your help and the video is great!

Login to Comment