airrecord 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90f1c342a1a2ed5bc00e4039374d9eea8e03ea79
4
- data.tar.gz: b31904546125ec0e92805f37876b69cb8b8d8604
3
+ metadata.gz: fa2416d29667f3621c84a16cf761cf065a98be8e
4
+ data.tar.gz: bbc197ae2c88828c9b90580e45d4cb011729ee21
5
5
  SHA512:
6
- metadata.gz: 032d7be64c9e5bca336acad19c9a9a0839f184c70f53f7837c6191d03ca0df3484626579e5cdfbc6922b2956a4bbac7adee572679026be261689b9148264ee7a
7
- data.tar.gz: 5905798573a9174a9f369cd50cbcef85c9c5e39b69bdfb3c001f048dca4c1b910787a62fe0c5d4fa956f1c67bf322dfa1d9a31070e763b678a334fa63dc53fc4
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 takes an
5
- approach to approaching Airtable more like a database from Ruby's point of view,
6
- inviting inspiration from ActiveRecord's API.
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
- This library is provided to enforce a mental model of a database, as described
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
- teas = Airrecord.table("key1", "app1", "Teas")
17
+ Airrecord.api_key = "key1"
23
18
 
24
- teas.records.each do |record|
25
- puts "#{record.id}: #{record[:name]}"
26
- end
19
+ class Tea < Airrecord::Table
20
+ self.base_key = "app1"
21
+ self.table_name = "Teas"
27
22
 
28
- p teas.find(teas.records.first.id)
29
- ```
23
+ has_many :brews, class: 'Brew', column: "Brews"
30
24
 
31
- Then there's the API which allows you to define tables as classes and
32
- relationships between them. This maps with ActiveRecord models. This makes
33
- working with relationships much easier, and allows you to define domain-specific
34
- logic on the models.
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 = "Hot Brews"
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
+ ![](https://cloud.githubusercontent.com/assets/97400/23580721/a0815df4-00bb-11e7-9abf-140a01625678.png)
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
- tea = Tea.all[2]
58
- brew = tea[:hot_brews].first
59
- brew[:tea].id == tea.id
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
- tea = Tea.new(Name: "omg tea", Type: ["Oolong"])
62
- tea.create
111
+ You can use `view` to only fetch records from a specific view. This is less
112
+ ad-hoc than `filterByFormula`:
63
113
 
64
- tea[:name] = "super omg tea"
65
- tea.save
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
- tea = Tea.find(tea.id)
68
- puts tea[:name]
69
- # => "super omg tea"
124
+ ```ruby
125
+ # Sort teas by the Name column in ascending order
126
+ Tea.all(sort: { Name: "asc" })
70
127
 
71
- brew = Brew.new(Tea: [tea], Rating: "3 - Fine")
72
- brew.create
128
+ # Sort teas by Type (green, black, oolong, ..) in descending order
129
+ Tea.all(sort: { Type: "desc" })
73
130
 
74
- tea = Tea.find(tea.id)
75
- puts tea[:hot_brews].first[:rating]
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
@@ -6,5 +6,7 @@ require "airrecord/client"
6
6
  require "airrecord/table"
7
7
 
8
8
  module Airrecord
9
+ extend self
9
10
  Error = Class.new(StandardError)
11
+ attr_accessor :api_key
10
12
  end
@@ -13,7 +13,8 @@ module Airrecord
13
13
  attr_accessor :base_key, :table_name, :api_key, :associations
14
14
 
15
15
  def client
16
- @@client ||= Client.new(api_key)
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)
@@ -1,3 +1,3 @@
1
1
  module Airrecord
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
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.1.4
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-02-25 00:00:00.000000000 Z
11
+ date: 2017-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday