usps-imis-api 1.0.0.pre.rc.4 → 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 -3
  3. data/Gemfile.lock +61 -27
  4. data/Readme.md +168 -32
  5. data/lib/usps/imis/api.rb +27 -39
  6. data/lib/usps/imis/business_object.rb +93 -40
  7. data/lib/usps/imis/config.rb +27 -10
  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/panels/education.rb +33 -0
  23. data/lib/usps/imis/panels/vsc.rb +32 -0
  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 +29 -3
  28. data/lib/usps/imis/version.rb +1 -1
  29. data/lib/usps/imis.rb +16 -13
  30. data/spec/lib/usps/imis/api_spec.rb +26 -13
  31. data/spec/lib/usps/imis/business_object_spec.rb +47 -13
  32. data/spec/lib/usps/imis/config_spec.rb +30 -4
  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 +3 -0
  43. data/usps-imis-api.gemspec +3 -1
  44. metadata +43 -15
  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 -65
  49. data/lib/usps/imis/panel/education.rb +0 -113
  50. data/lib/usps/imis/panel/vsc.rb +0 -111
  51. data/spec/lib/usps/imis/panel/base_panel_spec.rb +0 -32
  52. data/spec/lib/usps/imis/panel/education_spec.rb +0 -55
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb187f7e4a8497da991378eec6a9893c5c2a5e637b808a9225ab72ebd5ee385d
4
- data.tar.gz: 0d09118f25fce28b94ddbc1257e9670fe3454e34df094885bc9cf35cbb1a86a0
3
+ metadata.gz: 8f1db4f3db3cd61b247cdef10a3f017ea8efbe7fbf3f98ed17d80fbaa56947b7
4
+ data.tar.gz: 837b83d46d08d02bd6a27442adde8f5fbf7857050df06efd602583205745dad6
5
5
  SHA512:
6
- metadata.gz: f50698bc529a6d66d3c69e201d696cdfde1f24c7bf414ab1f655ebdf3b21e1bd512b8e00476ffa62e3ded1a50d72e82586b8f11c002291c5a8038ad5f4cdd2d6
7
- data.tar.gz: '09e2849e666c312d0a974efc14b5133ac6e2f0ef181a6715f83ae420b6ba48f6a1566cc749a1dcbce614989c9e5b9fa343ba4dd6a56f1ecd47a9a14b7572617a'
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:
@@ -32,8 +32,6 @@ Layout/SpaceInsideHashLiteralBraces:
32
32
  EnforcedStyleForEmptyBraces: no_space
33
33
  Layout/SpaceInsideArrayLiteralBrackets:
34
34
  EnforcedStyle: no_space
35
- Layout/LineLength:
36
- Max: 100
37
35
 
38
36
  Lint/UnusedMethodArgument:
39
37
  Enabled: true
data/Gemfile.lock CHANGED
@@ -1,81 +1,115 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- usps-imis-api (1.0.0.pre.rc.4)
4
+ usps-imis-api (1.0.0.pre.rc.7)
5
+ activesupport (~> 8.0)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- ast (2.4.2)
10
+ activesupport (8.1.0)
11
+ base64
12
+ bigdecimal
13
+ concurrent-ruby (~> 1.0, >= 1.3.1)
14
+ connection_pool (>= 2.2.5)
15
+ drb
16
+ i18n (>= 1.6, < 2)
17
+ json
18
+ logger (>= 1.4.2)
19
+ minitest (>= 5.1)
20
+ securerandom (>= 0.3)
21
+ tzinfo (~> 2.0, >= 2.0.5)
22
+ uri (>= 0.13.1)
23
+ ast (2.4.3)
24
+ base64 (0.3.0)
25
+ bigdecimal (3.3.1)
26
+ concurrent-ruby (1.3.5)
27
+ connection_pool (2.5.4)
10
28
  date (3.4.1)
11
- diff-lcs (1.5.1)
29
+ diff-lcs (1.6.2)
12
30
  docile (1.4.1)
