norairrecord 0.1.3 → 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
  SHA256:
3
- metadata.gz: ad051cbba9546de977583acfa8a41c9eab049dd1c1e269d65147f8973b18b84c
4
- data.tar.gz: e1b5e0c786361909da7c5eadea74b91331d4ab8ac92b9386719f4f240c811996
3
+ metadata.gz: a5f9d1c3992d715d55a1308918a574a00b83042da2e58bfbcc9389ff51e0b6cd
4
+ data.tar.gz: d4372b35b219e5ffd6384c42e3687dd9019dabbb48d8bb622d395eb8aadab90a
5
5
  SHA512:
6
- metadata.gz: e4dd0a771cd5f112f114753952e7ca36ca7299762de4b12f66ad931154019bc47a58b50d2cecfabd74a1393b74133d808e9e6ef9caa957a20535dfcf6d97b58b
7
- data.tar.gz: 59e4fec79c29e4d94a3e3a0fc18c83d22db0983aaca2d5a02b8fb946950c501be9521e944a7da7ce475184e697f471275961e47b35c8b2e4b74e6c7c4da79b78
6
+ metadata.gz: 0fdcca5fc8ee492d692fe1271d60052d82ddf4c18ff4ec275e464cf9871dd52cf9996e393ac01292bc780a31dc0681c92af1485282e6b11138fea35957ae1b18
7
+ data.tar.gz: f023f648cb134d4de8c0773fd1a2d0882393ea7109af105f2cd9b23fde4d1eeb9d519ee695ac341912a191d3d6a3b6b946efb3d7c8bc6d60ecdaf387dacfcef5
data/README.md CHANGED
@@ -29,4 +29,26 @@ stuff not in the OG:
29
29
  * custom UA
30
30
  * `Norairrecord.user_agent = "i'm the reason why you're getting 429s!"`
31
31
  * `Table#airtable_url`
32
- * what it says on the tin!
32
+ * what it says on the tin!
33
+ * `Table.has_subtypes`
34
+ * hokay so:
35
+ * ```ruby
36
+ class Friend < Norairrecord::Table
37
+ # base_key/table_name, etc...
38
+ has_subtypes "type", { # based on 'type' column...
39
+ "person" => "Person", # when 'person' instantiate record as Person
40
+ "shark" => "Shark"
41
+ }, strict: true # if strict, unmapped types will raise UnknownTypeError
42
+ # otherwise they will be instantiated as the base class
43
+ end
44
+
45
+ class Person < Friend; end
46
+ class Shark < Friend; end
47
+
48
+ Friend.all
49
+ => [<Person>, <Person>, <Shark>]
50
+ ```
51
+ * `Norairrecord::RecordNotFoundError`
52
+ * never again wonder if an error is because you goofed up an ID or you're getting ratelimited
53
+ * `where` argument on `has_many` lookups
54
+ * `Table#first`, `Table#first_where`
@@ -40,6 +40,7 @@ module Norairrecord
40
40
  end
41
41
 
42
42
  def handle_error(status, error)
43
+ raise RecordNotFoundError if status == 404
43
44
  if error.is_a?(Hash) && error['error']
44
45
  raise Error, "HTTP #{status}: #{error['error']['type']}: #{error['error']['message']}"
45
46
  else
@@ -3,8 +3,15 @@ require 'rubygems' # For Gem::Version
3
3
  module Norairrecord
4
4
  class Table
5
5
  class << self
6
- attr_accessor :base_key, :table_name
7
- attr_writer :api_key
6
+ attr_writer :api_key, :base_key, :table_name
7
+
8
+ def base_key
9
+ @base_key || (superclass < Table ? superclass.base_key : nil)
10
+ end
11
+
12
+ def table_name
13
+ @table_name || (superclass < Table ? superclass.table_name : nil)
14
+ end
8
15
 
9
16
  def client
10
17
  @@clients ||= {}
@@ -16,12 +23,12 @@ module Norairrecord
16
23
  end
17
24
 
18
25
  def has_many(method_name, options)
19
- define_method(method_name.to_sym) do
26
+ define_method(method_name.to_sym) do |where: nil, sort: nil|
20
27
  # Get association ids in reverse order, because Airtable's UI and API
21
28
  # sort associations in opposite directions. We want to match the UI.
22
29
  ids = (self[options.fetch(:column)] || []).reverse
23
30
  table = Kernel.const_get(options.fetch(:class))
24
- return table.find_many(ids) unless options[:single]
31
+ return table.find_many(ids, sort:, where:) unless options[:single]
25
32
 
26
33
  (id = ids.first) ? table.find(id) : nil
27
34
  end
@@ -37,23 +44,30 @@ module Norairrecord
37
44
 
