usps-imis-api 1.0.0.pre.rc.9 → 1.0.0.pre.rc.10
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 +4 -4
- data/Readme.md +1 -323
- data/lib/usps/imis/api.rb +11 -6
- data/lib/usps/imis/business_object.rb +11 -10
- data/lib/usps/imis/config.rb +6 -2
- data/lib/usps/imis/error.rb +1 -0
- data/lib/usps/imis/errors/missing_id_error.rb +15 -0
- data/lib/usps/imis/panels/base_panel.rb +2 -1
- data/lib/usps/imis/properties.rb +14 -14
- data/lib/usps/imis/query.rb +44 -23
- data/lib/usps/imis/requests.rb +5 -1
- data/lib/usps/imis/version.rb +1 -1
- data/spec/support/usps/vcr/config.rb +47 -0
- data/spec/support/usps/vcr/filters.rb +89 -0
- data/spec/support/usps/vcr.rb +8 -0
- metadata +6 -70
- data/.github/workflows/main.yml +0 -57
- data/.gitignore +0 -5
- data/.rspec +0 -2
- data/.rubocop.yml +0 -89
- data/.ruby-version +0 -1
- data/.simplecov +0 -8
- data/Gemfile +0 -16
- data/Gemfile.lock +0 -147
- data/Rakefile +0 -12
- data/bin/console +0 -21
- data/bin/setup +0 -8
- data/spec/fixtures/vcr_cassettes/.keep +0 -0
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_authorize/automatically_refreshes_an_expired_token.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_imis_id_for/gets_the_iMIS_ID.yml +0 -131
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_imis_id_for/with_a_query_error/wraps_errors.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_inspect/does_not_show_the_token_instance_variable.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_inspect/is_configured_to_exclude_the_token_instance_variable.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_on/chains_with_on_to_a_single_block.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_on/nests_on_and_with.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_on/returns_a_BusinessObject_without_a_block.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_on/sends_an_update_from_put.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_put/sends_an_update.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_put/when_receiving_a_response_error/wraps_the_error.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_query/collects_all_query_results.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_with/blocks_calling_imis_id_.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_with/blocks_calling_imis_id_for.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_with/sends_an_update_from_put.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_with/sends_an_update_from_update.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/_with/uses_a_panel_correctly.yml +0 -145
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Api/stores_the_initial_imis_id.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/_put_field/submits_the_correct_update_request.yml +0 -627
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/with_stubbed_data/_filter_fields/formats_fields_correctly.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/with_stubbed_data/_get/delegation_to_get_fields/delegates_to_get_fields.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/with_stubbed_data/_get/returns_multiple_values.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/with_stubbed_data/_get_field/returns_a_string_value.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/with_stubbed_data/_get_field/returns_an_integer_value.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_BusinessObject/with_stubbed_data/_get_fields/returns_multiple_values.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/_fetch/fetches_a_mapped_field.yml +0 -158
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/_fetch/raises_for_unmapped_updates.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/_fetch/supports_Hash_access_syntax.yml +0 -158
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/_fetch/supports_Hash_access_syntax_on_the_Api_directly.yml +0 -158
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/_update/raises_for_unmapped_updates.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/_update/sends_a_mapped_update.yml +0 -256
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Mapper/initialize_with_imis_id/stores_the_initial_imis_id.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_BasePanel/requires_business_object_to_be_defined.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_BasePanel/requires_payload_data_to_be_defined.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Education/api_example/_get/loads_a_specific_object.yml +0 -146
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Education/api_example/_get/returns_specific_fields.yml +0 -146
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Education/api_example/_get_field/returns_a_specific_field.yml +0 -146
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Education/api_example/_get_fields/returns_specific_fields.yml +0 -146
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Education/api_example/interacts_with_records_correctly.yml +0 -495
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Education/initialization_with_ID/can_initialize_with_an_iMIS_ID.yml +0 -67
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Vsc/_get/loads_a_specific_object.yml +0 -145
- data/spec/fixtures/vcr_cassettes/Usps_Imis_Panels_Vsc/handles_new_records_correctly.yml +0 -319
- data/spec/lib/usps/imis/api_spec.rb +0 -161
- data/spec/lib/usps/imis/business_object_spec.rb +0 -87
- data/spec/lib/usps/imis/config_spec.rb +0 -59
- data/spec/lib/usps/imis/data_spec.rb +0 -75
- data/spec/lib/usps/imis/error_spec.rb +0 -17
- data/spec/lib/usps/imis/errors/response_error_spec.rb +0 -107
- data/spec/lib/usps/imis/mapper_spec.rb +0 -55
- data/spec/lib/usps/imis/mocks/business_object_spec.rb +0 -65
- data/spec/lib/usps/imis/panels/base_panel_spec.rb +0 -33
- data/spec/lib/usps/imis/panels/education_spec.rb +0 -70
- data/spec/lib/usps/imis/panels/vsc_spec.rb +0 -37
- data/spec/lib/usps/imis/properties_spec.rb +0 -19
- data/spec/lib/usps/imis_spec.rb +0 -11
- data/spec/spec_helper.rb +0 -78
- data/usps-imis-api.gemspec +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebdfba18c4fc6164bd3e5fbf4fe366d2ca81dbe37420bca3035d705b2309319f
|
|
4
|
+
data.tar.gz: 0244bef394a72715ddf7a6e2219f37bcbc93f025ae87c70fe3928fcd45201b0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd525cf6c237a7d13addbbd1faeed94b0c8a2cdecc7392d116447e19b91ba69a6d21064c517ce8b1885152eb55d918e129f681238e7b7912d20b0b408a09e441
|
|
7
|
+
data.tar.gz: 7c56a167b87a406f23e89d1c4bc0fb964e19e05b89c36eb180b2deae3f157c47f25c69a8c95cc7eff0f32fb975872edcf4b8cab28ca11516b7b96be430e63e32
|
data/Readme.md
CHANGED
|
@@ -39,331 +39,9 @@ Usps::Imis.configure do |config|
|
|
|
39
39
|
end
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
When using `bin/console`, this configuration will be run by default.
|
|
43
|
-
|
|
44
|
-
Instantiate the API object:
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
api = Usps::Imis::Api.new
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
If you already have an iMIS ID to work with, you can pass that in immediately:
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
api = Usps::Imis::Api.new(imis_id: imis_id)
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Authentication
|
|
57
|
-
|
|
58
|
-
If a token is not available, this will automatically fetch one when needed. As long as that token
|
|
59
|
-
should still be valid, it will reuse the same token. If the token should expire, this will
|
|
60
|
-
automatically request a new token.
|
|
61
|
-
|
|
62
42
|
## Usage
|
|
63
43
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
To act on member data, you need to have the iMIS ID. If you already have access to that from the
|
|
67
|
-
database, you can skip this step.
|
|
68
|
-
|
|
69
|
-
To convert a member's certificate number into their iMIS ID, run the following method:
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
api.imis_id_for(certificate)
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
This will both return the ID, and store it for use with other requests. If you need to change which
|
|
76
|
-
member you are working with, just run this method again with the new certificate number.
|
|
77
|
-
|
|
78
|
-
You can also manually set the current ID, if you already have it for a given member
|
|
79
|
-
|
|
80
|
-
```ruby
|
|
81
|
-
api.imis_id = imis_id
|
|
82
|
-
```
|
|
83
|
-
|
|
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
|
|
95
|
-
|
|
96
|
-
To fetch member data, run e.g.:
|
|
97
|
-
|
|
98
|
-
```ruby
|
|
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
|
-
```
|
|
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
|
|
112
|
-
data = api.on('ABC_ASC_Individual_Demog').get
|
|
113
|
-
data['TotMMS']
|
|
114
|
-
data.raw['EntityTypeName']
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Alias: `read`
|
|
118
|
-
|
|
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.:
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
data = api.on('ABC_ASC_Individual_Demog').get_fields('TotMMS', 'MMS_Updated')
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Alias: `fetch_all`
|
|
144
|
-
|
|
145
|
-
#### PUT Fields
|
|
146
|
-
|
|
147
|
-
To update member data, run e.g.:
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
data = { 'MMS_Updated' => Time.now.strftime('%Y-%m-%dT%H:%M:%S'), 'TotMMS' => new_total }
|
|
151
|
-
update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
This method fetches the current data structure, and filters it down to just what you want to
|
|
155
|
-
update, to reduce the likelihood of update collisions or type validation failures.
|
|
156
|
-
|
|
157
|
-
Alias: `patch`
|
|
158
|
-
|
|
159
|
-
#### PUT
|
|
160
|
-
|
|
161
|
-
To update member data, run e.g.:
|
|
162
|
-
|
|
163
|
-
```ruby
|
|
164
|
-
update = api.on('ABC_ASC_Individual_Demog').put(complete_imis_object)
|
|
165
|
-
```
|
|
166
|
-
|
|
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`
|
|
172
|
-
|
|
173
|
-
#### POST
|
|
174
|
-
|
|
175
|
-
To create new member data, run e.g.:
|
|
176
|
-
|
|
177
|
-
```ruby
|
|
178
|
-
created = api.on('ABC_ASC_Individual_Demog').post(complete_imis_object)
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
This method requires a complete iMIS data structure.
|
|
182
|
-
|
|
183
|
-
Alias: `create`
|
|
184
|
-
|
|
185
|
-
#### DELETE
|
|
186
|
-
|
|
187
|
-
To remove member data, run e.g.:
|
|
188
|
-
|
|
189
|
-
```ruby
|
|
190
|
-
api.on('ABC_ASC_Individual_Demog').delete
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Alias: `destroy`
|
|
194
|
-
|
|
195
|
-
### QUERY
|
|
196
|
-
|
|
197
|
-
Run an IQA Query
|
|
198
|
-
|
|
199
|
-
`query_params` is a hash of shape: `{ param_name => param_value }`
|
|
200
|
-
|
|
201
|
-
```ruby
|
|
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
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Field Mapper
|
|
214
|
-
|
|
215
|
-
For fields that have already been mapped between the ITCom database and iMIS, you can use the
|
|
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
|
-
```
|
|
222
|
-
|
|
223
|
-
```ruby
|
|
224
|
-
api.mapper.update(mm: 15)
|
|
225
|
-
```
|
|
226
|
-
|
|
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
|
-
```
|
|
234
|
-
|
|
235
|
-
```ruby
|
|
236
|
-
api.update(mm: 15)
|
|
237
|
-
api[:mm] = 15
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
If there is no known mapping for the requested field, the Mapper will give up, but will provide
|
|
241
|
-
you with the standard API call syntax, and will suggest you inform ITCom leadership of the new
|
|
242
|
-
mapping you need.
|
|
243
|
-
|
|
244
|
-
### Panels
|
|
245
|
-
|
|
246
|
-
For supported panels (usually, business objects with composite identity keys), you can interact
|
|
247
|
-
with them in the same general way:
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
vsc = Usps::Imis::Panels::Vsc.new(imis_id: 6374)
|
|
251
|
-
|
|
252
|
-
vsc.get(1417)
|
|
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
|
-
|
|
262
|
-
created = vsc.create(certificate: 'E136924', year: 2024, count: 42)
|
|
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
|
|
272
|
-
|
|
273
|
-
vsc.update(certificate: 'E136924', year: 2024, count: 43, ordinal: ordinal)
|
|
274
|
-
|
|
275
|
-
vsc.put_fields(ordinal, 'Quantity' => 44)
|
|
276
|
-
vsc['Quantity'] = 44
|
|
277
|
-
|
|
278
|
-
vsc.destroy(ordinal)
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
If you already have an iMIS ID to work with, you can pass that in immediately:
|
|
282
|
-
|
|
283
|
-
```ruby
|
|
284
|
-
vsc = Usps::Imis::Panels::Vsc.new(imis_id: imis_id)
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
Panels are also accessible directly from the API object:
|
|
288
|
-
|
|
289
|
-
```ruby
|
|
290
|
-
api.panels.vsc.get(1417)
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### DSL Mode
|
|
294
|
-
|
|
295
|
-
Instead of manually setting the current iMIS ID, then running individual queries, you can instead
|
|
296
|
-
run queries in DSL mode. This specifies the iMIS ID for the scope of the block, then reverts to the
|
|
297
|
-
previous value.
|
|
298
|
-
|
|
299
|
-
```ruby
|
|
300
|
-
api.with(31092) do
|
|
301
|
-
# These requests are identical:
|
|
302
|
-
|
|
303
|
-
on('ABC_ASC_Individual_Demog') { put_fields('TotMMS' => 15) }
|
|
304
|
-
|
|
305
|
-
on('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15)
|
|
306
|
-
|
|
307
|
-
mapper.update(mm: 15)
|
|
308
|
-
|
|
309
|
-
update(mm: 15)
|
|
310
|
-
|
|
311
|
-
mapper[:mm] = 15
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# This request fetches the same data, but leaves the iMIS ID selected
|
|
315
|
-
api.with(31092)[:mm] = 15
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
```ruby
|
|
319
|
-
api.with(6374) do
|
|
320
|
-
panels.vsc.get(1417)
|
|
321
|
-
end
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
```ruby
|
|
325
|
-
api.with(31092) do
|
|
326
|
-
# These requests are identical:
|
|
327
|
-
|
|
328
|
-
on('ABC_ASC_Individual_Demog') do
|
|
329
|
-
get.raw['Properties']['$values'].find { it['Name'] == 'TotMMS' }['Value']['$value']
|
|
330
|
-
|
|
331
|
-
get['TotMMS']
|
|
332
|
-
|
|
333
|
-
get_field('TotMMS')
|
|
334
|
-
|
|
335
|
-
get_fields('TotMMS').first
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
on('ABC_ASC_Individual_Demog').get_field('TotMMS')
|
|
339
|
-
|
|
340
|
-
on('ABC_ASC_Individual_Demog')['TotMMS']
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
# These requests fetch the same data, but leave the iMIS ID selected
|
|
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
|
-
)
|
|
366
|
-
```
|
|
44
|
+
For more details and examples, refer to the [Wiki](https://github.com/unitedstatespowersquadrons/imis-api-ruby/wiki).
|
|
367
45
|
|
|
368
46
|
## Exception Handling
|
|
369
47
|
|
data/lib/usps/imis/api.rb
CHANGED
|
@@ -39,7 +39,6 @@ module Usps
|
|
|
39
39
|
# @param imis_id [Integer, String] iMIS ID to select immediately on initialization
|
|
40
40
|
#
|
|
41
41
|
def initialize(imis_id: nil)
|
|
42
|
-
authenticate
|
|
43
42
|
self.imis_id = imis_id if imis_id
|
|
44
43
|
end
|
|
45
44
|
|
|
@@ -62,8 +61,12 @@ module Usps
|
|
|
62
61
|
def imis_id_for(certificate)
|
|
63
62
|
raise Errors::LockedIdError if lock_imis_id
|
|
64
63
|
|
|
64
|
+
logger.debug "Fetching iMIS ID for #{certificate}"
|
|
65
|
+
|
|
65
66
|
begin
|
|
66
|
-
|
|
67
|
+
result = query(Imis.configuration.imis_id_query_name, { certificate: }).tap { logger.debug it }
|
|
68
|
+
page = result.page.tap { logger.debug it }
|
|
69
|
+
self.imis_id = page.first['ID'].to_i
|
|
67
70
|
rescue StandardError
|
|
68
71
|
raise Errors::NotFoundError, 'Member not found'
|
|
69
72
|
end
|
|
@@ -96,12 +99,14 @@ module Usps
|
|
|
96
99
|
end
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
# Build
|
|
102
|
+
# Build a Query interface
|
|
103
|
+
#
|
|
104
|
+
# Works with IQA queries and Business Objects
|
|
100
105
|
#
|
|
101
|
-
# @param query_name [String] Full path of the query
|
|
106
|
+
# @param query_name [String] Full path of the query, e.g. +$/_ABC/Fiander/iMIS_ID+
|
|
102
107
|
# @query_params [Hash] Conforms to pattern +{ param_name => param_value }+
|
|
103
108
|
#
|
|
104
|
-
# @return [
|
|
109
|
+
# @return [Usps::Imis::Query] Query wrapper
|
|
105
110
|
#
|
|
106
111
|
def query(query_name, query_params = {}) = Query.new(self, query_name, **query_params)
|
|
107
112
|
|
|
@@ -169,7 +174,7 @@ module Usps
|
|
|
169
174
|
json = JSON.parse(result.body)
|
|
170
175
|
|
|
171
176
|
@token = json['access_token']
|
|
172
|
-
@token_expiration = Time.
|
|
177
|
+
@token_expiration = Time.now - json['expires_in'] - 60
|
|
173
178
|
end
|
|
174
179
|
|
|
175
180
|
# URI for the authentication endpoint
|
|
@@ -33,6 +33,12 @@ module Usps
|
|
|
33
33
|
@ordinal = ordinal
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Run a query on the entire business object
|
|
37
|
+
#
|
|
38
|
+
# @return [Usps::Imis::Query] Query wrapper
|
|
39
|
+
#
|
|
40
|
+
def query = api.query(business_object_name)
|
|
41
|
+
|
|
36
42
|
# Get a business object for the current member
|
|
37
43
|
#
|
|
38
44
|
# If +fields+ is provided, will return only those field values
|
|
@@ -118,9 +124,6 @@ module Usps
|
|
|
118
124
|
|
|
119
125
|
private
|
|
120
126
|
|
|
121
|
-
def token = api.token
|
|
122
|
-
def token_expiration = api.token_expiration
|
|
123
|
-
|
|
124
127
|
def logger = Imis.logger('BusinessObject')
|
|
125
128
|
|
|
126
129
|
# Construct a business object API endpoint address
|
|
@@ -154,13 +157,7 @@ module Usps
|
|
|
154
157
|
# Skip unmodified fields
|
|
155
158
|
next unless fields.keys.include?(value['Name'])
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
new_value = fields[value['Name']]
|
|
159
|
-
if new_value.is_a?(String)
|
|
160
|
-
value['Value'] = new_value
|
|
161
|
-
else
|
|
162
|
-
value['Value']['$value'] = new_value
|
|
163
|
-
end
|
|
160
|
+
value['Value'] = Properties.wrap(fields[value['Name']])
|
|
164
161
|
|
|
165
162
|
# Add the completed field with the updated value
|
|
166
163
|
updated['Properties']['$values'] << value
|
|
@@ -173,6 +170,8 @@ module Usps
|
|
|
173
170
|
# Useful for stubbing data in tests
|
|
174
171
|
#
|
|
175
172
|
def raw_object
|
|
173
|
+
raise Errors::MissingIdError if api.imis_id.nil?
|
|
174
|
+
|
|
176
175
|
response = submit(uri, authorize(http_get))
|
|
177
176
|
result = Data.from_json(response.body)
|
|
178
177
|
JSON.pretty_generate(result).split("\n").each { logger.debug " -> #{it}" }
|
|
@@ -182,6 +181,8 @@ module Usps
|
|
|
182
181
|
# Upload an object to the API
|
|
183
182
|
#
|
|
184
183
|
def put_object(request, body)
|
|
184
|
+
raise Errors::MissingIdError if api.imis_id.nil?
|
|
185
|
+
|
|
185
186
|
request.body = JSON.dump(body)
|
|
186
187
|
response = submit(uri, authorize(request))
|
|
187
188
|
result = Data.from_json(response.body)
|
data/lib/usps/imis/config.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Usps
|
|
|
12
12
|
attr_reader :environment, :logger, :logger_level
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
|
-
@environment = defined?(Rails) ? Rails.env : ActiveSupport::StringInquirer.new('development')
|
|
15
|
+
@environment = defined?(::Rails) ? ::Rails.env : ActiveSupport::StringInquirer.new('development')
|
|
16
16
|
@imis_id_query_name = ENV.fetch('IMIS_ID_QUERY_NAME', nil)
|
|
17
17
|
@username = ENV.fetch('IMIS_USERNAME', nil)
|
|
18
18
|
@password = ENV.fetch('IMIS_PASSWORD', nil)
|
|
@@ -31,6 +31,10 @@ module Usps
|
|
|
31
31
|
@logger = ActiveSupport::TaggedLogging.new(logger)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def silence!
|
|
35
|
+
self.logger = Logger.new(nil)
|
|
36
|
+
end
|
|
37
|
+
|
|
34
38
|
# Environment-specific API endpoint hostname
|
|
35
39
|
#
|
|
36
40
|
# @return The API hostname for the current environment
|
|
@@ -44,7 +48,7 @@ module Usps
|
|
|
44
48
|
|
|
45
49
|
# Ruby 3.5 instance variable filter
|
|
46
50
|
#
|
|
47
|
-
def instance_variables_to_inspect = %i[@
|
|
51
|
+
def instance_variables_to_inspect = instance_variables - %i[@password @logger]
|
|
48
52
|
|
|
49
53
|
# Parameters to filter out of logging
|
|
50
54
|
#
|
data/lib/usps/imis/error.rb
CHANGED
|
@@ -47,6 +47,7 @@ require_relative 'errors/api_error'
|
|
|
47
47
|
require_relative 'errors/config_error'
|
|
48
48
|
require_relative 'errors/locked_id_error'
|
|
49
49
|
require_relative 'errors/mapper_error'
|
|
50
|
+
require_relative 'errors/missing_id_error'
|
|
50
51
|
require_relative 'errors/not_found_error'
|
|
51
52
|
require_relative 'errors/response_error'
|
|
52
53
|
require_relative 'errors/panel_unimplemented_error'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Errors
|
|
6
|
+
# Exception raised when attempting to access a +BusinessObject+ without an iMIS ID
|
|
7
|
+
#
|
|
8
|
+
class MissingIdError < Error
|
|
9
|
+
def initialize = super(message)
|
|
10
|
+
|
|
11
|
+
def message = 'Cannot access an individual Business Object without an iMIS ID'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -51,12 +51,13 @@ module Usps
|
|
|
51
51
|
|
|
52
52
|
# Update a single named field on a business object for the current member
|
|
53
53
|
#
|
|
54
|
+
# @param ordinal [Integer] The ordinal identifier for the desired object
|
|
54
55
|
# @param field [String] Name of the field
|
|
55
56
|
# @param value Value of the field
|
|
56
57
|
#
|
|
57
58
|
# @return [Usps::Imis::Data] Response data from the API
|
|
58
59
|
#
|
|
59
|
-
def put_field(field, value) = api.on(business_object, ordinal:).put_field(field, value)
|
|
60
|
+
def put_field(ordinal, field, value) = api.on(business_object, ordinal:).put_field(field, value)
|
|
60
61
|
alias []= put_field
|
|
61
62
|
|
|
62
63
|
# Update only specific fields on a Panel for the current member
|
data/lib/usps/imis/properties.rb
CHANGED
|
@@ -9,6 +9,19 @@ module Usps
|
|
|
9
9
|
#
|
|
10
10
|
def self.build(&) = new.build(&)
|
|
11
11
|
|
|
12
|
+
# Wrap value in the API-internal type structure
|
|
13
|
+
#
|
|
14
|
+
def self.wrap(value)
|
|
15
|
+
case value
|
|
16
|
+
when String then value
|
|
17
|
+
when Time, DateTime then value.strftime('%Y-%m-%dT%H:%I:%S')
|
|
18
|
+
when Integer then { '$type' => 'System.Int32', '$value' => value }
|
|
19
|
+
when true, false then { '$type' => 'System.Boolean', '$value' => value }
|
|
20
|
+
else
|
|
21
|
+
raise Errors::UnexpectedPropertyTypeError.from(value)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
12
25
|
# Build the data for the Properties field
|
|
13
26
|
#
|
|
14
27
|
def build
|
|
@@ -29,22 +42,9 @@ module Usps
|
|
|
29
42
|
@properties << {
|
|
30
43
|
'$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
|
|
31
44
|
'Name' => name,
|
|
32
|
-
'Value' => wrap(value)
|
|
45
|
+
'Value' => self.class.wrap(value)
|
|
33
46
|
}
|
|
34
47
|
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def wrap(value)
|
|
39
|
-
case value
|
|
40
|
-
when String then value
|
|
41
|
-
when Time, DateTime then value.strftime('%Y-%m-%dT%H:%I:%S')
|
|
42
|
-
when Integer then { '$type' => 'System.Int32', '$value' => value }
|
|
43
|
-
when true, false then { '$type' => 'System.Boolean', '$value' => value }
|
|
44
|
-
else
|
|
45
|
-
raise Errors::UnexpectedPropertyTypeError.from(value)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
data/lib/usps/imis/query.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Usps
|
|
4
4
|
module Imis
|
|
5
|
-
# API wrapper for IQA Queries
|
|
5
|
+
# API wrapper for IQA and Business Object Queries
|
|
6
6
|
#
|
|
7
7
|
class Query
|
|
8
8
|
include Enumerable
|
|
@@ -10,7 +10,7 @@ module Usps
|
|
|
10
10
|
|
|
11
11
|
# Endpoint for IQA query requests
|
|
12
12
|
#
|
|
13
|
-
|
|
13
|
+
IQA_PATH = 'api/Query'
|
|
14
14
|
|
|
15
15
|
# The parent +Api+ object
|
|
16
16
|
#
|
|
@@ -36,6 +36,10 @@ module Usps
|
|
|
36
36
|
#
|
|
37
37
|
attr_reader :count
|
|
38
38
|
|
|
39
|
+
# Whether the current query has a next page
|
|
40
|
+
#
|
|
41
|
+
attr_reader :next_page
|
|
42
|
+
|
|
39
43
|
# A new instance of +Query+
|
|
40
44
|
#
|
|
41
45
|
# @param api [Api] Parent to use for making requests
|
|
@@ -48,14 +52,18 @@ module Usps
|
|
|
48
52
|
@api = api
|
|
49
53
|
@query_name = query_name
|
|
50
54
|
@query_params = query_params
|
|
55
|
+
@query_params.merge!(QueryName: query_name) if iqa?
|
|
51
56
|
@page_size = page_size
|
|
52
57
|
@offset = offset
|
|
58
|
+
@count = 0
|
|
59
|
+
|
|
60
|
+
logger.debug "URI: #{uri}"
|
|
53
61
|
end
|
|
54
62
|
|
|
55
63
|
# Iterate through all results from the query
|
|
56
64
|
#
|
|
57
65
|
def each(&)
|
|
58
|
-
logger.info 'Running
|
|
66
|
+
logger.info 'Running'
|
|
59
67
|
|
|
60
68
|
items = []
|
|
61
69
|
find_each { items << it }
|
|
@@ -65,10 +73,10 @@ module Usps
|
|
|
65
73
|
# Iterate through all results from the query, fetching one page at a time
|
|
66
74
|
#
|
|
67
75
|
def find_each(&)
|
|
68
|
-
|
|
76
|
+
reset!
|
|
69
77
|
|
|
70
|
-
while
|
|
71
|
-
|
|
78
|
+
while page?
|
|
79
|
+
fetch_next.tap do |result_page|
|
|
72
80
|
result_page['Items']['$values'].map { it.except('$type') }.each(&)
|
|
73
81
|
end
|
|
74
82
|
end
|
|
@@ -76,14 +84,16 @@ module Usps
|
|
|
76
84
|
nil
|
|
77
85
|
end
|
|
78
86
|
|
|
79
|
-
# Fetch a
|
|
87
|
+
# Fetch a filtered query page, and update the current offset
|
|
80
88
|
#
|
|
81
|
-
def
|
|
89
|
+
def page = fetch_next['Items']['$values'].map { iqa? ? it.except('$type') : Imis::Data[it] }
|
|
82
90
|
|
|
83
91
|
# Fetch the next raw query page, and update the current offset
|
|
84
92
|
#
|
|
85
93
|
def fetch_next
|
|
86
|
-
|
|
94
|
+
return unless page?
|
|
95
|
+
|
|
96
|
+
logger.info "Fetching #{query_type} Query page"
|
|
87
97
|
|
|
88
98
|
result = fetch
|
|
89
99
|
|
|
@@ -94,33 +104,44 @@ module Usps
|
|
|
94
104
|
JSON.pretty_generate(result).split("\n").each { logger.debug " -> #{it}" }
|
|
95
105
|
|
|
96
106
|
@offset = result['NextOffset']
|
|
107
|
+
@next_page = result['HasNext']
|
|
97
108
|
|
|
98
109
|
result
|
|
99
110
|
end
|
|
100
111
|
|
|
112
|
+
# Fetch a raw query page
|
|
113
|
+
#
|
|
114
|
+
def fetch = JSON.parse(submit(uri, authorize(http_get)).body)
|
|
115
|
+
|
|
116
|
+
# Reset query paging progress
|
|
117
|
+
#
|
|
118
|
+
def reset!
|
|
119
|
+
return if next_page.nil?
|
|
120
|
+
|
|
121
|
+
logger.debug 'Resetting progress'
|
|
122
|
+
|
|
123
|
+
@count = 0
|
|
124
|
+
@offset = 0
|
|
125
|
+
@next_page = nil
|
|
126
|
+
end
|
|
127
|
+
|
|
101
128
|
# Ruby 3.5 instance variable filter
|
|
102
129
|
#
|
|
103
130
|
def instance_variables_to_inspect = instance_variables - %i[@api]
|
|
104
131
|
|
|
105
132
|
private
|
|
106
133
|
|
|
107
|
-
|
|
108
|
-
def
|
|
134
|
+
# Only skip if explicitly set
|
|
135
|
+
def page? = next_page.nil? || next_page
|
|
109
136
|
|
|
110
|
-
def
|
|
111
|
-
def
|
|
137
|
+
def iqa? = query_name.match?(/^\$/)
|
|
138
|
+
def query_type = iqa? ? :IQA : :'Business Object'
|
|
139
|
+
def path_params = query_params.merge({ Offset: offset, Limit: page_size }.compact)
|
|
140
|
+
def endpoint = iqa? ? IQA_PATH : "#{Imis::BusinessObject::API_PATH}/#{query_name}"
|
|
141
|
+
def path = "#{endpoint}?#{path_params.to_query}"
|
|
112
142
|
def uri = URI(File.join(Imis.configuration.hostname, path))
|
|
113
143
|
|
|
114
|
-
def logger = Imis.logger(
|
|
115
|
-
|
|
116
|
-
def reset!
|
|
117
|
-
logger.debug 'Resetting Query progress'
|
|
118
|
-
|
|
119
|
-
@count = 0
|
|
120
|
-
@offset = 0
|
|
121
|
-
|
|
122
|
-
{ 'HasNext' => true }
|
|
123
|
-
end
|
|
144
|
+
def logger = Imis.logger("#{query_type} Query")
|
|
124
145
|
end
|
|
125
146
|
end
|
|
126
147
|
end
|