usps-imis-api 1.0.0.pre.rc.5 → 1.0.0.pre.rc.7

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile.lock +36 -30
  4. data/Readme.md +140 -32
  5. data/lib/usps/imis/api.rb +27 -39
  6. data/lib/usps/imis/business_object.rb +87 -47
  7. data/lib/usps/imis/config.rb +14 -3
  8. data/lib/usps/imis/data.rb +72 -0
  9. data/lib/usps/imis/error.rb +51 -0
  10. data/lib/usps/imis/errors/api_error.rb +11 -0
  11. data/lib/usps/imis/errors/config_error.rb +11 -0
  12. data/lib/usps/imis/errors/locked_id_error.rb +15 -0
  13. data/lib/usps/imis/errors/mapper_error.rb +29 -0
  14. data/lib/usps/imis/errors/not_found_error.rb +11 -0
  15. data/lib/usps/imis/errors/panel_unimplemented_error.rb +34 -0
  16. data/lib/usps/imis/{error → errors}/response_error.rb +5 -8
  17. data/lib/usps/imis/errors/unexpected_property_type_error.rb +31 -0
  18. data/lib/usps/imis/mapper.rb +28 -20
  19. data/lib/usps/imis/mocks/business_object.rb +47 -0
  20. data/lib/usps/imis/mocks.rb +11 -0
  21. data/lib/usps/imis/panels/base_panel.rb +144 -0
  22. data/lib/usps/imis/{panel → panels}/education.rb +2 -2
  23. data/lib/usps/imis/{panel → panels}/vsc.rb +2 -2
  24. data/lib/usps/imis/panels.rb +25 -0
  25. data/lib/usps/imis/properties.rb +50 -0
  26. data/lib/usps/imis/query.rb +94 -0
  27. data/lib/usps/imis/requests.rb +27 -3
  28. data/lib/usps/imis/version.rb +1 -1
  29. data/lib/usps/imis.rb +15 -15
  30. data/spec/lib/usps/imis/api_spec.rb +26 -13
  31. data/spec/lib/usps/imis/business_object_spec.rb +44 -20
  32. data/spec/lib/usps/imis/config_spec.rb +2 -2
  33. data/spec/lib/usps/imis/data_spec.rb +66 -0
  34. data/spec/lib/usps/imis/{error/api_error_spec.rb → error_spec.rb} +1 -1
  35. data/spec/lib/usps/imis/{error → errors}/response_error_spec.rb +4 -4
  36. data/spec/lib/usps/imis/mapper_spec.rb +27 -3
  37. data/spec/lib/usps/imis/mocks/business_object_spec.rb +65 -0
  38. data/spec/lib/usps/imis/panels/base_panel_spec.rb +33 -0
  39. data/spec/lib/usps/imis/panels/education_spec.rb +70 -0
  40. data/spec/lib/usps/imis/{panel → panels}/vsc_spec.rb +6 -7
  41. data/spec/lib/usps/imis/properties_spec.rb +19 -0
  42. data/spec/spec_helper.rb +2 -0
  43. data/usps-imis-api.gemspec +1 -1
  44. metadata +28 -16
  45. data/lib/ext/hash.rb +0 -10
  46. data/lib/usps/imis/error/api_error.rb +0 -44
  47. data/lib/usps/imis/error/mapper_error.rb +0 -11
  48. data/lib/usps/imis/panel/base_panel.rb +0 -101
  49. data/lib/usps/imis/panel/panel_properties.rb +0 -52
  50. data/spec/lib/usps/imis/panel/base_panel_spec.rb +0 -32
  51. data/spec/lib/usps/imis/panel/education_spec.rb +0 -55
  52. data/spec/lib/usps/imis/panel/panel_properties_spec.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c0abf715f3c6019016bc6b8bdf867ce9d83234ba093c6150a4885997e9156f4
4
- data.tar.gz: bcd2727e5f74b7a8681ae8ea1c510539fc24b62085719b439a01a331989a59c9
3
+ metadata.gz: 8f1db4f3db3cd61b247cdef10a3f017ea8efbe7fbf3f98ed17d80fbaa56947b7
4
+ data.tar.gz: 837b83d46d08d02bd6a27442adde8f5fbf7857050df06efd602583205745dad6
5
5
  SHA512:
6
- metadata.gz: e0f98c36fd93ae643a949980e590fbb327cc40e1947ef5c8b530156e3961073082702dd38916fa27da4db10c642316c3c3db9f97d82d2f7c616032917cea22f1
7
- data.tar.gz: 3195ecb427cff0fea1c6d1cef0d877507c2bb7baaa3a3db6d669f281048a03de7fb06c15e3b5f1cfe36625ece30d2e890f05202accfaf56148de40dda0fdce4d
6
+ metadata.gz: 0e49fd7400ee2fb1932a4321944122f9e6864a89901e56b2c2a5f4b342f4426a039bd789e89159bdec3579725af733213ff5b4b9694d6a59d04901506a71f8b6
7
+ data.tar.gz: d9dff6ac6e989b163d009a6fe247fed18645a078f5cc00468f5b4011e47050336d561d4bef30f552c51fe531fec3c567086da4d2d5cd6a54bfe0d4a39246d2ff
data/.rubocop.yml CHANGED
@@ -1,4 +1,4 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rspec
3
3
 
4
4
  AllCops:
data/Gemfile.lock CHANGED
@@ -1,37 +1,36 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- usps-imis-api (1.0.0.pre.rc.5)
4
+ usps-imis-api (1.0.0.pre.rc.7)
5
5
  activesupport (~> 8.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (8.0.3)
10
+ activesupport (8.1.0)
11
11
  base64
12
- benchmark (>= 0.3)
13
12
  bigdecimal
14
13
  concurrent-ruby (~> 1.0, >= 1.3.1)
15
14
  connection_pool (>= 2.2.5)
16
15
  drb
17
16
  i18n (>= 1.6, < 2)
17
+ json
18
18
  logger (>= 1.4.2)
19
19
  minitest (>= 5.1)
20
20
  securerandom (>= 0.3)
21
21
  tzinfo (~> 2.0, >= 2.0.5)
22
22
  uri (>= 0.13.1)
23
- ast (2.4.2)
23
+ ast (2.4.3)
24
24
  base64 (0.3.0)
25
- benchmark (0.4.1)
26
25
  bigdecimal (3.3.1)
27
26
  concurrent-ruby (1.3.5)
28
27
  connection_pool (2.5.4)
29
28
  date (3.4.1)
30
- diff-lcs (1.5.1)
29
+ diff-lcs (1.6.2)
31
30
  docile (1.4.1)
32
- dotenv (3.1.4)
31
+ dotenv (3.1.8)
33
32
  drb (2.2.3)
34
- erb (5.0.3)
33
+ erb (5.1.1)
35
34
  i18n (1.14.7)
36
35
  concurrent-ruby (~> 1.0)
37
36
  io-console (0.8.1)
@@ -39,70 +38,77 @@ GEM
39
38
  pp (>= 0.6.0)
40
39
  rdoc (>= 4.0.0)
41
40
  reline (>= 0.4.2)
42
- json (2.7.2)
43
- language_server-protocol (3.17.0.3)
41
+ json (2.15.1)
42
+ language_server-protocol (3.17.0.5)
43
+ lint_roller (1.1.0)
44
44
  logger (1.7.0)
45
45
  minitest (5.26.0)
46
- parallel (1.26.3)
47
- parser (3.3.5.0)
46
+ parallel (1.27.0)
47
+ parser (3.3.9.0)
48
48
  ast (~> 2.4.1)
49
49
  racc
50
50
  pp (0.6.3)
51
51
  prettyprint
52
52
  prettyprint (0.2.0)
53
+ prism (1.6.0)
53
54
  psych (5.2.6)
54
55
  date
55
56
  stringio
56
57
  racc (1.8.1)
57
58
  rainbow (3.1.1)
58
- rake (13.2.1)
59
+ rake (13.3.0)
59
60
  rdoc (6.15.0)
60
61
  erb
61
62
  psych (>= 4.0.0)
62
63
  tsort
63
- regexp_parser (2.9.2)
64
+ regexp_parser (2.11.3)
64
65
  reline (0.6.2)
65
66
  io-console (~> 0.5)
66
- rspec (3.13.0)
67
+ rspec (3.13.2)
67
68
  rspec-core (~> 3.13.0)
68
69
  rspec-expectations (~> 3.13.0)
69
70
  rspec-mocks (~> 3.13.0)
70
- rspec-core (3.13.1)
71
+ rspec-core (3.13.6)
71
72
  rspec-support (~> 3.13.0)
72
- rspec-expectations (3.13.3)
73
+ rspec-expectations (3.13.5)
73
74
  diff-lcs (>= 1.2.0, < 2.0)
74
75
  rspec-support (~> 3.13.0)
75
- rspec-mocks (3.13.2)
76
+ rspec-mocks (3.13.6)
76
77
  diff-lcs (>= 1.2.0, < 2.0)
77
78
  rspec-support (~> 3.13.0)
78
- rspec-support (3.13.1)
79
- rubocop (1.66.1)
79
+ rspec-support (3.13.6)
80
+ rubocop (1.81.6)
80
81
  json (~> 2.3)
81
- language_server-protocol (>= 3.17.0)
82
+ language_server-protocol (~> 3.17.0.2)
83
+ lint_roller (~> 1.1.0)
82
84
  parallel (~> 1.10)
83
85
  parser (>= 3.3.0.2)
84
86
  rainbow (>= 2.2.2, < 4.0)
85
- regexp_parser (>= 2.4, < 3.0)
86
- rubocop-ast (>= 1.32.2, < 2.0)
87
+ regexp_parser (>= 2.9.3, < 3.0)
88
+ rubocop-ast (>= 1.47.1, < 2.0)
87
89
  ruby-progressbar (~> 1.7)
88
- unicode-display_width (>= 2.4.0, < 3.0)
89
- rubocop-ast (1.32.3)
90
- parser (>= 3.3.1.0)
91
- rubocop-rspec (3.1.0)
92
- rubocop (~> 1.61)
90
+ unicode-display_width (>= 2.4.0, < 4.0)
91
+ rubocop-ast (1.47.1)
92
+ parser (>= 3.3.7.2)
93
+ prism (~> 1.4)
94
+ rubocop-rspec (3.7.0)
95
+ lint_roller (~> 1.1)
96
+ rubocop (~> 1.72, >= 1.72.1)
93
97
  ruby-progressbar (1.13.0)
94
98
  securerandom (0.4.1)
95
99
  simplecov (0.22.0)
96
100
  docile (~> 1.1)
97
101
  simplecov-html (~> 0.11)
98
102
  simplecov_json_formatter (~> 0.1)
99
- simplecov-html (0.13.1)
103
+ simplecov-html (0.13.2)
100
104
  simplecov_json_formatter (0.1.4)
101
105
  stringio (3.1.7)
102
106
  tsort (0.2.0)
103
107
  tzinfo (2.0.6)
104
108
  concurrent-ruby (~> 1.0)
105
- unicode-display_width (2.6.0)
109
+ unicode-display_width (3.2.0)
110
+ unicode-emoji (~> 4.1)
111
+ unicode-emoji (4.1.0)
106
112
  uri (1.0.4)
107
113
 
108
114
  PLATFORMS
data/Readme.md CHANGED
@@ -13,7 +13,7 @@ gem install usps-imis-api
13
13
  or add this line to your Gemfile:
14
14
 
15
15
  ```ruby
16
- gem 'usps-imis-api', '~> 0.6.3'
16
+ gem 'usps-imis-api'
17
17
  ```
18
18
 
19
19
  ## Setup
@@ -32,6 +32,10 @@ Usps::Imis.configure do |config|
32
32
  config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
33
33
  config.username = ENV['IMIS_USERNAME']
34
34
  config.password = ENV['IMIS_PASSWORD']
35
+
36
+ # These options will use these defaults
37
+ config.logger = ActiveSupport::TaggedLogging.new(Logger.new($stdout))
38
+ config.logger.level = :info
35
39
  end
36
40
  ```
37
41
 
@@ -77,33 +81,72 @@ You can also manually set the current ID, if you already have it for a given mem
77
81
  api.imis_id = imis_id
78
82
  ```
79
83
 
80
- ### GET
84
+ #### Without an iMIS ID
85
+
86
+ Running requests without an iMIS ID set will result in query results returned from the API.
87
+
88
+ ### Business Object and Panel Actions
89
+
90
+ Business Objects and Panels support the following actions.
91
+
92
+ Panels require passing in the ordinal identifier as an argument, except for `POST`.
93
+
94
+ #### GET
81
95
 
82
96
  To fetch member data, run e.g.:
83
97
 
84
98
  ```ruby
85
- api.imis_id = 31092
99
+ data = api.on('ABC_ASC_Individual_Demog').get
100
+ ```
101
+
102
+ You can also pass in specific field names to filter the returned member data, e.g.:
103
+
104
+ ```ruby
105
+ data = api.on('ABC_ASC_Individual_Demog').get('TotMMS', 'MMS_Updated')
106
+ ```
86
107
 
108
+ The response from `get` behaves like a Hash, but directly accesses property values by name.
109
+ If you need to access the rest of the underlying data, use the `raw` method:
110
+
111
+ ```ruby
87
112
  data = api.on('ABC_ASC_Individual_Demog').get
113
+ data['TotMMS']
114
+ data.raw['EntityTypeName']
88
115
  ```
89
116
 
90
- ### GET Field
117
+ Alias: `read`
118
+
119
+ #### GET Field
91
120
 
92
121
  To fetch a specific field from member data, run e.g.:
93
122
 
94
123
  ```ruby
95
- api.imis_id = 31092
96
-
97
124
  tot_mms = api.on('ABC_ASC_Individual_Demog').get_field('TotMMS')
98
125
  ```
99
126
 
100
- ### PUT Fields
127
+ You can also access fields directly on the Business Object or Panel like a Hash:
101
128
 
102
- To update member data, run e.g.:
129
+ ```ruby
130
+ tot_mms = api.on('ABC_ASC_Individual_Demog')['TotMMS']
131
+ ```
132
+
133
+ Alias: `fetch`
134
+
135
+ #### GET Fields
136
+
137
+ To fetch multiple specific fields from member data, run e.g.:
103
138
 
104
139
  ```ruby
105
- api.imis_id = 31092
140
+ data = api.on('ABC_ASC_Individual_Demog').get_fields('TotMMS', 'MMS_Updated')
141
+ ```
142
+
143
+ Alias: `fetch_all`
106
144
 
145
+ #### PUT Fields
146
+
147
+ To update member data, run e.g.:
148
+
149
+ ```ruby
107
150
  data = { 'MMS_Updated' => Time.now.strftime('%Y-%m-%dT%H:%M:%S'), 'TotMMS' => new_total }
108
151
  update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
109
152
  ```
@@ -111,19 +154,23 @@ update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
111
154
  This method fetches the current data structure, and filters it down to just what you want to
112
155
  update, to reduce the likelihood of update collisions or type validation failures.
113
156
 
114
- ### PUT
157
+ Alias: `patch`
158
+
159
+ #### PUT
115
160
 
116
161
  To update member data, run e.g.:
117
162
 
118
163
  ```ruby
119
- api.imis_id = 31092
120
-
121
164
  update = api.on('ABC_ASC_Individual_Demog').put(complete_imis_object)
122
165
  ```
123
166
 
124
- This method requires a complete iMIS data structure.
167
+ This method requires a complete iMIS data structure. However, any properties not included will be
168
+ left unmodified (meaning this also effectively handles `PATCH`, though iMIS does not accept that
169
+ HTTP verb).
125
170
 
126
- ### POST
171
+ Alias: `update`
172
+
173
+ #### POST
127
174
 
128
175
  To create new member data, run e.g.:
129
176
 
@@ -133,17 +180,17 @@ created = api.on('ABC_ASC_Individual_Demog').post(complete_imis_object)
133
180
 
134
181
  This method requires a complete iMIS data structure.
135
182
 
136
- ### DELETE
183
+ Alias: `create`
184
+
185
+ #### DELETE
137
186
 
138
187
  To remove member data, run e.g.:
139
188
 
140
189
  ```ruby
141
- api.imis_id = 31092
142
-
143
190
  api.on('ABC_ASC_Individual_Demog').delete
144
191
  ```
145
192
 
146
- This returns a blank string on success.
193
+ Alias: `destroy`
147
194
 
148
195
  ### QUERY
149
196
 
@@ -152,19 +199,38 @@ Run an IQA Query
152
199
  `query_params` is a hash of shape: `{ param_name => param_value }`
153
200
 
154
201
  ```ruby
155
- api.query(query_name, query_params)
202
+ query = api.query(query_name, query_params)
203
+
204
+ query.each do |item|
205
+ # Download all pages of the query, then iterate on the results
206
+ end
207
+
208
+ query.find_each do |item|
209
+ # Iterate one page at a time, fetching new pages automatically
210
+ end
156
211
  ```
157
212
 
158
213
  ### Field Mapper
159
214
 
160
215
  For fields that have already been mapped between the ITCom database and iMIS, you can use the
161
- Mapper class to further simplify the update interface:
216
+ Mapper class to further simplify the fetch / update interfaces:
217
+
218
+ ```ruby
219
+ mm = api.mapper.fetch(:mm)
220
+ mm = api.mapper[:mm]
221
+ ```
162
222
 
163
223
  ```ruby
164
224
  api.mapper.update(mm: 15)
165
225
  ```
166
226
 
167
- For simplicity, you can also call `update` on the `Api` class directly:
227
+ For simplicity, you can also call `fetch` (or simply use Hash access syntax) and `update` on the
228
+ `Api` class directly:
229
+
230
+ ```ruby
231
+ api.fetch(:mm)
232
+ api[:mm]
233
+ ```
168
234
 
169
235
  ```ruby
170
236
  api.update(mm: 15)
@@ -180,24 +246,40 @@ For supported panels (usually, business objects with composite identity keys), y
180
246
  with them in the same general way:
181
247
 
182
248
  ```ruby
183
- vsc = Usps::Imis::Panel::Vsc.new
184
-
185
- vsc.api.imis_id = 6374
249
+ vsc = Usps::Imis::Panels::Vsc.new(imis_id: 6374)
186
250
 
187
251
  vsc.get(1417)
188
252
 
253
+ # All of these options are identical
254
+ #
255
+ vsc.get(1417, 'Quantity').first
256
+ vsc.get(1417)['Quantity']
257
+ vsc[1417, 'Quantity']
258
+ vsc.get(1417).raw['Properties']['$values'].find { it['Name'] == 'Quantity' }['Value']['$value']
259
+ vsc.get_field(1417, 'Quantity')
260
+
189
261
  created = vsc.create(certificate: 'E136924', year: 2024, count: 42)
190
- ordinal = created['Properties']['$values'][1]['Value']['$value']
262
+
263
+ # Get the Ordinal identifier from the response
264
+ #
265
+ # All of these options are identical
266
+ #
267
+ ordinal = created.ordinal
268
+ ordinal = created['Ordinal']
269
+ ordinal = created.raw['Properties']['$values'].find { it['Name'] == 'Ordinal' }['Value']['$value']
270
+ ordinal = created.raw['Identity']['IdentityElements']['$values'][1].to_i # Value is duplicated here
191
271
 
192
272
  vsc.update(certificate: 'E136924', year: 2024, count: 43, ordinal: ordinal)
193
273
 
274
+ vsc.put_fields(ordinal, 'Quantity' => 44)
275
+
194
276
  vsc.destroy(ordinal)
195
277
  ```
196
278
 
197
279
  If you already have an iMIS ID to work with, you can pass that in immediately:
198
280
 
199
281
  ```ruby
200
- vsc = Usps::Imis::Panel::Vsc.new(imis_id: imis_id)
282
+ vsc = Usps::Imis::Panels::Vsc.new(imis_id: imis_id)
201
283
  ```
202
284
 
203
285
  Panels are also accessible directly from the API object:
@@ -216,9 +298,9 @@ previous value.
216
298
  api.with(31092) do
217
299
  # These requests are identical:
218
300
 
219
- on('ABC_ASC_Individual_Demog') { put('TotMMS' => 15) }
301
+ on('ABC_ASC_Individual_Demog') { put_fields('TotMMS' => 15) }
220
302
 
221
- on('ABC_ASC_Individual_Demog').put('TotMMS' => 15)
303
+ on('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15)
222
304
 
223
305
  mapper.update(mm: 15)
224
306
 
@@ -237,21 +319,47 @@ api.with(31092) do
237
319
  # These requests are identical:
238
320
 
239
321
  on('ABC_ASC_Individual_Demog') do
240
- get['Properties']['$values'].find { |hash| hash['Name'] == 'TotMMS' }['Value']['$value']
241
- end
322
+ get.raw['Properties']['$values'].find { it['Name'] == 'TotMMS' }['Value']['$value']
323
+
324
+ get['TotMMS']
242
325
 
243
- on('ABC_ASC_Individual_Demog') { get_field('TotMMS') }
326
+ get_field('TotMMS')
327
+
328
+ get_fields('TotMMS').first
329
+ end
244
330
 
245
331
  on('ABC_ASC_Individual_Demog').get_field('TotMMS')
332
+
333
+ on('ABC_ASC_Individual_Demog')['TotMMS']
246
334
  end
247
335
 
248
336
  # This request fetches the same data, but leaves the iMIS ID selected
249
337
  api.with(31092).on('ABC_ASC_Individual_Demog').get_field('TotMMS')
250
338
  ```
251
339
 
340
+ ### Data Methods
341
+
342
+ Data responses from the API can be handled as a standard Hash using the `raw` method.
343
+
344
+ If you need to access all of the property values, you can use the `properties` method.
345
+ By default, this will exclude the `ID` and `Ordinal` properties; they can be included with
346
+ `properties(include_ids: true)`.
347
+
348
+ ## Test Data Mocking
349
+
350
+ You can use the provided Business Object Mock to generate stub data for rspec:
351
+
352
+ ```ruby
353
+ allow(api).to(
354
+ receive(:on).with('ABC_ASC_Individual_Demog').and_return(
355
+ Usps::Imis::Mocks::BusinessObject.new(TotMMS: 2)
356
+ )
357
+ )
358
+ ```
359
+
252
360
  ## Exception Handling
253
361
 
254
- All internal exceptions inherit from `Usps::Imis::ApiError`.
362
+ All internal exceptions inherit from `Usps::Imis::Error`.
255
363
 
256
364
  ## Automated Testing and Linting
257
365
 
data/lib/usps/imis/api.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'requests'
4
+ require_relative 'business_object'
5
+ require_relative 'mapper'
6
+ require_relative 'query'
7
+
3
8
  module Usps
4
9
  module Imis
5
10
  # The core API wrapper
@@ -11,10 +16,6 @@ module Usps
11
16
  #
12
17
  AUTHENTICATION_PATH = 'Token'
13
18
 
14
- # Endpoint for IQA query requests
15
- #
16
- QUERY_PATH = 'api/Query'
17
-
18
19
  # API bearer token
19
20
  #
20
21
  attr_reader :token
@@ -48,9 +49,9 @@ module Usps
48
49
  # @param id [Integer, String] iMIS ID to select for future requests
49
50
  #
50
51
  def imis_id=(id)
51
- raise Error::ApiError, 'Cannot change iMIS ID while locked' if lock_imis_id
52
+ raise Errors::LockedIdError if lock_imis_id
52
53
 
53
- @imis_id = id&.to_i&.to_s
54
+ @imis_id = id&.to_i
54
55
  end
55
56
 
56
57
  # Convert a member's certificate number into an iMIS ID number
@@ -60,13 +61,12 @@ module Usps
60
61
  # @return [String] Corresponding iMIS ID
61
62
  #
62
63
  def imis_id_for(certificate)
63
- raise Error::ApiError, 'Cannot change iMIS ID while locked' if lock_imis_id
64
+ raise Errors::LockedIdError if lock_imis_id
64
65
 
65
66
  begin
66
- result = query(Imis.configuration.imis_id_query_name, { certificate: })
67
- @imis_id = result['Items']['$values'][0]['ID']
67
+ self.imis_id = query(Imis.configuration.imis_id_query_name, { certificate: }).first['ID'].to_i
68
68
  rescue StandardError
69
- raise Error::ApiError, 'Member not found'
69
+ raise Errors::NotFoundError, 'Member not found'
70
70
  end
71
71
  end
72
72
 
@@ -97,7 +97,7 @@ module Usps
97
97
  end
98
98
  end
99
99
 
100
- # Run an IQA Query
100
+ # Build an IQA Query interface
101
101
  #
102
102
  # @param query_name [String] Full path of the query in IQA, e.g. +$/_ABC/Fiander/iMIS_ID+
103
103
  # @query_params [Hash] Conforms to pattern +{ param_name => param_value }+
@@ -105,21 +105,7 @@ module Usps
105
105
  # @return [Hash] Response data from the API
106
106
  #
107
107
  def query(query_name, query_params = {})
108
- query_params[:QueryName] = query_name
109
- path = "#{QUERY_PATH}?#{query_params.to_query}"
110
- uri = URI(File.join(Imis.configuration.hostname, path))
111
- request = Net::HTTP::Get.new(uri)
112
- result = submit(uri, authorize(request))
113
- JSON.parse(result.body)
114
- end
115
-
116
- # An instance of +BusinessObject+, using this instance as its parent +Api+
117
- #
118
- # @param business_object_name [String] Name of the business object
119
- # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
120
- #
121
- def business_object(business_object_name, url_id: nil)
122
- BusinessObject.new(self, business_object_name, url_id:)
108
+ Query.new(self, query_name, query_params)
123
109
  end
124
110
 
125
111
  # Run requests as DSL, with specific +BusinessObject+ only maintained for this scope
@@ -127,15 +113,13 @@ module Usps
127
113
  # If no block is given, this returns the specified +BusinessObject+.
128
114
  #
129
115
  # @param business_object_name [String] Name of the business object
130
- # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
116
+ # @param ordinal [Integer] Ordinal to build override ID param of the URL (e.g. used for Panels)
131
117
  #
132
- def on(business_object_name, url_id: nil, &)
133
- object = business_object(business_object_name, url_id:)
118
+ def on(business_object_name, ordinal: nil, &)
119
+ object = BusinessObject.new(self, business_object_name, ordinal:)
134
120
  return object unless block_given?
135
121
 
136
- result = nil
137
- object.tap { |obj| result = obj.instance_eval(&) }
138
- result
122
+ object.instance_eval(&)
139
123
  end
140
124
 
141
125
  # An instance of +Mapper+, using this instance as its parent +Api+
@@ -144,20 +128,20 @@ module Usps
144
128
  @mapper ||= Mapper.new(self)
145
129
  end
146
130
 
131
+ # Convenience alias for reading mapped fields
132
+ #
133
+ def fetch(field_key) = mapper.fetch(field_key)
134
+ alias [] fetch
135
+
147
136
  # Convenience alias for updating mapped fields
148
137
  #
149
- def update(data)
150
- mapper.update(data)
151
- end
138
+ def update(data) = mapper.update(data)
152
139
 
153
140
  # Convenience accessor for available Panel objects, each using this instance as its parent
154
141
  # +Api+
155
142
  #
156
143
  def panels
157
- @panels ||= Struct.new(:vsc, :education).new(
158
- Panel::Vsc.new(self),
159
- Panel::Education.new(self)
160
- )
144
+ @panels ||= Panels.all(self)
161
145
  end
162
146
 
163
147
  # Ruby 3.5 instance variable filter
@@ -166,9 +150,13 @@ module Usps
166
150
 
167
151
  private
168
152
 
153
+ def logger = Imis.logger('Api')
154
+
169
155
  # Authenticate to the iMIS API, and store the access token and expiration time
170
156
  #
171
157
  def authenticate
158
+ logger.debug 'Authenticating with iMIS'
159
+
172
160
  uri = URI(File.join(Imis.configuration.hostname, AUTHENTICATION_PATH))
173
161
  req = Net::HTTP::Post.new(uri)
174
162
  authentication_data = {