38
45
  alias has_one belongs_to
39
46
 
47
+ def has_subtypes(column, mapping, strict: false)
48
+ @subtype_column = column
49
+ @subtype_mapping = mapping
50
+ @subtype_strict = strict
51
+ end
52
+
40
53
  def find(id)
41
54
  response = client.connection.get("v0/#{base_key}/#{client.escape(table_name)}/#{id}")
42
55
  parsed_response = client.parse(response.body)
43
56
 
44
57
  if response.success?
45
- self.new(parsed_response["fields"], id: id, created_at: parsed_response["createdTime"])
58
+ self.new_with_subtype(parsed_response["fields"], id: id, created_at: parsed_response["createdTime"])
46
59
  else
47
60
  client.handle_error(response.status, parsed_response)
48
61
  end
49
62
  end
50
63
 
51
- def find_many(ids)
64
+ def find_many(ids, where: nil, sort: nil)
52
65
  return [] if ids.empty?
53
66
 
54
67
  or_args = ids.map { |id| "RECORD_ID() = '#{id}'"}.join(',')
55
68
  formula = "OR(#{or_args})"
56
- records(filter: formula).sort_by { |record| or_args.index(record.id) }
69
+ formula = "AND(#{formula},#{where})" if where
70
+ records(filter: formula, sort:).sort_by { |record| or_args.index(record.id) }
57
71
  end
58
72
 
59
73
  def update(id, update_hash = {}, options = {})
@@ -78,6 +92,18 @@ module Norairrecord
78
92
  new(fields).tap { |record| record.save(options) }
79
93
  end
80
94
 
95
+ def new_with_subtype(fields, id:, created_at:)
96
+ if @subtype_column
97
+ clazz = self
98
+ st = @subtype_mapping[fields[@subtype_column]]
99
+ raise Norairrecord::UnknownTypeError, "#{fields[@subtype_column]}?????" if @subtype_strict && st.nil?
100
+ clazz = Kernel.const_get(st) if st
101
+ clazz.new(fields, id:, created_at:)
102
+ else
103
+ self.new(fields, id: id, created_at: created_at)
104
+ end
105
+ end
106
+
81
107
  def records(filter: nil, sort: nil, view: nil, offset: nil, paginate: true, fields: nil, max_records: nil, page_size: nil)
82
108
  options = {}
83
109
  options[:filterByFormula] = filter if filter
@@ -100,8 +126,8 @@ module Norairrecord
100
126
 
101
127
  if response.success?
102
128
  records = parsed_response["records"]
103
- records = records.map { |record|
104
- self.new(record["fields"], id: record["id"], created_at: record["createdTime"])
129
+ records.map! { |record|
130
+ self.new_with_subtype(record["fields"], id: record["id"], created_at: record["createdTime"])
105
131
  }
106
132
 
107
133
  if paginate && parsed_response["offset"]
@@ -122,9 +148,24 @@ module Norairrecord
122
148
  client.handle_error(response.status, parsed_response)
123
149
  end
124
150
  end
151
+
152
+ def first(options = {})
153
+ records(**options.merge(max_records: 1)).first
154
+ end
155
+
156
+ def first_where(filter, options = {})
157
+ first(options.merge(filter:))
158
+ end
159
+
160
+ def where(filter, options = {})
161
+ records(**options.merge(filter:))
162
+ end
163
+
125
164
  alias all records
126
165
  end
127
166
 
167
+
168
+
128
169
  attr_reader :fields, :id, :created_at, :updated_keys
129
170
 
130
171
  # This is an awkward definition for Ruby 3 to remain backwards compatible.
@@ -1,3 +1,3 @@
1
1
  module Norairrecord
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/norairrecord.rb CHANGED
@@ -11,6 +11,8 @@ module Norairrecord
11
11
  attr_accessor :api_key, :throttle, :base_url, :user_agent
12
12
 
13
13
  Error = Class.new(StandardError)
14
+ UnknownTypeError = Class.new(Error)
15
+ RecordNotFoundError = Class.new(Error)
14
16
 
15
17
  def throttle?
16
18
  return true if @throttle.nil?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: norairrecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nora
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-06 00:00:00.000000000 Z
11
+ date: 2025-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -113,7 +113,7 @@ homepage: https://github.com/24c02/norairrecord
113
113
  licenses:
114
114
  - MIT
115
115
  metadata: {}
116
- post_install_message:
116
+ post_install_message:
117
117
  rdoc_options: []
118
118
  require_paths:
119
119
  - lib
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  version: '0'
130
130
  requirements: []
131
131
  rubygems_version: 3.5.16
132
- signing_key:
132
+ signing_key:
133
133
  specification_version: 4
134
134
  summary: Airtable client
135
135
  test_files: []