Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: c210b74556
Fetching contributors…

Cannot retrieve contributors at this time

761 lines (600 sloc) 24.795 kB

About

This is the Factual supported Ruby driver for Factual's public API.

This driver is supported via the Factual Developer Group

Overview

Basic Design

The driver allows you to create an authenticated handle to Factual. With a Factual handle, you can send queries and get results back.

Queries are created using the Factual handle, which provides a fluent interface to constructing your queries.

# You can chain the query methods, like this:
factual.table("places").filters("category" => "Food & Beverage > Restaurants").search("sushi", "sashimi")
  .geo("$circle" => {"$center" => [34.06021, -118.41828], "$meters" => 5000})
  .sort("name").page(2, :per => 10)

Results are returned as Ruby Arrays of Hashes, where each Hash is a result record.

Setup

The driver's gems are hosted at Rubygems.org. You can install the factual-api gem as follows:

$ gem install factual-api

Once the gem is installed, you can use it in your Ruby project like:

require 'factual'
factual = Factual.new("YOUR_KEY", "YOUR_SECRET")

Simple Read Examples

# Return entities from the Places dataset with names beginning with "starbucks"
factual.table("places").filters("name" => {"$bw" => "starbucks"}).rows
# Return entity names and non-blank websites from the Global dataset, for entities located in Thailand
factual.table("global").select(:name, :website)
  .filters({"country" => "TH", "website" => {"$blank" => false}})
# Return highly rated U.S. restaurants in Los Angeles with WiFi
factual.table("restaurants-us")
  .filters({"locality" => "los angeles", "rating" => {"$gte" => 4}, "wifi" => true}).rows

Simple Places Example

# Concordance information of a place
FACTUAL_ID = "110ace9f-80a7-47d3-9170-e9317624ebd9"
query = factual.crosswalk(FACTUAL_ID)
query.rows

# Or specify a place with its namespace_id and namespace
SIMPLEGEO_ID = "SG_6XIEi3qehN44LH8m8i86v0"
query = factual.crosswalk(SIMPLEGEO_ID, :simplegeo)
query.rows
# Returns resolved entities as an array of hashes
query = factual.resolve("name" => "McDonalds",
                        "address" => "10451 Santa Monica Blvd",
                        "region" => "CA",
                        "postcode" => "90025")

query.first["resolved"]   # true or false
query.rows                # all candidate rows
# Returns the nearest valid address information
query = factual.geocode(34.06021,-118.41828)
query.first
# Returns georeferenced attributes generated by Factual
query = factual.geopulse(34.06021,-118.41828).select("commercial_density", "commercial_profile", "income", "race")
query.first["income"]

More Read Examples

# 1. Specify the table Global
query = factual.table("global")
# 2. Filter results in country US
query = query.filters("country" => "US")
# 3. Search for "sushi" or "sashimi"
query = query.search("sushi", "sashimi")
# 4. Filter by geolocation
query = query.geo("$circle" => {"$center" => [34.06021, -118.41828], "$meters" => 5000})
# 5. Sort it 
query = query.sort("name")            # ascending 
query = query.sort_desc("name")       # descending
query = query.sort("address", "name") # sort by multiple columns
# 6. Page it
query = query.page(2, :per => 10)
# 7. Finally, get response in a hash or array of hashes
query.first    # return one row
query.rows     # return many rows
# 8. Returns total row counts that matches the criteria
query.total_count

Read API

All Top Level Query Parameters

Parameter Description Example
filters Restrict the data returned to conform to specific conditions. query = query.filters("name" => {"$bw" => "starbucks"})
get total row count returns the total count of the number of rows in the dataset that conform to the query. query.total_count
geo Restrict data to be returned to be within a geographical range based. (See the section on Geo Filters)
limit Limit the results query = query.limit(12)
page Limit the results to a specific "page". query = query.page(2, :per => 10)
search (across entity) Full text search across entity Find "sushi":
query = query.search("sushi")

Find "sushi" or "sashimi":
query = query.search("sushi", "sashimi")

Find "sushi" and "santa" and "monica":
query.search("sushi santa monica")

search (across field) Full text search on specific field query = query.filters({"name" => {"$search" => "cafe"}})
select Specifiy which fields to include in the query results. Note that the order of fields will not necessarily be preserved in the resulting response due to the nature Hashes. query = query.select(:name, :address, :locality, :region)
sort The field (or fields) to sort data on, as well as the direction of sort.

Sorts ascending by default, but supports both explicitly sorting ascending and descending, by using sort_asc or sort_desc. Supports $distance as a sort option if a geo-filter is specified.

