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