norairrecord 0.1.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 +7 -0
- data/.github/workflows/gem-push.yml +26 -0
- data/.gitignore +14 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +32 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/production-test.rb +11 -0
- data/bin/setup +8 -0
- data/lib/norairrecord/client.rb +50 -0
- data/lib/norairrecord/faraday_rate_limiter.rb +61 -0
- data/lib/norairrecord/table.rb +306 -0
- data/lib/norairrecord/version.rb +3 -0
- data/lib/norairrecord.rb +20 -0
- data/norairrecord.gemspec +30 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e43689c499a7d1d54963791d0dacd275d2cc9c8b0c2dc27b636884b51596bbee
|
4
|
+
data.tar.gz: '01970ece2dddb567110ebaaec5f855df7240d329f1403c33aad49e3632aa978e'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '097efbecab4facd4d22299b7b5629b015c032c93808342f88cb6cdb652d572b72e7d1a5c48d165827ea5d35e9be41c0d6d37aafb82a411474d302e14e911fb8b'
|
7
|
+
data.tar.gz: f166ce6e3be2bdd525c78168df6c9f0e7dc10bd95c779abe55b742a710fb904f8694bd2604c42d93264ff607a2df962fb07dc88d7d9610c7dc269f8ef2ea5e64
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: gemerald
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ "main" ]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
push:
|
9
|
+
name: Push gem to RubyGems.org
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
permissions:
|
13
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
14
|
+
contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
|
15
|
+
|
16
|
+
steps:
|
17
|
+
# Set up
|
18
|
+
- uses: actions/checkout@v4
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
bundler-cache: true
|
23
|
+
ruby-version: 3.3.5
|
24
|
+
|
25
|
+
# Release
|
26
|
+
- uses: rubygems/release-gem@v1
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2017 Simon Eskildsen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Norairrecord
|
2
|
+
|
3
|
+
[airrecord](https://github.com/sirupsen/airrecord) : norairrecord :: epinephrine : norepinephrine
|
4
|
+
|
5
|
+
stuff not in the OG:
|
6
|
+
* `Table#comment`
|
7
|
+
* ```ruby
|
8
|
+
rec.comment "pretty cool record!"
|
9
|
+
```
|
10
|
+
* `Table#patch`!
|
11
|
+
* ```ruby
|
12
|
+
rec.patch({ # this will fire off a request
|
13
|
+
"field 1" => "new value 1", # that only modifies
|
14
|
+
"field 2" => "new value 2", # the specified fields
|
15
|
+
})
|
16
|
+
```
|
17
|
+
* """""transactions"""""!
|
18
|
+
* they're not great but they kinda do a thing!
|
19
|
+
* ```ruby
|
20
|
+
rec.transaction do |rec| # pipes optional
|
21
|
+
# do some stuff to rec...
|
22
|
+
# all changes inside the block happen in 1 request
|
23
|
+
# none of the changes happen if an error is hit
|
24
|
+
end
|
25
|
+
```
|
26
|
+
* custom endpoint URL
|
27
|
+
* handy for inspecting/ratelimiting
|
28
|
+
* `Norairrecord.base_url = "https://somewhere_else"`
|
29
|
+
* custom UA
|
30
|
+
* `Norairrecord.user_agent = "i'm the reason why you're getting 429s!"`
|
31
|
+
* `Table#airtable_url`
|
32
|
+
* what it says on the tin!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "norairrecord"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative 'faraday_rate_limiter'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Norairrecord
|
5
|
+
class Client
|
6
|
+
attr_reader :api_key
|
7
|
+
attr_writer :connection
|
8
|
+
|
9
|
+
# Per Airtable's documentation you will get throttled for 30 seconds if you
|
10
|
+
# issue more than 5 requests per second. Airrecord is a good citizen.
|
11
|
+
AIRTABLE_RPS_LIMIT = 5
|
12
|
+
|
13
|
+
def initialize(api_key)
|
14
|
+
@api_key = api_key
|
15
|
+
end
|
16
|
+
|
17
|
+
def connection
|
18
|
+
@connection ||= Faraday.new(
|
19
|
+
url: Norairrecord.base_url || "https://api.airtable.com",
|
20
|
+
headers: {
|
21
|
+
"Authorization" => "Bearer #{api_key}",
|
22
|
+
"User-Agent" => Norairrecord.user_agent || "Airrecord (nora's version)/#{Norairrecord::VERSION}",
|
23
|
+
},
|
24
|
+
) do |conn|
|
25
|
+
if Norairrecord.throttle?
|
26
|
+
conn.request :airrecord_rate_limiter, requests_per_second: AIRTABLE_RPS_LIMIT
|
27
|
+
end
|
28
|
+
conn.adapter :net_http_persistent
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def escape(*args)
|
33
|
+
ERB::Util.url_encode(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse(body)
|
37
|
+
JSON.parse(body)
|
38
|
+
rescue JSON::ParserError
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_error(status, error)
|
43
|
+
if error.is_a?(Hash) && error['error']
|
44
|
+
raise Error, "HTTP #{status}: #{error['error']['type']}: #{error['error']['message']}"
|
45
|
+
else
|
46
|
+
raise Error, "HTTP #{status}: Communication error: #{error}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module Norairrecord
|
4
|
+
class FaradayRateLimiter < Faraday::Middleware
|
5
|
+
class << self
|
6
|
+
attr_accessor :requests
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(app, requests_per_second: nil, sleeper: nil)
|
10
|
+
super(app)
|
11
|
+
@rps = requests_per_second
|
12
|
+
@sleeper = sleeper || ->(seconds) { sleep(seconds) }
|
13
|
+
@mutex = Mutex.new
|
14
|
+
clear
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
@mutex.synchronize do
|
19
|
+
wait if too_many_requests_in_last_second?
|
20
|
+
@app.call(env).on_complete do |_response_env|
|
21
|
+
requests << Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
22
|
+
requests.shift if requests.size > @rps
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
self.class.requests = []
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def requests
|
34
|
+
self.class.requests
|
35
|
+
end
|
36
|
+
|
37
|
+
def too_many_requests_in_last_second?
|
38
|
+
return false unless @rps
|
39
|
+
return false unless requests.size >= @rps
|
40
|
+
|
41
|
+
window_span < 1.0
|
42
|
+
end
|
43
|
+
|
44
|
+
def wait
|
45
|
+
# Time to wait until making the next request to stay within limits.
|
46
|
+
# [1.1, 1.2, 1.3, 1.4, 1.5] => 1 - 0.4 => 0.6
|
47
|
+
wait_time = 1.0 - window_span
|
48
|
+
@sleeper.call(wait_time)
|
49
|
+
end
|
50
|
+
|
51
|
+
# [1.1, 1.2, 1.3, 1.4, 1.5] => 1.5 - 1.1 => 0.4
|
52
|
+
def window_span
|
53
|
+
requests.last - requests.first
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Faraday::Request.register_middleware(
|
59
|
+
# Avoid polluting the global middleware namespace with a prefix.
|
60
|
+
airrecord_rate_limiter: Norairrecord::FaradayRateLimiter
|
61
|
+
)
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'rubygems' # For Gem::Version
|
2
|
+
|
3
|
+
module Norairrecord
|
4
|
+
class Table
|
5
|
+
class << self
|
6
|
+
attr_accessor :base_key, :table_name
|
7
|
+
attr_writer :api_key
|
8
|
+
|
9
|
+
def client
|
10
|
+
@@clients ||= {}
|
11
|
+
@@clients[api_key] ||= Client.new(api_key)
|
12
|
+
end
|
13
|
+
|
14
|
+
def api_key
|
15
|
+
defined?(@api_key) ? @api_key : Norairrecord.api_key
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_many(method_name, options)
|
19
|
+
define_method(method_name.to_sym) do
|
20
|
+
# Get association ids in reverse order, because Airtable's UI and API
|
21
|
+
# sort associations in opposite directions. We want to match the UI.
|
22
|
+
ids = (self[options.fetch(:column)] || []).reverse
|
23
|
+
table = Kernel.const_get(options.fetch(:class))
|
24
|
+
return table.find_many(ids) unless options[:single]
|
25
|
+
|
26
|
+
(id = ids.first) ? table.find(id) : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
define_method("#{method_name}=".to_sym) do |value|
|
30
|
+
self[options.fetch(:column)] = Array(value).map(&:id).reverse
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def belongs_to(method_name, options)
|
35
|
+
has_many(method_name, options.merge(single: true))
|
36
|
+
end
|
37
|
+
|
38
|
+
alias has_one belongs_to
|
39
|
+
|
40
|
+
def find(id)
|
41
|
+
response = client.connection.get("v0/#{base_key}/#{client.escape(table_name)}/#{id}")
|
42
|
+
parsed_response = client.parse(response.body)
|
43
|
+
|
44
|
+
if response.success?
|
45
|
+
self.new(parsed_response["fields"], id: id, created_at: parsed_response["createdTime"])
|
46
|
+
else
|
47
|
+
client.handle_error(response.status, parsed_response)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_many(ids)
|
52
|
+
return [] if ids.empty?
|
53
|
+
|
54
|
+
or_args = ids.map { |id| "RECORD_ID() = '#{id}'"}.join(',')
|
55
|
+
formula = "OR(#{or_args})"
|
56
|
+
records(filter: formula).sort_by { |record| or_args.index(record.id) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def update(id, update_hash = {}, options = {})
|
60
|
+
# To avoid trying to update computed fields we *always* use PATCH
|
61
|
+
body = {
|
62
|
+
fields: update_hash,
|
63
|
+
**options
|
64
|
+
}.to_json
|
65
|
+
|
66
|
+
response = client.connection.patch("v0/#{base_key}/#{client.escape(table_name)}/#{id}", body, { 'Content-Type' => 'application/json' })
|
67
|
+
parsed_response = client.parse(response.body)
|
68
|
+
|
69
|
+
if response.success?
|
70
|
+
parsed_response["fields"]
|
71
|
+
else
|
72
|
+
client.handle_error(response.status, parsed_response)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def create(fields, options = {})
|
78
|
+
new(fields).tap { |record| record.save(options) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil)
|
82
|
+
options = {}
|
83
|
+
options[:filterByFormula] = filter if filter
|
84
|
+
|
85
|
+
if sort
|
86
|
+
options[:sort] = sort.map { |field, direction|
|
87
|
+
{ field: field.to_s, direction: direction }
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
options[:view] = view if view
|
92
|
+
options[:offset] = offset if offset
|
93
|
+
options[:fields] = fields if fields
|
94
|
+
options[:maxRecords] = max_records if max_records
|
95
|
+
options[:pageSize] = page_size if page_size
|
96
|
+
|
97
|
+
path = "v0/#{base_key}/#{client.escape(table_name)}/listRecords"
|
98
|
+
response = client.connection.post(path, options.to_json, { 'Content-Type' => 'application/json' })
|
99
|
+
parsed_response = client.parse(response.body)
|
100
|
+
|
101
|
+
if response.success?
|
102
|
+
records = parsed_response["records"]
|
103
|
+
records = records.map { |record|
|
104
|
+
self.new(record["fields"], id: record["id"], created_at: record["createdTime"])
|
105
|
+
}
|
106
|
+
|
107
|
+
if paginate && parsed_response["offset"]
|
108
|
+
records.concat(records(
|
109
|
+
filter: filter,
|
110
|
+
sort: sort,
|
111
|
+
view: view,
|
112
|
+
paginate: paginate,
|
113
|
+
fields: fields,
|
114
|
+
offset: parsed_response["offset"],
|
115
|
+
max_records: max_records,
|
116
|
+
page_size: page_size,
|
117
|
+
))
|
118
|
+
end
|
119
|
+
|
120
|
+
records
|
121
|
+
else
|
122
|
+
client.handle_error(response.status, parsed_response)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
alias all records
|
126
|
+
end
|
127
|
+
|
128
|
+
attr_reader :fields, :id, :created_at, :updated_keys
|
129
|
+
|
130
|
+
# This is an awkward definition for Ruby 3 to remain backwards compatible.
|
131
|
+
# It's easier to read by reading the 2.x definition below.
|
132
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0.0")
|
133
|
+
def initialize(*one, **two)
|
134
|
+
@id = one.first && two.delete(:id)
|
135
|
+
self.created_at = one.first && two.delete(:created_at)
|
136
|
+
self.fields = one.first || two
|
137
|
+
end
|
138
|
+
else
|
139
|
+
def initialize(fields, id: nil, created_at: nil)
|
140
|
+
@id = id
|
141
|
+
self.created_at = created_at
|
142
|
+
self.fields = fields
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def new_record?
|
147
|
+
!id
|
148
|
+
end
|
149
|
+
|
150
|
+
def [](key)
|
151
|
+
validate_key(key)
|
152
|
+
fields[key]
|
153
|
+
end
|
154
|
+
|
155
|
+
def []=(key, value)
|
156
|
+
validate_key(key)
|
157
|
+
return if fields[key] == value # no-op
|
158
|
+
|
159
|
+
@updated_keys << key
|
160
|
+
fields[key] = value
|
161
|
+
end
|
162
|
+
|
163
|
+
def patch(update_hash = {}, options = {})
|
164
|
+
update_hash.reject! { |key, value| @fields[key] == value }
|
165
|
+
return @fields if update_hash.empty? # don't hit AT if we don't have real changes
|
166
|
+
@fields.merge!(self.class.update(self.id, update_hash, options).reject { |key, _| updated_keys.include?(key) })
|
167
|
+
end
|
168
|
+
|
169
|
+
def create(options = {})
|
170
|
+
raise Error, "Record already exists (record has an id)" unless new_record?
|
171
|
+
|
172
|
+
body = {
|
173
|
+
fields: serializable_fields,
|
174
|
+
**options
|
175
|
+
}.to_json
|
176
|
+
|
177
|
+
response = client.connection.post("v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}", body, { 'Content-Type' => 'application/json' })
|
178
|
+
parsed_response = client.parse(response.body)
|
179
|
+
|
180
|
+
if response.success?
|
181
|
+
@id = parsed_response["id"]
|
182
|
+
self.created_at = parsed_response["createdTime"]
|
183
|
+
self.fields = parsed_response["fields"]
|
184
|
+
else
|
185
|
+
client.handle_error(response.status, parsed_response)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def save(options = {})
|
190
|
+
return create(options) if new_record?
|
191
|
+
return true if @updated_keys.empty?
|
192
|
+
|
193
|
+
update_hash = Hash[@updated_keys.map { |key|
|
194
|
+
[key, fields[key]]
|
195
|
+
}]
|
196
|
+
|
197
|
+
self.patch(update_hash, options)
|
198
|
+
end
|
199
|
+
|
200
|
+
def destroy
|
201
|
+
raise Error, "Unable to destroy new record" if new_record?
|
202
|
+
|
203
|
+
response = client.connection.delete("v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}")
|
204
|
+
parsed_response = client.parse(response.body)
|
205
|
+
|
206
|
+
if response.success?
|
207
|
+
true
|
208
|
+
else
|
209
|
+
client.handle_error(response.status, parsed_response)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def serializable_fields
|
214
|
+
fields
|
215
|
+
end
|
216
|
+
|
217
|
+
def comment(text)
|
218
|
+
response = client.connection.post("v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}/comments", {text:}.to_json, { 'Content-Type' => 'application/json' })
|
219
|
+
parsed_response = client.parse(response.body)
|
220
|
+
|
221
|
+
if response.success?
|
222
|
+
parsed_response['id']
|
223
|
+
else
|
224
|
+
client.handle_error(response.status, parsed_response)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def airtable_url
|
229
|
+
"https://airtable.com/#{self.class.base_key}/#{self.class.table_name}/#{self.id}"
|
230
|
+
end
|
231
|
+
|
232
|
+
def ==(other)
|
233
|
+
self.class == other.class &&
|
234
|
+
serializable_fields == other.serializable_fields
|
235
|
+
end
|
236
|
+
alias eql? ==
|
237
|
+
|
238
|
+
def hash
|
239
|
+
serializable_fields.hash
|
240
|
+
end
|
241
|
+
|
242
|
+
# ahahahahaha
|
243
|
+
def transaction(&block)
|
244
|
+
txn_updates = {}
|
245
|
+
|
246
|
+
singleton_class.alias_method :original_setter, :[]=
|
247
|
+
|
248
|
+
define_singleton_method(:[]=) do |key, value|
|
249
|
+
txn_updates[key] = value
|
250
|
+
end
|
251
|
+
|
252
|
+
begin
|
253
|
+
result = yield self
|
254
|
+
@updated_keys -= txn_updates.keys
|
255
|
+
if new_record?
|
256
|
+
@fields.merge!(txn_updates)
|
257
|
+
save
|
258
|
+
else
|
259
|
+
self.patch(txn_updates)
|
260
|
+
end
|
261
|
+
rescue => e
|
262
|
+
raise
|
263
|
+
ensure
|
264
|
+
singleton_class.alias_method :[]=, :original_setter
|
265
|
+
singleton_class.remove_method :original_setter
|
266
|
+
end
|
267
|
+
result
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
protected
|
272
|
+
|
273
|
+
def fields=(fields)
|
274
|
+
@updated_keys = []
|
275
|
+
@fields = fields
|
276
|
+
end
|
277
|
+
|
278
|
+
def created_at=(created_at)
|
279
|
+
return unless created_at
|
280
|
+
|
281
|
+
@created_at = Time.parse(created_at)
|
282
|
+
end
|
283
|
+
|
284
|
+
def client
|
285
|
+
self.class.client
|
286
|
+
end
|
287
|
+
|
288
|
+
def validate_key(key)
|
289
|
+
return true unless key.is_a?(Symbol)
|
290
|
+
|
291
|
+
raise(Error, [
|
292
|
+
"Airrecord 1.0 dropped support for Symbols as field names.",
|
293
|
+
"Please use the raw field name, a String, instead.",
|
294
|
+
"You might try: record['#{key.to_s.tr('_', ' ')}']"
|
295
|
+
].join("\n"))
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.table(api_key, base_key, table_name)
|
300
|
+
Class.new(Table) do |klass|
|
301
|
+
klass.table_name = table_name
|
302
|
+
klass.api_key = api_key
|
303
|
+
klass.base_key = base_key
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
data/lib/norairrecord.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "json"
|
2
|
+
require "faraday"
|
3
|
+
require 'faraday/net_http_persistent'
|
4
|
+
require "time"
|
5
|
+
require "norairrecord/version"
|
6
|
+
require "norairrecord/client"
|
7
|
+
require "norairrecord/table"
|
8
|
+
|
9
|
+
module Norairrecord
|
10
|
+
extend self
|
11
|
+
attr_accessor :api_key, :throttle, :base_url, :user_agent
|
12
|
+
|
13
|
+
Error = Class.new(StandardError)
|
14
|
+
|
15
|
+
def throttle?
|
16
|
+
return true if @throttle.nil?
|
17
|
+
|
18
|
+
@throttle
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'norairrecord/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "norairrecord"
|
9
|
+
spec.version = Norairrecord::VERSION
|
10
|
+
spec.authors = ["nora"]
|
11
|
+
spec.email = ["nora@hcb.pizza"]
|
12
|
+
|
13
|
+
spec.summary = %q{Airtable client}
|
14
|
+
spec.description = %q{screwed a cookie to the tabel}
|
15
|
+
spec.homepage = "https://github.com/24c02/norairrecord"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
spec.required_ruby_version = ">= 2.2"
|
23
|
+
|
24
|
+
spec.add_dependency "faraday", [">= 1.0", "< 3.0"]
|
25
|
+
spec.add_dependency "net-http-persistent"
|
26
|
+
spec.add_dependency "faraday-net_http_persistent"
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 2"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: norairrecord
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nora
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: net-http-persistent
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: faraday-net_http_persistent
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
description: screwed a cookie to the tabel
|
90
|
+
email:
|
91
|
+
- nora@hcb.pizza
|
92
|
+
executables: []
|
93
|
+
extensions: []
|
94
|
+
extra_rdoc_files: []
|
95
|
+
files:
|
96
|
+
- ".github/workflows/gem-push.yml"
|
97
|
+
- ".gitignore"
|
98
|
+
- ".travis.yml"
|
99
|
+
- Gemfile
|
100
|
+
- LICENSE
|
101
|
+
- README.md
|
102
|
+
- Rakefile
|
103
|
+
- bin/console
|
104
|
+
- bin/production-test.rb
|
105
|
+
- bin/setup
|
106
|
+
- lib/norairrecord.rb
|
107
|
+
- lib/norairrecord/client.rb
|
108
|
+
- lib/norairrecord/faraday_rate_limiter.rb
|
109
|
+
- lib/norairrecord/table.rb
|
110
|
+
- lib/norairrecord/version.rb
|
111
|
+
- norairrecord.gemspec
|
112
|
+
homepage: https://github.com/24c02/norairrecord
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2.2'
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubygems_version: 3.5.16
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Airtable client
|
135
|
+
test_files: []
|