Skip to content

Commit

Permalink
Merge pull request #33 from Meekohi/master
Browse files Browse the repository at this point in the history
Deprecate access via symbols
  • Loading branch information
sirupsen authored Nov 2, 2018
2 parents 5c32403 + 8322d5e commit 71c1096
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 110 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 1.0.0 (unreleased)

* 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]`)

# 0.2.5

* Deprecate using symbols instead of strings

# 0.2.4

* Don't flag as dirty if change is equal
Expand Down
68 changes: 27 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Tea < Airrecord::Table
self.base_key = "app1"
self.table_name = "Teas"

has_many :brews, class: 'Brew', column: "Brews"
has_many "Brews", class: 'Brew', column: "Brews"

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

def location
[self[:village], self[:country], self[:region]].compact.join(", ")
[self["Village"], self["Country"], self["Region"]].compact.join(", ")
end

def green?
self[:type] == "Green"
self["Type"] == "Green"
end
end

class Brew < Airrecord::Table
self.base_key = "app1"
self.table_name = "Brews"

belongs_to :tea, class: 'Tea', column: 'Tea'
belongs_to "Tea", class: 'Tea', column: 'Tea'

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

def done_brewing?
self[:created_at] + self[:duration] > Time.now
self["Created At"] + self["Duration"] > Time.now
end
end

teas = Tea.all
tea = teas.first
tea[:country] # access atribute
tea["Country"] # access atribute
tea.location # instance methods
tea[:brews] # associated brews
tea["Brews"] # associated brews
```

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

Tea.all.each do |record|
puts "#{record.id}: #{record[:name]}"
puts "#{record.id}: #{record["Name"]}"
end

Tea.find("rec3838")
Expand Down Expand Up @@ -107,7 +107,7 @@ class Tea < Airrecord::Table
self.table_name = "Teas"

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

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

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

# Sort teas by price in descending order
Tea.all(sort: { Price: "desc" })
Tea.all(sort: { "Price" => "desc" })
```

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

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

# Give me only the most recent teas
Tea.all(sort: { "Created At": "desc" }, paginate: false)
Tea.all(sort: { "Created At" => "desc" }, paginate: false)
```

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

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

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

### Updating

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

