airtable 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44001bc278b32cb324d355c6e89ef3ef0daee53b
4
+ data.tar.gz: f35bd0c0a6ce9b427684625d5e314913677b9e19
5
+ SHA512:
6
+ metadata.gz: f530c2ede4381894784a64eb3a4a13e6e7d77ffe632388939d84baea2bc361fe53393888e868781a7aec564e5361b664fa8466aefc3249309019451e079c6521
7
+ data.tar.gz: 1466e6989283a7475819b94b34bd819fb9e2e8345fdded5538abee6f011a48897c815294e39a142617a4ba06eec72f8296d671cda69c306a670fa41e53377089
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in airtable.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Nathan Esquenazi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Airtable Ruby Client
2
+
3
+ Easily connect to [airtable](https://airtable.com) data using ruby with access to all of the airtable features.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'airtable'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install airtable
18
+
19
+ ## Usage
20
+
21
+ ### Creating a Client
22
+
23
+ First, be sure to register for an [airtable](https://airtable.com) account, create a data worksheet and [get an api key](https://airtable.com/account). Now, setup your Airtable client:
24
+
25
+ ```ruby
26
+ # Pass in api key to client
27
+ @client = Airtable::Client.new("keyPCx5W")
28
+ ```
29
+
30
+ Your API key carries the same privileges as your user account, so be sure to keep it secret!
31
+
32
+ ### Accessing a Table
33
+
34
+ Now we can access any table in our Airsheet account by referencing the [API docs](https://airtable.com/api):
35
+
36
+ ```ruby
37
+ # Pass in the app key and table name
38
+ @table = @client.table("appPo84QuCy2BPgLk", "Table Name")
39
+ ```
40
+
41
+ ### Querying Records
42
+
43
+ Once you have access to a table from above, we can query a set of records in the table with:
44
+
45
+ ```ruby
46
+ @records = @table.records
47
+ ```
48
+
49
+ We can specify a `sort` order, `limit`, and `offset` as part of our query:
50
+
51
+ ```ruby
52
+ @records = @table.records(:sort => ["Name", :asc], :limit => 50)
53
+ @records # => [#<Airtable::Record :name=>"Bill Lowry", :email=>"billery@gmail.com">, ...]
54
+ @records.offset #=> "itrEN2TCbrcSN2BMs"
55
+ ```
56
+
57
+ This will return the records based on the query as well as an `offset` for the next round of records. We can then access the contents of any record:
58
+
59
+ ```ruby
60
+ @bill = @record.first
61
+ # => #<Airtable::Record :name=>"Bill Lowry", :email=>"billery@gmail.com", :id=>"rec02sKGVIzU65eV1">
62
+ @bill[:id] # => "rec02sKGVIzU65eV2"
63
+ @bill[:name] # => "Bill Lowry"
64
+ @bill[:email] # => "billery@gmail.com"
65
+ ```
66
+
67
+ Note that you can only request a maximimum of 100 records in a single query. To retrieve more records, use the "batch" feature below.
68
+
69
+ ### Batch Querying All Records
70
+
71
+ We can also query all records in the table through a series of batch requests with:
72
+
73
+ ```ruby
74
+ @records = @table.all(:sort => ["Name", :asc])
75
+ ```
76
+
77
+ This executes a variable number of network requests (100 records per batch) to retrieve all records in a sheet.
78
+
79
+ ### Finding a Record
80
+
81
+ Records can be queried by `id` using the `find` method on a table:
82
+
83
+ ```ruby
84
+ @record = @table.find("rec02sKGVIzU65eV2")
85
+ # => #<Airtable::Record :name=>"Bill Lowry", :email=>"billery@gmail.com", :id=>"rec02sKGVIzU65eV1">
86
+ ```
87
+
88
+ ### Inserting Records
89
+
90
+ Records can be inserted using the `create` method on a table:
91
+
92
+ ```ruby
93
+ @record = Airtable::Record.new(:name => "Sarah Jaine", :email => "sarah@jaine.com")
94
+ @table.create(record)
95
+ # => #<Airtable::Record :name=>"Sarah Jaine", :email=>"sarah@jaine.com", :id=>"rec03sKOVIzU65eV4">
96
+ ```
97
+
98
+ ### Updating Records
99
+
100
+ Records can be updated using the `update` method on a table:
101
+
102
+ ```ruby
103
+ @record[:email] = "sarahjaine@updated.com"
104
+ @table.update(record)
105
+ # => #<Airtable::Record :name=>"Sarah Jaine", :email=>"sarahjaine@updated.com", :id=>"rec03sKOVIzU65eV4">
106
+ ```
107
+
108
+ ### Deleting Records
109
+
110
+ Records can be updated using the `update` method on a table:
111
+
112
+ ```ruby
113
+ @table.destroy(record)
114
+ ```
115
+
116
+ ## Contributing
117
+
118
+ 1. Fork it ( https://github.com/nesquena/airtable-ruby/fork )
119
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
120
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
121
+ 4. Push to the branch (`git push origin my-new-feature`)
122
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = "test/*_test.rb"
7
+ end
data/airtable.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'airtable/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "airtable"
8
+ spec.version = Airtable::VERSION
9
+ spec.authors = ["Nathan Esquenazi"]
10
+ spec.email = ["nesquena@gmail.com"]
11
+ spec.summary = %q{Easily connect to airtable data using ruby}
12
+ spec.description = %q{Easily connect to airtable data using ruby with access to all of the airtable features.}
13
+ spec.homepage = "https://github.com/nesquena/airtable-ruby"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "httparty"
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "minitest", "~> 5.6.0"
25
+ spec.add_development_dependency "fakeweb", "~> 1.3"
26
+ end
data/lib/airtable.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'httparty'
2
+ require 'delegate'
3
+ require "airtable/version"
4
+ require 'airtable/resource'
5
+ require 'airtable/record'
6
+ require 'airtable/record_set'
7
+ require 'airtable/table'
8
+ require 'airtable/client'
9
+
10
+ module Airtable
11
+ # Nothing for now
12
+ end
@@ -0,0 +1,20 @@
1
+ # Allows access to data on airtable
2
+ #
3
+ # Fetch all records from table:
4
+ #
5
+ # client = Airtable::Client.new("keyPtVG4L4sVudsCx5W")
6
+ # client.table("appXXV84QuCy2BPgLk", "Sheet Name").all
7
+ #
8
+
9
+ module Airtable
10
+ class Client
11
+ def initialize(api_key)
12
+ @api_key = api_key
13
+ end
14
+
15
+ # table("appXXV84QuCy2BPgLk", "Sheet Name")
16
+ def table(app_token, worksheet_name)
17
+ Table.new(@api_key, app_token, worksheet_name)
18
+ end
19
+ end # Client
20
+ end # Airtable
@@ -0,0 +1,67 @@
1
+ module Airtable
2
+
3
+ class Record
4
+ def initialize(attrs={})
5
+ @columns_map = attrs.keys
6
+ @attrs = HashWithIndifferentAccess.new(Hash[attrs.map { |k, v| [ to_key(k), v ] }])
7
+ @attrs.map { |k, v| define_accessor(k) }
8
+ end
9
+
10
+ def id; @attrs["id"]; end
11
+ def id=(val); @attrs["id"] = val; end
12
+
13
+ # Return given attribute based on name or blank otherwise
14
+ def [](name)
15
+ @attrs.has_key?(to_key(name)) ? @attrs[to_key(name)] : ""
16
+ end
17
+
18
+ # Set the given attribute to value
19
+ def []=(name, value)
20
+ @attrs[to_key(name)] = value
21
+ define_accessor(name) unless respond_to?(name)
22
+ end
23
+
24
+ def inspect
25
+ "#<Airtable::Record #{attributes.map { |a, v| ":#{a}=>#{v.inspect}" }.join(", ")}>"
26
+ end
27
+
28
+ # Hash of attributes with underscored column names
29
+ def attributes; @attrs; end
30
+
31
+ # Hash with keys based on airtable original column names
32
+ def fields; Hash[@columns_map.map { |k| [ k, @attrs[to_key(k)] ] }]; end
33
+
34
+
35
+ def method_missing(name, *args, &blk)
36
+ # Accessor for attributes
37
+ if args.empty? && blk.nil? && @attrs.has_key?(name)
38
+ @attrs[name]
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def respond_to?(method_name, include_private = false)
45
+ @attrs.has_key?(name) || super
46
+ end
47
+
48
+ protected
49
+
50
+ def to_key(string)
51
+ string.is_a?(Symbol) ? string : underscore(string).to_sym
52
+ end
53
+
54
+ def underscore(string)
55
+ string.gsub(/::/, '/').
56
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
57
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
58
+ gsub(/\s/, '_').tr("-", "_").downcase
59
+ end
60
+
61
+ def define_accessor(name)
62
+ self.class.send(:define_method, name) { @attrs[name] }
63
+ end
64
+
65
+ end # Record
66
+
67
+ end # Airtable
@@ -0,0 +1,22 @@
1
+ module Airtable
2
+
3
+ # Contains records and the offset after a record query
4
+ class RecordSet < SimpleDelegator
5
+
6
+ attr_reader :records, :offset
7
+
8
+ # results = { "records" => [{ ... }], "offset" => "abc5643" }
9
+ # response from records api
10
+ def initialize(results)
11
+ # Parse records
12
+ @records = results && results["records"] ?
13
+ results["records"].map { |r| Record.new(r["fields"].merge("id" => r["id"])) } : []
14
+ # Store offset
15
+ @offset = results["offset"]
16
+ # Assign delegation object
17
+ __setobj__(@records)
18
+ end
19
+
20
+ end # Record
21
+
22
+ end # Airtable
@@ -0,0 +1,17 @@
1
+ module Airtable
2
+ # Base class for authorized resources sending network requests
3
+ class Resource
4
+ include HTTParty
5
+ base_uri 'https://api.airtable.com/v0/'
6
+ # debug_output $stdout
7
+
8
+ attr_reader :api_key, :app_token, :worksheet_name
9
+
10
+ def initialize(api_key, app_token, worksheet_name)
11
+ @api_key = api_key
12
+ @app_token = app_token
13
+ @worksheet_name = worksheet_name
14
+ self.class.headers({'Authorization' => "Bearer #{@api_key}"})
15
+ end
16
+ end # AuthorizedResource
17
+ end # Airtable
@@ -0,0 +1,73 @@
1
+ module Airtable
2
+
3
+ class Table < Resource
4
+ # Maximum results per request
5
+ LIMIT_MAX = 100
6
+
7
+ # Fetch all records iterating through offsets until retrieving the entire collection
8
+ # all(:sort => ["Name", :desc])
9
+ def all(options={})
10
+ offset = nil
11
+ results = []
12
+ begin
13
+ options.merge!(:limit => LIMIT_MAX, :offset => offset)
14
+ response = records(options)
15
+ results += response.records
16
+ offset = response.offset
17
+ end until offset.nil? || offset.empty? || results.empty?
18
+ results
19
+ end
20
+
21
+ # Fetch records from the sheet given the list options
22
+ # Options: limit = 100, offset = "as345g", sort = ["Name", "asc"]
23
+ # records(:sort => ["Name", :desc], :limit => 50, :offset => "as345g")
24
+ def records(options={})
25
+ options["sortField"], options["sortDirection"] = options.delete(:sort) if options[:sort]
26
+ results = self.class.get(worksheet_url, query: options).parsed_response
27
+ RecordSet.new(results)
28
+ end
29
+
30
+ # Returns record based given row id
31
+ def find(id)
32
+ result = self.class.get(worksheet_url + "/" + id).parsed_response
33
+ Record.new(result["fields"].merge("id" => result["id"])) if result.present? && result["id"]
34
+ end
35
+
36
+ # Creates a record by posting to airtable
37
+ def create(record)
38
+ result = self.class.post(worksheet_url,
39
+ :body => { "fields" => record.fields }.to_json,
40
+ :headers => { "Content-type" => "application/json" }).parsed_response
41
+ if result.present? && result["id"].present?
42
+ record.id = result["id"]
43
+ record
44
+ else # failed
45
+ false
46
+ end
47
+ end
48
+
49
+ # Replaces record in airtable based on id
50
+ def update(record)
51
+ result = self.class.put(worksheet_url + "/" + id,
52
+ :body => { "fields" => record.fields }.to_json,
53
+ :headers => { "Content-type" => "application/json" }).parsed_response
54
+ if result.present? && result["id"].present?
55
+ record
56
+ else # failed
57
+ false
58
+ end
59
+ end
60
+
61
+ # Deletes record in table based on id
62
+ def destroy(id)
63
+ self.class.delete(worksheet_url + "/" + id).parsed_response
64
+ end
65
+
66
+ protected
67
+
68
+ def worksheet_url
69
+ "/#{app_token}/#{URI.encode(worksheet_name)}"
70
+ end
71
+ end # Table
72
+
73
+ end # Airtable
@@ -0,0 +1,3 @@
1
+ module Airtable
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ describe Airtable do
4
+ before do
5
+ @client_key = "12345"
6
+ @app_key = "appXXV84Qu"
7
+ @sheet_name = "Test"
8
+ end
9
+
10
+ describe "with Airtable" do
11
+ it "should allow client to be created" do
12
+ @client = Airtable::Client.new(@client_key)
13
+ assert_kind_of Airtable::Client, @client
14
+ @table = @client.table(@app_key, @sheet_name)
15
+ assert_kind_of Airtable::Table, @table
16
+ end
17
+
18
+ it "should fetch record set" do
19
+ stub_airtable_response("https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}", { "records" => [], "offset" => "abcde" })
20
+ @table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
21
+ @records = @table.records
22
+ assert_equal "abcde", @records.offset
23
+ end
24
+ end # describe Airtable
25
+ end # Airtable
@@ -0,0 +1,10 @@
1
+ require 'airtable'
2
+ require 'fakeweb'
3
+ require 'minitest/pride'
4
+ require 'minitest/autorun'
5
+
6
+ FakeWeb.allow_net_connect = false
7
+
8
+ def stub_airtable_response(url, response)
9
+ FakeWeb.register_uri(:get, url, :body => response.to_json, :content_type => "application/json")
10
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: airtable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Esquenazi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 5.6.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 5.6.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeweb
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.3'
83
+ description: Easily connect to airtable data using ruby with access to all of the
84
+ airtable features.
85
+ email:
86
+ - nesquena@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - airtable.gemspec
97
+ - lib/airtable.rb
98
+ - lib/airtable/client.rb
99
+ - lib/airtable/record.rb
100
+ - lib/airtable/record_set.rb
101
+ - lib/airtable/resource.rb
102
+ - lib/airtable/table.rb
103
+ - lib/airtable/version.rb
104
+ - test/airtable_test.rb
105
+ - test/test_helper.rb
106
+ homepage: https://github.com/nesquena/airtable-ruby
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.0.6
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Easily connect to airtable data using ruby
130
+ test_files:
131
+ - test/airtable_test.rb
132
+ - test/test_helper.rb