fusion_tables 0.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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,25 @@
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
+ pkg
20
+
21
+ ## PROJECT::TEST
22
+ test/test_config.yml
23
+
24
+ ## PROJECT::SPECIFIC
25
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Simon Tokumine
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,122 @@
1
+ h1. fusion-tables
2
+
3
+ This API lets you easily interact with Google Fusion Tables from your Ruby application.
4
+
5
+ h2. Installation
6
+
7
+ gem install fusion-tables
8
+
9
+ h2. API examples
10
+
11
+ The best place to see what's going on is in the tests, but this is a good starting point.
12
+
13
+ <pre><code>
14
+ # Connect to service
15
+ @ft = GData::Client::FusionTables.new
16
+ @ft.clientlogin(username, password)
17
+
18
+ # Create a new table
19
+ # Configure the columns you want
20
+ cols = [{:name => "friend name", :datatype => 'string' },
21
+ {:name => "age", :datatype => 'number' },
22
+ {:name => "meeting time", :datatype => 'datetime' },
23
+ {:name => "where", :datatype => 'location' }]
24
+
25
+ # Create the table
26
+ @table = @ft.create_table "My upcoming meetings", cols
27
+ @table.id #=> 42342 (the table's google id)
28
+
29
+ # Insert data
30
+ data = [{"friend name" => "Eric Wimp",
31
+ "age" => 25,
32
+ "meeting time" => Time.utc(2010,"aug",10,20,15,1),
33
+ "where" => "29 Acacia Road, Nuttytown"]
34
+
35
+ @table.insert data
36
+
37
+ # Note - the insert method chunks the data array into 500 row chunks
38
+
39
+ # Count data
40
+ @table.count #=> 1
41
+
42
+ # Select data
43
+ @table.select #=> data
44
+ row_ids = @table.select "ROWID"
45
+
46
+ # Update data (not working)
47
+ # @table.update row_id, {:age => 26}
48
+
49
+ # Delete data
50
+ @table.delete row_id
51
+
52
+ # Drop tables
53
+ @ft.drop @table.id # table id
54
+ @ft.drop [@table1.id, @table2.id] # arrays of table ids
55
+ @ft.drop /yacht/ # regex
56
+ </code></pre>
57
+
58
+ h2. Fusion Tables secret Geospatial Sauce
59
+
60
+ 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 ultra-fast generating of custom google map layers across massive datasets*
61
+
62
+ Fusion Tables supports the following geometry types:
63
+
64
+ * lat/long
65
+ * addresses (automatically geocodes them for you)
66
+ * KML (point, polyline, polygon, multipolygon)
67
+
68
+ h2. Thousands of geometries rendered in near real time, integrated with google maps v3
69
+
70
+ Adding a fusion tables datalayer with many points/polygons to your v3 map is as simple as:
71
+
72
+ <pre><code>
73
+ layer = new google.maps.FusionTablesLayer(139529);
74
+ </code></pre>
75
+
76
+ That's it
77
+
78
+ You can also refine the tiles by SQL, and can even do so dynamically:
79
+
80
+ <pre><code>
81
+ layer = new google.maps.FusionTablesLayer(198945, {
82
+ query: "SELECT address FROM 198945 WHERE ridership > 5000"}
83
+ );
84
+ </code></pre>
85
+
86
+ Finally, fusion tables also lets you make Heatmaps
87
+
88
+ <pre><code>
89
+ layer = new google.maps.FusionTablesLayer(136705, {
90
+ heatmap: true
91
+ });
92
+ </code></pre>
93
+
94
+ 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
95
+
96
+ read "more here":http://code.google.com/apis/maps/documentation/javascript/overlays.html#FusionTables
97
+
98
+ h2. A word of warning...
99
+
100
+ # Google has a recent habit of deleting projects. This is still a labs project, and may disappear at any time
101
+ # The API is still very young and *will change*
102
+ # 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
103
+
104
+ h2. Note on Patches/Pull Requests
105
+
106
+ * Fork the project.
107
+ * Make your feature addition or bug fix.
108
+ * Add tests for it. This is important so I don't break it in a
109
+ future version unintentionally.
110
+ * Commit, do not mess with rakefile, version, or history.
111
+ (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)
112
+ * Send me a pull request. Bonus points for topic branches.
113
+
114
+ h2. Copyright
115
+
116
+ Largely based on Tom Verbeure's work for MTBGuru: http://code.google.com/p/mtbguru-fusiontables/
117
+
118
+ Copyright (c) 2010 Simon Tokumine. See LICENSE for details.
119
+
120
+
121
+
122
+
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 = "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 = "simon@tinypla.net"
11
+ gem.homepage = "http://github.com/tokumine/fusion-tables"
12
+ gem.authors = ["Simon Tokumine", "Tom Verbeure"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ gem.add_dependency "gdata", ">= 1.1.1"
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/TODO ADDED
@@ -0,0 +1,9 @@
1
+ Areas of improvement:
2
+
3
+ * response format json/xml
4
+ * "create table" should return name
5
+ * describe table should include name
6
+ * multiple batch statements for all actions (only insert now)
7
+ * docs need updating, esp on response codes
8
+ * response codes need to be more standardised
9
+ * irritating can't include ROWID with * in selects
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,69 @@
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{fusion_tables}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Simon Tokumine", "Tom Verbeure"]
12
+ s.date = %q{2010-08-10}
13
+ s.description = %q{A simple Google Fusion Tables API wrapper. Supports bulk inserts and most API functions}
14
+ s.email = %q{simon@tinypla.net}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.textile",
18
+ "TODO"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".gitignore",
23
+ "LICENSE",
24
+ "README.textile",
25
+ "Rakefile",
26
+ "TODO",
27
+ "VERSION",
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
+ "test/README",
35
+ "test/helper.rb",
36
+ "test/test_client.rb",
37
+ "test/test_config.yml.sample",
38
+ "test/test_ext.rb",
39
+ "test/test_table.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/tokumine/fusion-tables}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.7}
45
+ s.summary = %q{Google Fusion Tables API wrapper}
46
+ s.test_files = [
47
+ "test/helper.rb",
48
+ "test/test_client.rb",
49
+ "test/test_ext.rb",
50
+ "test/test_table.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ s.add_runtime_dependency(%q<gdata>, [">= 1.1.1"])
60
+ else
61
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
62
+ s.add_dependency(%q<gdata>, [">= 1.1.1"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
66
+ s.add_dependency(%q<gdata>, [">= 1.1.1"])
67
+ end
68
+ end
69
+
@@ -0,0 +1,6 @@
1
+ require 'csv'
2
+ require 'gdata'
3
+ require 'fusion_tables/client/fusion_tables'
4
+ require 'fusion_tables/ext/fusion_tables'
5
+ require 'fusion_tables/data/data'
6
+ require 'fusion_tables/data/table'
@@ -0,0 +1,77 @@
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 = "http://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
+
45
+ # Overrides auth_handler= so if the authentication changes,
46
+ # the session cookie is cleared.
47
+ def auth_handler=(handler)
48
+ @session_cookie = nil
49
+ return super(handler)
50
+ end
51
+
52
+ # Overrides make_request to handle 500 redirects with a session cookie.
53
+ def make_request(method, url, body = '', retries = 10)
54
+ begin
55
+ response = super(method, url, body)
56
+ rescue GData::Client::ServerError => e
57
+ if e.response.status_code == 500 and retries > 0
58
+ sleep_time = 11 - retries
59
+ sleep sleep_time # <= Fusion tables has rate of 5 calls per second. Be nice, get longer
60
+ @session_cookie = e.response.headers['set-cookie']
61
+ return self.make_request(method, url, body, retries - 1)
62
+ else
63
+ return e.response
64
+ end
65
+ end
66
+ end
67
+
68
+ # Custom prepare_headers to include the session cookie if it exists
69
+ def prepare_headers
70
+ if @session_cookie
71
+ @headers['cookie'] = @session_cookie
72
+ end
73
+ super
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,61 @@
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
+ CSV::Reader.parse(response.body) do |row|
37
+ if first
38
+ first = false
39
+ headers = row.map { |x|x.strip.downcase.gsub(" ","_").to_sym }
40
+ next
41
+ end
42
+ body << Hash[*headers.zip(row).flatten]
43
+ end
44
+ self.new :headers => headers, :body => body
45
+ end
46
+
47
+ # Implement enumerable
48
+ def each
49
+ @body.each { |i| yield i }
50
+ end
51
+
52
+ private
53
+ # Encodes row according to type
54
+ def encode
55
+
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,142 @@
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
+
34
+ # Sets up data types from google
35
+ #
36
+ def describe
37
+ GData::Client::FusionTables::Data.parse(@client.sql_get("DESCRIBE #{@id}")).body
38
+ end
39
+
40
+ # Runs select and returns data obj
41
+ #
42
+ # Define columns and SQL conditions separatly
43
+ #
44
+ # See http://code.google.com/apis/fusiontables/docs/developers_reference.html#Select
45
+ #
46
+ # use columns=ROWID to select row ids
47
+ #
48
+ def select columns="*", conditions=nil
49
+ sql = "SELECT #{columns} FROM #{@id} #{conditions}"
50
+ GData::Client::FusionTables::Data.parse(@client.sql_get(sql)).body
51
+ end
52
+
53
+ # Returns a count of rows. SQL conditions optional
54
+ #
55
+ def count conditions=nil
56
+ select("count()", conditions).first.values.first.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
+
70
+ # encode values to insert
71
+ data = encode data
72
+
73
+ # Chunk up the data and send
74
+ chunk = ""
75
+ data.each_with_index do |d,i|
76
+ chunk << "INSERT INTO #{@id} (#{ d.keys.join(",") }) VALUES (#{ d.values.join(",") });"
77
+ if (i+1) % 500 == 0 || (i+1) == data.size
78
+ begin
79
+ @client.sql_post(chunk)
80
+ chunk = ""
81
+ rescue => e
82
+ raise "INSERT to table:#{@id} failed on row #{i} with #{e}"
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ # Runs update on rows and return data obj
89
+ # No bulk update, so may aswell drop table and start again
90
+ #
91
+ # TODO: FIXME
92
+ #
93
+ #def update row_id, data
94
+ # data = encode([data]).first
95
+ # data = data.to_a.map{|x| x.join("=")}.join(", ")
96
+ #
97
+ # sql = "UPDATE #{@id} SET #{data} WHERE ROWID = #{row_id}"
98
+ # GData::Client::FusionTables::Data.parse(@client.sql_post(sql)).body
99
+ #end
100
+
101
+ # delete row
102
+ # no bulk delete so may aswell drop table and start again
103
+ def delete row_id
104
+ sql = "DELETE FROM #{@id} WHERE rowid='#{row_id}'"
105
+ GData::Client::FusionTables::Data.parse(@client.sql_post(sql)).body
106
+ end
107
+
108
+
109
+ def get_headers
110
+ @headers ||= describe
111
+ end
112
+
113
+ def encode data
114
+ data.inject([]) do |ar,h|
115
+ ret = {}
116
+ h.each do |key, value|
117
+ ret[key] = case get_datatype(key)
118
+ when "number" then "#{value}"
119
+ when "datetime" then "'#{value.strftime("%m-%d-%Y")}'"
120
+ else "'#{value.gsub(/\\/, '\&\&').gsub(/'/, "''")}'"
121
+ end
122
+ end
123
+ ar << ret
124
+ ar
125
+ end
126
+ end
127
+
128
+ #
129
+ # Returns datatype of given column name
130
+ #
131
+ def get_datatype column_name
132
+ get_headers
133
+
134
+ @headers.each do |h|
135
+ return h[:type] if h[:name] == column_name.to_s
136
+ end
137
+ raise ArgumentError "The column doesn't exist"
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,96 @@
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
+ # Show a list of fusion tables
20
+ def show_tables
21
+ data = GData::Client::FusionTables::Data.parse(self.sql_get("SHOW TABLES"))
22
+ data.inject([]) do |x, row|
23
+ x << GData::Client::FusionTables::Table.new(self, row)
24
+ x
25
+ end
26
+ end
27
+
28
+
29
+ # Create a new table. Return the corresponding table
30
+ #
31
+ # Columns specified as [{:name => 'my_col_name', :type => 'my_type'}]
32
+ #
33
+ # Type must be one of:
34
+ #
35
+ # * number
36
+ # * string
37
+ # * location
38
+ # * datetime
39
+ #
40
+ def create_table(table_name, columns)
41
+
42
+ # Sanity check name
43
+ table_name = table_name.strip.gsub(/ /,'_')
44
+
45
+ # ensure all column types are valid
46
+ columns.each do |col|
47
+ if !DATATYPES.include? col[:type].downcase
48
+ raise ArgumentError, "Ensure input types are: 'number', 'string', 'location' or 'datetime'"
49
+ end
50
+ end
51
+
52
+ # generate sql
53
+ fields = columns.map{ |col| "'#{col[:name]}': #{col[:type].upcase}" }.join(", ")
54
+ sql = "CREATE TABLE #{table_name} (#{fields})"
55
+
56
+ # create table
57
+ resp = self.sql_post(sql)
58
+ raise "unknown column type" if resp.body == "Unknown column type."
59
+
60
+ # construct table object and return
61
+ table_id = resp.body.split("\n")[1].chomp.to_i
62
+ table = GData::Client::FusionTables::Table.new(self, :table_id => table_id, :name => table_name)
63
+ table.get_headers
64
+ table
65
+ end
66
+
67
+ # Drops Fusion Tables
68
+ #
69
+ # options can be:
70
+ #
71
+ # * an integer for single drop
72
+ # * array of integers for multi drop
73
+ # * a regex against table_name for flexible multi_drop
74
+ #
75
+ def drop(options)
76
+ # collect ids
77
+ ids = []
78
+ ids << options if options.class == Integer || options.class == String || Fixnum
79
+ ids = options if options.class == Array
80
+
81
+ if options.class == Regexp
82
+ tables = show_tables
83
+ ids = tables.map { |table| table.id if options =~ table.name }.compact
84
+ end
85
+
86
+ # drop tables
87
+ delete_count = 0
88
+ ids.each do |id|
89
+ resp = self.sql_post("DROP TABLE #{id}")
90
+ delete_count += 1 if resp.body.strip.downcase == 'ok'
91
+ end
92
+ delete_count
93
+ end
94
+ end
95
+ end
96
+ end
data/test/README ADDED
@@ -0,0 +1,3 @@
1
+ To run the tests, make a test_config.yml file using test/test_config.yml.sample as a base
2
+
3
+ If you get GData::Client::CaptchaError's, that usually means your credentials are not correct
data/test/helper.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'fusion_tables'
8
+
9
+ class Test::Unit::TestCase
10
+
11
+ def init_config
12
+ if not defined? @config_file
13
+ begin
14
+ @config_file = YAML::load_file(File.join(File.dirname(__FILE__), 'test_config.yml'))
15
+ rescue
16
+ puts "Please configure your test_config.yml file using test_config.yml.sample as base"
17
+ end
18
+ end
19
+ @config_file
20
+ end
21
+
22
+ def username
23
+ @config_file['username']
24
+ end
25
+
26
+ def password
27
+ @config_file['password']
28
+ end
29
+
30
+ def table_name
31
+ @config_file['table_name']
32
+ end
33
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ username: test
2
+ password: test
3
+ table_name: "test_table"
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
@@ -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,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fusion_tables
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Simon Tokumine
14
+ - Tom Verbeure
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-08-10 00:00:00 +01:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: thoughtbot-shoulda
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: gdata
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 17
45
+ segments:
46
+ - 1
47
+ - 1
48
+ - 1
49
+ version: 1.1.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: A simple Google Fusion Tables API wrapper. Supports bulk inserts and most API functions
53
+ email: simon@tinypla.net
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - LICENSE
60
+ - README.textile
61
+ - TODO
62
+ files:
63
+ - .document
64
+ - .gitignore
65
+ - LICENSE
66
+ - README.textile
67
+ - Rakefile
68
+ - TODO
69
+ - VERSION
70
+ - fusion_tables.gemspec
71
+ - lib/fusion_tables.rb
72
+ - lib/fusion_tables/client/fusion_tables.rb
73
+ - lib/fusion_tables/data/data.rb
74
+ - lib/fusion_tables/data/table.rb
75
+ - lib/fusion_tables/ext/fusion_tables.rb
76
+ - test/README
77
+ - test/helper.rb
78
+ - test/test_client.rb
79
+ - test/test_config.yml.sample
80
+ - test/test_ext.rb
81
+ - test/test_table.rb
82
+ has_rdoc: true
83
+ homepage: http://github.com/tokumine/fusion-tables
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options:
88
+ - --charset=UTF-8
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirements: []
110
+
111
+ rubyforge_project:
112
+ rubygems_version: 1.3.7
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Google Fusion Tables API wrapper
116
+ test_files:
117
+ - test/helper.rb
118
+ - test/test_client.rb
119
+ - test/test_ext.rb
120
+ - test/test_table.rb