airtable2 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e76de7af1e8850fd762c839dcee996e5ce71e10c800548e991f80500da1c68d
4
- data.tar.gz: 83ea6edb7e3a1473b1531f7dc3cee9273f6abdf2864e4c2bfa98051bd6667ea0
3
+ metadata.gz: 8f8968193253823a000c9f38c14185208b89a4dfd77d9ff6ab0a5b5f8b3f595e
4
+ data.tar.gz: 06c31cfbcc19f156ab7e498e7e6b6f9f0ca4b749aecb523124f85df7d8a452c2
5
5
  SHA512:
6
- metadata.gz: 651355e1f16d59ed0c96ba2088c65824bc97c707cc1cf2d34967a13a399abf6ff1e00bd424c38bc2ce5efc24bb47e0594c24b2e3ae606940cb3311a4c4da5b77
7
- data.tar.gz: b5e46f6aee0d721703feaa4f615f819ac433a64d5dd625297d8d12479032dd7bf940684ab3027ef65bcdabd9d1175399ecc4ad758dabaa68155a201cdbc05435
6
+ metadata.gz: 6bbbc8e341a9a16969df15e605d0e379138915066968abd3581b635d6bf66349526ae7f88d483384a5248f6f72c97f1f85d32e2a4a7e3e49c223f339d07cedc0
7
+ data.tar.gz: f8f0c2a7f3400519a07082a7c1640ae80947c7d4c4143d6b0d82e50068c01176d0f3aaeeeee392d9926647574f65185fc69382a147fa68b0bad4c2603dc2f59d
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ -o docs
2
+ -r README.md
3
+ -q
4
+ --protected
5
+ --private
data/README.md CHANGED
@@ -10,7 +10,7 @@ This is a fork of an abandoned [previous wrapper](https://github.com/nesquena/ai
10
10
 
11
11
  Add this line to your application's Gemfile:
12
12
 
13
- gem 'airtable2', github: 'https://github.com/aseroff/airtable-ruby', branch: 'main'
13
+ gem 'airtable2'
14
14
 
15
15
  And then execute:
16
16
 
@@ -41,14 +41,20 @@ and its tables
41
41
  @tables = @base.tables
42
42
  ```
43
43
 
44
- and create a new table
44
+ and a table's records, so you can navigate the has_many chain the way God intended:
45
45
 
46
46
  ```ruby
47
- @table = @base.create_table({ name: 'Names', description: 'A list of names', fields: [{ name: 'name', type: 'singleLineText' }] })
47
+ @client.bases.first.tables.first.records.first
48
48
  ```
49
49
 
50
50
  ### Manipulating Tables
51
51
 
52
+ Create a new table with:
53
+
54
+ ```ruby
55
+ @table = @base.create_table({ name: 'Names', description: 'A list of names', fields: [{ name: 'name', type: 'singleLineText' }] })
56
+ ```
57
+
52
58
  You can update at a table's metadata with the `update` method:
53
59
 
54
60
  ```ruby
@@ -86,6 +92,10 @@ Or as a convenience, you can delete all records with the `dump` method
86
92
  @table.dump
87
93
  ```
88
94
 
95
+ ## Complete documentation
96
+
97
+ YARD-generated documentation is hosted on [GitHub Pages](https://aseroff.github.io/airtable-ruby/).
98
+
89
99
  ## Contributing
90
100
 
91
101
  1. Fork it ( https://github.com/aseroff/airtable-ruby/fork )
data/airtable.gemspec CHANGED
@@ -9,19 +9,17 @@ Gem::Specification.new do |spec|
9
9
  spec.version = Airtable::VERSION
10
10
  spec.authors = ['Andrew Seroff', 'Nathan Esquenazi', 'Alexander Sorokin']
11
11
  spec.email = ['andy@seroff.co']
12
- spec.summary = 'Easily connect to airtable data using ruby'
12
+ spec.summary = 'For when Airrecord is just too much.'
13
13
  spec.description = 'Easily connect to airtable data using ruby with access to all of the airtable features.'
14
14
  spec.homepage = 'https://github.com/aseroff/airtable-ruby'
15
15
  spec.license = 'MIT'
16
16
 
17
- spec.files = `git ls-files -z`.split("\x0")
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(docs|test)/}) }
18
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.required_ruby_version = '>= 3.1'
22
- spec.add_dependency 'activesupport'
23
- spec.add_dependency 'httparty'
24
- spec.add_dependency 'json'
22
+ spec.add_dependency 'httparty', '>= 0.14.0'
25
23
 
26
24
  spec.add_development_dependency 'bundler'
27
25
  spec.add_development_dependency 'minitest'
@@ -30,6 +28,7 @@ Gem::Specification.new do |spec|
30
28
  spec.add_development_dependency 'rubocop-md'
31
29
  spec.add_development_dependency 'rubocop-minitest'
32
30
  spec.add_development_dependency 'rubocop-performance'
31
+ spec.add_development_dependency 'rubocop-rake'
33
32
  spec.add_development_dependency 'webmock'
34
33
  spec.metadata['rubygems_mfa_required'] = 'true'
35
34
  end
data/lib/airtable/base.rb CHANGED
@@ -3,12 +3,13 @@
3
3
  # Object corresponding to an Airtable Base
4
4
  class Airtable::Base < Airtable::Resource
5
5
  def initialize(token, id)
6
- @token = token
6
+ super(token)
7
7
  @id = id
8
- self.class.headers({ 'Authorization': "Bearer #{@token}", 'Content-Type': 'application/json' })
9
8
  end
10
9
 
11
10
  # Expects {name:,description:,fields:[]}
11
+ # @see https://airtable.com/developers/web/api/create-table
12
+ # @return [Airtable::Table]
12
13
  def create_table(table_data)
13
14
  response = self.class.post("#{base_url}/tables",
14
15
  body: table_data.to_json).parsed_response
@@ -18,6 +19,8 @@ class Airtable::Base < Airtable::Resource
18
19
  Airtable::Table.new @token, @id, response
19
20
  end
20
21
 
22
+ # @see https://airtable.com/developers/web/api/get-base-schema
23
+ # @return [Array]<Airtable::Table>
21
24
  def tables
22
25
  response = self.class.get("#{base_url}/tables")
23
26
 
@@ -26,7 +29,14 @@ class Airtable::Base < Airtable::Resource
26
29
  response['tables'].map { Airtable::Table.new(@token, @id, _1) }
27
30
  end
28
31
 
32
+ # Instantiate table in base
33
+ # @return [Airtable::Table]
34
+ def table(table_id)
35
+ Airtable::Table.new(@token, @id, table_id)
36
+ end
37
+
29
38
  protected
30
39
 
40
+ # Endpoint for bases
31
41
  def base_url = "/v0/meta/bases/#{@id}"
32
42
  end
@@ -1,12 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Client carrying authorization token
4
- class Airtable::Client
5
- def initialize(token)
6
- @token = token
4
+ class Airtable::Client < Airtable::Resource
5
+ # @see https://airtable.com/developers/web/api/list-bases
6
+ # @return [Array]<Airtable::Base>
7
+ def bases
8
+ response = self.class.get('/v0/meta/bases').parsed_response
9
+
10
+ check_and_raise_error(response)
11
+
12
+ response['bases'].map { Airtable::Base.new(@token, _1['id']) }
7
13
  end
8
14
 
15
+ # @see https://airtable.com/developers/web/api/create-base
16
+ # def create_base(base_data)
17
+ # response = self.class.post('/v0/meta/bases'
18
+ # body: base_data.to_json).parsed_response
19
+ # check_and_raise_error(response)
20
+ # Airtable::Base.new @token, response
21
+ # end
22
+
23
+ # Instantiate base
24
+ # @return [Airtable::Base]
9
25
  def base(base_id)
10
26
  Airtable::Base.new(@token, base_id)
11
27
  end
28
+
29
+ # @see https://airtable.com/developers/web/api/get-user-id-scopes
30
+ # @return [Hash]
31
+ def whoami
32
+ response = self.class.get('/v0/meta/whoami').parsed_response
33
+
34
+ check_and_raise_error(response)
35
+
36
+ response
37
+ end
12
38
  end
@@ -5,12 +5,11 @@ class Airtable::Record < Airtable::Resource
5
5
  attr_reader :fields
6
6
 
7
7
  def initialize(token, base_id, table_id, api_response)
8
- @token = token
8
+ super(token)
9
9
  @base_id = base_id
10
10
  @table_id = table_id
11
11
  api_response.deep_symbolize_keys.each do |key, value|
12
12
  instance_variable_set(:"@#{key}", value)
13
13
  end
14
- self.class.headers({ 'Authorization': "Bearer #{@token}", 'Content-Type': 'application/json' })
15
14
  end
16
15
  end
@@ -13,6 +13,7 @@ class Airtable::Resource
13
13
  self.class.headers({ 'Authorization': "Bearer #{@token}", 'Content-Type': 'application/json' })
14
14
  end
15
15
 
16
+ # If API response is an error, raises an Airtable::Error with the error message
16
17
  def check_and_raise_error(response)
17
18
  response['error'] ? raise(Error, response['error']) : false
18
19
  end
@@ -5,14 +5,15 @@ class Airtable::Table < Airtable::Resource
5
5
  attr_reader :name
6
6
 
7
7
  def initialize(token, base_id, api_response)
8
- @token = token
8
+ super(token)
9
9
  @base_id = base_id
10
10
  api_response.deep_symbolize_keys.each do |key, value|
11
11
  instance_variable_set(:"@#{key}", value)
12
12
  end
13
- self.class.headers({ 'Authorization': "Bearer #{@token}", 'Content-Type': 'application/json' })
14
13
  end
15
14
 
15
+ # @see https://airtable.com/developers/web/api/list-records
16
+ # @return [Array<Airtable::Record>]
16
17
  def records
17
18
  response = self.class.get(table_url)
18
19
 
@@ -21,6 +22,14 @@ class Airtable::Table < Airtable::Resource
21
22
  response['records'].map { Airtable::Record.new(@token, @base_id, @table_id, _1) }
22
23
  end
23
24
 
25
+ # Instantiate record in table
26
+ # @return [Airtable::Table]
27
+ def record(record_id)
28
+ Airtable::Table.new(@token, @base_id, @id, record_id)
29
+ end
30
+
31
+ # @see https://airtable.com/developers/web/api/update-table
32
+ # @return [Airtable::Table]
24
33
  def update(table_data)
25
34
  response = self.class.patch("/v0/meta/bases/#{@base_id}/tables/#{@id}",
26
35
  body: table_data.to_json).parsed_response
@@ -30,7 +39,9 @@ class Airtable::Table < Airtable::Resource
30
39
  Airtable::Table.new @token, @base_id, response
31
40
  end
32
41
 
33
- # limit 10 records
42
+ # @note API maximum of 10 records at a time
43
+ # @see https://airtable.com/developers/web/api/create-records
44
+ # @return [Array<Airtable::Record>]
34
45
  def add_records(records)
35
46
  response = self.class.post(table_url,
36
47
  body: { records: Array(records).map { |fields| { fields: } } }.to_json).parsed_response
@@ -40,6 +51,9 @@ class Airtable::Table < Airtable::Resource
40
51
  response['records'].map { Airtable::Record.new(@token, @base_id, @id, _1) }
41
52
  end
42
53
 
54
+ # @note API maximum of 10 records at a time
55
+ # @see https://airtable.com/developers/web/api/delete-multiple-records
56
+ # @return [Array] Deleted record ids
43
57
  def delete_records(record_ids)
44
58
  params = Array(record_ids).compact.map { "records[]=#{_1}" }.join('&')
45
59
  response = self.class.delete("#{table_url}?#{params}").parsed_response
@@ -49,6 +63,7 @@ class Airtable::Table < Airtable::Resource
49
63
  record_ids
50
64
  end
51
65
 
66
+ # Deletes all table's records
52
67
  def dump
53
68
  records.map(&:id).each_slice(10) do |record_id_set|
54
69
  delete_records(record_id_set)
@@ -58,5 +73,6 @@ class Airtable::Table < Airtable::Resource
58
73
 
59
74
  protected
60
75
 
76
+ # Endpoint for tables
61
77
  def table_url = "/v0/#{@base_id}/#{@id}"
62
78
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version
4
4
  module Airtable
5
- VERSION = '0.2.0'
5
+ VERSION = '0.2.2'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airtable2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Seroff
@@ -10,44 +10,30 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-10-26 00:00:00.000000000 Z
13
+ date: 2024-10-28 00:00:00.000000000 Z
14
14
  dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: activesupport
17
- requirement: !ruby/object:Gem::Requirement
18
- requirements:
19
- - - ">="
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- version: '0'
29
15
  - !ruby/object:Gem::Dependency
30
16
  name: httparty
31
17
  requirement: !ruby/object:Gem::Requirement
32
18
  requirements:
33
19
  - - ">="
34
20
  - !ruby/object:Gem::Version
35
- version: '0'
21
+ version: 0.14.0
36
22
  type: :runtime
37
23
  prerelease: false
38
24
  version_requirements: !ruby/object:Gem::Requirement
39
25
  requirements:
40
26
  - - ">="
41
27
  - !ruby/object:Gem::Version
42
- version: '0'
28
+ version: 0.14.0
43
29
  - !ruby/object:Gem::Dependency
44
- name: json
30
+ name: bundler
45
31
  requirement: !ruby/object:Gem::Requirement
46
32
  requirements:
47
33
  - - ">="
48
34
  - !ruby/object:Gem::Version
49
35
  version: '0'
50
- type: :runtime
36
+ type: :development
51
37
  prerelease: false
52
38
  version_requirements: !ruby/object:Gem::Requirement
53
39
  requirements:
@@ -55,7 +41,7 @@ dependencies:
55
41
  - !ruby/object:Gem::Version
56
42
  version: '0'
57
43
  - !ruby/object:Gem::Dependency
58
- name: bundler
44
+ name: minitest
59
45
  requirement: !ruby/object:Gem::Requirement
60
46
  requirements:
61
47
  - - ">="
@@ -69,7 +55,7 @@ dependencies:
69
55
  - !ruby/object:Gem::Version
70
56
  version: '0'
71
57
  - !ruby/object:Gem::Dependency
72
- name: minitest
58
+ name: rake
73
59
  requirement: !ruby/object:Gem::Requirement
74
60
  requirements:
75
61
  - - ">="
@@ -83,7 +69,7 @@ dependencies:
83
69
  - !ruby/object:Gem::Version
84
70
  version: '0'
85
71
  - !ruby/object:Gem::Dependency
86
- name: rake
72
+ name: rubocop
87
73
  requirement: !ruby/object:Gem::Requirement
88
74
  requirements:
89
75
  - - ">="
@@ -97,7 +83,7 @@ dependencies:
97
83
  - !ruby/object:Gem::Version
98
84
  version: '0'
99
85
  - !ruby/object:Gem::Dependency
100
- name: rubocop
86
+ name: rubocop-md
101
87
  requirement: !ruby/object:Gem::Requirement
102
88
  requirements:
103
89
  - - ">="
@@ -111,7 +97,7 @@ dependencies:
111
97
  - !ruby/object:Gem::Version
112
98
  version: '0'
113
99
  - !ruby/object:Gem::Dependency
114
- name: rubocop-md
100
+ name: rubocop-minitest
115
101
  requirement: !ruby/object:Gem::Requirement
116
102
  requirements:
117
103
  - - ">="
@@ -125,7 +111,7 @@ dependencies:
125
111
  - !ruby/object:Gem::Version
126
112
  version: '0'
127
113
  - !ruby/object:Gem::Dependency
128
- name: rubocop-minitest
114
+ name: rubocop-performance
129
115
  requirement: !ruby/object:Gem::Requirement
130
116
  requirements:
131
117
  - - ">="
@@ -139,7 +125,7 @@ dependencies:
139
125
  - !ruby/object:Gem::Version
140
126
  version: '0'
141
127
  - !ruby/object:Gem::Dependency
142
- name: rubocop-performance
128
+ name: rubocop-rake
143
129
  requirement: !ruby/object:Gem::Requirement
144
130
  requirements:
145
131
  - - ">="
@@ -175,6 +161,7 @@ extensions: []
175
161
  extra_rdoc_files: []
176
162
  files:
177
163
  - ".gitignore"
164
+ - ".yardopts"
178
165
  - Gemfile
179
166
  - LICENSE.txt
180
167
  - README.md
@@ -188,9 +175,6 @@ files:
188
175
  - lib/airtable/resource.rb
189
176
  - lib/airtable/table.rb
190
177
  - lib/airtable/version.rb
191
- - test/airtable_test.rb
192
- - test/record_test.rb
193
- - test/test_helper.rb
194
178
  homepage: https://github.com/aseroff/airtable-ruby
195
179
  licenses:
196
180
  - MIT
@@ -214,5 +198,5 @@ requirements: []
214
198
  rubygems_version: 3.5.21
215
199
  signing_key:
216
200
  specification_version: 4
217
- summary: Easily connect to airtable data using ruby
201
+ summary: For when Airrecord is just too much.
218
202
  test_files: []
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- describe Airtable do
6
- before do
7
- @client_key = '12345'
8
- @app_key = 'appXXV84Qu'
9
- @sheet_name = 'Test'
10
- end
11
-
12
- describe 'with Airtable' do
13
- it 'should allow client to be created' do
14
- @client = Airtable::Client.new(@client_key)
15
-
16
- assert_kind_of Airtable::Client, @client
17
- @table = @client.table(@app_key, @sheet_name)
18
-
19
- assert_kind_of Airtable::Table, @table
20
- end
21
-
22
- it 'should fetch record set' do
23
- stub_airtable_response!("https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}",
24
- { 'records' => [], 'offset' => 'abcde' })
25
- @table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
26
- @records = @table.records
27
-
28
- assert_equal 'abcde', @records.offset
29
- end
30
-
31
- it 'should select records based on a formula' do
32
- query_str = "OR(RECORD_ID() = 'recXYZ1', RECORD_ID() = 'recXYZ2', RECORD_ID() = 'recXYZ3', RECORD_ID() = 'recXYZ4')"
33
- escaped_query = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER.call(filterByFormula: query_str)
34
- request_url = "https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}?#{escaped_query}"
35
- stub_airtable_response!(request_url, { 'records' => [] })
36
- @table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
37
- @select_records = @table.select(formula: query_str)
38
-
39
- assert_empty @select_records.records
40
- end
41
-
42
- it 'should raise an ArgumentError if a formula is not a string' do
43
- stub_airtable_response!("https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}",
44
- { 'records' => [], 'offset' => 'abcde' })
45
- @table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
46
- _ { proc { @table.select(formula: { foo: 'bar' }) } }.must_raise ArgumentError
47
- end
48
-
49
- it 'should allow creating records' do
50
- stub_airtable_response!("https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}",
51
- { 'fields' => { 'name' => 'Sarah Jaine', 'email' => 'sarah@jaine.com', 'foo' => 'bar' }, 'id' => '12345' }, :post)
52
- table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
53
- record = Airtable::Record.new(name: 'Sarah Jaine', email: 'sarah@jaine.com')
54
- table.create(record)
55
-
56
- assert_equal '12345', record['id']
57
- assert_equal 'bar', record['foo']
58
- end
59
-
60
- it 'should allow updating records' do
61
- record_id = '12345'
62
- stub_airtable_response!("https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}/#{record_id}",
63
- { 'fields' => { 'name' => 'Sarah Jaine', 'email' => 'sarah@jaine.com', 'foo' => 'bar' }, 'id' => record_id }, :put)
64
- table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
65
- record = Airtable::Record.new(name: 'Sarah Jaine', email: 'sarah@jaine.com', id: record_id)
66
- table.update(record)
67
-
68
- assert_equal '12345', record['id']
69
- assert_equal 'bar', record['foo']
70
- end
71
-
72
- it 'should raise an error when the API returns an error' do
73
- stub_airtable_response!("https://api.airtable.com/v0/#{@app_key}/#{@sheet_name}",
74
- { 'error' => { 'type' => 'UNKNOWN_COLUMN_NAME', 'message' => 'Could not find fields foo' } }, :post, 422)
75
- table = Airtable::Client.new(@client_key).table(@app_key, @sheet_name)
76
- record = Airtable::Record.new(foo: 'bar')
77
- assert_raises Airtable::Error do
78
- table.create(record)
79
- end
80
- end
81
- end
82
- end
data/test/record_test.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- describe Airtable do
6
- describe Airtable::Record do
7
- it 'should not return id in fields_for_update' do
8
- record = Airtable::Record.new(name: 'Sarah Jaine', email: 'sarah@jaine.com', id: 12_345)
9
- _(record.fields_for_update).wont_include(:id)
10
- end
11
-
12
- it 'returns new columns in fields_for_update' do
13
- record = Airtable::Record.new(name: 'Sarah Jaine', email: 'sarah@jaine.com', id: 12_345)
14
- record[:website] = 'http://sarahjaine.com'
15
- _(record.fields_for_update).must_include(:website)
16
- end
17
-
18
- it 'returns fields_for_update in original capitalization' do
19
- record = Airtable::Record.new('Name' => 'Sarah Jaine')
20
- _(record.fields_for_update).must_include('Name')
21
- end
22
- end
23
- end
data/test/test_helper.rb DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'airtable'
4
- require 'webmock/minitest'
5
- require 'minitest/pride'
6
- require 'minitest/autorun'
7
-
8
- def stub_airtable_response!(url, response, method = :get, status = 200)
9
- stub_request(method, url)
10
- .to_return(
11
- body: response.to_json,
12
- status: status,
13
- headers: { 'Content-Type' => 'application/json' }
14
- )
15
- end