Skip to content

Commit 71c1096

Browse files
authored
Merge pull request #33 from Meekohi/master
Deprecate access via symbols
2 parents 5c32403 + 8322d5e commit 71c1096

File tree

6 files changed

+120
-110
lines changed

6 files changed

+120
-110
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 1.0.0 (unreleased)
2+
3+
* 1.0.0 will introduce breaking changes, including removing support for symbols. To update, change snake-case symbols to their correct column names (for example, `record["First Name"]` instead of `record[:first_name]`)
4+
5+
# 0.2.5
6+
7+
* Deprecate using symbols instead of strings
8+
19
# 0.2.4
210

311
* Don't flag as dirty if change is equal

README.md

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Tea < Airrecord::Table
2020
self.base_key = "app1"
2121
self.table_name = "Teas"
2222

23-
has_many :brews, class: 'Brew', column: "Brews"
23+
has_many "Brews", class: 'Brew', column: "Brews"
2424

2525
def self.chinese
2626
all(filter: '{Country} = "China"')
@@ -31,34 +31,34 @@ class Tea < Airrecord::Table
3131
end
3232

3333
def location
34-
[self[:village], self[:country], self[:region]].compact.join(", ")
34+
[self["Village"], self["Country"], self["Region"]].compact.join(", ")
3535
end
3636

3737
def green?
38-
self[:type] == "Green"
38+
self["Type"] == "Green"
3939
end
4040
end
4141

4242
class Brew < Airrecord::Table
4343
self.base_key = "app1"
4444
self.table_name = "Brews"
4545

46-
belongs_to :tea, class: 'Tea', column: 'Tea'
46+
belongs_to "Tea", class: 'Tea', column: 'Tea'
4747

4848
def self.hot
4949
all(filter: "{Temperature} > 90")
5050
end
5151

5252
def done_brewing?
53-
self[:created_at] + self[:duration] > Time.now
53+
self["Created At"] + self["Duration"] > Time.now
5454
end
5555
end
5656

5757
teas = Tea.all
5858
tea = teas.first
59-
tea[:country] # access atribute
59+
tea["Country"] # access atribute
6060
tea.location # instance methods
61-
tea[:brews] # associated brews
61+
tea["Brews"] # associated brews
6262
```
6363

6464
A short-hand API for definitions and more ad-hoc querying is also available:
@@ -67,7 +67,7 @@ A short-hand API for definitions and more ad-hoc querying is also available:
6767
Tea = Airrecord.table("api_key", "app_key", "Teas")
6868

6969
Tea.all.each do |record|
70-
puts "#{record.id}: #{record[:name]}"
70+
puts "#{record.id}: #{record["Name"]}"
7171
end
7272

7373
Tea.find("rec3838")
@@ -107,7 +107,7 @@ class Tea < Airrecord::Table
107107
self.table_name = "Teas"
108108

109109
def location
110-
[self[:village], self[:country], self[:region]].compact.join(", ")
110+
[self["Village"], self["Country"], self["Region"]].compact.join(", ")
111111
end
112112
end
113113
```
@@ -158,17 +158,16 @@ The `sort` option can be used to sort results returned from the Airtable API.
158158

159159
```ruby
160160
# Sort teas by the Name column in ascending order
161-
Tea.all(sort: { Name: "asc" })
161+
Tea.all(sort: { "Name" => "asc" })
162162

163163
# Sort teas by Type (green, black, oolong, ..) in descending order
164-
Tea.all(sort: { Type: "desc" })
164+
Tea.all(sort: { "Type" => "desc" })
165165

166166
# Sort teas by price in descending order
167-
Tea.all(sort: { Price: "desc" })
167+
Tea.all(sort: { "Price" => "desc" })
168168
```
169169

170-
Note again that the key _must_ be the full column name. Snake-cased variants do
171-
not work here.
170+
Note again that the key _must_ be the full column name.
172171

173172
As mentioned above, by default Airrecord will return results from all pages.
174173
This can be slow if you have 1000s of records. You may wish to use the `view`
@@ -181,7 +180,7 @@ calls. Airrecord will _always_ fetch the maximum possible amount of records
181180
Tea.all(paginate: false)
182181

183182
# Give me only the most recent teas
184-
Tea.all(sort: { "Created At": "desc" }, paginate: false)
183+
Tea.all(sort: { "Created At" => "desc" }, paginate: false)
185184
```
186185

187186
### Creating
@@ -192,16 +191,15 @@ Creating a new record is done through `#create`.
192191
tea = Tea.new("Name" => "Feng Gang", "Type" => "Green", "Country" => "China")
193192
tea.create # creates the record
194193
tea.id # id of the new record
195-
tea[:name] # "Feng Gang", accessed through snake-cased name
194+
tea["Name"] # "Feng Gang"
196195
```
197196

198-
Note that when instantiating the new record the column names (keys of the passed
199-
named parameters) need to match the exact column names in Airtable, otherwise
200-
Airrecord will throw an error that no column matches it.
197+
Note that column names need to match the exact column names in Airtable,
198+
otherwise Airrecord will throw an error that no column matches it.
201199

202-
In the future I hope to provide more convient names for these (snake-cased),
203-
however, this is error-prone without a proper schema API from Airtable which has
204-
still not been released.
200+
_Earlier versions of airrecord provided methods for snake-cased column names
201+
and symbols, however this proved error-prone without a proper schema API from
202+
Airtable which has still not been released._
205203

206204
### Updating
207205

@@ -210,11 +208,7 @@ Airtable with `#save`.
210208

