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

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 +4 -1
  3. data/Gemfile.lock +36 -30
  4. data/Readme.md +149 -33
  5. data/lib/usps/imis/api.rb +32 -39
  6. data/lib/usps/imis/business_object.rb +97 -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 +53 -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 +51 -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 +154 -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 +20 -15
  30. data/spec/lib/usps/imis/api_spec.rb +26 -13
  31. data/spec/lib/usps/imis/business_object_spec.rb +70 -33
  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: 07d2a2b0c67186b6c3e2a8c8f3dd1694341b399d2ae95bbffcc8db568a3cdca2
4
+ data.tar.gz: b6315dd73aff2c70aabf352dfa2b25b905cf11404894a37814a77d7c2c349ef7
5
5
  SHA512:
6
- metadata.gz: e0f98c36fd93ae643a949980e590fbb327cc40e1947ef5c8b530156e3961073082702dd38916fa27da4db10c642316c3c3db9f97d82d2f7c616032917cea22f1
7
- data.tar.gz: 3195ecb427cff0fea1c6d1cef0d877507c2bb7baaa3a3db6d669f281048a03de7fb06c15e3b5f1cfe36625ece30d2e890f05202accfaf56148de40dda0fdce4d
6
+ metadata.gz: 7e4bd50ec6137ffed9a3035d135fbe62d5ec66846e6b11e4b4c3f2db38cb5f20d09f9ca9b41553291a649859dd8aade13318eed85e0d92645e60734545a3f5c2
7
+ data.tar.gz: 3bd9f6eb055de1fb5aecbd1c40166f7db9a781cecf5fb93fbe8086636c379c8081d5a97271c5a6f27354439cb7410bf1f13cd7a1c39cb6ccb36ede0ccecf5630
data/.rubocop.yml CHANGED
@@ -1,4 +1,4 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rspec
3
3
 
4
4
  AllCops:
@@ -84,3 +84,6 @@ Security/JSONLoad:
84
84
  Enabled: true
85
85
  Security/YAMLLoad:
86
86
  Enabled: true
87
+
88
+ RSpec/NestedGroups:
89
+ Max: 4
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.8)
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.:
86
103
 
104
+ ```ruby
105
+ data = api.on('ABC_ASC_Individual_Demog').get('TotMMS', 'MMS_Updated')
106
+ ```
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`
144
+
145
+ #### PUT Fields
106
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,22 +199,42 @@ 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)
237
+ api[:mm] = 15
171
238
  ```
172
239
 
173
240
  If there is no known mapping for the requested field, the Mapper will give up, but will provide
@@ -180,24 +247,41 @@ For supported panels (usually, business objects with composite identity keys), y
180
247
  with them in the same general way:
181
248
 
182
249
  ```ruby
183
- vsc = Usps::Imis::Panel::Vsc.new
184
-
185
- vsc.api.imis_id = 6374
250
+ vsc = Usps::Imis::Panels::Vsc.new(imis_id: 6374)
186
251
 
187
252
  vsc.get(1417)
188
253
 
254
+ # All of these options are identical
255
+ #
256
+ vsc.get(1417, 'Quantity').first
257
+ vsc.get(1417)['Quantity']
258
+ vsc[1417, 'Quantity']
259
+ vsc.get(1417).raw['Properties']['$values'].find { it['Name'] == 'Quantity' }['Value']['$value']
260
+ vsc.get_field(1417, 'Quantity')
261
+
189
262
  created = vsc.create(certificate: 'E136924', year: 2024, count: 42)
190
- ordinal = created['Properties']['$values'][1]['Value']['$value']
263
+
264
+ # Get the Ordinal identifier from the response
265
+ #
266
+ # All of these options are identical
267
+ #
268
+ ordinal = created.ordinal
269
+ ordinal = created['Ordinal']
270
+ ordinal = created.raw['Properties']['$values'].find { it['Name'] == 'Ordinal' }['Value']['$value']
271
+ ordinal = created.raw['Identity']['IdentityElements']['$values'][1].to_i # Value is duplicated here
191
272
 
192
273
  vsc.update(certificate: 'E136924', year: 2024, count: 43, ordinal: ordinal)
193
274
 
275
+ vsc.put_fields(ordinal, 'Quantity' => 44)
276
+ vsc['Quantity'] = 44
277
+
194
278
  vsc.destroy(ordinal)
195
279
  ```
196
280
 
197
281
  If you already have an iMIS ID to work with, you can pass that in immediately:
198
282
 
