airtable 0.0.2

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.
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