airrecord 0.1.2 → 0.1.3
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/airrecord.gemspec +2 -2
- data/lib/airrecord.rb +3 -303
- data/lib/airrecord/client.rb +37 -0
- data/lib/airrecord/table.rb +244 -0
- data/lib/airrecord/version.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34c528021b09a5445d86bdf54f171f09874a1fe7
|
4
|
+
data.tar.gz: e854c5670ed5e96c1bd66effa1538f5c522becb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63b9fc0ad061aa526a5b117d8db1477e9260730e74f4656728f23c857b629ed4d5aefcde96a8f516c25a079476338087fe89711f04b93df9201607893ba6058f
|
7
|
+
data.tar.gz: 1cde11c40f0d44d55e5f2519b5505228595f67fd3343a0cdc620feb2d11ae4edeba29e4cc8ce04e9a31d3ee778e87989ffc51d09cca444a469848a04379e4881
|
data/airrecord.gemspec
CHANGED
@@ -18,8 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency 'faraday', '~> 0.10
|
22
|
-
spec.add_dependency "net-http-persistent", '~> 2.9
|
21
|
+
spec.add_dependency 'faraday', '~> 0.10'
|
22
|
+
spec.add_dependency "net-http-persistent", '~> 2.9'
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler", "~> 1.12"
|
25
25
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/airrecord.rb
CHANGED
@@ -1,309 +1,9 @@
|
|
1
|
-
require "airrecord/version"
|
2
1
|
require "json"
|
3
2
|
require "faraday"
|
3
|
+
require "airrecord/version"
|
4
|
+
require "airrecord/client"
|
5
|
+
require "airrecord/table"
|
4
6
|
|
5
7
|
module Airrecord
|
6
8
|
Error = Class.new(StandardError)
|
7
|
-
|
8
|
-
# TODO: This would be much simplified if we had a schema instead. Hopefully
|
9
|
-
# one day Airtable will add this, but to simplify and crush the majority of
|
10
|
-
# the bugs that hide in here (which would be related to the dynamic schema) we
|
11
|
-
# may just query the first page and infer a schema from there that can be
|
12
|
-
# overridden on the specific classes.
|
13
|
-
#
|
14
|
-
# Right now I bet there's a bunch of bugs around similar named column keys (in
|
15
|
-
# terms of capitalization), it's inconsistent and non-obvious that `create`
|
16
|
-
# doesn't use the same column keys as everything else.
|
17
|
-
class Table
|
18
|
-
class << self
|
19
|
-
attr_accessor :base_key, :table_name, :api_key, :associations
|
20
|
-
|
21
|
-
def client
|
22
|
-
@@client ||= Client.new(api_key)
|
23
|
-
end
|
24
|
-
|
25
|
-
def has_many(name, options)
|
26
|
-
@associations ||= []
|
27
|
-
@associations << {
|
28
|
-
field: name.to_sym,
|
29
|
-
}.merge(options)
|
30
|
-
end
|
31
|
-
|
32
|
-
def belongs_to(name, options)
|
33
|
-
has_many(name, options.merge(single: true))
|
34
|
-
end
|
35
|
-
|
36
|
-
def find(id)
|
37
|
-
response = client.connection.get("/v0/#{base_key}/#{client.escape(table_name)}/#{id}")
|
38
|
-
parsed_response = client.parse(response.body)
|
39
|
-
|
40
|
-
if response.success?
|
41
|
-
self.new(parsed_response["fields"], id: id)
|
42
|
-
else
|
43
|
-
client.handle_error(response.status, parsed_response)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def schema
|
48
|
-
# handle associations as specific field type
|
49
|
-
# TODO: what if there's an overlap in alias and keys??
|
50
|
-
schema = {}
|
51
|
-
|
52
|
-
records(paginate: false).each do |record|
|
53
|
-
record.fields.keys.each do |key|
|
54
|
-
unless schema.find { |column| column[:key] == key }
|
55
|
-
schema[key] = {
|
56
|
-
key: key,
|
57
|
-
type: :field,
|
58
|
-
alias: underscore(key),
|
59
|
-
}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
if @associations
|
65
|
-
@associations.each do |assoc|
|
66
|
-
schema[assoc[:field]][:type] = :association
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
schema
|
71
|
-
end
|
72
|
-
|
73
|
-
def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil)
|
74
|
-
options = {}
|
75
|
-
options[:filterByFormula] = filter if filter
|
76
|
-
|
77
|
-
if sort
|
78
|
-
options[:sort] = sort.map { |field, direction|
|
79
|
-
{ field: field.to_s, direction: direction }
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
options[:view] = view if view
|
84
|
-
options[:offset] = offset if offset
|
85
|
-
options[:fields] = fields if fields
|
86
|
-
|
87
|
-
path = "/v0/#{base_key}/#{client.escape(table_name)}"
|
88
|
-
response = client.connection.get(path, options)
|
89
|
-
parsed_response = client.parse(response.body)
|
90
|
-
|
91
|
-
if response.success?
|
92
|
-
records = parsed_response["records"]
|
93
|
-
records = records.map { |record|
|
94
|
-
self.new(record["fields"], id: record["id"], created_at: record["createdTime"])
|
95
|
-
}
|
96
|
-
|
97
|
-
if paginate && parsed_response["offset"]
|
98
|
-
records.concat(records(
|
99
|
-
filter: filter,
|
100
|
-
sort: sort,
|
101
|
-
view: view,
|
102
|
-
paginate: paginate,
|
103
|
-
fields: fields,
|
104
|
-
offset: parsed_response["offset"],
|
105
|
-
))
|
106
|
-
end
|
107
|
-
|
108
|
-
records
|
109
|
-
else
|
110
|
-
client.handle_error(response.status, parsed_response)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
alias_method :all, :records
|
114
|
-
end
|
115
|
-
|
116
|
-
attr_reader :fields, :column_mappings, :id, :created_at, :updated_fields
|
117
|
-
|
118
|
-
def initialize(fields, id: nil, created_at: nil)
|
119
|
-
@id = id
|
120
|
-
self.created_at = created_at
|
121
|
-
self.fields = fields
|
122
|
-
end
|
123
|
-
|
124
|
-
def new_record?
|
125
|
-
!id
|
126
|
-
end
|
127
|
-
|
128
|
-
def [](key)
|
129
|
-
value = nil
|
130
|
-
|
131
|
-
if fields[key]
|
132
|
-
value = fields[key]
|
133
|
-
elsif column_mappings[key]
|
134
|
-
value = fields[column_mappings[key]]
|
135
|
-
end
|
136
|
-
|
137
|
-
if association = self.association(key)
|
138
|
-
klass = Kernel.const_get(association[:class])
|
139
|
-
associations = value.map { |id_or_obj|
|
140
|
-
id_or_obj = id_or_obj.respond_to?(:id) ? id_or_obj.id : id_or_obj
|
141
|
-
klass.find(id_or_obj)
|
142
|
-
}
|
143
|
-
return associations.first if association[:single]
|
144
|
-
associations
|
145
|
-
else
|
146
|
-
type_cast(value)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def []=(key, value)
|
151
|
-
if fields[key]
|
152
|
-
@updated_keys << key
|
153
|
-
fields[key] = value
|
154
|
-
elsif column_mappings[key]
|
155
|
-
@updated_keys << column_mappings[key]
|
156
|
-
fields[column_mappings[key]] = value
|
157
|
-
else
|
158
|
-
@updated_keys << key
|
159
|
-
fields[key] = value
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def create
|
164
|
-
raise Error, "Record already exists" unless new_record?
|
165
|
-
|
166
|
-
body = { fields: serializable_fields }.to_json
|
167
|
-
response = client.connection.post("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}", body, { 'Content-Type': 'application/json' })
|
168
|
-
parsed_response = client.parse(response.body)
|
169
|
-
|
170
|
-
if response.success?
|
171
|
-
@id = parsed_response["id"]
|
172
|
-
self.created_at = parsed_response["createdTime"]
|
173
|
-
self.fields = parsed_response["fields"]
|
174
|
-
else
|
175
|
-
client.handle_error(response.status, parsed_response)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def save
|
180
|
-
raise Error, "Unable to save a new record" if new_record?
|
181
|
-
|
182
|
-
return true if @updated_keys.empty?
|
183
|
-
|
184
|
-
# To avoid trying to update computed fields we *always* use PATCH
|
185
|
-
body = {
|
186
|
-
fields: Hash[@updated_keys.map { |key|
|
187
|
-
[key, fields[key]]
|
188
|
-
}]
|
189
|
-
}.to_json
|
190
|
-
|
191
|
-
response = client.connection.patch("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}", body, { 'Content-Type': 'application/json' })
|
192
|
-
parsed_response = client.parse(response.body)
|
193
|
-
|
194
|
-
if response.success?
|
195
|
-
self.fields = parsed_response
|
196
|
-
else
|
197
|
-
client.handle_error(response.status, parsed_response)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def destroy
|
202
|
-
raise Error, "Unable to destroy new record" if new_record?
|
203
|
-
|
204
|
-
response = client.connection.delete("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}")
|
205
|
-
parsed_response = client.parse(response.body)
|
206
|
-
|
207
|
-
if response.success?
|
208
|
-
true
|
209
|
-
else
|
210
|
-
client.handle_error(response.status, parsed_response)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def serializable_fields(fields = self.fields)
|
215
|
-
Hash[fields.map { |(key, value)|
|
216
|
-
if association(key)
|
217
|
-
[key, value.map(&:id)]
|
218
|
-
else
|
219
|
-
[key, value]
|
220
|
-
end
|
221
|
-
}]
|
222
|
-
end
|
223
|
-
|
224
|
-
protected
|
225
|
-
|
226
|
-
def association(key)
|
227
|
-
if self.class.associations
|
228
|
-
self.class.associations.find { |association|
|
229
|
-
association[:column].to_s == column_mappings[key].to_s || association[:column].to_s == key.to_s
|
230
|
-
}
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
def fields=(fields)
|
235
|
-
@updated_keys = []
|
236
|
-
@column_mappings = Hash[fields.keys.map { |key| [underscore(key), key] }]
|
237
|
-
@fields = fields
|
238
|
-
end
|
239
|
-
|
240
|
-
def self.underscore(key)
|
241
|
-
key.to_s.strip.gsub(/\W+/, "_").downcase.to_sym
|
242
|
-
end
|
243
|
-
|
244
|
-
def underscore(key)
|
245
|
-
self.class.underscore(key)
|
246
|
-
end
|
247
|
-
|
248
|
-
def created_at=(created_at)
|
249
|
-
return unless created_at
|
250
|
-
@created_at = Time.parse(created_at)
|
251
|
-
end
|
252
|
-
|
253
|
-
def client
|
254
|
-
self.class.client
|
255
|
-
end
|
256
|
-
|
257
|
-
def type_cast(value)
|
258
|
-
if value =~ /\d{4}-\d{2}-\d{2}/
|
259
|
-
Time.parse(value + " UTC")
|
260
|
-
else
|
261
|
-
value
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
def self.table(api_key, base_key, table_name)
|
267
|
-
Class.new(Table) do |klass|
|
268
|
-
klass.table_name = table_name
|
269
|
-
klass.api_key = api_key
|
270
|
-
klass.base_key = base_key
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
class Client
|
275
|
-
attr_reader :api_key
|
276
|
-
attr_writer :connection
|
277
|
-
|
278
|
-
def initialize(api_key)
|
279
|
-
@api_key = api_key
|
280
|
-
end
|
281
|
-
|
282
|
-
def connection
|
283
|
-
@connection ||= Faraday.new(url: "https://api.airtable.com", headers: {
|
284
|
-
"Authorization" => "Bearer #{api_key}",
|
285
|
-
"X-API-VERSION" => "0.1.0",
|
286
|
-
}) { |conn|
|
287
|
-
conn.adapter :net_http_persistent
|
288
|
-
}
|
289
|
-
end
|
290
|
-
|
291
|
-
def escape(*args)
|
292
|
-
URI.escape(*args)
|
293
|
-
end
|
294
|
-
|
295
|
-
def parse(body)
|
296
|
-
JSON.parse(body)
|
297
|
-
rescue JSON::ParserError
|
298
|
-
nil
|
299
|
-
end
|
300
|
-
|
301
|
-
def handle_error(status, error)
|
302
|
-
if error.is_a?(Hash)
|
303
|
-
raise Error, "HTTP #{status}: #{error['error']["type"]}: #{error['error']['message']}"
|
304
|
-
else
|
305
|
-
raise Error, "HTTP #{status}: Communication error: #{error}"
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|
309
9
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Airrecord
|
2
|
+
class Client
|
3
|
+
attr_reader :api_key
|
4
|
+
attr_writer :connection
|
5
|
+
|
6
|
+
def initialize(api_key)
|
7
|
+
@api_key = api_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def connection
|
11
|
+
@connection ||= Faraday.new(url: "https://api.airtable.com", headers: {
|
12
|
+
"Authorization" => "Bearer #{api_key}",
|
13
|
+
"X-API-VERSION" => "0.1.0",
|
14
|
+
}) { |conn|
|
15
|
+
conn.adapter :net_http_persistent
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def escape(*args)
|
20
|
+
URI.escape(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(body)
|
24
|
+
JSON.parse(body)
|
25
|
+
rescue JSON::ParserError
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_error(status, error)
|
30
|
+
if error.is_a?(Hash)
|
31
|
+
raise Error, "HTTP #{status}: #{error['error']["type"]}: #{error['error']['message']}"
|
32
|
+
else
|
33
|
+
raise Error, "HTTP #{status}: Communication error: #{error}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
module Airrecord
|
2
|
+
# TODO: This would be much simplified if we had a schema instead. Hopefully
|
3
|
+
# one day Airtable will add this, but to simplify and crush the majority of
|
4
|
+
# the bugs that hide in here (which would be related to the dynamic schema) we
|
5
|
+
# may just query the first page and infer a schema from there that can be
|
6
|
+
# overridden on the specific classes.
|
7
|
+
#
|
8
|
+
# Right now I bet there's a bunch of bugs around similar named column keys (in
|
9
|
+
# terms of capitalization), it's inconsistent and non-obvious that `create`
|
10
|
+
# doesn't use the same column keys as everything else.
|
11
|
+
class Table
|
12
|
+
class << self
|
13
|
+
attr_accessor :base_key, :table_name, :api_key, :associations
|
14
|
+
|
15
|
+
def client
|
16
|
+
@@client ||= Client.new(api_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def has_many(name, options)
|
20
|
+
@associations ||= []
|
21
|
+
@associations << {
|
22
|
+
field: name.to_sym,
|
23
|
+
}.merge(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def belongs_to(name, options)
|
27
|
+
has_many(name, options.merge(single: true))
|
28
|
+
end
|
29
|
+
|
30
|
+
def find(id)
|
31
|
+
response = client.connection.get("/v0/#{base_key}/#{client.escape(table_name)}/#{id}")
|
32
|
+
parsed_response = client.parse(response.body)
|
33
|
+
|
34
|
+
if response.success?
|
35
|
+
self.new(parsed_response["fields"], id: id)
|
36
|
+
else
|
37
|
+
client.handle_error(response.status, parsed_response)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil)
|
42
|
+
options = {}
|
43
|
+
options[:filterByFormula] = filter if filter
|
44
|
+
|
45
|
+
if sort
|
46
|
+
options[:sort] = sort.map { |field, direction|
|
47
|
+
{ field: field.to_s, direction: direction }
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
options[:view] = view if view
|
52
|
+
options[:offset] = offset if offset
|
53
|
+
options[:fields] = fields if fields
|
54
|
+
|
55
|
+
path = "/v0/#{base_key}/#{client.escape(table_name)}"
|
56
|
+
response = client.connection.get(path, options)
|
57
|
+
parsed_response = client.parse(response.body)
|
58
|
+
|
59
|
+
if response.success?
|
60
|
+
records = parsed_response["records"]
|
61
|
+
records = records.map { |record|
|
62
|
+
self.new(record["fields"], id: record["id"], created_at: record["createdTime"])
|
63
|
+
}
|
64
|
+
|
65
|
+
if paginate && parsed_response["offset"]
|
66
|
+
records.concat(records(
|
67
|
+
filter: filter,
|
68
|
+
sort: sort,
|
69
|
+
view: view,
|
70
|
+
paginate: paginate,
|
71
|
+
fields: fields,
|
72
|
+
offset: parsed_response["offset"],
|
73
|
+
))
|
74
|
+
end
|
75
|
+
|
76
|
+
records
|
77
|
+
else
|
78
|
+
client.handle_error(response.status, parsed_response)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias_method :all, :records
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :fields, :column_mappings, :id, :created_at, :updated_fields
|
85
|
+
|
86
|
+
def initialize(fields, id: nil, created_at: nil)
|
87
|
+
@id = id
|
88
|
+
self.created_at = created_at
|
89
|
+
self.fields = fields
|
90
|
+
end
|
91
|
+
|
92
|
+
def new_record?
|
93
|
+
!id
|
94
|
+
end
|
95
|
+
|
96
|
+
def [](key)
|
97
|
+
value = nil
|
98
|
+
|
99
|
+
if fields[key]
|
100
|
+
value = fields[key]
|
101
|
+
elsif column_mappings[key]
|
102
|
+
value = fields[column_mappings[key]]
|
103
|
+
end
|
104
|
+
|
105
|
+
if association = self.association(key)
|
106
|
+
klass = Kernel.const_get(association[:class])
|
107
|
+
associations = value.map { |id_or_obj|
|
108
|
+
id_or_obj = id_or_obj.respond_to?(:id) ? id_or_obj.id : id_or_obj
|
109
|
+
klass.find(id_or_obj)
|
110
|
+
}
|
111
|
+
return associations.first if association[:single]
|
112
|
+
associations
|
113
|
+
else
|
114
|
+
type_cast(value)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def []=(key, value)
|
119
|
+
if fields[key]
|
120
|
+
@updated_keys << key
|
121
|
+
fields[key] = value
|
122
|
+
elsif column_mappings[key]
|
123
|
+
@updated_keys << column_mappings[key]
|
124
|
+
fields[column_mappings[key]] = value
|
125
|
+
else
|
126
|
+
@updated_keys << key
|
127
|
+
fields[key] = value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def create
|
132
|
+
raise Error, "Record already exists (record has an id)" unless new_record?
|
133
|
+
|
134
|
+
body = { fields: serializable_fields }.to_json
|
135
|
+
response = client.connection.post("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}", body, { 'Content-Type': 'application/json' })
|
136
|
+
parsed_response = client.parse(response.body)
|
137
|
+
|
138
|
+
if response.success?
|
139
|
+
@id = parsed_response["id"]
|
140
|
+
self.created_at = parsed_response["createdTime"]
|
141
|
+
self.fields = parsed_response["fields"]
|
142
|
+
else
|
143
|
+
client.handle_error(response.status, parsed_response)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def save
|
148
|
+
raise Error, "Unable to save a new record" if new_record?
|
149
|
+
|
150
|
+
return true if @updated_keys.empty?
|
151
|
+
|
152
|
+
# To avoid trying to update computed fields we *always* use PATCH
|
153
|
+
body = {
|
154
|
+
fields: Hash[@updated_keys.map { |key|
|
155
|
+
[key, fields[key]]
|
156
|
+
}]
|
157
|
+
}.to_json
|
158
|
+
|
159
|
+
response = client.connection.patch("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}", body, { 'Content-Type': 'application/json' })
|
160
|
+
parsed_response = client.parse(response.body)
|
161
|
+
|
162
|
+
if response.success?
|
163
|
+
self.fields = parsed_response
|
164
|
+
else
|
165
|
+
client.handle_error(response.status, parsed_response)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def destroy
|
170
|
+
raise Error, "Unable to destroy new record" if new_record?
|
171
|
+
|
172
|
+
response = client.connection.delete("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}")
|
173
|
+
parsed_response = client.parse(response.body)
|
174
|
+
|
175
|
+
if response.success?
|
176
|
+
true
|
177
|
+
else
|
178
|
+
client.handle_error(response.status, parsed_response)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def serializable_fields(fields = self.fields)
|
183
|
+
Hash[fields.map { |(key, value)|
|
184
|
+
if association(key)
|
185
|
+
assocs = value.map { |assoc|
|
186
|
+
assoc.respond_to?(:id) ? assoc.id : assoc
|
187
|
+
}
|
188
|
+
[key, assocs]
|
189
|
+
else
|
190
|
+
[key, value]
|
191
|
+
end
|
192
|
+
}]
|
193
|
+
end
|
194
|
+
|
195
|
+
protected
|
196
|
+
|
197
|
+
def association(key)
|
198
|
+
if self.class.associations
|
199
|
+
self.class.associations.find { |association|
|
200
|
+
association[:column].to_s == column_mappings[key].to_s || association[:column].to_s == key.to_s
|
201
|
+
}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def fields=(fields)
|
206
|
+
@updated_keys = []
|
207
|
+
@column_mappings = Hash[fields.keys.map { |key| [underscore(key), key] }]
|
208
|
+
@fields = fields
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.underscore(key)
|
212
|
+
key.to_s.strip.gsub(/\W+/, "_").downcase.to_sym
|
213
|
+
end
|
214
|
+
|
215
|
+
def underscore(key)
|
216
|
+
self.class.underscore(key)
|
217
|
+
end
|
218
|
+
|
219
|
+
def created_at=(created_at)
|
220
|
+
return unless created_at
|
221
|
+
@created_at = Time.parse(created_at)
|
222
|
+
end
|
223
|
+
|
224
|
+
def client
|
225
|
+
self.class.client
|
226
|
+
end
|
227
|
+
|
228
|
+
def type_cast(value)
|
229
|
+
if value =~ /\d{4}-\d{2}-\d{2}/
|
230
|
+
Time.parse(value + " UTC")
|
231
|
+
else
|
232
|
+
value
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.table(api_key, base_key, table_name)
|
238
|
+
Class.new(Table) do |klass|
|
239
|
+
klass.table_name = table_name
|
240
|
+
klass.api_key = api_key
|
241
|
+
klass.base_key = base_key
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
data/lib/airrecord/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airrecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Eskildsen
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.10
|
19
|
+
version: '0.10'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.10
|
26
|
+
version: '0.10'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: net-http-persistent
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.9
|
33
|
+
version: '2.9'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 2.9
|
40
|
+
version: '2.9'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -111,6 +111,8 @@ files:
|
|
111
111
|
- bin/console
|
112
112
|
- bin/setup
|
113
113
|
- lib/airrecord.rb
|
114
|
+
- lib/airrecord/client.rb
|
115
|
+
- lib/airrecord/table.rb
|
114
116
|
- lib/airrecord/version.rb
|
115
117
|
homepage: https://github.com/sirupsen/airtable
|
116
118
|
licenses: []
|