13
- dotenv (3.1.4)
14
- erb (5.0.3)
31
+ dotenv (3.1.8)
32
+ drb (2.2.3)
33
+ erb (5.1.1)
34
+ i18n (1.14.7)
35
+ concurrent-ruby (~> 1.0)
15
36
  io-console (0.8.1)
16
37
  irb (1.15.2)
17
38
  pp (>= 0.6.0)
18
39
  rdoc (>= 4.0.0)
19
40
  reline (>= 0.4.2)
20
- json (2.7.2)
21
- language_server-protocol (3.17.0.3)
22
- parallel (1.26.3)
23
- parser (3.3.5.0)
41
+ json (2.15.1)
42
+ language_server-protocol (3.17.0.5)
43
+ lint_roller (1.1.0)
44
+ logger (1.7.0)
45
+ minitest (5.26.0)
46
+ parallel (1.27.0)
47
+ parser (3.3.9.0)
24
48
  ast (~> 2.4.1)
25
49
  racc
26
50
  pp (0.6.3)
27
51
  prettyprint
28
52
  prettyprint (0.2.0)
53
+ prism (1.6.0)
29
54
  psych (5.2.6)
30
55
  date
31
56
  stringio
32
57
  racc (1.8.1)
33
58
  rainbow (3.1.1)
34
- rake (13.2.1)
59
+ rake (13.3.0)
35
60
  rdoc (6.15.0)
36
61
  erb
37
62
  psych (>= 4.0.0)
38
63
  tsort
39
- regexp_parser (2.9.2)
64
+ regexp_parser (2.11.3)
40
65
  reline (0.6.2)
41
66
  io-console (~> 0.5)
42
- rspec (3.13.0)
67
+ rspec (3.13.2)
43
68
  rspec-core (~> 3.13.0)
44
69
  rspec-expectations (~> 3.13.0)
45
70
  rspec-mocks (~> 3.13.0)
46
- rspec-core (3.13.1)
71
+ rspec-core (3.13.6)
47
72
  rspec-support (~> 3.13.0)
48
- rspec-expectations (3.13.3)
73
+ rspec-expectations (3.13.5)
49
74
  diff-lcs (>= 1.2.0, < 2.0)
50
75
  rspec-support (~> 3.13.0)
51
- rspec-mocks (3.13.2)
76
+ rspec-mocks (3.13.6)
52
77
  diff-lcs (>= 1.2.0, < 2.0)
53
78
  rspec-support (~> 3.13.0)
54
- rspec-support (3.13.1)
55
- rubocop (1.66.1)
79
+ rspec-support (3.13.6)
80
+ rubocop (1.81.6)
56
81
  json (~> 2.3)
57
- language_server-protocol (>= 3.17.0)
82
+ language_server-protocol (~> 3.17.0.2)
83
+ lint_roller (~> 1.1.0)
58
84
  parallel (~> 1.10)
59
85
  parser (>= 3.3.0.2)
60
86
  rainbow (>= 2.2.2, < 4.0)
61
- regexp_parser (>= 2.4, < 3.0)
62
- rubocop-ast (>= 1.32.2, < 2.0)
87
+ regexp_parser (>= 2.9.3, < 3.0)
88
+ rubocop-ast (>= 1.47.1, < 2.0)
63
89
  ruby-progressbar (~> 1.7)
64
- unicode-display_width (>= 2.4.0, < 3.0)
65
- rubocop-ast (1.32.3)
66
- parser (>= 3.3.1.0)
67
- rubocop-rspec (3.1.0)
68
- 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)
69
97
  ruby-progressbar (1.13.0)
98
+ securerandom (0.4.1)
70
99
  simplecov (0.22.0)
71
100
  docile (~> 1.1)
72
101
  simplecov-html (~> 0.11)
73
102
  simplecov_json_formatter (~> 0.1)
74
- simplecov-html (0.13.1)
103
+ simplecov-html (0.13.2)
75
104
  simplecov_json_formatter (0.1.4)
76
105
  stringio (3.1.7)
77
106
  tsort (0.2.0)
78
- unicode-display_width (2.6.0)
107
+ tzinfo (2.0.6)
108
+ concurrent-ruby (~> 1.0)
109
+ unicode-display_width (3.2.0)
110
+ unicode-emoji (~> 4.1)
111
+ unicode-emoji (4.1.0)
112
+ uri (1.0.4)
79
113
 
