VinDecoder

vin_decoder is a lightweight Ruby client for the NHTSA vPIC API. It decodes 17-character VINs and exposes a tidy domain object so you can access make, model, year, powertrain data, and more without sifting through raw JSON.

This is used on Wenmar Pro, a shop management software.

Installation

Add the gem to your project (when published):

gem 'vin_decoder'

Or install directly from source:

git clone https://github.com/Wenmar-Pro/vin_decoder.git
cd vin_decoder
bundle install

Configuration

Use the top-level VinDecoder.configure block to customize the base URL (handy for mocking) or request timeout. Defaults target the official NHTSA API with a 10-second timeout.

VinDecoder.configure do |config|
  config.base_url = 'https://vpic.nhtsa.dot.gov/api/vehicles'
  config.timeout = 5
end

Usage

Module-level shortcut

vehicle = VinDecoder.decode('1HGCM82633A004352')

if vehicle.valid?
  puts [vehicle.year, vehicle.make, vehicle.model].compact.join(' ')
  # => "2003 HONDA Accord"
else
  warn 'VIN could not be decoded'
end

Client instance

client = VinDecoder::Client.new
vehicle = client.decode('1HGCM82633A004352')

if vehicle.valid?
  puts "Engine: #{vehicle.engine} (#{vehicle.engine_cylinders} cylinders)"
  puts "Fuel: #{vehicle.fuel_type}"
  puts "Body style: #{vehicle.body_style}"
  puts "Transmission: #{vehicle.transmission}"
  puts "Drivetrain: #{vehicle.drivetrain}"
  puts "Submodel: #{vehicle.submodel}"
  puts "Fuel type (raw accessor): #{vehicle['FuelTypePrimary']}"
  puts "Lot size (dynamic): #{vehicle.lot_size}"
else
  warn 'VIN could not be decoded'
end

The client raises:

  • VinDecoder::NotFoundError when the API responds with HTTP 404.
  • VinDecoder::ApiError for any other non-200 HTTP status code.

Vehicle object

Each VinDecoder::Vehicle exposes the entire response payload:

  • Common helper methods: make, model, year, trim, submodel, body_style, engine, engine_cylinders, transmission, drivetrain, fuel_type, vin.
  • Hash-style access with string or symbol keys (vehicle['ModelYear'] / vehicle[:ModelYear]).
  • Dynamic snake_case readers derived from the raw keys (vehicle.vehicle_descriptor, vehicle.fuel_type_secondary). Missing fields raise NoMethodError so mistakes are caught quickly.
  • to_h — Returns a plain Ruby hash with all structured fields, suitable for serializing or passing to form builders:
vehicle.to_h
# => {
#   vin: "1HGCM82633A004352",
#   year: "2003",
#   make: "HONDA",
#   model: "Accord",
#   submodel: "EX-V6",
#   body_style: "...",
#   engine: "HONDA V6 240",
#   transmission: "...",
#   drivetrain: "...",
#   fuel_type: "Gasoline",
#   raw_data: { ... },
#   trim: "EX-V6",
#   engine_cylinders: "6"
# }

Rails Integration

Add the gem to your Rails app (from RubyGems or a local path while developing).

# Gemfile
gem 'vin_decoder', path: '../path/to/vin_decoder'

Create an initializer to configure timeouts and base URL:

# config/initializers/vin_decoder.rb
VinDecoder.configure do |config|
  config.timeout = ENV.fetch('VIN_DECODER_TIMEOUT', 5).to_i
  config.base_url = ENV.fetch('VIN_DECODER_BASE_URL', 'https://vpic.nhtsa.dot.gov/api/vehicles')
end

Wrap the client in a PORO/service for controllers or jobs:

# app/services/vin_lookup.rb
class VinLookup
  def initialize(client: VinDecoder::Client.new)
    @client = client
  end

  def call(vin)
    vehicle = @client.decode(vin)
    raise VinDecoder::NotFoundError unless vehicle.valid?

    vehicle
  end
end

Example controller usage:

class VehiclesController < ApplicationController
  def show
    @vehicle = VinLookup.new.call(params[:vin])
  rescue VinDecoder::NotFoundError
    redirect_to root_path, alert: 'VIN not found'
  end
end

You can also enqueue lookups via ActiveJob/Sidekiq and cache results to avoid repeated external calls.

Testing & VCR Cassettes

Tests run via RSpec and rely on VCR/WebMock for deterministic HTTP interactions.

bundle exec rspec

To refresh the live cassette, delete spec/fixtures/vcr_cassettes/decode_success.yml and rerun the specs. VCR will connect to the real API once, capture the response, and reuse it on subsequent runs.

Publishing Checklist

  1. Ensure version in lib/vin_decoder/version.rb reflects the release.
  2. Update CHANGELOG or release notes as needed (add one if not yet created).
  3. Build the gem and inspect the package contents:
   gem build vin_decoder.gemspec
   gem install vin_decoder-<version>.gem
  1. Push the artifact to RubyGems:
   gem push vin_decoder-<version>.gem
  1. Tag the release in git and push tags to GitHub for reference.

That's it—you now have a fully tested VIN decoder client ready for reuse or publication.