Supports $relevance as a sort option if a full text search is specified either using the q parameter or using the $search operator in the filter parameter.

By default, any query with a full text search will be sorted by relevance.

Any query with a geo filter will be sorted by distance from the reference point. If both a geo filter and full text search are present, the default will be relevance followed by distance.

query = query.sort("name")
query = query.sort_desc("$distance") query = query.sort_asc("name").sort_desc("rating")

Row Filters

The driver supports various row filter logic. For example:

# Returns records from the Places dataset with names beginning with "starbucks"
factual.table("places").filters("name" => {"$bw" => "starbucks"}).rows

Supported row filter logic

Predicate Description Example
$eq equal to query = query.filters("region" => {"$eq" => "CA"})
$neq not equal to query = query.filters("region" => {"$neq" => "CA"})
search full text search query = query.search("sushi")
$in equals any of query = query.filters("region" => {"$in" => ["CA", "NM", "NY"]})
$nin does not equal any of query = query.filters("region" => {"$nin" => ["CA", "NM", "NY"]})
$bw begins with query = query.filters("name" => {"$bw" => "starbucks"})
$nbw does not begin with query = query.filters("name" => {"$nbw" => "starbucks"})
$bwin begins with any of query = query.filters("name" => {"$bwin" => ["starbucks", "coffee", "tea"]})
$nbwin does not begin with any of query = query.filters("name" => {"$nbwin" => ["starbucks", "coffee", "tea"]})
$blank test to see if a value is (or is not) blank or null query = query.filters("tel" => {"$blank" => true})
query = query.filters("website" => {"$blank" => false})
$gt greater than query = query.filters("rating" => {"$gt" => 7.5})
$gte greater than or equal query = query.filters("rating" => {"$gte" => 7.5})
$lt less than query = query.filters("rating" => {"$lt" => 7.5})
$lte less than or equal query = query.filters("rating" => {"$lte" => 7.5})

AND

Filters can be logically AND'd together. For example:

# name begins with "coffee" AND tel is not blank
query = query.filters({ "$and" => [{"name" => {"$bw" => "coffee"}}, {"tel" => {"$blank" => false}}] })

OR

Filters can be logically OR'd. For example:

# name begins with "coffee" OR tel is not blank
query = query.filters({ "$or" => [{"name" => {"$bw" => "coffee"}}, {"tel" => {"$blank" => false}}] })

Combined ANDs and ORs

You can nest AND and OR logic to whatever level of complexity you need. For example:

# (name begins with "Starbucks") OR (name begins with "Coffee")
# OR
# (name full text search matches on "tea" AND tel is not blank)
query = query.filters({ "$or" => [ {"$or" => [ {"name" => {"$bw" => "starbucks"}},
                                               {"name" => {"$bw" => "coffee"}}]},
                                   {"$and" => [ {"name" => {"$search" => "tea"}},
                                                {"tel" => {"$blank" => false}} ]} ]})

Crosswalk

The driver fully supports Factual's Crosswalk feature, which lets you "crosswalk" the web and relate entities between Factual's data and that of other web authorities.

(See the Crosswalk Blog for more background.)

Simple Crosswalk Example

# Get all Crosswalk data for a Place with a specific FactualID
factual.crosswalk("110ace9f-80a7-47d3-9170-e9317624ebd9").rows

Supported Crosswalk Options

only

You can specify which namespaces you want, like this:

# Get Crosswalk data for only yelp and facebook for a Place with a specific FactualID
factual.crosswalk("110ace9f-80a7-47d3-9170-e9317624ebd9").only(:yelp, :facebook).rows

This will generally return 1 record per requested namespace.

limit

You can limit the amount of Crosswalk results you get back, like this:

# Get only 3 Crosswalk results for a Place with a specific FactualID
factual.crosswalk("110ace9f-80a7-47d3-9170-e9317624ebd9").limit(3).rows

Resolve

The driver fully supports Factual's Resolve feature, which lets you start with incomplete data you may have for an entity, and get potential entity matches back from Factual.

Each result record will include a confidence score ("similarity"), and a flag indicating whether Factual decided the entity is the correct resolved match with a high degree of accuracy ("resolved").

For any Resolve query, there will be 0 or 1 entities returned with "resolved"=true. If there was a full match, it is guaranteed to be the first record in the response Array.

(See the Resolve Blog for more background.)

Simple Resolve Examples

# Returns resolved entities as an array of hashes
query = factual.resolve("name" => "McDonalds",
                        "address" => "10451 Santa Monica Blvd",
                        "region" => "CA",
                        "postcode" => "90025")