80
114
  PLATFORMS
81
115
  arm64-darwin-23
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.0'
16
+ gem 'usps-imis-api'
17
17
  ```
18
18
 
19
19
  ## Setup
@@ -25,11 +25,17 @@ require 'dotenv/load' # Optionally load environment variables from `.env` file
25
25
  require 'usps/imis'
26
26
 
27
27
  Usps::Imis.configure do |config|
28
- config.environment = :development # Rails.env
29
- config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
28
+ # This will default to `Rails.env` if available.
29
+ config.environment = :development
30
30
 
31
+ # These options will default to the listed `ENV` variable if available.
32
+ config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
31
33
  config.username = ENV['IMIS_USERNAME']
32
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
33
39
  end
34
40
  ```
35
41
 
@@ -75,23 +81,72 @@ You can also manually set the current ID, if you already have it for a given mem
75
81
  api.imis_id = imis_id
76
82
  ```
77
83
 
78
- ### 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
79
95
 
80
96
  To fetch member data, run e.g.:
81
97
 
82
98
  ```ruby
83
- api.imis_id = 31092
99
+ data = api.on('ABC_ASC_Individual_Demog').get
100
+ ```
84
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
+ ```
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
85
112
  data = api.on('ABC_ASC_Individual_Demog').get
113
+ data['TotMMS']
114
+ data.raw['EntityTypeName']
86
115
  ```
87
116
 
88
- ### PUT Fields
117
+ Alias: `read`
89
118
 
90
- To update member data, run e.g.:
119
+ #### GET Field
120
+
121
+ To fetch a specific field from member data, run e.g.:
122
+
123
+ ```ruby
124
+ tot_mms = api.on('ABC_ASC_Individual_Demog').get_field('TotMMS')
125
+ ```
126
+
127
+ You can also access fields directly on the Business Object or Panel like a Hash:
128
+
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.:
91
138
 
92
139
  ```ruby
93
- api.imis_id = 31092
140
+ data = api.on('ABC_ASC_Individual_Demog').get_fields('TotMMS', 'MMS_Updated')
141
+ ```
94
142
 
143
+ Alias: `fetch_all`
144
+
145
+ #### PUT Fields
146
+
147
+ To update member data, run e.g.:
148
+
149
+ ```ruby
95
150
  data = { 'MMS_Updated' => Time.now.strftime('%Y-%m-%dT%H:%M:%S'), 'TotMMS' => new_total }
96
151
  update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
97
152
  ```
@@ -99,19 +154,23 @@ update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
99
154
  This method fetches the current data structure, and filters it down to just what you want to
100
155
  update, to reduce the likelihood of update collisions or type validation failures.
101
156
 
102
- ### PUT
157
+ Alias: `patch`
158
+
159
+ #### PUT
103
160
 
104
161
  To update member data, run e.g.:
105
162
 
106
163
  ```ruby
107
- api.imis_id = 31092
108
-
109
164
  update = api.on('ABC_ASC_Individual_Demog').put(complete_imis_object)
110
165
  ```
111
166
 
112
- 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).
170
+
171
+ Alias: `update`
113
172
 
114
- ### POST
173
+ #### POST
115
174
 
116
175
  To create new member data, run e.g.:
117
176
 
@@ -121,17 +180,17 @@ created = api.on('ABC_ASC_Individual_Demog').post(complete_imis_object)
121
180
 
122
181
  This method requires a complete iMIS data structure.
123
182
 
124
- ### DELETE
183
+ Alias: `create`
184
+
185
+ #### DELETE
125
186
 
126
187
  To remove member data, run e.g.:
127
188
 
128
189
  ```ruby
129
- api.imis_id = 31092
130
-
131
190
  api.on('ABC_ASC_Individual_Demog').delete
132
191
  ```
133
192
 
134
- This returns a blank string on success.
193
+ Alias: `destroy`
135
194
 
136
195
  ### QUERY
137
196
 
@@ -140,19 +199,38 @@ Run an IQA Query
140
199
  `query_params` is a hash of shape: `{ param_name => param_value }`
141
200
 
142
201
  ```ruby
