airrecord 0.1.4 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +238 -43
- data/lib/airrecord.rb +2 -0
- data/lib/airrecord/table.rb +6 -1
- data/lib/airrecord/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa2416d29667f3621c84a16cf761cf065a98be8e
|
4
|
+
data.tar.gz: bbc197ae2c88828c9b90580e45d4cb011729ee21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94ba94acd00c8262b7d77b75641721cfe4f61d2c9f8bd93b567154e128eb03ade3c3ddfda2d0e9db4b09b5bfd1d3c151901e6144f57f5238b73ce702843b75ea
|
7
|
+
data.tar.gz: 83392e39f08594d19976e2803c11c0e7ef4e34aec2a731bb569a845808d6311a67d9787393e884e44e67d2776aaf1934cc04270ec65a4043218938e012ec5ad1
|
data/README.md
CHANGED
@@ -1,81 +1,276 @@
|
|
1
1
|
# Airrecord
|
2
2
|
|
3
|
-
Airrecord is an alternative to
|
4
|
-
[`airtable-ruby`](https://github.com/airtable/airtable-ruby). Airrecord
|
5
|
-
|
6
|
-
|
3
|
+
Airrecord is an alternative Airtable Ruby libary to
|
4
|
+
[`airtable-ruby`](https://github.com/airtable/airtable-ruby). Airrecord attempts
|
5
|
+
to enforce a more [database-like API to
|
6
|
+
Airtable](http://sirupsen.com/minimum-viable-airtable/).
|
7
|
+
|
8
|
+
You can add this line to your Gemfile to use Airrecord:
|
7
9
|
|
8
10
|
```ruby
|
9
11
|
gem 'airrecord'
|
10
12
|
```
|
11
13
|
|
12
|
-
|
13
|
-
in [this post](http://sirupsen.com/minimum-viable-airtable/). Furthermore as an
|
14
|
-
alternative to the [official
|
15
|
-
library](https://github.com/airtable/airtable-ruby).
|
16
|
-
|
17
|
-
## Examples
|
18
|
-
|
19
|
-
There's a simple API that allows more ad-hoc querying of Airtable:
|
14
|
+
A quick example to give an idea of the API that Airrecord provides:
|
20
15
|
|
21
16
|
```ruby
|
22
|
-
|
17
|
+
Airrecord.api_key = "key1"
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
class Tea < Airrecord::Table
|
20
|
+
self.base_key = "app1"
|
21
|
+
self.table_name = "Teas"
|
27
22
|
|
28
|
-
|
29
|
-
```
|
23
|
+
has_many :brews, class: 'Brew', column: "Brews"
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
def location
|
26
|
+
[self[:village], self[:country], self[:region]].compact.join(", ")
|
27
|
+
end
|
28
|
+
end
|
35
29
|
|
36
|
-
```ruby
|
37
30
|
class Brew < Airrecord::Table
|
38
|
-
self.api_key = "key1"
|
39
31
|
self.base_key = "app1"
|
40
|
-
self.table_name = "
|
32
|
+
self.table_name = "Brews"
|
41
33
|
|
42
34
|
belongs_to :tea, class: 'Tea', column: 'Tea'
|
43
35
|
end
|
44
36
|
|
37
|
+
teas = Tea.all
|
38
|
+
tea = teas.first
|
39
|
+
tea[:country] # access atribute
|
40
|
+
tea.location # instance methods
|
41
|
+
tea[:brews] # associated brews
|
42
|
+
```
|
43
|
+
|
44
|
+
## Documentation
|
45
|
+
|
46
|
+
### Authentication
|
47
|
+
|
48
|
+
To obtain your API client, navigate to the [Airtable's API
|
49
|
+
page](https://airtable.com/api), select your base and obtain your API key and
|
50
|
+
application token.
|
51
|
+
|
52
|
+

|
53
|
+
|
54
|
+
You can provide a global API key with:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
Airrecord.api_key = "your api key"
|
58
|
+
```
|
59
|
+
|
60
|
+
The app token has to be set on the definitions of the tables (see API below).
|
61
|
+
You can override the API key per table.
|
62
|
+
|
63
|
+
### Table
|
64
|
+
|
65
|
+
The Airrecord API is centered around definitions of `Airrecord::Table` from
|
66
|
+
which the definitions of your tables inherit. This is analogous to
|
67
|
+
`ActiveRecord::Base`. For example, we may have a Base to track teas we have
|
68
|
+
tried.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Airrecord.api_key = "your api key" # see authentication section
|
72
|
+
|
45
73
|
class Tea < Airrecord::Table
|
46
|
-
self.api_key = "key1"
|
47
74
|
self.base_key = "app1"
|
48
75
|
self.table_name = "Teas"
|
49
76
|
|
50
|
-
has_many :hot_brews, class: 'Brew', column: "Hot Brews"
|
51
|
-
|
52
77
|
def location
|
53
78
|
[self[:village], self[:country], self[:region]].compact.join(", ")
|
54
79
|
end
|
55
80
|
end
|
81
|
+
```
|
82
|
+
|
83
|
+
This gives us a class that maps to records in a table. Class methods are
|
84
|
+
available to fetch records on the table.
|
85
|
+
|
86
|
+
### Listing Records
|
87
|
+
|
88
|
+
Retrieval of multiple records is done through `#all`. To get all records in a
|
89
|
+
table:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Tea.all # array of Tea instances
|
93
|
+
```
|
94
|
+
|
95
|
+
You can use all options supported by the API (they are documented on the [API page for your base](https://airtable.com/api)). By default `#all` will traverse all pages, see below on how to control pagination.
|
96
|
+
|
97
|
+
To use `filterbyFormula` to filter returned records:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# Retrieve all teas from China
|
101
|
+
Tea.all(filter: "{Country} == "China")
|
102
|
+
|
103
|
+
# Retrieve all teas created in the past week
|
104
|
+
Tea.all(filter: "DATETIME_DIFF(CREATED_TIME(), TODAY(), 'days') < 7")
|
105
|
+
```
|
56
106
|
|
57
|
-
|
58
|
-
|
59
|
-
|
107
|
+
This filtering can, of course, also be done in Ruby directly after calling
|
108
|
+
`#all` without `filter`, however, it may be more efficient to let Airtable
|
109
|
+
filter if you have a lot of records.
|
60
110
|
|
61
|
-
|
62
|
-
|
111
|
+
You can use `view` to only fetch records from a specific view. This is less
|
112
|
+
ad-hoc than `filterByFormula`:
|
63
113
|
|
64
|
-
|
65
|
-
tea
|
114
|
+
```ruby
|
115
|
+
# Retrieve all teas in the green tea view
|
116
|
+
Tea.all(view: "Green")
|
117
|
+
|
118
|
+
# Retrieve all Japanese teas
|
119
|
+
Tea.all(view: "Japan")
|
120
|
+
```
|
121
|
+
|
122
|
+
The `sort` option can be used to sort results returned from the Airtable API.
|
66
123
|
|
67
|
-
|
68
|
-
|
69
|
-
|
124
|
+
```ruby
|
125
|
+
# Sort teas by the Name column in ascending order
|
126
|
+
Tea.all(sort: { Name: "asc" })
|
70
127
|
|
71
|
-
|
72
|
-
|
128
|
+
# Sort teas by Type (green, black, oolong, ..) in descending order
|
129
|
+
Tea.all(sort: { Type: "desc" })
|
73
130
|
|
74
|
-
|
75
|
-
|
76
|
-
# => "3 - Fine"
|
131
|
+
# Sort teas by price in descending order
|
132
|
+
Tea.all(sort: { Price: "desc" })
|
77
133
|
```
|
78
134
|
|
135
|
+
Note again that the key _must_ be the full column name. Snake-cased variants do
|
136
|
+
not work here.
|
137
|
+
|
138
|
+
As mentioned above, by default Airrecord will return results from all pages.
|
139
|
+
This can be slow if you have 1000s of records. You may wish to use the `view`
|
140
|
+
and/or `filter` option to sort in the results early, instead of doing 10s of
|
141
|
+
calls. Airrecord will _always_ fetch the maximum possible amount of records
|
142
|
+
(100). This means that fetching 1,000 records will take 10 (at least) roundtrips. You can disable pagination (which fetches the first page) by passing `paginate: false`. This is especially useful if you're looking to fetch a set of recent records from a view or formula in tandem with a `sort`:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
# Only fetch the first page. Sorting is undefined.
|
146
|
+
Tea.all(paginate: false)
|
147
|
+
|
148
|
+
# Give me only the most recent teas
|
149
|
+
Tea.all(sort: { "Created At": "desc" }, paginate: false)
|
150
|
+
```
|
151
|
+
|
152
|
+
### Creating
|
153
|
+
|
154
|
+
Creating a new record is done through `#create`.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
tea = Tea.new("Name" => "Feng Gang", "Type" => "Green", "Country" => "China")
|
158
|
+
tea.create # creates the record
|
159
|
+
tea.id # id of the new record
|
160
|
+
tea[:name] # "Feng Gang", accessed through snake-cased name
|
161
|
+
```
|
162
|
+
|
163
|
+
Note that when instantiating the new record the column names (keys of the passed
|
164
|
+
named parameters) need to match the exact column names in Airtable, otherwise
|
165
|
+
Airrecord will throw an error that no column matches it.
|
166
|
+
|
167
|
+
In the future I hope to provide more convient names for these (snake-cased),
|
168
|
+
however, this is error-prone without a proper schema API from Airtable which has
|
169
|
+
still not been released.
|
170
|
+
|
171
|
+
### Updating
|
172
|
+
|
173
|
+
Updating a record is done by changing the attributes and persistent to
|
174
|
+
Airtable with `#save`.
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
tea = Tea.find("someid")
|
178
|
+
tea[:name] = "Feng Gang Organic"
|
179
|
+
|
180
|
+
# Since the Village column is not set, we do not have access to a snake-cased
|
181
|
+
variant since the mapping is not determined. For all we know, the correct column
|
182
|
+
name could be "VilLlaGe". Therefore, we must use the proper column name.
|
183
|
+
tea["Village"] = "Feng Gang"
|
184
|
+
|
185
|
+
tea.save # persist to Airtable
|
186
|
+
```
|
187
|
+
|
188
|
+
### Deleting
|
189
|
+
|
190
|
+
An instantiated record can be deleted through `#destroy`:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
tea = Tea.find("rec839")
|
194
|
+
tea.destroy # deletes record
|
195
|
+
```
|
196
|
+
|
197
|
+
### Associations
|
198
|
+
|
199
|
+
Airrecord supports managing associations between tables by linking
|
200
|
+
`Airrecord::Table` classes. To continue with our tea example, we may have
|
201
|
+
another table in the base to track brews of a specific tea (temperature,
|
202
|
+
steeping time, rating, ..). A tea thus has many brews:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
class Tea < Airrecord::Table
|
206
|
+
self.base_key = "app1"
|
207
|
+
self.table_name = "Teas"
|
208
|
+
|
209
|
+
has_many :brews, class: 'Brew', column: "Brews"
|
210
|
+
end
|
211
|
+
|
212
|
+
class Brew < Airrecord::Table
|
213
|
+
self.base_key = "app1"
|
214
|
+
self.table_name = "Brews"
|
215
|
+
|
216
|
+
belongs_to :tea, class: 'Tea', column: 'Tea'
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
This gives us access to a bunch of convenience methods to handle the assocation
|
221
|
+
between the two tables. Note that the two tables need to be in the same base
|
222
|
+
(i.e. have the same base key) otherwise this will _not_ work as Airtable does
|
223
|
+
_not_ support associations across Bases.
|
224
|
+
|
225
|
+
### Retrieving associated records
|
226
|
+
|
227
|
+
To retrieve records from associations to a record:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
tea = Tea.find('rec84')
|
231
|
+
tea[:brews] # brews associated with tea
|
232
|
+
```
|
233
|
+
|
234
|
+
This in turn works the other way too:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
brew = Brew.find('rec849')
|
238
|
+
brew[:tea] # the associated tea instance
|
239
|
+
```
|
240
|
+
|
241
|
+
### Creating associated records
|
242
|
+
|
243
|
+
You can easily associate records with each other:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
tea = Tea.find('rec849829')
|
247
|
+
# This will create a brew associated with the specific tea
|
248
|
+
Brew.create("Tea" => tea, "Temperature" => "80", "Time" => "4m", "Rating" => "5")
|
249
|
+
```
|
250
|
+
|
251
|
+
### Ad-hoc API
|
252
|
+
|
253
|
+
Airrtable provides a simple, ad-hoc API that will instantiate an anonymous
|
254
|
+
`Airrecord::Table` for you on the fly with the configured key, app, and table.
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
teas = Airrecord.table("key1", "app1", "Teas")
|
258
|
+
|
259
|
+
teas.records.each do |record|
|
260
|
+
puts "#{record.id}: #{record[:name]}"
|
261
|
+
end
|
262
|
+
|
263
|
+
p teas.find(teas.records.first.id)
|
264
|
+
```
|
265
|
+
|
266
|
+
### Snake-cased helper methods
|
267
|
+
|
268
|
+
When retrieving an existing record from Airtable, snake-cased helper names are
|
269
|
+
available to index attributes. These are _only_ available on retrieved records,
|
270
|
+
and _only_ if the column was set. If it's `nil`, it will not exist. That means
|
271
|
+
if you want to set column that has a `nil` value for a column type, you'll have
|
272
|
+
to fully type it out.
|
273
|
+
|
79
274
|
## Contributing
|
80
275
|
|
81
276
|
Contributions will be happily accepted in the form of Github Pull Requests!
|
data/lib/airrecord.rb
CHANGED
data/lib/airrecord/table.rb
CHANGED
@@ -13,7 +13,8 @@ module Airrecord
|
|
13
13
|
attr_accessor :base_key, :table_name, :api_key, :associations
|
14
14
|
|
15
15
|
def client
|
16
|
-
@@
|
16
|
+
@@clients ||= {}
|
17
|
+
@@clients[api_key] ||= Client.new(api_key)
|
17
18
|
end
|
18
19
|
|
19
20
|
def has_many(name, options)
|
@@ -27,6 +28,10 @@ module Airrecord
|
|
27
28
|
has_many(name, options.merge(single: true))
|
28
29
|
end
|
29
30
|
|
31
|
+
def api_key
|
32
|
+
@api_key || Airrecord.api_key
|
33
|
+
end
|
34
|
+
|
30
35
|
def find(id)
|
31
36
|
response = client.connection.get("/v0/#{base_key}/#{client.escape(table_name)}/#{id}")
|
32
37
|
parsed_response = client.parse(response.body)
|
data/lib/airrecord/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airrecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Eskildsen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|