How to Connect to BigQuery from Ruby

6 min readBigQuery

BigQuery has no host, port, or connection string. It is a REST API you authenticate against with a Google Cloud identity, so "connecting" from Ruby means installing the client library and getting credentials right. There is one official gem, google-cloud-bigquery, and it handles auth, query execution, and result streaming. This guide covers the credential ladder, the two project fields people confuse, parameterized queries, and the cost controls you should set before running anything against real data.

The library

LibraryLayerWhen to use itVersion (as of July 2026)
google-cloud-bigqueryOfficial Google clientEverything: queries, loads, exports, table management1.64.0

There is no ActiveRecord adapter worth recommending and no low-level driver to choose between. BigQuery is an API, not a wire-protocol database, so unlike PostgreSQL or MySQL there is no ecosystem of competing drivers. Use the official gem.

Installing

gem install google-cloud-bigquery

Or in a Gemfile:

gem "google-cloud-bigquery", "~> 1.64"

No native extension, no client library to install. Authentication is the only setup step that takes any thought.

Authentication: the credential ladder

The gem uses Application Default Credentials (ADC), the same mechanism every Google Cloud client library uses. It looks for credentials in this order:

  1. GOOGLE_APPLICATION_CREDENTIALS environment variable pointing at a service account JSON key file.
  2. gcloud auth application-default login credentials, for local development.
  3. The attached service account, when running on Google Cloud (GCE, Cloud Run, GKE). No key file needed, which is the recommended production setup.

The simplest local path:

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json

Then in Ruby:

require "google/cloud/bigquery"
 
bigquery = Google::Cloud::Bigquery.new(project_id: "my-billing-project")
 
results = bigquery.query "SELECT name, count FROM `my-data-project.census.names` LIMIT 10"
results.each { |row| puts "#{row[:name]}: #{row[:count]}" }

You can also pass credentials explicitly instead of relying on the environment:

bigquery = Google::Cloud::Bigquery.new(
  project_id:  "my-billing-project",
  credentials: "/path/to/service-account.json"
)

On Google Cloud infrastructure, attach a service account to the workload and drop the key file entirely. Long-lived JSON keys are a security liability; avoid them where the platform can inject identity for you.

The billing project vs data project split

This trips up almost everyone the first time. There are two separate project concepts:

  • The billing project is where the query runs and where the compute cost is charged. This is the project_id you pass when creating the client.
  • The data project is where the tables live. It appears in the fully qualified table name: `project.dataset.table`.

They are often the same project, but they do not have to be. You can run a query billed to your project against a public dataset in bigquery-public-data. If you get a permissions error, check which project the failure refers to: you need bigquery.jobs.create on the billing project (the roles/bigquery.jobUser role) and bigquery.tables.getData on the data project (roles/bigquery.dataViewer). Missing either produces a permission denied that looks identical at first glance.

Parameterized queries

BigQuery supports both named and positional parameters. Always parameterize user input; string interpolation into SQL is an injection risk here as everywhere.

Named parameters:

results = bigquery.query(
  "SELECT name, count
   FROM `bigquery-public-data.usa_names.usa_1910_2013`
   WHERE state = @state AND number >= @min
   ORDER BY count DESC LIMIT 20",
  params: { state: "CA", min: 100 }
)

Positional parameters use ? and an array:

results = bigquery.query(
  "SELECT name FROM `proj.ds.people` WHERE age >= ? AND city = ?",
  params: [21, "Zurich"]
)

For a NULL value where BigQuery cannot infer the type, pass the type explicitly with the types option so the parameter binds correctly rather than erroring on ambiguous type.

The dataset location trap

BigQuery datasets are created in a specific location (US, EU, asia-northeast1, and so on), and a query only sees datasets in the location it runs in. If your dataset is in EU and your query runs in the default US, BigQuery reports that the table was not found, which reads like a typo or a permissions problem but is actually a location mismatch.

Set the location explicitly when it is not the default:

results = bigquery.query(
  "SELECT * FROM `proj.eu_dataset.sales` LIMIT 100",
  location: "EU"
)

If a table you know exists returns "not found," check the dataset location before anything else.

Controlling cost: dry run and byte caps

BigQuery bills by bytes scanned, and a careless SELECT * on a large partitioned table can scan terabytes. Two controls prevent surprise bills.

A dry run estimates bytes scanned without running the query or incurring cost:

job = bigquery.query_job(
  "SELECT * FROM `proj.ds.events`",
  dryrun: true
)
puts "Would scan #{job.bytes_processed} bytes"

A byte cap makes the query fail rather than run if it would scan more than a limit you set:

results = bigquery.query(
  "SELECT * FROM `proj.ds.events` WHERE event_date = '2026-07-03'",
  maximum_bytes_billed: 10 * 1024**3   # fail if it would scan more than 10 GB
)

Setting maximum_bytes_billed on queries that run against user-controlled input or large tables is cheap insurance. Combine it with partition filters so you scan only the days you need.

Large result sets

bigquery.query loads results into memory. For large results, page through them or read from the destination table. The gem returns a paginated data object, so iterate with results.all (which fetches subsequent pages automatically) rather than assuming a single page holds everything:

results.all do |row|
  process(row)
end

Common errors

Error / symptomLikely cause
Table "not found" but it existsDataset is in a different location than the query is running in
Permission denied on jobs.createMissing bigquery.jobUser on the billing project
Permission denied reading a tableMissing bigquery.dataViewer on the data project
Could not load default credentialsNo GOOGLE_APPLICATION_CREDENTIALS, no gcloud ADC login, no attached service account
Query cost far higher than expectedNo partition filter and no maximum_bytes_billed cap

For loading data into BigQuery, see our guides on importing CSV to BigQuery and importing JSON to BigQuery.

Mako connects to BigQuery, PostgreSQL, ClickHouse, and more with AI-powered autocomplete. Try it free at mako.ai.

Mako — open source

Skip the terminal. Use Mako.

Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.