143
- 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
144
211
  ```
145
212
 
146
213
  ### Field Mapper
147
214
 
148
215
  For fields that have already been mapped between the ITCom database and iMIS, you can use the
149
- 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
+ ```
150
222
 
151
223
  ```ruby
152
224
  api.mapper.update(mm: 15)
153
225
  ```
154
226
 
155
- 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
+ ```
156
234
 
157
235
  ```ruby
158
236
  api.update(mm: 15)
@@ -168,24 +246,40 @@ For supported panels (usually, business objects with composite identity keys), y
168
246
  with them in the same general way:
169
247
 
170
248
  ```ruby
171
- vsc = Usps::Imis::Panel::Vsc.new
172
-
173
- vsc.api.imis_id = 6374
249
+ vsc = Usps::Imis::Panels::Vsc.new(imis_id: 6374)
174
250
 
175
251
  vsc.get(1417)
176
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
+
177
261
  created = vsc.create(certificate: 'E136924', year: 2024, count: 42)
178
- 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
179
271
 
180
272
  vsc.update(certificate: 'E136924', year: 2024, count: 43, ordinal: ordinal)
181
273
 
274
+ vsc.put_fields(ordinal, 'Quantity' => 44)
275
+
182
276
  vsc.destroy(ordinal)
183
277
  ```
184
278
 
185
279
  If you already have an iMIS ID to work with, you can pass that in immediately:
186
280
 
187
281
  ```ruby
188
- vsc = Usps::Imis::Panel::Vsc.new(imis_id: imis_id)
282
+ vsc = Usps::Imis::Panels::Vsc.new(imis_id: imis_id)
189
283
  ```
190
284
 
191
285
  Panels are also accessible directly from the API object:
@@ -202,11 +296,11 @@ previous value.
202
296
 
203
297
  ```ruby
204
298
  api.with(31092) do
205
- # These four requests are identical:
299
+ # These requests are identical:
206
300
 
207
- on('ABC_ASC_Individual_Demog') { put('TotMMS' => 15) }
301
+ on('ABC_ASC_Individual_Demog') { put_fields('TotMMS' => 15) }
208
302
 
209
- on('ABC_ASC_Individual_Demog').put('TotMMS' => 15)
303
+ on('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15)
210
304
 
211
305
  mapper.update(mm: 15)
212
306
 
@@ -220,12 +314,52 @@ api.with(6374) do
220
314
  end
221
315
  ```
222
316
 
223
- ## Exception Handling
317
+ ```ruby
318
+ api.with(31092) do
319
+ # These requests are identical:
224
320
 
225
- Exception and error response handling will be added later.
321
+ on('ABC_ASC_Individual_Demog') do
322
+ get.raw['Properties']['$values'].find { it['Name'] == 'TotMMS' }['Value']['$value']
226
323
 
227
- To print exception information to STDERR when raising, set the environment
228
- variable `IMIS_ERROR_LOG_TO_STDERR=true`.
324
+ get['TotMMS']
325
+
326
+ get_field('TotMMS')
327
+
328
+ get_fields('TotMMS').first
329
+ end
330
+
331
+ on('ABC_ASC_Individual_Demog').get_field('TotMMS')
332
+
333
+ on('ABC_ASC_Individual_Demog')['TotMMS']
334
+ end
335
+
336
+ # This request fetches the same data, but leaves the iMIS ID selected
337
+ api.with(31092).on('ABC_ASC_Individual_Demog').get_field('TotMMS')
338
+ ```
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
+
360
+ ## Exception Handling
361
+
362
+ All internal exceptions inherit from `Usps::Imis::Error`.
229
363
 
230
364
  ## Automated Testing and Linting
231
365
 
@@ -241,6 +375,8 @@ Linting is available by running:
241
375
  bundle exec rubocop
242
376
  ```
243
377
 
378
+ 100% branch coverage is enforced on the test suite.
379
+
244
380
  ### GitHub Actions
245
381
 
246
382
  Testing and linting are automatically run on every push.
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 = {