Class: Airrecord::Table

Inherits:
Object
  • Object
show all
Defined in:
lib/airrecord/table.rb

Overview

TODO: This would be much simplified if we had a schema instead. Hopefully one day Airtable will add this, but to simplify and crush the majority of the bugs that hide in here (which would be related to the dynamic schema) we may just query the first page and infer a schema from there that can be overridden on the specific classes.

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.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fields, id: nil, created_at: nil) ⇒ Table

Returns a new instance of Table.



95
96
97
98
99
# File 'lib/airrecord/table.rb', line 95

def initialize(fields, id: nil, created_at: nil)
  @id = id
  self.created_at = created_at
  self.fields = fields
end

Class Attribute Details

.api_keyObject

Returns the value of attribute api_key.



13
14
15
# File 'lib/airrecord/table.rb', line 13

def api_key
  @api_key
end

.associationsObject

Returns the value of attribute associations.



13
14
15
# File 'lib/airrecord/table.rb', line 13

def associations
  @associations
end

.base_keyObject

Returns the value of attribute base_key.



13
14
15
# File 'lib/airrecord/table.rb', line 13

def base_key
  @base_key
end

.table_nameObject

Returns the value of attribute table_name.



13
14
15
# File 'lib/airrecord/table.rb', line 13

def table_name
  @table_name
end

Instance Attribute Details

#column_mappingsObject (readonly)

Returns the value of attribute column_mappings.



93
94
95
# File 'lib/airrecord/table.rb', line 93

def column_mappings
  @column_mappings
end

#created_atObject

Returns the value of attribute created_at.



93
94
95
# File 'lib/airrecord/table.rb', line 93

def created_at
  @created_at
end

#fieldsObject

Returns the value of attribute fields.



93
94
95
# File 'lib/airrecord/table.rb', line 93

def fields
  @fields
end

#idObject (readonly)

Returns the value of attribute id.



93
94
95
# File 'lib/airrecord/table.rb', line 93

def id
  @id
end

#updated_fieldsObject (readonly)

Returns the value of attribute updated_fields.



93
94
95
# File 'lib/airrecord/table.rb', line 93

def updated_fields
  @updated_fields
end

Class Method Details

.belongs_to(name, options) ⇒ Object



27
28
29
# File 'lib/airrecord/table.rb', line 27

def belongs_to(name, options)
  has_many(name, options.merge(single: true))
end

.clientObject



15
16
17
18
# File 'lib/airrecord/table.rb', line 15

def client
  @@clients ||= {}
  @@clients[api_key] ||= Client.new(api_key)
end

.find(id) ⇒ Object



35
36
37
38
39
40
41
42
43
44
# File 'lib/airrecord/table.rb', line 35

def find(id)
  response = client.connection.get("/v0/#{base_key}/#{client.escape(table_name)}/#{id}")
  parsed_response = client.parse(response.body)

  if response.success?
    self.new(parsed_response["fields"], id: id)
  else
    client.handle_error(response.status, parsed_response)
  end
end

.has_many(name, options) ⇒ Object



20
21
22
23
24
25
# File 'lib/airrecord/table.rb', line 20

def has_many(name, options)
  @associations ||= []
  @associations << {
    field: name.to_sym,
  }.merge(options)
end

.records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil) ⇒ Object Also known as: all



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/airrecord/table.rb', line 46

def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil)
  options = {}
  options[:filterByFormula] = filter if filter

  if sort
    options[:sort] = sort.map { |field, direction|
      { field: field.to_s, direction: direction }
    }
  end

  options[:view] = view if view
  options[:offset] = offset if offset
  options[:fields] = fields if fields
  options[:maxRecords] = max_records if max_records
  options[:pageSize] = page_size if page_size

  path = "/v0/#{base_key}/#{client.escape(table_name)}"
  response = client.connection.get(path, options)
  parsed_response = client.parse(response.body)

  if response.success?
    records = parsed_response["records"]
    records = records.map { |record|
      self.new(record["fields"], id: record["id"], created_at: record["createdTime"])
    }

    if paginate && parsed_response["offset"]
      records.concat(records(
        filter: filter,
        sort: sort,
        view: view,
        paginate: paginate,
        fields: fields,
        offset: parsed_response["offset"],
        max_records: max_records,
        page_size: page_size,
      ))
    end

    records
  else
    client.handle_error(response.status, parsed_response)
  end
end

Instance Method Details

#==(other) ⇒ Object



205
206
207
208
# File 'lib/airrecord/table.rb', line 205

def ==(other)
  self.class == other.class &&
    serializable_fields == other.serializable_fields
end

#[](key) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/airrecord/table.rb', line 105

def [](key)
  value = nil

  if fields[key]
    value = fields[key]
  elsif column_mappings[key]
    value = fields[column_mappings[key]]
  end

  if association = self.association(key)
    klass = Kernel.const_get(association[:class])
    associations = value.map { |id_or_obj|
      id_or_obj = id_or_obj.respond_to?(:id) ? id_or_obj.id : id_or_obj
      klass.find(id_or_obj)
    }
    return associations.first if association[:single]
    associations
  else
    type_cast(value)
  end
end

#[]=(key, value) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/airrecord/table.rb', line 127

def []=(key, value)
  if fields[key]
    @updated_keys << key
    fields[key] = value
  elsif column_mappings[key]
    @updated_keys << column_mappings[key]
    fields[column_mappings[key]] = value
  else
    @updated_keys << key
    fields[key] = value
  end
end

#createObject

Raises:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/airrecord/table.rb', line 140

def create
  raise Error, "Record already exists (record has an id)" unless new_record?

  body = { fields: serializable_fields }.to_json
  response = client.connection.post("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}", body, { 'Content-Type': 'application/json' })
  parsed_response = client.parse(response.body)

  if response.success?
    @id = parsed_response["id"]
    self.created_at = parsed_response["createdTime"]
    self.fields = parsed_response["fields"]
  else
    client.handle_error(response.status, parsed_response)
  end
end

#destroyObject

Raises:



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/airrecord/table.rb', line 178

def destroy
  raise Error, "Unable to destroy new record" if new_record?

  response = client.connection.delete("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}")
  parsed_response = client.parse(response.body)

  if response.success?
    true
  else
    client.handle_error(response.status, parsed_response)
  end
end

#new_record?Boolean

Returns:

  • (Boolean)


101
102
103
# File 'lib/airrecord/table.rb', line 101

def new_record?
  !id
end

#saveObject

Raises:



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/airrecord/table.rb', line 156

def save
  raise Error, "Unable to save a new record" if new_record?

  return true if @updated_keys.empty?

  # To avoid trying to update computed fields we *always* use PATCH
  body = {
    fields: Hash[@updated_keys.map { |key|
      [key, fields[key]]
    }]
  }.to_json

  response = client.connection.patch("/v0/#{self.class.base_key}/#{client.escape(self.class.table_name)}/#{self.id}", body, { 'Content-Type': 'application/json' })
  parsed_response = client.parse(response.body)

  if response.success?
    self.fields = parsed_response
  else
    client.handle_error(response.status, parsed_response)
  end
end

#serializable_fields(fields = self.fields) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/airrecord/table.rb', line 191

def serializable_fields(fields = self.fields)
  Hash[fields.map { |(key, value)|
    if association(key)
      value = [ value ] unless value.is_a?(Enumerable)
      assocs = value.map { |assoc|
        assoc.respond_to?(:id) ? assoc.id : assoc
      }               
      [key, assocs]
    else
      [key, value]
    end
    }]
end