How to Connect to BigQuery from Ruby
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
| Library | Layer | When to use it | Version (as of July 2026) |
|---|---|---|---|
google-cloud-bigquery | Official Google client | Everything: queries, loads, exports, table management | 1.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-bigqueryOr 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:
GOOGLE_APPLICATION_CREDENTIALSenvironment variable pointing at a service account JSON key file.gcloud auth application-default logincredentials, for local development.- 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.jsonThen 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_idyou 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)
endCommon errors
| Error / symptom | Likely cause |
|---|---|
| Table "not found" but it exists | Dataset is in a different location than the query is running in |
Permission denied on jobs.create | Missing bigquery.jobUser on the billing project |
| Permission denied reading a table | Missing bigquery.dataViewer on the data project |
| Could not load default credentials | No GOOGLE_APPLICATION_CREDENTIALS, no gcloud ADC login, no attached service account |
| Query cost far higher than expected | No 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.
Skip the terminal. Use Mako.
Connect your database, write queries with AI assistance, and import/export data in clicks. Free to start.