211209
```ruby
212210
tea = Tea.find("someid")
213-
tea[:name] = "Feng Gang Organic"
214-
215-
# Since the Village column is not set, we do not have access to a snake-cased
216-
# variant since the mapping is not determined. For all we know, the correct column
217-
# name could be "VilLlaGe". Therefore, we must use the proper column name.
211+
tea["Name"] = "Feng Gang Organic"
218212
tea["Village"] = "Feng Gang"
219213

220214
tea.save # persist to Airtable
@@ -236,7 +230,7 @@ providing the URL. Unfortunately, it does not allow uploading directly.
236230

237231
```ruby
238232
word = World.find("cantankerous")
239-
word["Pronounciation"] = [{url: "https://s3.ca-central-1.amazonaws.com/word-pronunciations/cantankerous.mp3}]
233+
word["Pronounciation"] = [{url: "https://s3.ca-central-1.amazonaws.com/word-pronunciations/cantankerous.mp3"}]
240234
word.save
241235
```
242236

@@ -274,14 +268,14 @@ class Tea < Airrecord::Table
274268
self.base_key = "app1"
275269
self.table_name = "Teas"
276270

277-
has_many :brews, class: 'Brew', column: "Brews"
271+
has_many "Brews", class: 'Brew', column: "Brews"
278272
end
279273

280274
class Brew < Airrecord::Table
281275
self.base_key = "app1"
282276
self.table_name = "Brews"
283277

284-
belongs_to :tea, class: 'Tea', column: 'Tea'
278+
belongs_to "Tea", class: 'Tea', column: 'Tea'
285279
end
286280
```
287281

@@ -296,14 +290,14 @@ To retrieve records from associations to a record:
296290

297291
```ruby
298292
tea = Tea.find('rec84')
299-
tea[:brews] # brews associated with tea
293+
tea["Brews"] # brews associated with tea
300294
```
301295

302296
This in turn works the other way too:
303297

304298
```ruby
305299
brew = Brew.find('rec849')
306-
brew[:tea] # the associated tea instance
300+
brew["Tea"] # the associated tea instance
307301
```
308302

309303
### Creating associated records
@@ -328,20 +322,12 @@ around.
328322
Tea = Airrecord.table("api_key", "app_key", "Teas")
329323

330324
Tea.all.each do |record|
331-
puts "#{record.id}: #{record[:name]}"
325+
puts "#{record.id}: #{record["Name"]}"
332326
end
333327

334328
Tea.find("rec3838")
335329
```
336330

337-
### Snake-cased helper methods
338-
339-
When retrieving an existing record from Airtable, snake-cased helper names are
340-
available to index attributes. These are _only_ available on retrieved records,
341-
and _only_ if the column was set. If it's `nil`, it will not exist. That means
342-
if you want to set column that has a `nil` value for a column type, you'll have
343-
to fully type it out.
344-
345331
### Production Middlewares
346332

347333
For production use-cases, it's worth considering adding retries and circuit

lib/airrecord/table.rb

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,24 @@ module Airrecord
88
# Right now I bet there's a bunch of bugs around similar named column keys (in
99
# terms of capitalization), it's inconsistent and non-obvious that `create`
1010
# doesn't use the same column keys as everything else.
11+
#
12+
# 2018-11-01
13+
# deprecate_symbols: long-term plan is to force everyone to use raw strings,
14+
# to match the Airtable behavior. For now we'll just warn when using symbols
15+
# with a deprecation notice.
16+
1117
class Table
18+
def deprecate_symbols
19+
self.class.deprecate_symbols
20+
end
21+
1222
class << self
1323
attr_accessor :base_key, :table_name, :api_key, :associations
1424

25+
def deprecate_symbols
26+
warn Kernel.caller.first + ": warning: Using symbols with airrecord is deprecated."
27+
end
28+
1529
def client
1630
@@clients ||= {}
1731
@@clients[api_key] ||= Client.new(api_key)
@@ -20,7 +34,7 @@ def client
2034
def has_many(name, options)
2135
@associations ||= []
2236
@associations << {
23-
field: name.to_sym,
37+
field: name.to_sym, # todo: deprecate_symbols
2438
}.merge(options)
2539
end
2640

@@ -49,6 +63,7 @@ def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fiel
4963

5064
if sort
5165
options[:sort] = sort.map { |field, direction|
66+
deprecate_symbols if field.is_a? Symbol
5267
{ field: field.to_s, direction: direction }
5368
}
5469
end
@@ -106,8 +121,10 @@ def [](key)
106121
value = nil
107122

108123
if fields[key]
124+
deprecate_symbols if key.is_a? Symbol
109125
value = fields[key]
110126
elsif column_mappings[key]
127+
deprecate_symbols if key.is_a? Symbol
111128
value = fields[column_mappings[key]]
112129
end
113130

@@ -125,11 +142,13 @@ def [](key)
125142
end
126143

127144
def []=(key, value)
145+
deprecate_symbols if key.is_a? Symbol
128146
if fields[key]
129147
return if fields[key] == value # no-op
130148
@updated_keys << key
131149
fields[key] = value
132150
elsif column_mappings[key]
151+
deprecate_symbols
133152
return if fields[column_mappings[key]] == value # no-op
134153
@updated_keys << column_mappings[key]
135154
fields[column_mappings[key]] = value
@@ -221,15 +240,15 @@ def association(key)
221240

222241
def fields=(fields)
223242
@updated_keys = []
224-
@column_mappings = Hash[fields.keys.map { |key| [underscore(key), key] }]
243+
@column_mappings = Hash[fields.keys.map { |key| [underscore(key), key] }] # TODO remove (deprecate_symbols)
225244
@fields = fields
226245
end
227246

228-
def self.underscore(key)
247+
def self.underscore(key) # TODO remove (deprecate_symbols)
229248
key.to_s.strip.gsub(/\W+/, "_").downcase.to_sym
230249
end
231250

232-
def underscore(key)
251+
def underscore(key) # TODO remove (deprecate_symbols)
233252
self.class.underscore(key)
234253
end
235254

@@ -249,6 +268,7 @@ def type_cast(value)
249268
value
250269
end
251270
end
271+
252272
end
253273

254274
def self.table(api_key, base_key, table_name)

lib/airrecord/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Airrecord
2-
VERSION = "0.2.4"
2+
VERSION = "0.2.5"
33
end

test/associations_test.rb

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ class Tea < Airrecord::Table
55
self.base_key = "app1"
66
self.table_name = "Teas"
77

8-
has_many :brews, class: "Brew", column: "Brews"
8+
has_many "Brews", class: "Brew", column: "Brews"
99
end
1010

1111
class Brew < Airrecord::Table
1212
self.api_key = "key1"
1313
self.base_key = "app1"
1414
self.table_name = "Brews"
1515

16-
belongs_to :tea, class: "Tea", column: "Tea"
16+
belongs_to "Tea", class: "Tea", column: "Tea"
1717
end
1818

1919
class AssociationsTest < MiniTest::Test
@@ -25,42 +25,42 @@ def setup
2525
end
2626

2727
def test_has_many_associations
28-
tea = Tea.new(Name: "Dong Ding", Brews: ["rec2"])
28+
tea = Tea.new("Name" => "Dong Ding", "Brews" => ["rec2"])
2929

30-
record = Brew.new(Name: "Good brew")
30+
record = Brew.new("Name" => "Good brew")
3131
stub_find_request(record, id: "rec2", table: Brew)
3232

33-
assert_equal 1, tea[:brews].size
34-
assert_kind_of Airrecord::Table, tea[:brews].first
35-
assert_equal "rec2", tea[:brews].first.id
33+
assert_equal 1, tea["Brews"].size
34+
assert_kind_of Airrecord::Table, tea["Brews"].first
35+
assert_equal "rec2", tea["Brews"].first.id
3636
end
3737

3838
def test_belongs_to
39-
brew = Brew.new(Name: "Good Brew", Tea: ["rec1"])
40-
tea = Tea.new(Name: "Dong Ding", Brews: ["rec2"])
39+
brew = Brew.new("Name" => "Good Brew", "Tea" => ["rec1"])
40+
tea = Tea.new("Name" => "Dong Ding", "Brews" => ["rec2"])
4141
stub_find_request(tea, table: Tea, id: "rec1")
4242

43-
assert_equal "rec1", brew[:tea].id
43+
assert_equal "rec1", brew["Tea"].id
4444
end
4545

4646
def test_build_association_and_post_id
47-
tea = Tea.new({Name: "Jingning", Brews: []}, id: "rec1")
48-
brew = Brew.new(Name: "greeaat", Tea: [tea])
47+
tea = Tea.new({"Name" => "Jingning", "Brews" => []}, id: "rec1")
48+
brew = Brew.new("Name" => "greeaat", "Tea" => [tea])
4949
stub_post_request(brew, table: Brew)
5050

5151
brew.create
5252

5353
stub_find_request(tea, table: Tea, id: "rec1")
54-
assert_equal tea.id, brew[:tea].id
54+
assert_equal tea.id, brew["Tea"].id
5555
end
5656

5757
def test_build_association_from_strings
58-
tea = Tea.new({Name: "Jingning", Brews: ["rec2"]})
58+
tea = Tea.new({"Name" => "Jingning", "Brews" => ["rec2"]})
5959
stub_post_request(tea, table: Tea)
6060

6161
tea.create
6262

6363
stub_find_request(Brew.new({}), table: Brew, id: "rec2")
64-
assert_equal 1, tea[:brews].count
64+
assert_equal 1, tea["Brews"].count
6565
end
6666
end

0 commit comments

Comments
 (0)