factual-api 1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +14 -3
- data/README.md +344 -11
- data/lib/factual.rb +55 -2
- data/lib/factual/api.rb +73 -13
- data/lib/factual/multi.rb +38 -0
- data/lib/factual/query/base.rb +11 -2
- data/lib/factual/query/crosswalk.rb +1 -1
- data/lib/factual/query/facets.rb +41 -0
- data/lib/factual/query/geocode.rb +15 -0
- data/lib/factual/query/geopulse.rb +21 -0
- data/lib/factual/query/monetize.rb +24 -0
- data/lib/factual/query/resolve.rb +1 -1
- data/lib/factual/query/table.rb +7 -1
- data/lib/factual/write/base.rb +34 -0
- data/lib/factual/write/flag.rb +37 -0
- data/lib/factual/write/submit.rb +40 -0
- metadata +11 -5
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,20 @@
|
|
1
|
+
## v1.2
|
2
|
+
* releasing geo features, facets, writes, multi and monetize
|
3
|
+
|
4
|
+
## v1.1.9
|
5
|
+
* added geocode, geopulse and world-geographies
|
6
|
+
* added facets
|
7
|
+
* adding writes (submit, flag)
|
8
|
+
* updated documents
|
9
|
+
* added multi
|
10
|
+
* added monetize
|
11
|
+
|
1
12
|
## v1.1
|
2
|
-
* debug mode
|
3
|
-
* raw read
|
13
|
+
* added debug mode
|
14
|
+
* added raw read
|
4
15
|
|
5
16
|
## v1.0.3
|
6
|
-
*
|
17
|
+
* added crosswalk(namespace_id, namespace)
|
7
18
|
|
8
19
|
## v1.0.2
|
9
20
|
* lazy getting total_count of a query
|
data/README.md
CHANGED
@@ -2,12 +2,6 @@
|
|
2
2
|
|
3
3
|
This is the Factual supported Ruby driver for [Factual's public API](http://developer.factual.com/display/docs/Factual+Developer+APIs+Version+3).
|
4
4
|
|
5
|
-
This API supports queries to Factual's Read, Schema, Crosswalk, and Resolve APIs. Full documentation is available on the Factual website:
|
6
|
-
|
7
|
-
* [Read](http://developer.factual.com/display/docs/Factual+Developer+APIs+Version+3): Search the data
|
8
|
-
* [Schema](http://developer.factual.com/display/docs/Core+API+-+Schema): Get table metadata
|
9
|
-
* [Crosswalk](http://developer.factual.com/display/docs/Places+API+-+Crosswalk): Get third-party IDs
|
10
|
-
* [Resolve](http://developer.factual.com/display/docs/Places+API+-+Resolve): Enrich your data and match it against Factual's
|
11
5
|
|
12
6
|
This driver is supported via the [Factual Developer Group](https://groups.google.com/group/factual_developers)
|
13
7
|
|
@@ -62,7 +56,7 @@ factual.table("restaurants-us")
|
|
62
56
|
.filters({"locality" => "los angeles", "rating" => {"$gte" => 4}, "wifi" => true}).rows
|
63
57
|
````
|
64
58
|
|
65
|
-
## Simple
|
59
|
+
## Simple Places Example
|
66
60
|
|
67
61
|
````ruby
|
68
62
|
# Concordance information of a place
|
@@ -76,8 +70,6 @@ query = factual.crosswalk(SIMPLEGEO_ID, :simplegeo)
|
|
76
70
|
query.rows
|
77
71
|
````
|
78
72
|
|
79
|
-
## Simple Resolve Example
|
80
|
-
|
81
73
|
````ruby
|
82
74
|
# Returns resolved entities as an array of hashes
|
83
75
|
query = factual.resolve("name" => "McDonalds",
|
@@ -89,6 +81,18 @@ query.first["resolved"] # true or false
|
|
89
81
|
query.rows # all candidate rows
|
90
82
|
````
|
91
83
|
|
84
|
+
````ruby
|
85
|
+
# Returns the nearest valid address information
|
86
|
+
query = factual.geocode(34.06021,-118.41828)
|
87
|
+
query.first
|
88
|
+
````
|
89
|
+
|
90
|
+
````ruby
|
91
|
+
# Returns georeferenced attributes generated by Factual
|
92
|
+
query = factual.geopulse(34.06021,-118.41828).select("commercial_density", "commercial_profile", "income", "race")
|
93
|
+
query.first["income"]
|
94
|
+
````
|
95
|
+
|
92
96
|
## More Read Examples
|
93
97
|
|
94
98
|
````ruby
|
@@ -393,6 +397,299 @@ You can query Factual for entities located within a geographic area. For example
|
|
393
397
|
query = query.geo("$circle" => {"$center" => [34.06021, -118.41828], "$meters" => 5000})
|
394
398
|
````
|
395
399
|
|
400
|
+
# Facets
|
401
|
+
|
402
|
+
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.
|
403
|
+
|
404
|
+
For example:
|
405
|
+
|
406
|
+
````ruby
|
407
|
+
# Returns a count of Starbucks by country
|
408
|
+
factual.facets("global").select("country").search("starbucks").columns
|
409
|
+
````
|
410
|
+
|
411
|
+
## All Top Level Facets Parameters
|
412
|
+
|
413
|
+
<table>
|
414
|
+
<col width="33%"/>
|
415
|
+
<col width="33%"/>
|
416
|
+
<col width="33%"/>
|
417
|
+
<tr>
|
418
|
+
<th>Parameter</th>
|
419
|
+
<th>Description</th>
|
420
|
+
<th>Example</th>
|
421
|
+
</tr>
|
422
|
+
<tr>
|
423
|
+
<td>select</td>
|
424
|
+
<td>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.</td>
|
425
|
+
<td><tt>.select("region", "locality")</tt></td>
|
426
|
+
</tr>
|
427
|
+
<tr>
|
428
|
+
<td>limit</td>
|
429
|
+
<td>The maximum number of unique facet values that can be returned for a single field. Range is 1-250. The default is 25.</td>
|
430
|
+
<td><tt>.limit(10)</tt></td>
|
431
|
+
</tr>
|
432
|
+
<tr>
|
433
|
+
<td>filters</td>
|
434
|
+
<td>Restrict the data returned to conform to specific conditions.</td>
|
435
|
+
<td><tt>.filters("name" => {"$bw" => "starbucks"})</tt></td>
|
436
|
+
</tr>
|
437
|
+
<tr>
|
438
|
+
<td>include_count</td>
|
439
|
+
<td>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 <tt>.getTotalRowCount()</tt>.</td>
|
440
|
+
<td><tt>.include_count</tt></td>
|
441
|
+
</tr>
|
442
|
+
<tr>
|
443
|
+
<td>geo</td>
|
444
|
+
<td>Restrict data to be returned to be within a geographical range.</td>
|
445
|
+
<td>(See the section on Geo Filters)</td>
|
446
|
+
</tr>
|
447
|
+
<tr>
|
448
|
+
<td>search</td>
|
449
|
+
<td>Full text search query string.</td>
|
450
|
+
<td>
|
451
|
+
Find "sushi":<br><tt>.search("sushi")</tt><p>
|
452
|
+
Find "sushi" or "sashimi":<br><tt>.search("sushi, sashimi")</tt><p>
|
453
|
+
Find "sushi" and "santa" and "monica":<br><tt>.search("sushi santa monica")</tt>
|
454
|
+
</td>
|
455
|
+
</tr>
|
456
|
+
</table>
|
457
|
+
|
458
|
+
</table>
|
459
|
+
|
460
|
+
|
461
|
+
# Flag
|
462
|
+
|
463
|
+
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.
|
464
|
+
|
465
|
+
## Simple Flag Example
|
466
|
+
|
467
|
+
````ruby
|
468
|
+
# User user123 flags row 0545b03f-9413-44ed-8882-3a9a461848da in the global dataset as inaccurate
|
469
|
+
factual.flag("global", "0545b03f-9413-44ed-8882-3a9a461848da", :inaccurate, "user123").write
|
470
|
+
````
|
471
|
+
|
472
|
+
The reason for flagging must be one of the following:
|
473
|
+
<ul>
|
474
|
+
<li>:duplicate
|
475
|
+
<li>:nonexistent
|
476
|
+
<li>:inaccurate
|
477
|
+
<li>:inappropriate
|
478
|
+
<li>:spam
|
479
|
+
<li>:other
|
480
|
+
</ul>
|
481
|
+
|
482
|
+
You can also create a Flag object and set it with optional parameters beforing writing it. For example:
|
483
|
+
|
484
|
+
````ruby
|
485
|
+
flag = factual.flag("global", "0545b03f-9413-44ed-8882-3a9a461848da", :inaccurate, "user123")
|
486
|
+
flag = flag.comment("this row is outdated").reference("http://www.example.com/somepage.html")
|
487
|
+
flag.write
|
488
|
+
````
|
489
|
+
|
490
|
+
## Optional Flag Parameters
|
491
|
+
|
492
|
+
<table>
|
493
|
+
<tr>
|
494
|
+
<th>Parameter</th>
|
495
|
+
<th>Description</th>
|
496
|
+
<th>Example</th>
|
497
|
+
</tr>
|
498
|
+
<tr>
|
499
|
+
<td>comment</td>
|
500
|
+
<td>Any english text comment that may help explain your corrections.</td>
|
501
|
+
<td><tt>.comment("this row is outdated")</tt></td>
|
502
|
+
</tr>
|
503
|
+
<tr>
|
504
|
+
<td>reference</td>
|
505
|
+
<td>A reference to a URL, title, person, etc. that is the source of this data.</td>
|
506
|
+
<td><tt>.reference("http://www.example.com/somepage.html")</tt></td>
|
507
|
+
</tr>
|
508
|
+
</table>
|
509
|
+
|
510
|
+
|
511
|
+
# Geopulse
|
512
|
+
|
513
|
+
The driver fully supports Factual's <a href="http://developer.factual.com/display/DOCS/Places+API+-+Geopulse">Geopulse</a> 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.
|
514
|
+
|
515
|
+
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.
|
516
|
+
|
517
|
+
## Simple Geopulse Example
|
518
|
+
|
519
|
+
The <tt>geopulse</tt> method fetches results based on the given point:
|
520
|
+
|
521
|
+
````ruby
|
522
|
+
query = factual.geopulse(34.06021, -118.41828).select("commercial_density", "commercial_profile")
|
523
|
+
query.first
|
524
|
+
````
|
525
|
+
|
526
|
+
|
527
|
+
## All Top Level Geopulse Parameters
|
528
|
+
|
529
|
+
<table>
|
530
|
+
<tr>
|
531
|
+
<th>Parameter</th>
|
532
|
+
<th>Description</th>
|
533
|
+
<th>Example</th>
|
534
|
+
</tr>
|
535
|
+
<tr>
|
536
|
+
<td>geo</td>
|
537
|
+
<td>A geographic point around which information is retrieved.</td>
|
538
|
+
<td><tt>factual.geopulse(34.06021, -118.41828)</tt></td>
|
539
|
+
</tr>
|
540
|
+
<tr>
|
541
|
+
<td>select</td>
|
542
|
+
<td>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.</td>
|
543
|
+
<td><tt>.select("commercial_density", "commercial_profile")</tt></td>
|
544
|
+
</tr>
|
545
|
+
</table>
|
546
|
+
|
547
|
+
Available pulses include commercial_density, commercial_profile, income, race, hispanic, and age_by_gender.
|
548
|
+
|
549
|
+
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](http://developer.factual.com/display/docs/Places+API+-+Geopulse).
|
550
|
+
|
551
|
+
|
552
|
+
# Reverse Geocoder
|
553
|
+
|
554
|
+
The driver fully supports Factual's <a href="http://developer.factual.com/display/DOCS/Places+API+-+Reverse+Geocoder">Reverse Geocoder</a> feature, which returns the nearest valid address given a longitude and latitude.
|
555
|
+
|
556
|
+
## Simple Reverse Geocoder Example
|
557
|
+
|
558
|
+
The <tt>geocode</tt> method fetches results based on the given point:
|
559
|
+
|
560
|
+
````ruby
|
561
|
+
query = factual.geocode(34.06021, -118.41828)
|
562
|
+
query.first
|
563
|
+
````
|
564
|
+
|
565
|
+
## All Top Level Reverse Geocoder Parameters
|
566
|
+
|
567
|
+
<table>
|
568
|
+
<tr>
|
569
|
+
<th>Parameter</th>
|
570
|
+
<th>Description</th>
|
571
|
+
<th>Example</th>
|
572
|
+
</tr>
|
573
|
+
<tr>
|
574
|
+
<td>geo</td>
|
575
|
+
<td>A valid geographic point for which the closest address is retrieved.</td>
|
576
|
+
<td><tt>factual.geocode(34.06021, -118.41828)</tt></td>
|
577
|
+
</tr>
|
578
|
+
</table>
|
579
|
+
|
580
|
+
|
581
|
+
# World Geographies
|
582
|
+
|
583
|
+
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.
|
584
|
+
|
585
|
+
Common use cases include:
|
586
|
+
|
587
|
+
* Determining all cities within a state or all postal codes in a city
|
588
|
+
* Creating a type-ahead placename lookup
|
589
|
+
* Validating data against city, state, country and county names
|
590
|
+
* A translation table to convert between the search key used by a user, i.e. '慕尼黑' or 'Munich' for the native 'München'
|
591
|
+
|
592
|
+
You can use the <tt>query</tt> function to query World Geographies, supplying "world-geographies" as the table name.
|
593
|
+
|
594
|
+
Examples:
|
595
|
+
|
596
|
+
````ruby
|
597
|
+
# Get all towns surrounding Philadelphia
|
598
|
+
query = factual.table("world-geographies").select("neighbors").filters(:factual_id => "08ca0f62-8f76-11e1-848f-cfd5bf3ef515")
|
599
|
+
query.rows
|
600
|
+
````
|
601
|
+
|
602
|
+
````ruby
|
603
|
+
# Find the town zipcode 95008 belongs to
|
604
|
+
query = factual.table("world-geographies").filters(
|
605
|
+
{"$and" => [ {:name => "95008"},
|
606
|
+
{:country => "us"} ]} )
|
607
|
+
query.rows
|
608
|
+
````
|
609
|
+
|
610
|
+
````ruby
|
611
|
+
# Searching by placename, placetype, country and geographic hierarchy
|
612
|
+
query = factual.table("world-geographies").filters(
|
613
|
+
{"$and" => [ {:name => "wayne"},
|
614
|
+
{:country => "us"},
|
615
|
+
{:placetype => "locality"},
|
616
|
+
{:ancestors => {:"$search" => "08666f5c-8f76-11e1-848f-cfd5bf3ef515"}} ]} )
|
617
|
+
|
618
|
+
query.rows
|
619
|
+
````
|
620
|
+
|
621
|
+
For more details about World Geographies, including schema, see [the main API docs for World Geographies](http://developer.factual.com/display/docs/World+Geographies).
|
622
|
+
|
623
|
+
|
624
|
+
# Submit
|
625
|
+
|
626
|
+
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.
|
627
|
+
|
628
|
+
|
629
|
+
## Simple Submit Examples
|
630
|
+
|
631
|
+
The <tt>submit</tt> method is a contribution to edit an existing row or add a new row:
|
632
|
+
|
633
|
+
````ruby
|
634
|
+
# Submit a new row to Factual's global dataset
|
635
|
+
factual.submit("global", "user123").values({"name" => "McDenny's", "address" => "1 Main St.", "locality" => "Bedrock", "region" => "BC"}).write
|
636
|
+
````
|
637
|
+
|
638
|
+
````ruby
|
639
|
+
# Submit a correction to an existing row in Factual's places dataset.
|
640
|
+
# Also set optional comment and reference
|
641
|
+
submit = factual.submit("places", "user123").values({:name => "McDenny's"})
|
642
|
+
submit = submit.comment("They changed their name last month").reference("http://www.example.com/mypage.html")
|
643
|
+
submit.write
|
644
|
+
````
|
645
|
+
|
646
|
+
|
647
|
+
## Required Submit Parameters
|
648
|
+
|
649
|
+
<table>
|
650
|
+
<tr>
|
651
|
+
<th>Parameter</th>
|
652
|
+
<th>Description</th>
|
653
|
+
<th>Example</th>
|
654
|
+
</tr>
|
655
|
+
<tr>
|
656
|
+
<td>values</td>
|
657
|
+
<td>A hash of names and values to be added to a Factual table</td>
|
658
|
+
<td><tt>{"name" => "McDenny's", "address" => "1 Main St.", "locality" => "Bedrock", "region" => "BC"}</tt></td>
|
659
|
+
</tr>
|
660
|
+
<tr>
|
661
|
+
<td>user</td>
|
662
|
+
<td>An arbitrary token representing the user contributing the data.</td>
|
663
|
+
<td><tt>user123</tt></td>
|
664
|
+
</tr>
|
665
|
+
</table>
|
666
|
+
|
667
|
+
## Optional Submit Parameters
|
668
|
+
|
669
|
+
<table>
|
670
|
+
<tr>
|
671
|
+
<th>Parameter</th>
|
672
|
+
<th>Description</th>
|
673
|
+
<th>Example</th>
|
674
|
+
</tr>
|
675
|
+
<tr>
|
676
|
+
<td>factual_id</td>
|
677
|
+
<td>The factual_id of the row to which you want to submit a correction</td>
|
678
|
+
<td><tt>.factual_id("0545b03f-9413-44ed-8882-3a9a461848da")</tt></td>
|
679
|
+
</tr>
|
680
|
+
<tr>
|
681
|
+
<td>comment</td>
|
682
|
+
<td>Any english text comment that may help explain your corrections.</td>
|
683
|
+
<td><tt>.comment("They changed their name last month")</tt></td>
|
684
|
+
</tr>
|
685
|
+
<tr>
|
686
|
+
<td>reference</td>
|
687
|
+
<td>A reference to a URL, title, person, etc. that is the source of this data.</td>
|
688
|
+
<td><tt>.reference("http://www.example.com/mypage.html")</tt></td>
|
689
|
+
</tr>
|
690
|
+
</table>
|
691
|
+
|
692
|
+
|
396
693
|
# Schema
|
397
694
|
|
398
695
|
You can query Factual for the detailed schema of any specific table in Factual. For example:
|
@@ -413,15 +710,51 @@ forward-slash) and the request will be made using your OAuth token:
|
|
413
710
|
factual.read('/t/restaurants-us?filters={"name":{"$bw":"Star"}}&include_count=true')
|
414
711
|
````
|
415
712
|
|
713
|
+
# Monetize
|
714
|
+
|
715
|
+
The <a href="http://developer.factual.com/display/docs/Places+API+-+Monetize">Monetize API</a> enables you to find deals for places in Factual's Global Places database.
|
716
|
+
|
717
|
+
````ruby
|
718
|
+
query = @factual.monetize.search('sushi').filters(:place_postcode => 90067)
|
719
|
+
query.rows
|
720
|
+
````
|
721
|
+
|
722
|
+
# Multi
|
723
|
+
|
724
|
+
The <a href="http://developer.factual.com/display/docs/Core+API+-+Multi">multi API call</a> enables making multiple API GET requests on the same connection. Multi supports the read, crosswalk, resolve, facets, geocode and geopulse API calls.
|
725
|
+
|
726
|
+
````ruby
|
727
|
+
places_query = @factual.table("places").search('sushi').filters(:postcode => 90067)
|
728
|
+
geocode_query = @factual.geocode(34.06021,-118.41828)
|
729
|
+
|
730
|
+
responses = @factual.multi(
|
731
|
+
:nearby_sushi => places_query,
|
732
|
+
:factual_inc => geocode_query)
|
733
|
+
|
734
|
+
responses[:nearby_sushi].first
|
735
|
+
responses[:factual_inc].first
|
736
|
+
````
|
737
|
+
|
416
738
|
# Debug Mode
|
417
739
|
|
418
|
-
To see the query paths generated by the driver you can use it in debug mode by passing true as
|
740
|
+
To see the query paths generated by the driver you can use it in debug mode by passing :debug => true as
|
419
741
|
the third argument of the Factual constructor. Here is an example in irb:
|
420
742
|
|
421
743
|
|
422
744
|
````ruby
|
423
|
-
> factual = Factual.new(key, secret, true)
|
745
|
+
> factual = Factual.new(key, secret, :debug => true)
|
424
746
|
> factual.table("places").filters("name" => {"$bw" => "starbucks"})
|
425
747
|
Request: http://api.v3.factual.com/t/places?filters=%7B%22name%22%3A%7B%22%24bw%22%3A%22starbucks%22%7D%7D
|
426
748
|
=> [{"address"=>"11290 Donner Pass Rd", "category"=>"Food & Beverage > Cafes, Coffee Houses & Tea Houses", ...
|
427
749
|
````
|
750
|
+
|
751
|
+
# Where to Get Help
|
752
|
+
|
753
|
+
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:
|
754
|
+
|
755
|
+
* What you did to surface the bug
|
756
|
+
* What you expected to happen
|
757
|
+
* What actually happened
|
758
|
+
* Detailed stack trace and/or line numbers
|
759
|
+
|
760
|
+
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](http://support.factual.com/factual).
|
data/lib/factual.rb
CHANGED
@@ -1,18 +1,31 @@
|
|
1
1
|
require 'oauth'
|
2
2
|
require 'factual/api'
|
3
3
|
require 'factual/query/table'
|
4
|
+
require 'factual/query/facets'
|
4
5
|
require 'factual/query/resolve'
|
5
6
|
require 'factual/query/crosswalk'
|
7
|
+
require 'factual/query/monetize'
|
8
|
+
require 'factual/query/geocode'
|
9
|
+
require 'factual/query/geopulse'
|
10
|
+
require 'factual/write/flag'
|
11
|
+
require 'factual/write/submit'
|
12
|
+
require 'factual/multi'
|
6
13
|
|
7
14
|
class Factual
|
8
|
-
def initialize(key, secret,
|
9
|
-
|
15
|
+
def initialize(key, secret, options = {})
|
16
|
+
debug_mode = options[:debug].nil? ? false : options[:debug]
|
17
|
+
host = options[:host]
|
18
|
+
@api = API.new(generate_token(key, secret), debug_mode, host)
|
10
19
|
end
|
11
20
|
|
12
21
|
def table(table_id_or_alias)
|
13
22
|
Query::Table.new(@api, "t/#{table_id_or_alias}")
|
14
23
|
end
|
15
24
|
|
25
|
+
def facets(table_id_or_alias)
|
26
|
+
Query::Facets.new(@api, "t/#{table_id_or_alias}")
|
27
|
+
end
|
28
|
+
|
16
29
|
def crosswalk(namespace_id, namespace = nil)
|
17
30
|
if namespace
|
18
31
|
Query::Crosswalk.new(@api, :namespace_id => namespace_id, :namespace => namespace)
|
@@ -21,14 +34,54 @@ class Factual
|
|
21
34
|
end
|
22
35
|
end
|
23
36
|
|
37
|
+
def monetize
|
38
|
+
Query::Monetize.new(@api)
|
39
|
+
end
|
40
|
+
|
24
41
|
def resolve(values)
|
25
42
|
Query::Resolve.new(@api, :values => values)
|
26
43
|
end
|
27
44
|
|
45
|
+
def geocode(lat, lng)
|
46
|
+
Query::Geocode.new(@api, lat, lng)
|
47
|
+
end
|
48
|
+
|
49
|
+
def geopulse(lat, lng)
|
50
|
+
Query::Geopulse.new(@api, lat, lng)
|
51
|
+
end
|
52
|
+
|
28
53
|
def read(path)
|
29
54
|
@api.raw_read(path)
|
30
55
|
end
|
31
56
|
|
57
|
+
def multi(queries)
|
58
|
+
multi = Multi.new(@api, queries)
|
59
|
+
multi.send
|
60
|
+
end
|
61
|
+
|
62
|
+
def flag(table, factual_id, problem, user)
|
63
|
+
flag_params = {
|
64
|
+
:table => table,
|
65
|
+
:factual_id => factual_id,
|
66
|
+
:problem => problem,
|
67
|
+
:user => user }
|
68
|
+
|
69
|
+
Write::Flag.new(@api, flag_params)
|
70
|
+
end
|
71
|
+
|
72
|
+
def submit(*params)
|
73
|
+
values = {}
|
74
|
+
values = params.last if params.last.is_a? Hash
|
75
|
+
|
76
|
+
table, user, factual_id = params
|
77
|
+
submit_params = {
|
78
|
+
:table => table,
|
79
|
+
:user => user,
|
80
|
+
:factual_id => factual_id,
|
81
|
+
:values => values }
|
82
|
+
Write::Submit.new(@api, submit_params)
|
83
|
+
end
|
84
|
+
|
32
85
|
private
|
33
86
|
|
34
87
|
def generate_token(key, secret)
|
data/lib/factual/api.rb
CHANGED
@@ -3,46 +3,82 @@ require 'cgi'
|
|
3
3
|
|
4
4
|
class Factual
|
5
5
|
class API
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
VERSION = "1.2"
|
7
|
+
API_V3_HOST = "api.v3.factual.com"
|
8
|
+
DRIVER_VERSION_TAG = "factual-ruby-driver-v" + VERSION
|
9
|
+
PARAM_ALIASES = { :search => :q, :sort_asc => :sort }
|
9
10
|
|
10
|
-
def initialize(access_token, debug_mode = false)
|
11
|
+
def initialize(access_token, debug_mode = false, host = nil)
|
11
12
|
@access_token = access_token
|
12
13
|
@debug_mode = debug_mode
|
14
|
+
@host = host || API_V3_HOST
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
17
|
+
def get(query, other_params = {})
|
16
18
|
merged_params = query.params.merge(other_params)
|
17
19
|
handle_request(query.action || :read, query.path, merged_params)
|
18
20
|
end
|
19
21
|
|
22
|
+
def post(request)
|
23
|
+
response = make_request("http://" + @host + request.path, request.body, :post)
|
24
|
+
payload = JSON.parse(response.body)
|
25
|
+
handle_payload(payload)
|
26
|
+
end
|
27
|
+
|
20
28
|
def schema(query)
|
21
|
-
handle_request(:schema, query.path
|
29
|
+
handle_request(:schema, query.path, query.params)["view"]
|
22
30
|
end
|
23
31
|
|
24
32
|
def raw_read(path)
|
25
|
-
payload = JSON.parse(make_request("
|
33
|
+
payload = JSON.parse(make_request("http://#{@host}#{path}").body)
|
26
34
|
handle_payload(payload)
|
27
35
|
end
|
28
36
|
|
37
|
+
def full_path(action, path, params)
|
38
|
+
fp = "/#{path}"
|
39
|
+
fp += "/#{action}" unless action == :read
|
40
|
+
fp += "?#{query_string(params)}"
|
41
|
+
end
|
42
|
+
|
29
43
|
private
|
30
44
|
|
31
45
|
def handle_request(action, path, params)
|
32
|
-
url = "
|
33
|
-
|
46
|
+
url = "http://#{@host}" + full_path(action, path, params)
|
47
|
+
|
34
48
|
payload = JSON.parse(make_request(url).body)
|
35
|
-
|
49
|
+
|
50
|
+
if (path == :multi)
|
51
|
+
payload.inject({}) do |res, item|
|
52
|
+
name, p = item
|
53
|
+
res[name] = handle_payload(p)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
handle_payload(payload)
|
57
|
+
end
|
36
58
|
end
|
37
59
|
|
38
60
|
def handle_payload(payload)
|
39
|
-
raise StandardError.new(payload
|
61
|
+
raise StandardError.new(payload.to_json) unless payload["status"] == "ok"
|
40
62
|
payload["response"]
|
41
63
|
end
|
42
64
|
|
43
|
-
def make_request(url)
|
65
|
+
def make_request(url, body=nil, method=:get)
|
66
|
+
start_time = Time.now
|
67
|
+
|
44
68
|
headers = { "X-Factual-Lib" => DRIVER_VERSION_TAG }
|
45
|
-
|
69
|
+
|
70
|
+
res = if (method == :get)
|
71
|
+
@access_token.get(url, headers)
|
72
|
+
elsif (method == :post)
|
73
|
+
@access_token.post(url, body, headers)
|
74
|
+
else
|
75
|
+
raise StandardError.new("Unknown http method")
|
76
|
+
end
|
77
|
+
|
78
|
+
elapsed_time = (Time.now - start_time) * 1000
|
79
|
+
debug(url, method, headers, body, res, elapsed_time) if @debug_mode
|
80
|
+
|
81
|
+
res
|
46
82
|
end
|
47
83
|
|
48
84
|
def query_string(params)
|
@@ -54,5 +90,29 @@ class Factual
|
|
54
90
|
|
55
91
|
query_array.join("&")
|
56
92
|
end
|
93
|
+
|
94
|
+
def debug(url, method, headers, body, res, elapsed_time)
|
95
|
+
res_headers = res.to_hash.inject({}) do |h, kv|
|
96
|
+
k, v = kv
|
97
|
+
h[k] = v.join(',')
|
98
|
+
h
|
99
|
+
end
|
100
|
+
|
101
|
+
puts "--- Driver version: #{DRIVER_VERSION_TAG}"
|
102
|
+
puts "--- request debug ---"
|
103
|
+
puts "req url: #{url}"
|
104
|
+
puts "req method: #{method.upcase}"
|
105
|
+
puts "req headers: #{JSON.pretty_generate(headers)}"
|
106
|
+
puts "req body: #{body}" if body
|
107
|
+
puts "---------------------"
|
108
|
+
puts "--- response debug ---"
|
109
|
+
puts "resp status code: #{res.code}"
|
110
|
+
puts "resp status message: #{res.message}"
|
111
|
+
puts "resp headers: #{JSON.pretty_generate(res_headers)}"
|
112
|
+
puts "resp body: #{res.body}"
|
113
|
+
puts "---------------------"
|
114
|
+
puts "Elapsed time: #{elapsed_time} msecs"
|
115
|
+
puts
|
116
|
+
end
|
57
117
|
end
|
58
118
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Factual
|
2
|
+
class Multi
|
3
|
+
attr_reader :action, :path, :params
|
4
|
+
|
5
|
+
def initialize(api, queries)
|
6
|
+
@api = api
|
7
|
+
@queries = queries
|
8
|
+
|
9
|
+
@action = nil
|
10
|
+
@path = :multi
|
11
|
+
@params = queries_param
|
12
|
+
|
13
|
+
@responses = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def send
|
17
|
+
res = @api.get(self)
|
18
|
+
@queries.each do |name, query|
|
19
|
+
query.populate(res[name])
|
20
|
+
@responses[name] = query
|
21
|
+
end
|
22
|
+
|
23
|
+
@responses
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def queries_param
|
29
|
+
query_urls = {}
|
30
|
+
@queries.each do |name, query|
|
31
|
+
query_urls[name] = query.full_path
|
32
|
+
end
|
33
|
+
|
34
|
+
{ :queries => query_urls }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/factual/query/base.rb
CHANGED
@@ -27,7 +27,7 @@ class Factual
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def total_count
|
30
|
-
resp = @api.
|
30
|
+
resp = @api.get(self, :include_count => true, :limit => 1)
|
31
31
|
resp["total_row_count"]
|
32
32
|
end
|
33
33
|
|
@@ -35,6 +35,15 @@ class Factual
|
|
35
35
|
@schema ||= @api.schema(self)
|
36
36
|
end
|
37
37
|
|
38
|
+
# TODO move to Multiable module, and support multi writes
|
39
|
+
def full_path
|
40
|
+
@api.full_path(@action, @path, @params)
|
41
|
+
end
|
42
|
+
|
43
|
+
def populate(query_response)
|
44
|
+
@response = query_response
|
45
|
+
end
|
46
|
+
|
38
47
|
private
|
39
48
|
|
40
49
|
def form_value(args)
|
@@ -43,7 +52,7 @@ class Factual
|
|
43
52
|
end
|
44
53
|
|
45
54
|
def response
|
46
|
-
@response ||= @api.
|
55
|
+
@response ||= @api.get(self)
|
47
56
|
end
|
48
57
|
end
|
49
58
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'factual/query/base'
|
2
|
+
|
3
|
+
class Factual
|
4
|
+
module Query
|
5
|
+
class Facets < Base
|
6
|
+
DEFAULT_LIMIT = 20
|
7
|
+
VALID_PARAMS = [
|
8
|
+
:filters, :search, :geo,
|
9
|
+
:select,
|
10
|
+
:limit, :min_count,
|
11
|
+
:include_count
|
12
|
+
]
|
13
|
+
|
14
|
+
def initialize(api, path, params = {})
|
15
|
+
@path = path
|
16
|
+
@action = :facets
|
17
|
+
super(api, params)
|
18
|
+
end
|
19
|
+
|
20
|
+
VALID_PARAMS.each do |param|
|
21
|
+
define_method(param) do |*args|
|
22
|
+
Facets.new(@api, @path, @params.merge(param => form_value(args)))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
columns.each do |col, data|
|
28
|
+
block.call(col, data)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](col)
|
33
|
+
columns[col]
|
34
|
+
end
|
35
|
+
|
36
|
+
def columns
|
37
|
+
response["data"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'factual/query/base'
|
2
|
+
|
3
|
+
class Factual
|
4
|
+
module Query
|
5
|
+
class Geocode < Base
|
6
|
+
def initialize(api, lat, lng)
|
7
|
+
@path = "places/geocode"
|
8
|
+
@action = :read
|
9
|
+
@params = {:geo => {"$point" => [lat, lng]}}
|
10
|
+
|
11
|
+
super(api, @params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'factual/query/base'
|
2
|
+
|
3
|
+
class Factual
|
4
|
+
module Query
|
5
|
+
class Geopulse < Base
|
6
|
+
def initialize(api, lat, lng, params={})
|
7
|
+
@path = "places/geopulse"
|
8
|
+
@action = :read
|
9
|
+
@lat = lat
|
10
|
+
@lng = lng
|
11
|
+
|
12
|
+
@params = {:geo => {"$point" => [lat, lng]}}.merge(params)
|
13
|
+
super(api, @params)
|
14
|
+
end
|
15
|
+
|
16
|
+
def select(*args)
|
17
|
+
self.class.new(@api, @lat, @lng, :select => form_value(args))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'factual/query/base'
|
2
|
+
|
3
|
+
class Factual
|
4
|
+
module Query
|
5
|
+
class Monetize < Base
|
6
|
+
VALID_PARAMS = [
|
7
|
+
:filters, :search, :geo,
|
8
|
+
:limit, :offset
|
9
|
+
]
|
10
|
+
|
11
|
+
def initialize(api, params = {})
|
12
|
+
@path = "places/monetize"
|
13
|
+
@action = :read
|
14
|
+
super(api, params)
|
15
|
+
end
|
16
|
+
|
17
|
+
VALID_PARAMS.each do |param|
|
18
|
+
define_method(param) do |*args|
|
19
|
+
self.class.new(@api, @params.merge(param => form_value(args)))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/factual/query/table.rb
CHANGED
@@ -4,6 +4,12 @@ class Factual
|
|
4
4
|
module Query
|
5
5
|
class Table < Base
|
6
6
|
DEFAULT_LIMIT = 20
|
7
|
+
VALID_PARAMS = [
|
8
|
+
:filters, :search, :geo,
|
9
|
+
:sort, :select,
|
10
|
+
:limit, :offset,
|
11
|
+
:include_count
|
12
|
+
]
|
7
13
|
|
8
14
|
def initialize(api, path, params = {})
|
9
15
|
@path = path
|
@@ -11,7 +17,7 @@ class Factual
|
|
11
17
|
super(api, params)
|
12
18
|
end
|
13
19
|
|
14
|
-
|
20
|
+
VALID_PARAMS.each do |param|
|
15
21
|
define_method(param) do |*args|
|
16
22
|
Table.new(@api, @path, @params.merge(param => form_value(args)))
|
17
23
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Factual
|
2
|
+
module Write
|
3
|
+
class Base
|
4
|
+
def initialize(api, params)
|
5
|
+
@api = api
|
6
|
+
@params = params
|
7
|
+
end
|
8
|
+
|
9
|
+
def path
|
10
|
+
raise "Virtual method called"
|
11
|
+
end
|
12
|
+
|
13
|
+
def body
|
14
|
+
keys = @params.keys.reject { |key| [:table, :factual_id].include?(key) }
|
15
|
+
keys.map { |key| "#{key}=#{CGI.escape(stringify(@params[key]))}" }.join("&")
|
16
|
+
end
|
17
|
+
|
18
|
+
def write
|
19
|
+
@api.post(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def stringify(value)
|
25
|
+
value.class == Hash ? value.to_json : value.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def form_value(args)
|
29
|
+
args = args.map { |arg| arg.is_a?(String) ? arg.strip : arg }
|
30
|
+
args.length == 1 ? args.first : args.join(',')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'factual/write/base'
|
2
|
+
|
3
|
+
class Factual
|
4
|
+
module Write
|
5
|
+
class Flag < Base
|
6
|
+
PROBLEMS = [:duplicate, :nonexistent, :inaccurate, :inappropriate, :spam, :other]
|
7
|
+
VALID_KEYS = [:table, :factual_id, :problem, :user, :comment, :debug, :reference]
|
8
|
+
|
9
|
+
def initialize(api, params)
|
10
|
+
validate_params(params)
|
11
|
+
super(api, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
VALID_KEYS.each do |key|
|
15
|
+
define_method(key) do |*args|
|
16
|
+
Flag.new(@api, @params.merge(key => form_value(args)))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
"/t/#{@params[:table]}/#{@params[:factual_id]}/flag"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def validate_params(params)
|
27
|
+
params.keys.each do |key|
|
28
|
+
raise "Invalid flag option: #{key}" unless VALID_KEYS.include?(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
unless PROBLEMS.include?(params[:problem])
|
32
|
+
raise "Flag problem should be one of the following: #{PROBLEMS.join(", ")}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'factual/write/base'
|
2
|
+
|
3
|
+
class Factual
|
4
|
+
module Write
|
5
|
+
class Submit < Base
|
6
|
+
VALID_KEYS = [
|
7
|
+
:table, :user,
|
8
|
+
:factual_id, :values,
|
9
|
+
:comment, :reference
|
10
|
+
]
|
11
|
+
|
12
|
+
def initialize(api, params)
|
13
|
+
validate_params(params)
|
14
|
+
super(api, params)
|
15
|
+
end
|
16
|
+
|
17
|
+
VALID_KEYS.each do |key|
|
18
|
+
define_method(key) do |*args|
|
19
|
+
Submit.new(@api, @params.merge(key => form_value(args)))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
if @params[:factual_id]
|
25
|
+
"/t/#{@params[:table]}/#{@params[:factual_id]}/submit"
|
26
|
+
else
|
27
|
+
"/t/#{@params[:table]}/submit"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_params(params)
|
34
|
+
params.keys.each do |key|
|
35
|
+
raise "Invalid submit option: #{key}" unless VALID_KEYS.include?(key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: factual-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: "1.
|
5
|
+
version: "1.2"
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Rudiger Lippert
|
@@ -11,8 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2012-
|
15
|
-
default_executable:
|
14
|
+
date: 2012-06-21 00:00:00 Z
|
16
15
|
dependencies:
|
17
16
|
- !ruby/object:Gem::Dependency
|
18
17
|
name: oauth
|
@@ -59,14 +58,21 @@ extra_rdoc_files: []
|
|
59
58
|
|
60
59
|
files:
|
61
60
|
- lib/factual/api.rb
|
61
|
+
- lib/factual/multi.rb
|
62
62
|
- lib/factual/query/base.rb
|
63
63
|
- lib/factual/query/crosswalk.rb
|
64
|
+
- lib/factual/query/facets.rb
|
65
|
+
- lib/factual/query/geocode.rb
|
66
|
+
- lib/factual/query/geopulse.rb
|
67
|
+
- lib/factual/query/monetize.rb
|
64
68
|
- lib/factual/query/resolve.rb
|
65
69
|
- lib/factual/query/table.rb
|
70
|
+
- lib/factual/write/base.rb
|
71
|
+
- lib/factual/write/flag.rb
|
72
|
+
- lib/factual/write/submit.rb
|
66
73
|
- lib/factual.rb
|
67
74
|
- README.md
|
68
75
|
- CHANGELOG.md
|
69
|
-
has_rdoc: true
|
70
76
|
homepage: http://github.com/Factual/factual-ruby-driver
|
71
77
|
licenses: []
|
72
78
|
|
@@ -90,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
96
|
requirements: []
|
91
97
|
|
92
98
|
rubyforge_project:
|
93
|
-
rubygems_version: 1.
|
99
|
+
rubygems_version: 1.8.24
|
94
100
|
signing_key:
|
95
101
|
specification_version: 3
|
96
102
|
summary: Ruby driver for Factual
|