199
283
  ```ruby
200
- vsc = Usps::Imis::Panel::Vsc.new(imis_id: imis_id)
284
+ vsc = Usps::Imis::Panels::Vsc.new(imis_id: imis_id)
201
285
  ```
202
286
 
203
287
  Panels are also accessible directly from the API object:
@@ -216,14 +300,19 @@ previous value.
216
300
  api.with(31092) do
217
301
  # These requests are identical:
218
302
 
219
- on('ABC_ASC_Individual_Demog') { put('TotMMS' => 15) }
303
+ on('ABC_ASC_Individual_Demog') { put_fields('TotMMS' => 15) }
220
304
 
221
- on('ABC_ASC_Individual_Demog').put('TotMMS' => 15)
305
+ on('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15)
222
306
 
223
307
  mapper.update(mm: 15)
224
308
 
225
309
  update(mm: 15)
310
+
311
+ mapper[:mm] = 15
226
312
  end
313
+
314
+ # This request fetches the same data, but leaves the iMIS ID selected
315
+ api.with(31092)[:mm] = 15
227
316
  ```
228
317
 
229
318
  ```ruby
@@ -237,21 +326,48 @@ api.with(31092) do
237
326
  # These requests are identical:
238
327
 
239
328
  on('ABC_ASC_Individual_Demog') do
240
- get['Properties']['$values'].find { |hash| hash['Name'] == 'TotMMS' }['Value']['$value']
241
- end
329
+ get.raw['Properties']['$values'].find { it['Name'] == 'TotMMS' }['Value']['$value']
330
+
331
+ get['TotMMS']
242
332
 
243
- on('ABC_ASC_Individual_Demog') { get_field('TotMMS') }
333
+ get_field('TotMMS')
334
+
335
+ get_fields('TotMMS').first
336
+ end
244
337
 
245
338
  on('ABC_ASC_Individual_Demog').get_field('TotMMS')
339
+
340
+ on('ABC_ASC_Individual_Demog')['TotMMS']
246
341
  end
247
342
 
248
- # This request fetches the same data, but leaves the iMIS ID selected
343
+ # These requests fetch the same data, but leave the iMIS ID selected
249
344
  api.with(31092).on('ABC_ASC_Individual_Demog').get_field('TotMMS')
345
+ api.with(31092).on('ABC_ASC_Individual_Demog')['TotMMS']
346
+ ```
347
+
348
+ ### Data Methods
349
+
350
+ Data responses from the API can be handled as a standard Hash using the `raw` method.
351
+
352
+ If you need to access all of the property values, you can use the `properties` method.
353
+ By default, this will exclude the `ID` and `Ordinal` properties; they can be included with
354
+ `properties(include_ids: true)`.
355
+
356
+ ## Test Data Mocking
357
+
358
+ You can use the provided Business Object Mock to generate stub data for rspec:
359
+
360
+ ```ruby
361
+ allow(api).to(
362
+ receive(:on).with('ABC_ASC_Individual_Demog').and_return(
363
+ Usps::Imis::Mocks::BusinessObject.new(TotMMS: 2)
364
+ )
365
+ )
250
366
  ```
251
367
 
252
368
  ## Exception Handling
253
369
 
254
- All internal exceptions inherit from `Usps::Imis::ApiError`.
370
+ All internal exceptions inherit from `Usps::Imis::Error`.
255
371
 
256
372
  ## Automated Testing and Linting
257
373
 
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,25 @@ 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 put_field(field_key, value) = update(field_key => value)
139
+ alias []= put_field
140
+
141
+ # Convenience alias for updating mapped fields
142
+ #
143
+ def update(data) = mapper.update(data)
152
144
 
153
145
  # Convenience accessor for available Panel objects, each using this instance as its parent
154
146
  # +Api+
155
147
  #
156
148
  def panels
157
- @panels ||= Struct.new(:vsc, :education).new(
158
- Panel::Vsc.new(self),
159
- Panel::Education.new(self)
160
- )
149
+ @panels ||= Panels.all(self)
161
150
  end
162
151
 
163
152
  # Ruby 3.5 instance variable filter
@@ -166,9 +155,13 @@ module Usps
166
155
 
167
156
  private
168
157
 
158
+ def logger = Imis.logger('Api')
159
+
169
160
  # Authenticate to the iMIS API, and store the access token and expiration time
170
161
  #
171
162
  def authenticate
163
+ logger.debug 'Authenticating with iMIS'
164
+
172
165
  uri = URI(File.join(Imis.configuration.hostname, AUTHENTICATION_PATH))
173
166
  req = Net::HTTP::Post.new(uri)
174
167
  authentication_data = {