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 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
- * adding crosswalk(namespace_id, namespace)
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 Crosswalk Example
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, debug_mode = false)
9
- @api = API.new(generate_token(key, secret), debug_mode)
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
- API_V3_HOST = "http://api.v3.factual.com"
7
- DRIVER_VERSION_TAG = "factual-ruby-driver-1.0"
8
- PARAM_ALIASES = { :search => :q, :sort_asc => :sort }
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 execute(query, other_params={})
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 + "/schema", query.params)["view"]
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("#{API_V3_HOST}#{path}").body)
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 = "#{API_V3_HOST}/#{path}?#{query_string(params)}"
33
- puts "Request: #{url}" if @debug_mode
46
+ url = "http://#{@host}" + full_path(action, path, params)
47
+
34
48
  payload = JSON.parse(make_request(url).body)
35
- handle_payload(payload)
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["message"]) unless payload["status"] == "ok"
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
- @access_token.get(url, headers)
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
@@ -27,7 +27,7 @@ class Factual
27
27
  end
28
28
 
29
29
  def total_count
30
- resp = @api.execute(self, :include_count => true, :limit => 1)
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.execute(self)
55
+ @response ||= @api.get(self)
47
56
  end
48
57
  end
49
58
  end
@@ -5,7 +5,7 @@ class Factual
5
5
  class Crosswalk < Base
6
6
  def initialize(api, params = {})
7
7
  @path = "places/crosswalk"
8
- @action = :crosswalk
8
+ @action = :read
9
9
  super(api, params)
10
10
  end
11
11
 
@@ -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
@@ -5,7 +5,7 @@ class Factual
5
5
  class Resolve < Base
6
6
  def initialize(api, params = {})
7
7
  @path = "places/resolve"
8
- @action = :resolve
8
+ @action = :read
9
9
  super(api, params)
10
10
  end
11
11
 
@@ -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
- [:filters, :search, :geo, :sort, :select, :limit, :offset, :include_count].each do |param|
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.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-03-30 00:00:00 +08:00
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.6.2
99
+ rubygems_version: 1.8.24
94
100
  signing_key:
95
101
  specification_version: 3
96
102
  summary: Ruby driver for Factual