google_fusion_tables 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +26 -0
- data/LICENSE +20 -0
- data/README.textile +179 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/examples/compare_tweets.rb +143 -0
- data/examples/credentials.example.yml +4 -0
- data/fusion_tables.gemspec +76 -0
- data/lib/fusion_tables.rb +6 -0
- data/lib/fusion_tables/client/fusion_tables.rb +76 -0
- data/lib/fusion_tables/data/data.rb +72 -0
- data/lib/fusion_tables/data/table.rb +146 -0
- data/lib/fusion_tables/ext/fusion_tables.rb +124 -0
- data/pkg/fusion_tables-0.1.0.gem +0 -0
- data/pkg/fusion_tables-0.1.1.gem +0 -0
- data/pkg/fusion_tables-0.1.2.gem +0 -0
- data/pkg/fusion_tables-0.2.0.gem +0 -0
- data/pkg/fusion_tables-0.2.1.gem +0 -0
- data/pkg/fusion_tables-0.2.2.gem +0 -0
- data/test/README +3 -0
- data/test/helper.rb +34 -0
- data/test/test_client.rb +22 -0
- data/test/test_config.yml.sample +3 -0
- data/test/test_ext.rb +58 -0
- data/test/test_table.rb +76 -0
- metadata +101 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
examples/credentials.yml
|
20
|
+
|
21
|
+
## PROJECT::TEST
|
22
|
+
test/test_config.yml
|
23
|
+
|
24
|
+
|
25
|
+
## PROJECT::SPECIFIC
|
26
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kareem Hashem
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
h1. fusion-tables
|
2
|
+
|
3
|
+
This gem lets you easily interact with Google Fusion Tables from your Ruby application.
|
4
|
+
|
5
|
+
h2. Gem Dependencies
|
6
|
+
|
7
|
+
* gdata_19 >= 1.1.2
|
8
|
+
|
9
|
+
h2. Installation
|
10
|
+
|
11
|
+
bc. gem install google_fusion_tables
|
12
|
+
|
13
|
+
h2. Rubies
|
14
|
+
|
15
|
+
Tested on:
|
16
|
+
|
17
|
+
* 1.8.7
|
18
|
+
* 1.9.2-p0
|
19
|
+
|
20
|
+
h2. To Use
|
21
|
+
|
22
|
+
bc. require 'google_fusion_tables'
|
23
|
+
|
24
|
+
or in Rails 2.3.x
|
25
|
+
|
26
|
+
bc. config.gem 'google_fusion_tables'
|
27
|
+
|
28
|
+
h2. API examples
|
29
|
+
|
30
|
+
"Twitter example":http://github.com/tokumine/fusion-tables/blob/master/examples/compare_tweets.rb
|
31
|
+
"Boris bike example":http://github.com/tokumine/fusion-tables/blob/master/examples/boris_bikes.rb
|
32
|
+
"Tests":http://github.com/tokumine/fusion-tables/tree/master/test/
|
33
|
+
|
34
|
+
Here is a brief rundown:
|
35
|
+
|
36
|
+
<pre><code># Connect to service
|
37
|
+
@ft = GData::Client::FusionTables.new
|
38
|
+
@ft.clientlogin(username, password)
|
39
|
+
|
40
|
+
# Browse existing tables
|
41
|
+
@ft.show_tables
|
42
|
+
# => [table_1, table_2]
|
43
|
+
|
44
|
+
# Getting table id suitable for using with google maps (see more below)
|
45
|
+
table_1.id #=> 42342 (the table's google id)
|
46
|
+
|
47
|
+
# Count data
|
48
|
+
table_1.count #=> 1
|
49
|
+
|
50
|
+
# Select data
|
51
|
+
table_1.select
|
52
|
+
#=> data
|
53
|
+
|
54
|
+
# Select data with conditions
|
55
|
+
table_1.select "name", "WHERE x=n"
|
56
|
+
#=> data
|
57
|
+
|
58
|
+
# Select ROWIDs
|
59
|
+
row_ids = table_1.select "ROWID"
|
60
|
+
|
61
|
+
# Drop tables
|
62
|
+
@ft.drop table_1.id # table id
|
63
|
+
@ft.drop [table_1.id, table_2.id] # arrays of table ids
|
64
|
+
@ft.drop /yacht/ # regex on table name
|
65
|
+
|
66
|
+
# Creating a table
|
67
|
+
cols = [{:name => "friend name", :type => 'string' },
|
68
|
+
{:name => "age", :type => 'number' },
|
69
|
+
{:name => "meeting time", :type => 'datetime' },
|
70
|
+
{:name => "where", :type => 'location' }]
|
71
|
+
|
72
|
+
new_table = @ft.create_table "My upcoming meetings", cols
|
73
|
+
|
74
|
+
# Inserting rows (auto chunks every 500)
|
75
|
+
data = [{"friend name" => "Eric Wimp",
|
76
|
+
"age" => 25,
|
77
|
+
"meeting time" => Time.utc(2010,"aug",10,20,15,1),
|
78
|
+
"where" => "29 Acacia Road, Nuttytown"}]
|
79
|
+
new_table.insert data
|
80
|
+
|
81
|
+
# Delete row
|
82
|
+
new_table.delete row_id
|
83
|
+
</code></pre>
|
84
|
+
|
85
|
+
Currently UPDATE query is not implemented, you could always delete all data and start over again.
|
86
|
+
|
87
|
+
But you could also do this:
|
88
|
+
|
89
|
+
<pre><code>
|
90
|
+
require 'active_support/core_ext/array/grouping'
|
91
|
+
|
92
|
+
# get the table as above..
|
93
|
+
|
94
|
+
data = []
|
95
|
+
list = table.select("*", "WHERE location contains '%20' ORDER BY permalink LIMIT 2")
|
96
|
+
|
97
|
+
list.each do |item|
|
98
|
+
data << item
|
99
|
+
id = table.select("ROWID", "WHERE permalink = '%s'" % item[:permalink])
|
100
|
+
id = id.first[:rowid]
|
101
|
+
puts "deleting %d" % id
|
102
|
+
table.delete(id)
|
103
|
+
end
|
104
|
+
|
105
|
+
#modify location, remove text that breaks longlat value..
|
106
|
+
data.map {|item| item[:location] = item[:location].gsub('%20','')}
|
107
|
+
|
108
|
+
data.in_groups_of(50, false) do |group|
|
109
|
+
puts "inserting to fusion table: %d items" % group.size
|
110
|
+
table.insert(group)
|
111
|
+
end
|
112
|
+
</code></pre>
|
113
|
+
|
114
|
+
h2. Fusion Tables secret Geospatial Sauce
|
115
|
+
|
116
|
+
*"Geolocated Tweets example":http://tables.googlelabs.com/DataSource?snapid=73106*
|
117
|
+
|
118
|
+
Fusion Tables is a labs product from Google. You can "read more here":http://tables.googlelabs.com/, but the key thing is that it gives you *access to the google tile mill for fast generation of google map layers across large datasets*
|
119
|
+
|
120
|
+
Fusion Tables supports the following geometry types:
|
121
|
+
|
122
|
+
* lat/long
|
123
|
+
* addresses (automatically geocodes them for you)
|
124
|
+
* KML (point, polyline, polygon, multipolygon)
|
125
|
+
|
126
|
+
h2. Integrate with google maps v3
|
127
|
+
|
128
|
+
Adding a fusion tables datalayer with many points/polygons to your v3 map is as simple as:
|
129
|
+
|
130
|
+
bc. layer = new google.maps.FusionTablesLayer(139529);
|
131
|
+
|
132
|
+
That's it
|
133
|
+
|
134
|
+
You can also refine the tiles by SQL, and can even do so dynamically:
|
135
|
+
|
136
|
+
<pre><code>
|
137
|
+
layer = new google.maps.FusionTablesLayer(198945, {
|
138
|
+
query: "SELECT address FROM 198945 WHERE ridership > 5000"}
|
139
|
+
);
|
140
|
+
</code></pre>
|
141
|
+
|
142
|
+
Finally, fusion tables also lets you make Heatmaps
|
143
|
+
|
144
|
+
<pre><code>
|
145
|
+
layer = new google.maps.FusionTablesLayer(136705, {
|
146
|
+
heatmap: true
|
147
|
+
});
|
148
|
+
</code></pre>
|
149
|
+
|
150
|
+
You can also export your data (filtered and geocoded) to KML. As an example, here are "all the Gasoline filling stations in the UK":http://tables.googlelabs.com/exporttable?query=select+col0%2Ccol1%2Ccol2%2Ccol3%2Ccol4%2Ccol5%2Ccol6%2Ccol12%2Ccol13%2Ccol14%2Ccol15%2Ccol16%2Ccol17%2Ccol18%2Ccol19%2Ccol20%2Ccol21+from+214045+&o=kmllink&g=col0
|
151
|
+
|
152
|
+
read "more here":http://code.google.com/apis/maps/documentation/javascript/overlays.html#FusionTables
|
153
|
+
|
154
|
+
h2. Known Issues
|
155
|
+
|
156
|
+
# The gem uses the Google gdata_19 gem which conflicts with the GData2 gem. Uninstall gdata2 to regain sanity.
|
157
|
+
# Currently you have to make a table public before you can display it on a map, unfortunately, this can only be done on the web interface. A suggested workaround is to put all your data in 1 big public table, and then query for the data you want to display based off a key/flag column
|
158
|
+
|
159
|
+
h2. Note on Patches/Pull Requests
|
160
|
+
|
161
|
+
* Fork the project.
|
162
|
+
* Make your feature addition or bug fix.
|
163
|
+
* Add tests for it. This is important so I don't break it in a
|
164
|
+
future version unintentionally.
|
165
|
+
* Commit, do not mess with rakefile, version, or history.
|
166
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
167
|
+
* Send me a pull request. Bonus points for topic branches.
|
168
|
+
|
169
|
+
h2. Acknowledgments
|
170
|
+
|
171
|
+
A huge thank you to: Tom Verbeure, Simon Tokumine
|
172
|
+
|
173
|
+
h2. Copyright
|
174
|
+
|
175
|
+
Copyright (c) 2011 Kareem Hashem. See LICENSE for details.
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "google_fusion_tables"
|
8
|
+
gem.summary = %Q{Google Fusion Tables API wrapper}
|
9
|
+
gem.description = %Q{A simple Google Fusion Tables API wrapper. Supports bulk inserts and most API functions}
|
10
|
+
gem.email = "eng.kareem.hashem@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/kimohashem/fusion-tables"
|
12
|
+
gem.authors = ["Kareem Hashem"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.add_dependency "gdata_19", ">= 1.1.2"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rcov/rcovtask'
|
31
|
+
Rcov::RcovTask.new do |test|
|
32
|
+
test.libs << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
rescue LoadError
|
37
|
+
task :rcov do
|
38
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
task :test => :check_dependencies
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'rake/rdoctask'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
49
|
+
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = "fusion_tables #{version}"
|
52
|
+
rdoc.rdoc_files.include('README*')
|
53
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
54
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.2
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Twitter Fusion Tables Mashup
|
4
|
+
# S.Tokumine 2010
|
5
|
+
#
|
6
|
+
# looks for tweets in the live stream around certain
|
7
|
+
# cities and posts them to fusion tables with KML attached
|
8
|
+
#
|
9
|
+
# Gem dependencies:
|
10
|
+
#
|
11
|
+
# tweetstream
|
12
|
+
# GeoRuby
|
13
|
+
# fusion_tables
|
14
|
+
#
|
15
|
+
# Output from running this for an evening
|
16
|
+
# http://tables.googlelabs.com/DataSource?snapid=72509
|
17
|
+
#
|
18
|
+
require 'rubygems'
|
19
|
+
require 'tweetstream'
|
20
|
+
require 'geo_ruby'
|
21
|
+
include GeoRuby
|
22
|
+
include SimpleFeatures
|
23
|
+
require 'fusion_tables'
|
24
|
+
require 'time'
|
25
|
+
require 'yaml'
|
26
|
+
|
27
|
+
class Object
|
28
|
+
def try(method, *args, &block)
|
29
|
+
send(method, *args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Configure settings
|
34
|
+
config = YAML::load_file(File.join(File.dirname(__FILE__), 'credentials.yml'))
|
35
|
+
DEFAULT_SRID = 4328
|
36
|
+
|
37
|
+
# Twitter places
|
38
|
+
places = {
|
39
|
+
:san_francisco => [-122.75,36.8,-121.75,37.8],
|
40
|
+
:new_york => [-74,40,-73,41],
|
41
|
+
:tokyo => [139.3,35,140.3,36],
|
42
|
+
:london => [-0.54,51.2,0.46,52.2],
|
43
|
+
:madrid => [-4.2,40,-3.2,41],
|
44
|
+
:paris => [1.75,48.5,2.75, 49.5],
|
45
|
+
:beijing => [115.9,39,116.9,40],
|
46
|
+
:mumbai => [72.75,18.88,73.75,19.88],
|
47
|
+
}
|
48
|
+
|
49
|
+
# Configure fusion tables
|
50
|
+
ft = GData::Client::FusionTables.new
|
51
|
+
ft.clientlogin(config["google_username"], config["google_password"])
|
52
|
+
table_name = "TwitterFusion"
|
53
|
+
cols = [
|
54
|
+
{:name => 'screen_name', :type => 'string'},
|
55
|
+
{:name => 'avatar', :type => 'string'},
|
56
|
+
{:name => 'text', :type => 'string'},
|
57
|
+
{:name => 'created', :type => 'datetime'},
|
58
|
+
{:name => 'url', :type => 'string'},
|
59
|
+
{:name => 'location', :type => 'location'},
|
60
|
+
{:name => 'iso', :type => 'location'},
|
61
|
+
{:name => 'country_name', :type => 'location'},
|
62
|
+
{:name => 'city', :type => 'string'}
|
63
|
+
]
|
64
|
+
|
65
|
+
# Create FT if it doesn't exist
|
66
|
+
tables = ft.show_tables
|
67
|
+
table = tables.select{|t| t.name == table_name}.first
|
68
|
+
table = ft.create_table(table_name, cols) if !table
|
69
|
+
|
70
|
+
# Configure Twitter stream client
|
71
|
+
data = []
|
72
|
+
tw = TweetStream::Client.new(config["twitter_username"],config["twitter_password"])
|
73
|
+
|
74
|
+
# configure friendly rate limit handling
|
75
|
+
tw.on_limit do |skip_count|
|
76
|
+
sleep 5
|
77
|
+
end
|
78
|
+
|
79
|
+
# start searching twitter stream and posting to FT
|
80
|
+
tw.filter(:locations => places.values.join(",")) do |tweet|
|
81
|
+
begin
|
82
|
+
|
83
|
+
country = "unknown"
|
84
|
+
iso = "unknown"
|
85
|
+
begin
|
86
|
+
country = tweet.try(:[],:place).try(:[], :country)
|
87
|
+
iso = tweet.try(:[],:place).try(:[], :country_code)
|
88
|
+
rescue
|
89
|
+
end
|
90
|
+
|
91
|
+
# Divine the tweets geometry
|
92
|
+
#
|
93
|
+
# overly complex due to
|
94
|
+
# * some US tweets have their lat/longs flipped (but not all...)
|
95
|
+
# * some geo tweets are made using a "place" envelope rather than exact lat/kng
|
96
|
+
if tweet[:geo]
|
97
|
+
if iso == 'US' && tweet[:geo][:coordinates][1] > 0
|
98
|
+
p = Point.from_x_y(tweet[:geo][:coordinates][0],tweet[:geo][:coordinates][1])
|
99
|
+
else
|
100
|
+
p = Point.from_x_y(tweet[:geo][:coordinates][1],tweet[:geo][:coordinates][0])
|
101
|
+
end
|
102
|
+
else
|
103
|
+
p = Polygon.from_coordinates(tweet[:place][:bounding_box][:coordinates]).envelope.center
|
104
|
+
end
|
105
|
+
|
106
|
+
# work out which city the tweet is from by testing with an extended bounding box
|
107
|
+
# BBox extention needed as twitter returns things outside our defined bboxes...
|
108
|
+
city = "unknown"
|
109
|
+
places.each do |key, value|
|
110
|
+
if !(p.x < value[0]-1 || p.x > value[2]+1 || p.y < value[1]-1 || p.y > value[3]+1)
|
111
|
+
city = key.to_s.gsub("_"," ")
|
112
|
+
break
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# pack data
|
117
|
+
data << {
|
118
|
+
"screen_name" => tweet[:user][:screen_name],
|
119
|
+
"avatar" => tweet[:user][:profile_image_url],
|
120
|
+
"text" => tweet[:text],
|
121
|
+
"created" => Time.parse(tweet[:created_at]),
|
122
|
+
"url" => "http://twitter.com/#{tweet[:user][:screen_name]}/status/#{tweet[:id]}",
|
123
|
+
"location" => p.as_kml,
|
124
|
+
"iso" => iso,
|
125
|
+
"country_name" => country,
|
126
|
+
"city" => city
|
127
|
+
}
|
128
|
+
rescue => e
|
129
|
+
puts "ERROR: #{e.inspect}, #{e.backtrace}"
|
130
|
+
#let sleeping dogs lie...
|
131
|
+
end
|
132
|
+
|
133
|
+
# let us know how we're doing
|
134
|
+
puts "#{50-data.size}: #{city}, #{tweet.text}"
|
135
|
+
|
136
|
+
# Post to fusion tables
|
137
|
+
if data.size == 50
|
138
|
+
puts "sending data to fusion tables..."
|
139
|
+
ft_data = data
|
140
|
+
data = []
|
141
|
+
table.insert ft_data
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{google_fusion_tables}
|
8
|
+
s.version = "1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Kareem Hashem"]
|
12
|
+
s.date = %q{2011-01-26}
|
13
|
+
s.description = %q{A simple Google Fusion Tables API wrapper. Supports bulk inserts and most API functions}
|
14
|
+
s.email = %q{eng.kareem.hashem@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.textile"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.textile",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"examples/compare_tweets.rb",
|
27
|
+
"examples/credentials.example.yml",
|
28
|
+
"fusion_tables.gemspec",
|
29
|
+
"lib/fusion_tables.rb",
|
30
|
+
"lib/fusion_tables/client/fusion_tables.rb",
|
31
|
+
"lib/fusion_tables/data/data.rb",
|
32
|
+
"lib/fusion_tables/data/table.rb",
|
33
|
+
"lib/fusion_tables/ext/fusion_tables.rb",
|
34
|
+
"pkg/fusion_tables-0.1.0.gem",
|
35
|
+
"pkg/fusion_tables-0.1.1.gem",
|
36
|
+
"pkg/fusion_tables-0.1.2.gem",
|
37
|
+
"pkg/fusion_tables-0.2.0.gem",
|
38
|
+
"pkg/fusion_tables-0.2.1.gem",
|
39
|
+
"pkg/fusion_tables-0.2.2.gem",
|
40
|
+
"test/README",
|
41
|
+
"test/helper.rb",
|
42
|
+
"test/test_client.rb",
|
43
|
+
"test/test_config.yml.sample",
|
44
|
+
"test/test_ext.rb",
|
45
|
+
"test/test_table.rb"
|
46
|
+
]
|
47
|
+
s.homepage = %q{http://github.com/kimohashem/fusion-tables}
|
48
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = %q{1.3.7}
|
51
|
+
s.summary = %q{Google Fusion Tables API wrapper}
|
52
|
+
s.test_files = [
|
53
|
+
"test/helper.rb",
|
54
|
+
"test/test_client.rb",
|
55
|
+
"test/test_ext.rb",
|
56
|
+
"test/test_table.rb",
|
57
|
+
"examples/compare_tweets.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
66
|
+
s.add_runtime_dependency(%q<gdata_19>, [">= 1.1.2"])
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
69
|
+
s.add_dependency(%q<gdata_19>, [">= 1.1.2"])
|
70
|
+
end
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
73
|
+
s.add_dependency(%q<gdata_19>, [">= 1.1.2"])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Copyright (C) 2010 Tom Verbeure, Simon Tokumine
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module GData
|
16
|
+
module Client
|
17
|
+
class FusionTables < Base
|
18
|
+
|
19
|
+
SERVICE_URL = "https://tables.googlelabs.com/api/query"
|
20
|
+
DATATYPES = %w(number string location datetime)
|
21
|
+
|
22
|
+
def initialize(options = {})
|
23
|
+
options[:clientlogin_service] ||= 'fusiontables'
|
24
|
+
options[:headers] = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
25
|
+
super(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def sql_encode(sql)
|
29
|
+
"sql=" + CGI::escape(sql)
|
30
|
+
end
|
31
|
+
|
32
|
+
def sql_get(sql)
|
33
|
+
resp = self.get(SERVICE_URL + "?" + sql_encode(sql))
|
34
|
+
end
|
35
|
+
|
36
|
+
def sql_post(sql)
|
37
|
+
resp = self.post(SERVICE_URL, sql_encode(sql))
|
38
|
+
end
|
39
|
+
|
40
|
+
def sql_put(sql)
|
41
|
+
resp = self.put(SERVICE_URL, sql_encode(sql))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Overrides auth_handler= so if the authentication changes,
|
45
|
+
# the session cookie is cleared.
|
46
|
+
def auth_handler=(handler)
|
47
|
+
@session_cookie = nil
|
48
|
+
return super(handler)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Overrides make_request to handle 500 redirects with a session cookie.
|
52
|
+
def make_request(method, url, body = '', retries = 10)
|
53
|
+
begin
|
54
|
+
response = super(method, url, body)
|
55
|
+
rescue GData::Client::ServerError => e
|
56
|
+
if e.response.status_code == 500 and retries > 0
|
57
|
+
sleep_time = 11 - retries
|
58
|
+
sleep sleep_time # <= Fusion tables has rate of 5 calls per second. Be nice, get longer
|
59
|
+
@session_cookie = e.response.headers['set-cookie']
|
60
|
+
return self.make_request(method, url, body, retries - 1)
|
61
|
+
else
|
62
|
+
return e.response
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Custom prepare_headers to include the session cookie if it exists
|
68
|
+
def prepare_headers
|
69
|
+
if @session_cookie
|
70
|
+
@headers['cookie'] = @session_cookie
|
71
|
+
end
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright (C) 2010 Simon Tokumine
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module GData
|
16
|
+
module Client
|
17
|
+
class FusionTables < Base
|
18
|
+
class Data
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
attr_reader :headers, :body, :live
|
22
|
+
alias :to_h :body
|
23
|
+
|
24
|
+
# configures headers hash and sets up
|
25
|
+
def initialize options
|
26
|
+
@headers = options[:headers]
|
27
|
+
@body = options[:body]
|
28
|
+
end
|
29
|
+
|
30
|
+
# Reads in CSV
|
31
|
+
def self.parse response
|
32
|
+
body = []
|
33
|
+
headers = []
|
34
|
+
|
35
|
+
first = true
|
36
|
+
if CSV.const_defined? :Reader
|
37
|
+
CSV::Reader.parse(response.body) do |row|
|
38
|
+
if first
|
39
|
+
first = false
|
40
|
+
headers = row.map { |x|x.strip.downcase.gsub(" ","_").to_sym }
|
41
|
+
next
|
42
|
+
end
|
43
|
+
body << Hash[*headers.zip(row).flatten]
|
44
|
+
end
|
45
|
+
else
|
46
|
+
CSV.parse(response.body) do |row|
|
47
|
+
if first
|
48
|
+
first = false
|
49
|
+
headers = row.map { |x|x.strip.downcase.gsub(" ","_").to_sym }
|
50
|
+
next
|
51
|
+
end
|
52
|
+
body << Hash[*headers.zip(row).flatten]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
self.new :headers => headers, :body => body
|
56
|
+
end
|
57
|
+
|
58
|
+
# Implement enumerable
|
59
|
+
def each
|
60
|
+
@body.each { |i| yield i }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
# Encodes row according to type
|
65
|
+
def encode
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# Copyright (C) 2010 Simon Tokumine
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module GData
|
16
|
+
module Client
|
17
|
+
class FusionTables < Base
|
18
|
+
class Table
|
19
|
+
attr_reader :headers, :id, :name
|
20
|
+
|
21
|
+
# configures headers hash and sets up
|
22
|
+
#
|
23
|
+
# eg options: {:table_id => "x", :name => "y"}
|
24
|
+
#
|
25
|
+
def initialize client, options
|
26
|
+
raise ArgumentError, "need ft client" if client.class != GData::Client::FusionTables
|
27
|
+
raise ArgumentError, "need table_id and name hash" if !options.has_key?(:name) || !options.has_key?(:table_id)
|
28
|
+
@client = client
|
29
|
+
@id = options[:table_id]
|
30
|
+
@name = options[:name]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets up data types from google
|
34
|
+
#
|
35
|
+
def describe
|
36
|
+
@client.execute "DESCRIBE #{@id}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Runs select and returns data obj
|
40
|
+
#
|
41
|
+
# Define columns and SQL conditions separatly
|
42
|
+
#
|
43
|
+
# See http://code.google.com/apis/fusiontables/docs/developers_reference.html#Select
|
44
|
+
#
|
45
|
+
# use columns=ROWID to select row ids
|
46
|
+
#
|
47
|
+
def select columns="*", conditions=nil
|
48
|
+
@client.execute "SELECT #{columns} FROM #{@id} #{conditions}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a count of rows. SQL conditions optional
|
52
|
+
#
|
53
|
+
# Note: handles odd FT response: when table has 0 rows, returns empty array.
|
54
|
+
def count conditions=nil
|
55
|
+
result = select("count()", conditions)
|
56
|
+
result.empty? ? 0 : result.first[:"count()"].to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Outputs data to an array of concatenated INSERT SQL statements
|
61
|
+
#
|
62
|
+
# format should be:
|
63
|
+
#
|
64
|
+
# [{:col_1 => data, :col2 => data}, {:col_1 => data, :col2 => data}]
|
65
|
+
#
|
66
|
+
# Fields are escaped and formatted for FT based on type
|
67
|
+
#
|
68
|
+
def insert data
|
69
|
+
data = [data] unless data.respond_to?(:to_ary)
|
70
|
+
|
71
|
+
# encode values to insert
|
72
|
+
data = encode data
|
73
|
+
|
74
|
+
# Chunk up the data and send
|
75
|
+
chunk = ""
|
76
|
+
data.each_with_index do |d,i|
|
77
|
+
chunk << "INSERT INTO #{@id} (#{ d.keys.join(",") }) VALUES (#{ d.values.join(",") });"
|
78
|
+
if (i+1) % 10 == 0 || (i+1) == data.size
|
79
|
+
begin
|
80
|
+
@client.sql_post(chunk)
|
81
|
+
chunk = ""
|
82
|
+
rescue => e
|
83
|
+
raise "INSERT to table:#{@id} failed on row #{i} with #{e}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Runs update on row specified and return data obj
|
91
|
+
def update row_id, data
|
92
|
+
data = encode([data]).first
|
93
|
+
data = data.to_a.map{|x| x.join("=")}.join(", ")
|
94
|
+
@client.execute "UPDATE #{@id} SET #{data} WHERE ROWID = '#{row_id}'"
|
95
|
+
end
|
96
|
+
|
97
|
+
# delete row
|
98
|
+
def delete row_id
|
99
|
+
@client.execute "DELETE FROM #{@id} WHERE rowid='#{row_id}'"
|
100
|
+
end
|
101
|
+
|
102
|
+
# delete all rows
|
103
|
+
def truncate!
|
104
|
+
@client.execute "DELETE FROM #{@id}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_headers
|
108
|
+
@headers ||= describe
|
109
|
+
end
|
110
|
+
|
111
|
+
def encode data
|
112
|
+
data.inject([]) do |ar,h|
|
113
|
+
ret = {}
|
114
|
+
h.each do |key, value|
|
115
|
+
if value.nil?
|
116
|
+
#empty string for nils
|
117
|
+
ret["'#{key.to_s}'"] = "''"
|
118
|
+
else
|
119
|
+
ret["'#{key.to_s}'"] = case get_datatype(key)
|
120
|
+
when "number" then "#{value}"
|
121
|
+
when "datetime" then "'#{value.strftime("%m-%d-%Y %H:%M:%S")}'"
|
122
|
+
else "'#{value.gsub(/\\/, '\&\&').gsub(/'/, "''")}'"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
ar << ret
|
127
|
+
ar
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Returns datatype of given column name
|
133
|
+
#
|
134
|
+
def get_datatype column_name
|
135
|
+
get_headers
|
136
|
+
@headers.each do |h|
|
137
|
+
return h[:type] if h[:name].force_encoding('utf-8') == column_name.to_s
|
138
|
+
end
|
139
|
+
raise ArgumentError, "The column #{column_name} doesn't exist"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# Copyright (C) 2010 Tom Verbeure, Simon Tokumine
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module GData
|
16
|
+
module Client
|
17
|
+
class FusionTables < Base
|
18
|
+
|
19
|
+
# Helper method to run FT SQL and return FT data object
|
20
|
+
def execute(sql)
|
21
|
+
http_req = sql.upcase.match(/^(DESCRIBE|SHOW|SELECT)/) ? :sql_get : :sql_post
|
22
|
+
GData::Client::FusionTables::Data.parse(self.send(http_req, sql)).body
|
23
|
+
end
|
24
|
+
|
25
|
+
# Show a list of fusion tables
|
26
|
+
def show_tables
|
27
|
+
data = self.execute "SHOW TABLES"
|
28
|
+
|
29
|
+
data.inject([]) do |x, row|
|
30
|
+
x << GData::Client::FusionTables::Table.new(self, row)
|
31
|
+
x
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a new table. Return the corresponding table
|
36
|
+
#
|
37
|
+
# Columns specified as [{:name => 'my_col_name', :type => 'my_type'}]
|
38
|
+
#
|
39
|
+
# Type must be one of:
|
40
|
+
#
|
41
|
+
# * number
|
42
|
+
# * string
|
43
|
+
# * location
|
44
|
+
# * datetime
|
45
|
+
#
|
46
|
+
def create_table(table_name, columns)
|
47
|
+
|
48
|
+
# Sanity check name
|
49
|
+
table_name = table_name.strip.gsub(/ /,'_')
|
50
|
+
# surrounded the table_name with '' to support the non latin languages
|
51
|
+
table_name = "'"+table_name+"'"
|
52
|
+
# ensure all column types are valid
|
53
|
+
columns.each do |col|
|
54
|
+
if !DATATYPES.include? col[:type].downcase
|
55
|
+
raise ArgumentError, "Ensure input types are: 'number', 'string', 'location' or 'datetime'"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# generate sql
|
60
|
+
fields = columns.map{ |col| "'#{col[:name]}': #{col[:type].upcase}" }.join(", ")
|
61
|
+
sql = "CREATE TABLE #{table_name} (#{fields})"
|
62
|
+
|
63
|
+
# create table
|
64
|
+
resp = self.sql_post(sql)
|
65
|
+
raise "unknown column type" if resp.body == "Unknown column type."
|
66
|
+
|
67
|
+
# construct table object and return
|
68
|
+
table_id = resp.body.split("\n")[1].chomp.to_i
|
69
|
+
table = GData::Client::FusionTables::Table.new(self, :table_id => table_id, :name => table_name)
|
70
|
+
table.get_headers
|
71
|
+
table
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_view(table_id, view_name, columns, filter)
|
75
|
+
# Sanity check name
|
76
|
+
view_name = view_name.strip.gsub(/ /,'_')
|
77
|
+
# surrounded the table_name with '' to support the non latin languages
|
78
|
+
view_name = "'"+view_name+"'"
|
79
|
+
|
80
|
+
|
81
|
+
# generate sql
|
82
|
+
fields = columns.collect{|x| "'"+x+"'"}.join(", ")
|
83
|
+
sql = "CREATE VIEW #{view_name} AS (SELECT #{fields} FROM #{table_id})"
|
84
|
+
unless filter.empty?
|
85
|
+
filter = filter.map{ |col| "'#{col[:name]}' = '#{col[:value]}'" }.join(" AND ")
|
86
|
+
sql = "CREATE VIEW #{view_name} AS (SELECT #{fields} FROM #{table_id} WHERE #{filter})"
|
87
|
+
else
|
88
|
+
sql = "CREATE VIEW #{view_name} AS (SELECT #{fields} FROM #{table_id})"
|
89
|
+
end
|
90
|
+
# create view
|
91
|
+
resp = self.sql_post(sql)
|
92
|
+
raise "unknown column type" if resp.body == "Unknown column type."
|
93
|
+
end
|
94
|
+
|
95
|
+
# Drops Fusion Tables
|
96
|
+
#
|
97
|
+
# options can be:
|
98
|
+
#
|
99
|
+
# * an integer for single drop
|
100
|
+
# * array of integers for multi drop
|
101
|
+
# * a regex against table_name for flexible multi_drop
|
102
|
+
#
|
103
|
+
def drop(options)
|
104
|
+
# collect ids
|
105
|
+
ids = []
|
106
|
+
ids << options if options.class == Integer || options.class == String || Fixnum
|
107
|
+
ids = options if options.class == Array
|
108
|
+
|
109
|
+
if options.class == Regexp
|
110
|
+
tables = show_tables
|
111
|
+
ids = tables.map { |table| table.id if options =~ table.name }.compact
|
112
|
+
end
|
113
|
+
|
114
|
+
# drop tables
|
115
|
+
delete_count = 0
|
116
|
+
ids.each do |id|
|
117
|
+
resp = self.sql_post("DROP TABLE #{id}")
|
118
|
+
delete_count += 1 if resp.body.strip.downcase == 'ok'
|
119
|
+
end
|
120
|
+
delete_count
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/test/README
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'fusion_tables'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
|
12
|
+
def init_config
|
13
|
+
if not defined? @config_file
|
14
|
+
begin
|
15
|
+
@config_file = YAML::load_file(File.join(File.dirname(__FILE__), 'test_config.yml'))
|
16
|
+
rescue
|
17
|
+
puts "Please configure your test_config.yml file using test_config.yml.sample as base"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@config_file
|
21
|
+
end
|
22
|
+
|
23
|
+
def username
|
24
|
+
@config_file['username']
|
25
|
+
end
|
26
|
+
|
27
|
+
def password
|
28
|
+
@config_file['password']
|
29
|
+
end
|
30
|
+
|
31
|
+
def table_name
|
32
|
+
@config_file['table_name']
|
33
|
+
end
|
34
|
+
end
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestClient < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The fusion_tables client library" do
|
6
|
+
setup do
|
7
|
+
init_config
|
8
|
+
@ft = GData::Client::FusionTables.new
|
9
|
+
@ft.clientlogin(username, password)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "be properly setup" do
|
13
|
+
assert_equal @ft.clientlogin_service, "fusiontables"
|
14
|
+
assert_equal @ft.headers["Content-Type"], "application/x-www-form-urlencoded"
|
15
|
+
end
|
16
|
+
|
17
|
+
should "be able to authenticate with the google services" do
|
18
|
+
assert_equal @ft.auth_handler.service, "fusiontables"
|
19
|
+
assert @ft.auth_handler.token
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/test/test_ext.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestExt < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The Fusion Tables helper functions" do
|
6
|
+
setup do
|
7
|
+
init_config
|
8
|
+
@ft = GData::Client::FusionTables.new
|
9
|
+
@ft.clientlogin(username, password)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
should "raise ArgumentError if supply unknown types to it" do
|
14
|
+
assert_raise ArgumentError do
|
15
|
+
@ft.create_table "test table", [{:name => "test_col", :type => "billys birthday" }]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
should "let you create a table if you get everything right" do
|
20
|
+
table = @ft.create_table "test_table", [{:name => "test_col", :type => "string" }]
|
21
|
+
assert_equal table.class, GData::Client::FusionTables::Table
|
22
|
+
@ft.drop(table.id)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "correct your table name to a certain degree on create" do
|
26
|
+
table = @ft.create_table "test table", [{:name => "test col", :type => "string" }]
|
27
|
+
assert_equal table.name, "test_table"
|
28
|
+
@ft.drop(table.id)
|
29
|
+
end
|
30
|
+
|
31
|
+
should "return you a list of your fusion tables" do
|
32
|
+
resp = @ft.show_tables
|
33
|
+
assert_equal resp.first.class, GData::Client::FusionTables::Table if resp.first
|
34
|
+
end
|
35
|
+
|
36
|
+
should "be possible to delete a table with an id" do
|
37
|
+
table = @ft.create_table "test_table", [{:name => "test col", :type => "string" }]
|
38
|
+
assert_equal @ft.drop(table.id), 1
|
39
|
+
end
|
40
|
+
|
41
|
+
should "be possible to delete tables with an array of ids" do
|
42
|
+
table1 = @ft.create_table "test_table", [{:name => "test col", :type => "string" }]
|
43
|
+
table2 = @ft.create_table "test_table", [{:name => "test col", :type => "string" }]
|
44
|
+
assert_equal @ft.drop([table1.id, table2.id]), 2
|
45
|
+
end
|
46
|
+
|
47
|
+
should "be possible to delete multiple tables with a regex" do
|
48
|
+
table1 = @ft.create_table "test_table", [{:name => "test col", :type => "string" }]
|
49
|
+
table2 = @ft.create_table "test_table", [{:name => "test col", :type => "string" }]
|
50
|
+
assert_equal @ft.drop(/^test_/), 2
|
51
|
+
end
|
52
|
+
|
53
|
+
should "return zero if passed a silly id" do
|
54
|
+
assert_equal @ft.drop(235243875629384756), 0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/test/test_table.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestTable < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "uploading data to FT" do
|
6
|
+
setup do
|
7
|
+
init_config
|
8
|
+
@ft = GData::Client::FusionTables.new
|
9
|
+
@ft.clientlogin(username, password)
|
10
|
+
@table = @ft.create_table "test", [{:name => 'firstname', :type => 'string'},
|
11
|
+
{:name => 'phone', :type => 'number'},
|
12
|
+
{:name => 'dob', :type => 'datetime'},
|
13
|
+
{:name => 'house', :type => 'location'}]
|
14
|
+
end
|
15
|
+
|
16
|
+
should "format data and prep for upload" do
|
17
|
+
data = @table.encode [{:firstname => "\\bob's piz\za",
|
18
|
+
:phone => 12,
|
19
|
+
:dob => Time.utc(2010,"aug",10,20,15,1),
|
20
|
+
:house => "POINT(1,1)"}]
|
21
|
+
|
22
|
+
row = data.first
|
23
|
+
assert_equal row[:firstname], "'\\\\bob''s pizza'"
|
24
|
+
assert_equal row[:phone], "#{12}"
|
25
|
+
assert_equal row[:dob], "'08-10-2010'"
|
26
|
+
assert_equal row[:house], "'POINT(1,1)'"
|
27
|
+
end
|
28
|
+
|
29
|
+
should "be able to insert 1 row of data" do
|
30
|
+
data = 1.times.inject([]) { |a,i|
|
31
|
+
a << {:firstname => "\\bob's piz\za-#{i}",
|
32
|
+
:phone => 12,
|
33
|
+
:dob => Time.utc(2010,"aug",10,20,15,1),
|
34
|
+
:house => '<Point><coordinates>-74.006393,40.714172,0</coordinates></Point>'}
|
35
|
+
}
|
36
|
+
|
37
|
+
@table.insert data
|
38
|
+
end
|
39
|
+
|
40
|
+
should "be able to insert 501 rows of data" do
|
41
|
+
data = 501.times.inject([]) { |a,i|
|
42
|
+
a << {:firstname => "Person-#{i}",
|
43
|
+
:phone => 12,
|
44
|
+
:dob => Time.utc(2010,"aug",10,20,15,1),
|
45
|
+
:house => "<Point><coordinates>#{180-rand(360)},#{90-rand(180)},0</coordinates></Point>"}
|
46
|
+
}
|
47
|
+
|
48
|
+
@table.insert data
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
should "be able to count the number of rows" do
|
53
|
+
data = 2.times.inject([]) { |a,i|
|
54
|
+
a << {:firstname => "Person-#{i}",
|
55
|
+
:phone => 12,
|
56
|
+
:dob => Time.utc(2010,"aug",10,20,15,1),
|
57
|
+
:house => "<Point><coordinates>#{180-rand(360)},#{90-rand(180)},0</coordinates></Point>"}
|
58
|
+
}
|
59
|
+
|
60
|
+
@table.insert data
|
61
|
+
assert_equal @table.count, 2
|
62
|
+
end
|
63
|
+
|
64
|
+
should "be able to select the rows" do
|
65
|
+
data = 2.times.inject([]) { |a,i|
|
66
|
+
a << {:firstname => "Person-#{i}",
|
67
|
+
:phone => 12,
|
68
|
+
:dob => Time.utc(2010,"aug",10,20,15,1),
|
69
|
+
:house => "<Point><coordinates>1,1,0</coordinates></Point>"}
|
70
|
+
}
|
71
|
+
|
72
|
+
@table.insert data
|
73
|
+
assert_equal @table.select, [{:firstname=>"Person-0", :phone=>"12", :dob=>"08-10-2010", :house=>"<Point><coordinates>1,1,0</coordinates></Point>"}, {:firstname=>"Person-1", :phone=>"12", :dob=>"08-10-2010", :house=>"<Point><coordinates>1,1,0</coordinates></Point>"}]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: google_fusion_tables
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kareem Hashem
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-01-26 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thoughtbot-shoulda
|
16
|
+
requirement: &19191900 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *19191900
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: gdata_19
|
27
|
+
requirement: &19191420 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.2
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *19191420
|
36
|
+
description: A simple Google Fusion Tables API wrapper. Supports bulk inserts and
|
37
|
+
most API functions
|
38
|
+
email: eng.kareem.hashem@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.textile
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.textile
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- examples/compare_tweets.rb
|
52
|
+
- examples/credentials.example.yml
|
53
|
+
- fusion_tables.gemspec
|
54
|
+
- lib/fusion_tables.rb
|
55
|
+
- lib/fusion_tables/client/fusion_tables.rb
|
56
|
+
- lib/fusion_tables/data/data.rb
|
57
|
+
- lib/fusion_tables/data/table.rb
|
58
|
+
- lib/fusion_tables/ext/fusion_tables.rb
|
59
|
+
- pkg/fusion_tables-0.1.0.gem
|
60
|
+
- pkg/fusion_tables-0.1.1.gem
|
61
|
+
- pkg/fusion_tables-0.1.2.gem
|
62
|
+
- pkg/fusion_tables-0.2.0.gem
|
63
|
+
- pkg/fusion_tables-0.2.1.gem
|
64
|
+
- pkg/fusion_tables-0.2.2.gem
|
65
|
+
- test/README
|
66
|
+
- test/helper.rb
|
67
|
+
- test/test_client.rb
|
68
|
+
- test/test_config.yml.sample
|
69
|
+
- test/test_ext.rb
|
70
|
+
- test/test_table.rb
|
71
|
+
homepage: http://github.com/kimohashem/fusion-tables
|
72
|
+
licenses: []
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options:
|
75
|
+
- --charset=UTF-8
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.15
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Google Fusion Tables API wrapper
|
96
|
+
test_files:
|
97
|
+
- test/helper.rb
|
98
|
+
- test/test_client.rb
|
99
|
+
- test/test_ext.rb
|
100
|
+
- test/test_table.rb
|
101
|
+
- examples/compare_tweets.rb
|