query.first["resolved"] # true or false
query.rows              # all candidate rows

Geo Filters

You can query Factual for entities located within a geographic area. For example:

query = query.geo("$circle" => {"$center" => [34.06021, -118.41828], "$meters" => 5000})

Facets

The driver fully supports Factual's Facets feature, which lets you return row counts for Factual tables, grouped by facets of data. For example, you may want to query all businesses within 1 mile of a location and for a count of those businesses by category.

For example:

# Returns a count of Starbucks by country
factual.facets("global").select("country").search("starbucks").columns

All Top Level Facets Parameters

Parameter Description Example
select The fields for which facets should be generated. The response will not be ordered identically to this list, nor will it reflect any nested relationships between fields. .select("region", "locality")
limit The maximum number of unique facet values that can be returned for a single field. Range is 1-250. The default is 25. .limit(10)
filters Restrict the data returned to conform to specific conditions. .filters("name" => {"$bw" => "starbucks"})
include_count Include a count of the total number of rows in the dataset that conform to the request based on included filters. Requesting the row count will increase the time required to return a response. The default behavior is to NOT include a row count. When the row count is requested, the Response object will contain a valid total row count via .getTotalRowCount(). .include_count
geo Restrict data to be returned to be within a geographical range. (See the section on Geo Filters)
search Full text search query string. Find "sushi":
.search("sushi")

Find "sushi" or "sashimi":
.search("sushi, sashimi")

Find "sushi" and "santa" and "monica":
.search("sushi santa monica")

Flag

Factual's Flag feature enables flagging problematic rows in Factual tables. Use this feature if you are requesting for an entity to be deleted or merged into a duplicate record.

Simple Flag Example

# User user123 flags row 0545b03f-9413-44ed-8882-3a9a461848da in the global dataset as inaccurate
factual.flag("global", "0545b03f-9413-44ed-8882-3a9a461848da", :inaccurate, "user123").write

The reason for flagging must be one of the following:

  • :duplicate
  • :nonexistent
  • :inaccurate
  • :inappropriate
  • :spam
  • :other

You can also create a Flag object and set it with optional parameters beforing writing it. For example:

flag = factual.flag("global", "0545b03f-9413-44ed-8882-3a9a461848da", :inaccurate, "user123")
flag = flag.comment("this row is outdated").reference("http://www.example.com/somepage.html")
flag.write

Optional Flag Parameters

Parameter Description Example
comment Any english text comment that may help explain your corrections. .comment("this row is outdated")
reference A reference to a URL, title, person, etc. that is the source of this data. .reference("http://www.example.com/somepage.html")

Geopulse

The driver fully supports Factual's Geopulse feature, which provides point-based access to geographic attributes: you provide a long/lat coordinate pair, we provide everything we can know about that geography.

The Geopulse API is made up of several "pulses". Pulses are georeferenced attributes generated by Factual, sourced from openly available content (such as the US Census), or provided to Factual by proprietary third-parties.

Simple Geopulse Example

The geopulse method fetches results based on the given point:

query = factual.geopulse(34.06021, -118.41828).select("commercial_density", "commercial_profile")
query.first

All Top Level Geopulse Parameters

Parameter Description Example
geo A geographic point around which information is retrieved. factual.geopulse(34.06021, -118.41828)
select What fields to include in the query results. Note that the order of fields will not necessarily be preserved in the resulting JSON response due to the nature of JSON hashes. .select("commercial_density", "commercial_profile")

Available pulses include commercial_density, commercial_profile, income, race, hispanic, and age_by_gender.

You can see a full list of available Factual pulses and their possible return values, as well as full documentation, in the Factual API docs for Geopulse.

Reverse Geocoder

The driver fully supports Factual's Reverse Geocoder feature, which returns the nearest valid address given a longitude and latitude.

Simple Reverse Geocoder Example

The geocode method fetches results based on the given point:

query = factual.geocode(34.06021, -118.41828)
query.first

All Top Level Reverse Geocoder Parameters

Parameter Description Example
geo A valid geographic point for which the closest address is retrieved. factual.geocode(34.06021, -118.41828)

World Geographies

World Geographies contains administrative geographies (states, counties, countries), natural geographies (rivers, oceans, continents), and assorted geographic miscallaney. This resource is intended to complement Factual's Global Places and add utility to any geo-related content.

Common use cases include:

  • Determining all cities within a state or all postal codes in a city
  • Creating a type-ahead placename lookup
  • Validating data against city, state, country and county names
  • A translation table to convert between the search key used by a user, i.e. '慕尼黑' or 'Munich' for the native 'München'

You can use the query function to query World Geographies, supplying "world-geographies" as the table name.

Examples:

# Get all towns surrounding Philadelphia
query = factual.table("world-geographies").select("neighbors").filters(:factual_id => "08ca0f62-8f76-11e1-848f-cfd5bf3ef515")
query.rows
# Find the town zipcode 95008 belongs to
query = factual.table("world-geographies").filters(
    {"$and" => [ {:name => "95008"},
                 {:country => "us"} ]} )
query.rows
# Searching by placename, placetype, country and geographic hierarchy
query = factual.table("world-geographies").filters(
    {"$and" => [ {:name => "wayne"},
                 {:country => "us"},
                 {:placetype => "locality"},
                 {:ancestors => {:"$search" => "08666f5c-8f76-11e1-848f-cfd5bf3ef515"}} ]} )

query.rows

For more details about World Geographies, including schema, see the main API docs for World Geographies.

Submit

The driver fully supports Factual's Submit feature, which enables you to submit edits to existing rows and/or submit new rows of data in Factual tables. For information on deleting records, see Flag.

Simple Submit Examples

The submit method is a contribution to edit an existing row or add a new row:

# Submit a new row to Factual's global dataset
factual.submit("global", "user123").values({"name" => "McDenny's", "address" => "1 Main St.", "locality" => "Bedrock", "region" => "BC"}).write
# Submit a correction to an existing row in Factual's places dataset.
# Also set optional comment and reference
submit = factual.submit("places", "user123").values({:name => "McDenny's"})
submit = submit.comment("They changed their name last month").reference("http://www.example.com/mypage.html")
submit.write

Required Submit Parameters

Parameter Description Example
values A hash of names and values to be added to a Factual table {"name" => "McDenny's", "address" => "1 Main St.", "locality" => "Bedrock", "region" => "BC"}
user An arbitrary token representing the user contributing the data. user123

Optional Submit Parameters

Parameter Description Example
factual_id The factual_id of the row to which you want to submit a correction .factual_id("0545b03f-9413-44ed-8882-3a9a461848da")
comment Any english text comment that may help explain your corrections. .comment("They changed their name last month")
reference A reference to a URL, title, person, etc. that is the source of this data. .reference("http://www.example.com/mypage.html")

Schema

You can query Factual for the detailed schema of any specific table in Factual. For example:

# Returns a hash of metadata for the table named "global", including an array of fields
factual.table("global").schema

Raw Read Queries

You can perform any read queries documented in the Factual API using the raw read query. Just supply the full path (including the first forward-slash) and the request will be made using your OAuth token:

# Find rows in the restaurant database whose name begins with "Star" and return both the data and a total count of the matched rows
factual.read('/t/restaurants-us?filters={"name":{"$bw":"Star"}}&include_count=true')

Monetize

The Monetize API enables you to find deals for places in Factual's Global Places database.

query = @factual.monetize.search('sushi').filters(:place_postcode => 90067)
query.rows

Multi

The multi API call enables making multiple API GET requests on the same connection. Multi supports the read, crosswalk, resolve, facets, geocode and geopulse API calls.

places_query = @factual.table("places").search('sushi').filters(:postcode => 90067)
geocode_query = @factual.geocode(34.06021,-118.41828)

responses = @factual.multi(
  :nearby_sushi => places_query,
  :factual_inc => geocode_query)

responses[:nearby_sushi].first
responses[:factual_inc].first

Debug Mode

To see the query paths generated by the driver you can use it in debug mode by passing :debug => true as the third argument of the Factual constructor. Here is an example in irb:

> factual = Factual.new(key, secret, :debug => true)
> factual.table("places").filters("name" => {"$bw" => "starbucks"})
Request: http://api.v3.factual.com/t/places?filters=%7B%22name%22%3A%7B%22%24bw%22%3A%22starbucks%22%7D%7D
 => [{"address"=>"11290 Donner Pass Rd", "category"=>"Food & Beverage > Cafes, Coffee Houses & Tea Houses", ...

Where to Get Help

If you think you've identified a specific bug in this driver, please file an issue in the github repo. Please be as specific as you can, including:

  • What you did to surface the bug
  • What you expected to happen
  • What actually happened
  • Detailed stack trace and/or line numbers

If you are having any other kind of issue, such as unexpected data or strange behaviour from Factual's API (or you're just not sure WHAT'S going on), please contact us through GetSatisfaction.

Jump to Line
Something went wrong with that request. Please try again.