```ruby
tea = Tea.find("someid")
tea[:name] = "Feng Gang Organic"

# Since the Village column is not set, we do not have access to a snake-cased
# variant since the mapping is not determined. For all we know, the correct column
# name could be "VilLlaGe". Therefore, we must use the proper column name.
tea["Name"] = "Feng Gang Organic"
tea["Village"] = "Feng Gang"

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

```ruby
word = World.find("cantankerous")
word["Pronounciation"] = [{url: "https://s3.ca-central-1.amazonaws.com/word-pronunciations/cantankerous.mp3}]
word["Pronounciation"] = [{url: "https://s3.ca-central-1.amazonaws.com/word-pronunciations/cantankerous.mp3"}]
word.save
```

Expand Down Expand Up @@ -274,14 +268,14 @@ class Tea < Airrecord::Table
self.base_key = "app1"
self.table_name = "Teas"

has_many :brews, class: 'Brew', column: "Brews"
has_many "Brews", class: 'Brew', column: "Brews"
end

class Brew < Airrecord::Table
self.base_key = "app1"
self.table_name = "Brews"

belongs_to :tea, class: 'Tea', column: 'Tea'
belongs_to "Tea", class: 'Tea', column: 'Tea'
end
```

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

```ruby
tea = Tea.find('rec84')
tea[:brews] # brews associated with tea
tea["Brews"] # brews associated with tea
```

This in turn works the other way too:

```ruby
brew = Brew.find('rec849')
brew[:tea] # the associated tea instance
brew["Tea"] # the associated tea instance
```

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

Tea.all.each do |record|
puts "#{record.id}: #{record[:name]}"
puts "#{record.id}: #{record["Name"]}"
end

Tea.find("rec3838")
```

### Snake-cased helper methods

When retrieving an existing record from Airtable, snake-cased helper names are
available to index attributes. These are _only_ available on retrieved records,
and _only_ if the column was set. If it's `nil`, it will not exist. That means
if you want to set column that has a `nil` value for a column type, you'll have
to fully type it out.

### Production Middlewares

For production use-cases, it's worth considering adding retries and circuit
Expand Down
28 changes: 24 additions & 4 deletions lib/airrecord/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,24 @@ module Airrecord
# Right now I bet there's a bunch of bugs around similar named column keys (in
# terms of capitalization), it's inconsistent and non-obvious that `create`
# doesn't use the same column keys as everything else.
#
# 2018-11-01
# deprecate_symbols: long-term plan is to force everyone to use raw strings,
# to match the Airtable behavior. For now we'll just warn when using symbols
# with a deprecation notice.

class Table
def deprecate_symbols
self.class.deprecate_symbols
end

class << self
attr_accessor :base_key, :table_name, :api_key, :associations

def deprecate_symbols
warn Kernel.caller.first + ": warning: Using symbols with airrecord is deprecated."
end

def client
@@clients ||= {}
@@clients[api_key] ||= Client.new(api_key)
Expand All @@ -20,7 +34,7 @@ def client
def has_many(name, options)
@associations ||= []
@associations << {
field: name.to_sym,
field: name.to_sym, # todo: deprecate_symbols
}.merge(options)
end

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

if sort
options[:sort] = sort.map { |field, direction|
deprecate_symbols if field.is_a? Symbol
{ field: field.to_s, direction: direction }
}
end
Expand Down Expand Up @@ -106,8 +121,10 @@ def [](key)
value = nil

if fields[key]
deprecate_symbols if key.is_a? Symbol
value = fields[key]
elsif column_mappings[key]
deprecate_symbols if key.is_a? Symbol
value = fields[column_mappings[key]]
end

Expand All @@ -125,11 +142,13 @@ def [](key)
end

def []=(key, value)
deprecate_symbols if key.is_a? Symbol
if fields[key]
return if fields[key] == value # no-op
@updated_keys << key
fields[key] = value
elsif column_mappings[key]
deprecate_symbols
return if fields[column_mappings[key]] == value # no-op
@updated_keys << column_mappings[key]
fields[column_mappings[key]] = value
Expand Down Expand Up @@ -221,15 +240,15 @@ def association(key)

def fields=(fields)
@updated_keys = []
@column_mappings = Hash[fields.keys.map { |key| [underscore(key), key] }]
@column_mappings = Hash[fields.keys.map { |key| [underscore(key), key] }] # TODO remove (deprecate_symbols)
@fields = fields
end

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

def underscore(key)
def underscore(key) # TODO remove (deprecate_symbols)
self.class.underscore(key)
end

Expand All @@ -249,6 +268,7 @@ def type_cast(value)
value
end
end

end

def self.table(api_key, base_key, table_name)
Expand Down
2 changes: 1 addition & 1 deletion lib/airrecord/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Airrecord
VERSION = "0.2.4"
VERSION = "0.2.5"
end
30 changes: 15 additions & 15 deletions test/associations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ class Tea < Airrecord::Table
self.base_key = "app1"
self.table_name = "Teas"

has_many :brews, class: "Brew", column: "Brews"
has_many "Brews", class: "Brew", column: "Brews"
end

class Brew < Airrecord::Table
self.api_key = "key1"
self.base_key = "app1"
self.table_name = "Brews"

belongs_to :tea, class: "Tea", column: "Tea"
belongs_to "Tea", class: "Tea", column: "Tea"
end

class AssociationsTest < MiniTest::Test
Expand All @@ -25,42 +25,42 @@ def setup
end

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

record = Brew.new(Name: "Good brew")
record = Brew.new("Name" => "Good brew")
stub_find_request(record, id: "rec2", table: Brew)

assert_equal 1, tea[:brews].size
assert_kind_of Airrecord::Table, tea[:brews].first
assert_equal "rec2", tea[:brews].first.id
assert_equal 1, tea["Brews"].size
assert_kind_of Airrecord::Table, tea["Brews"].first
assert_equal "rec2", tea["Brews"].first.id
end

def test_belongs_to
brew = Brew.new(Name: "Good Brew", Tea: ["rec1"])
tea = Tea.new(Name: "Dong Ding", Brews: ["rec2"])
brew = Brew.new("Name" => "Good Brew", "Tea" => ["rec1"])
tea = Tea.new("Name" => "Dong Ding", "Brews" => ["rec2"])
stub_find_request(tea, table: Tea, id: "rec1")

assert_equal "rec1", brew[:tea].id
assert_equal "rec1", brew["Tea"].id
end

def test_build_association_and_post_id
tea = Tea.new({Name: "Jingning", Brews: []}, id: "rec1")
brew = Brew.new(Name: "greeaat", Tea: [tea])
tea = Tea.new({"Name" => "Jingning", "Brews" => []}, id: "rec1")
brew = Brew.new("Name" => "greeaat", "Tea" => [tea])
stub_post_request(brew, table: Brew)

brew.create

stub_find_request(tea, table: Tea, id: "rec1")
assert_equal tea.id, brew[:tea].id
assert_equal tea.id, brew["Tea"].id
end

def test_build_association_from_strings
tea = Tea.new({Name: "Jingning", Brews: ["rec2"]})
tea = Tea.new({"Name" => "Jingning", "Brews" => ["rec2"]})
stub_post_request(tea, table: Tea)

tea.create

stub_find_request(Brew.new({}), table: Brew, id: "rec2")
assert_equal 1, tea[:brews].count
assert_equal 1, tea["Brews"].count
end
end
Loading

0 comments on commit 71c1096

Please sign in to comment.