usps-imis-api 0.11.27 → 1.0.0.pre.rc.1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +57 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +88 -0
  6. data/.ruby-version +1 -0
  7. data/.simplecov +8 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +95 -0
  10. data/Rakefile +12 -0
  11. data/Readme.md +191 -19
  12. data/bin/console +21 -0
  13. data/bin/setup +8 -0
  14. data/lib/ext/hash.rb +10 -0
  15. data/lib/usps/imis/api.rb +138 -177
  16. data/lib/usps/imis/config.rb +10 -68
  17. data/lib/usps/imis/error/api.rb +26 -0
  18. data/lib/usps/imis/error/mapper.rb +9 -0
  19. data/lib/usps/imis/{errors/response_error.rb → error/response.rb} +7 -34
  20. data/lib/usps/imis/mapper.rb +21 -90
  21. data/lib/usps/imis/panel/base_panel.rb +42 -0
  22. data/lib/usps/imis/panel/education.rb +111 -0
  23. data/lib/usps/imis/panel/vsc.rb +109 -0
  24. data/lib/usps/imis/version.rb +1 -1
  25. data/lib/usps/imis.rb +17 -33
  26. data/spec/lib/usps/imis/api_spec.rb +143 -0
  27. data/spec/lib/usps/imis/config_spec.rb +33 -0
  28. data/spec/lib/usps/imis/error/api_spec.rb +17 -0
  29. data/spec/lib/usps/imis/error/response_spec.rb +107 -0
  30. data/spec/lib/usps/imis/mapper_spec.rb +31 -0
  31. data/spec/lib/usps/imis/panel/base_panel_spec.rb +32 -0
  32. data/spec/lib/usps/imis/panel/education_spec.rb +55 -0
  33. data/spec/lib/usps/imis/panel/vsc_spec.rb +38 -0
  34. data/spec/lib/usps/imis_spec.rb +11 -0
  35. data/spec/spec_helper.rb +35 -0
  36. data/usps-imis-api.gemspec +18 -0
  37. metadata +33 -98
  38. data/bin/imis +0 -8
  39. data/lib/usps/imis/base_data.rb +0 -62
  40. data/lib/usps/imis/blank_object.rb +0 -62
  41. data/lib/usps/imis/business_object.rb +0 -230
  42. data/lib/usps/imis/command_line/interface.rb +0 -161
  43. data/lib/usps/imis/command_line/options_parser.rb +0 -136
  44. data/lib/usps/imis/command_line/performers.rb +0 -80
  45. data/lib/usps/imis/command_line.rb +0 -15
  46. data/lib/usps/imis/data.rb +0 -56
  47. data/lib/usps/imis/error.rb +0 -55
  48. data/lib/usps/imis/errors/api_error.rb +0 -11
  49. data/lib/usps/imis/errors/command_line_error.rb +0 -11
  50. data/lib/usps/imis/errors/config_error.rb +0 -11
  51. data/lib/usps/imis/errors/locked_id_error.rb +0 -15
  52. data/lib/usps/imis/errors/mapper_error.rb +0 -29
  53. data/lib/usps/imis/errors/missing_id_error.rb +0 -15
  54. data/lib/usps/imis/errors/not_found_error.rb +0 -11
  55. data/lib/usps/imis/errors/panel_unimplemented_error.rb +0 -34
  56. data/lib/usps/imis/errors/unexpected_property_type_error.rb +0 -31
  57. data/lib/usps/imis/logger.rb +0 -19
  58. data/lib/usps/imis/logger_formatter.rb +0 -32
  59. data/lib/usps/imis/logger_helpers.rb +0 -17
  60. data/lib/usps/imis/mocks/business_object.rb +0 -47
  61. data/lib/usps/imis/mocks.rb +0 -11
  62. data/lib/usps/imis/panels/base_panel.rb +0 -124
  63. data/lib/usps/imis/panels/education.rb +0 -29
  64. data/lib/usps/imis/panels/vsc.rb +0 -28
  65. data/lib/usps/imis/panels.rb +0 -25
  66. data/lib/usps/imis/party_data.rb +0 -88
  67. data/lib/usps/imis/properties.rb +0 -60
  68. data/lib/usps/imis/query.rb +0 -153
  69. data/lib/usps/imis/requests.rb +0 -68
  70. data/spec/support/usps/vcr/config.rb +0 -47
  71. data/spec/support/usps/vcr/filters.rb +0 -89
  72. data/spec/support/usps/vcr.rb +0 -8
@@ -1,230 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'requests'
4
- require_relative 'data'
5
- require_relative 'party_data'
6
-
7
- module Usps
8
- module Imis
9
- # Configured accessor for a specific Business Object
10
- #
11
- class BusinessObject
12
- include Requests
13
-
14
- # Endpoint for general API requests
15
- #
16
- API_PATH = 'api'
17
-
18
- # The parent +Api+ object
19
- #
20
- attr_reader :api
21
-
22
- # Name of the iMIS Business Object
23
- #
24
- attr_reader :business_object_name
25
-
26
- # Ordinal to build override ID param of the URL (e.g. used for Panels)
27
- #
28
- attr_reader :ordinal
29
-
30
- # Tagged logger
31
- #
32
- attr_reader :logger
33
-
34
- # A new instance of +BusinessObject+
35
- #
36
- def initialize(api, business_object_name, ordinal: nil)
37
- @api = api
38
- @business_object_name = business_object_name
39
- @ordinal = ordinal
40
- @logger ||= Imis.logger('BusinessObject')
41
- end
42
-
43
- # Run a query on the entire business object
44
- #
45
- # @return [Usps::Imis::Query] Query wrapper
46
- #
47
- def query = api.query(business_object_name)
48
-
49
- # Support passthrough for Api#with
50
- #
51
- def with(imis_id, &)
52
- # Bring into local scope
53
- wrapper_business_object_name = business_object_name
54
- wrapper_ordinal = ordinal
55
-
56
- api.with(imis_id) do
57
- on(wrapper_business_object_name, ordinal: wrapper_ordinal, &)
58
- end
59
- end
60
-
61
- # Get a business object for the current member
62
- #
63
- # If +fields+ is provided, will return only those field values
64
- #
65
- # @param fields [String] Field names to return
66
- #
67
- # @return [Usps::Imis::Data, Array<Usps::Imis::Data>] Response data from the API
68
- #
69
- def get(*fields) = fields.any? ? get_fields(*fields) : raw_object
70
- alias read get
71
-
72
- # Get a single named field from a business object for the current member
73
- #
74
- # @param field [String] Field name to return
75
- #
76
- # @return Response data field value from the API
77
- #
78
- def get_field(field) = raw_object[field]
79
- alias fetch get_field
80
- alias [] get_field
81
-
82
- # Get named fields from a business object for the current member
83
- #
84
- # @param names [Array<String>] Field names to return
85
- #
86
- # @return [Array] Response data from the API
87
- #
88
- def get_fields(*fields)
89
- values = raw_object
90
- fields.map { values[it] }
91
- end
92
- alias fetch_all get_fields
93
-
94
- # Update a single named field on a business object for the current member
95
- #
96
- # @param field [String] Name of the field
97
- # @param value Value of the field
98
- #
99
- # @return [Usps::Imis::Data] Response data from the API
100
- #
101
- def put_field(field, value) = put(filter_fields(field => value))
102
- alias []= put_field
103
-
104
- # Update only specific fields on a business object for the current member
105
- #
106
- # @param fields [Hash] Conforms to pattern +{ field_key => value }+
107
- #
108
- # @return [Usps::Imis::Data] Response data from the API
109
- #
110
- def put_fields(fields) = put(filter_fields(fields))
111
- alias patch put_fields
112
-
113
- # Update a business object for the current member
114
- #
115
- # Any properties not included will be left unmodified
116
- #
117
- # @param body [Hash] Full raw API object data
118
- #
119
- # @return [Usps::Imis::Data] Response data from the API
120
- #
121
- def put(body) = put_object(http_put, body)
122
- alias update put
123
-
124
- # Create a business object for the current member
125
- #
126
- # @param body [Hash] Full raw API object data
127
- #
128
- # @return [Usps::Imis::Data] Response data from the API
129
- #
130
- def post(body) = put_object(http_post, body)
131
- alias create post
132
-
133
- # Remove a business object for the current member
134
- #
135
- # @return [true] Only on success response (i.e. blank string from the API)
136
- #
137
- def delete = submit(uri, authorize(http_delete)).body == '' # rubocop:disable Naming/PredicateMethod
138
- alias destroy delete
139
-
140
- # Build a blank object for the current iMIS ID
141
- #
142
- def blank_object(&) = BlankObject.new(self).build(&)
143
-
144
- # Create a business object for the current member, starting with a blank object and building properties
145
- #
146
- def post_from_blank(&) = post(blank_object(&))
147
- alias create_from_blank post_from_blank
148
-
149
- # Ruby 3.5 instance variable filter
150
- #
151
- def instance_variables_to_inspect = instance_variables - %i[@api @logger]
152
-
153
- private
154
-
155
- # Construct a business object API endpoint address
156
- #
157
- def uri(id: nil)
158
- full_path = "#{API_PATH}/#{business_object_name}/#{id_for_uri(id)}"
159
- URI(File.join(Imis.configuration.hostname, full_path))
160
- end
161
-
162
- # Select the correct ID to use in the URI
163
- #
164
- def id_for_uri(id = nil)
165
- return CGI.escape(id) unless id.nil?
166
- return CGI.escape("~#{api.imis_id}|#{ordinal}") if ordinal
167
-
168
- api.imis_id.to_s
169
- end
170
-
171
- # Manually assemble the matching data structure, with fields in the correct order
172
- #
173
- def filter_fields(fields)
174
- block_not_supported!
175
-
176
- existing = get
177
-
178
- JSON.parse(JSON.dump(existing)).tap do |updated|
179
- # Preserve the iMIS ID, as well as the Ordinal (if present)
180
- updated['Properties']['$values'], properties =
181
- existing.raw['Properties']['$values'].partition { %w[ID Ordinal].include?(it['Name']) }
182
-
183
- # Iterate through all existing fields
184
- properties.each do |value|
185
- # Skip unmodified fields
186
- next unless fields.keys.include?(value['Name'])
187
-
188
- value['Value'] = Properties.wrap(fields[value['Name']])
189
-
190
- # Add the completed field with the updated value
191
- updated['Properties']['$values'] << value
192
- end
193
- end
194
- end
195
-
196
- # Get a raw object response from the API
197
- #
198
- # Useful for stubbing data in tests
199
- #
200
- def raw_object
201
- raise Errors::MissingIdError if api.imis_id.nil?
202
-
203
- response = submit(uri, authorize(http_get))
204
- klass = business_object_name == 'Party' ? PartyData : Data
205
- result = klass.from_json(response.body)
206
- logger.json result
207
- result
208
- end
209
-
210
- # Upload an object to the API
211
- #
212
- def put_object(request, body)
213
- raise Errors::MissingIdError if api.imis_id.nil?
214
-
215
- request.body = JSON.dump(body)
216
- response = submit(uri, authorize(request))
217
- klass = business_object_name == 'Party' ? PartyData : Data
218
- result = klass.from_json(response.body)
219
- logger.json result
220
- result
221
- end
222
-
223
- def block_not_supported!
224
- return unless business_object_name == 'Party'
225
-
226
- raise Errors::ApiError, 'The Party business object does not support updating specific fields'
227
- end
228
- end
229
- end
230
- end
@@ -1,161 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'performers'
4
-
5
- module Usps
6
- module Imis
7
- module CommandLine
8
- # Command line interface for the Api
9
- #
10
- class Interface
11
- include Performers
12
-
13
- NAME = 'USPS iMIS API - Ruby'
14
-
15
- # CLI options that indicate the response is a raw Hash rather than a Data object,
16
- # and should not be simplified
17
- #
18
- RAW_HASH_RESPONSE_OPTIONS = %i[business_objects auth_token].freeze
19
-
20
- # Options passed in from the command line
21
- #
22
- attr_reader :options
23
-
24
- # Tagged logger
25
- #
26
- attr_reader :logger
27
-
28
- # Initialize an +Interface+ and run it with the provided options
29
- #
30
- # @param [**Hash] Options from the CLI options parser
31
- #
32
- def self.run(...) = new(...).run
33
-
34
- # A new instance of +Interface+
35
- #
36
- # @param [**Hash] Options from the CLI options parser
37
- #
38
- def initialize(**)
39
- @options = input_options.merge(**)
40
- options[:version] = true if default_options?
41
-
42
- configure! if options[:config]
43
- logging!
44
- @logger ||= Imis.logger('CommandLine')
45
- end
46
-
47
- # Run the configured action on the API
48
- #
49
- def run
50
- logger.info 'Running'
51
- logger.debug 'CLI Options:'
52
- logger.json(options.dup.tap { it[:token] = '[FILTERED]' if it[:token] })
53
-
54
- set_member
55
-
56
- result = simplify(perform!)
57
-
58
- output { result }
59
-
60
- result
61
- end
62
-
63
- private
64
-
65
- # :nocov:
66
- def input_options
67
- return OptionsParser.new.options unless ENV.fetch('CI', false) || ENV.fetch('TESTING', false)
68
-
69
- {
70
- raw: false,
71
- quiet: false,
72
- log: false,
73
- log_level: 'info',
74
- version: false
75
- }
76
- end
77
- # :nocov:
78
-
79
- def api
80
- @api ||=
81
- if options[:token]
82
- Usps::Imis::Api.with_token(options[:token])
83
- else
84
- Usps::Imis::Api.new
85
- end
86
- end
87
-
88
- def set_member
89
- case options
90
- in certificate: then api.imis_id_for(certificate)
91
- in id: then api.imis_id = id
92
- else
93
- # Query
94
- end
95
-
96
- logger.debug "iMIS ID: #{api.imis_id}" if api.imis_id
97
- end
98
-
99
- def simplify(data)
100
- return data.to_a if data.is_a?(Query)
101
- return data if options[:raw] || RAW_HASH_RESPONSE_OPTIONS.any? { options[it] }
102
-
103
- if data.is_a?(PartyData)
104
- logger.debug 'Returning simplified PartyData#properties'
105
- data.properties
106
- elsif data.is_a?(Hash)
107
- # Hash includes Usps::Imis::Data
108
- logger.debug 'Returning simplified Data#properties'
109
- data.properties(include_ids: options[:include_ids])
110
- elsif data.is_a?(Array) && data.all? { it.is_a?(Hash) }
111
- logger.debug 'Returning simplified Array<Data#properties>'
112
- data.map { it.properties(include_ids: options[:include_ids]) }
113
- else
114
- data
115
- end
116
- end
117
-
118
- # :nocov:
119
- def output(&)
120
- if ENV.fetch('SUPPRESS_OUTPUT', false)
121
- logger.debug 'Output suppressed'
122
- return
123
- end
124
-
125
- puts JSON.dump(yield) if block_given?
126
- end
127
- # :nocov:
128
-
129
- # Supports YAML or JSON config data
130
- #
131
- def configure!
132
- config_data = YAML.safe_load_file(options[:config])
133
-
134
- Usps::Imis.configure do |config|
135
- config_data.each do |key, value|
136
- config.public_send("#{key}=", value)
137
- end
138
- end
139
- end
140
-
141
- def logging!
142
- Usps::Imis.configure do |config|
143
- # :nocov:
144
- if options[:quiet] || ENV.fetch('SUPPRESS_OUTPUT', false)
145
- config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
146
- elsif options[:log] || config.logger_file
147
- # Use default, or configured file
148
- else
149
- config.logger = ActiveSupport::TaggedLogging.new(Logger.new($stderr))
150
- end
151
- # :nocov:
152
-
153
- config.logger.level = options[:log_level]
154
- end
155
- end
156
-
157
- def default_options? = options[:log_level] == 'info' && options.except(:log_level).values.none?
158
- end
159
- end
160
- end
161
- end
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Usps
4
- module Imis
5
- module CommandLine
6
- # Command line options parser
7
- #
8
- class OptionsParser
9
- OPTIONS = {
10
- # IDs
11
- certificate: ['Member certificate number', { type: :string }],
12
- id: ['Member iMIS ID', { type: :integer }],
13
-
14
- # Primary interactions
15
- on: ['Business Object name', { type: :string }],
16
- panel: ['Panel name', { type: :string }],
17
- query: ['IQA Query or Business Object name to query', { type: :string, short: :Q }],
18
- mapper: ['Interact with mapped fields', { short: :M }],
19
- map: ["Shorthand for #{'-Mf'.green} to access a single mapped field", { type: :string }],
20
- business_objects: ['List available Business Objects'],
21
-
22
- # Alternate verbs
23
- create: ["Send a #{'POST'.cyan} request", { short: :P }],
24
- delete: ["Send a #{'DELETE'.cyan} request", { short: :D }],
25
-
26
- # Data
27
- ordinal: ['Ordinal ID within a Panel', { type: :integer }],
28
- field: ['Specific field to return or update', { type: :string }],
29
- fields: ['Specific field(s) to return', { type: :strings, short: :F }],
30
- data: ['JSON string input', { type: :string }],
31
-
32
- # Iteractions for supporting other language wrappers
33
- auth_token: ['Return an auth token for other language wrappers', { short: :T }],
34
- token: ['Provide an existing auth token', { type: :string }],
35
-
36
- # General config
37
- config: ['Path to the JSON/YAML config file to use', { type: :string, short: :C }],
38
- raw: ['Return raw JSON output, rather than simplified data', { short: :R }],
39
- include_ids: ["Include any #{'iMIS ID'.yellow} and #{'Ordinal'.yellow} properties in returned data"],
40
- quiet: ["Suppress logging to #{'STDERR'.red}"],
41
- log: ["Redirect logging to #{'STDOUT'.red}"],
42
- log_level: ['Set the logging level', { type: :string, default: 'info', short: :L }]
43
- }.freeze
44
-
45
- CONFLICTING_OPTION_GROUPS = [
46
- %i[certificate id],
47
- %i[on panel query mapper map business_objects auth_token],
48
- %i[field fields map query],
49
- %i[raw include_ids],
50
- %i[quiet log_level],
51
- %i[quiet log],
52
-
53
- %i[create delete],
54
-
55
- %i[create mapper],
56
- %i[create query],
57
- %i[create map],
58
- %i[create field],
59
- %i[create fields],
60
-
61
- %i[delete mapper],
62
- %i[delete query],
63
- %i[delete map],
64
- %i[delete field],
65
- %i[delete fields],
66
- %i[delete data],
67
- %i[delete raw]
68
- ].freeze
69
-
70
- attr_reader :arguments, :options
71
-
72
- def self.banner_header(version)
73
- <<~BANNER
74
- #{version.bold.blue}
75
- #{'P/R/C Julian Fiander, SN'.gray}\n \n
76
- BANNER
77
- end
78
-
79
- def self.banner_contents
80
- <<~BANNER
81
- #{'Usage'.underline}
82
-
83
- #{'imis'.bold} #{'[options]'.gray}
84
-
85
-
86
- #{'Further Help'.underline}
87
-
88
- For an explanation of how to provide API configuration, more details on the options,
89
- and usage examples, please refer to the wiki:
90
-
91
- https://github.com/unitedstatespowersquadrons/imis-api-ruby/wiki/Command-Line
92
-
93
-
94
- #{'Options'.underline}
95
- BANNER
96
- end
97
-
98
- def initialize
99
- @options = parse_options.compact
100
- @arguments = ARGV # Not currently used
101
-
102
- Optimist.educate if ARGV.empty? && defaults? # DEV: This shadows setting the --version flag by default
103
-
104
- # :nocov:
105
- @options[:data] = read_stdin if stdin?
106
- # :nocov:
107
-
108
- @options[:data] = JSON.parse(@options[:data]) if @options[:data]
109
- end
110
-
111
- private
112
-
113
- def parse_options
114
- Optimist.options do
115
- version "#{Interface::NAME} (v#{Usps::Imis::VERSION})"
116
-
117
- banner OptionsParser.banner_header(version)
118
- banner OptionsParser.banner_contents
119
-
120
- OPTIONS.each { |option, data| opt(option, *data) }
121
- CONFLICTING_OPTION_GROUPS.each { |group| conflicts(*group) }
122
-
123
- educate_on_error
124
- end
125
- end
126
-
127
- # :nocov:
128
- def stdin? = $stdin.wait_readable(0)
129
- def read_stdin = $stdin.read.chomp
130
- # :nocov:
131
-
132
- def defaults? = options[:log_level] == 'info' && options.except(:log_level).values.none?
133
- end
134
- end
135
- end
136
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Usps
4
- module Imis
5
- module CommandLine
6
- # Command line interface perform matchers
7
- #
8
- # @private
9
- #
10
- module Performers
11
- private
12
-
13
- def perform!
14
- case convert(options)
15
- in mapper:, **extra then perform_mapper(mapper, **extra)
16
- in on:, **extra then perform_on(on, **extra)
17
- in panel:, **extra then perform_panel(panel, **extra)
18
- in query:, **extra then api.query(query, extra[:data])
19
- in business_objects: true then api.business_objects
20
- in auth_token: true then api.auth_token
21
- in certificate: then api.imis_id
22
- in version: true then "#{Interface::NAME} (v#{Imis::VERSION})"
23
- end
24
- rescue NoMatchingPatternError => e
25
- raise Errors::CommandLineError, "Unable to match pattern from options: #{e.message}"
26
- end
27
-
28
- def convert(options)
29
- options.dup.tap do |converted|
30
- case converted
31
- in map: then converted.merge!(mapper: api.mapper, field: map)
32
- in mapper: true then converted[:mapper] = api.mapper
33
- in on: then converted[:on] = api.on(on)
34
- in panel: then converted[:panel] = api.panels.public_send(panel)
35
- else
36
- # Nothing to convert
37
- end
38
-
39
- # Remove mapper flag when false
40
- converted.delete(:mapper) unless converted[:mapper]
41
- end
42
- end
43
-
44
- def perform_mapper(mapper, **options)
45
- case options
46
- in field:, data: then mapper.put_field(field.to_sym, data)
47
- in fields: then mapper.get_fields(*fields)
48
- in field: then mapper.get_field(field.to_sym)
49
- in data: then mapper.update(data)
50
- end
51
- end
52
-
53
- def perform_on(on, **options)
54
- case options
55
- in delete: true then on.delete
56
- in create: true, data: then on.post(data)
57
- in data:, field: then on.put_field(field, data)
58
- in data: then on.put_fields(data)
59
- in fields: then on.get_fields(*fields)
60
- in field: then on.get_field(field)
61
- else
62
- on.get
63
- end
64
- end
65
-
66
- def perform_panel(panel, **options)
67
- case options
68
- in delete: true, ordinal: then panel.delete(ordinal)
69
- in create: true, data: then panel.post(data)
70
- in ordinal:, data:, field: then panel.put_field(ordinal, field, data)
71
- in ordinal:, data: then panel.put_fields(ordinal, data)
72
- in ordinal:, fields: then panel.get_fields(ordinal, *fields)
73
- in ordinal:, field: then panel.get_field(ordinal, field)
74
- in ordinal: then panel.get(ordinal)
75
- end
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'optimist'
4
- require 'colorize'
5
-
6
- module Usps
7
- module Imis
8
- # Wrapper for the command line interface
9
- #
10
- module CommandLine; end
11
- end
12
- end
13
-
14
- require_relative 'command_line/options_parser'
15
- require_relative 'command_line/interface'
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'pp'
4
- require 'stringio'
5
-
6
- require_relative 'base_data'
7
-
8
- module Usps
9
- module Imis
10
- # Convenience wrapper for accessing specific properties within an API data response
11
- #
12
- class Data < BaseData
13
- # The Business Object or Panel name
14
- #
15
- def entity = raw['EntityTypeName']
16
-
17
- # Access the iMIS ID property
18
- #
19
- def imis_id = self['ID'].to_i
20
- alias id imis_id
21
-
22
- # Access the Ordinal identifier property (if present)
23
- #
24
- def ordinal = self['Ordinal']&.to_i
25
-
26
- # Access an individual property value by name
27
- #
28
- def [](property_name)
29
- property = property_values.find { it['Name'] == property_name }
30
- return if property.nil?
31
-
32
- value = property['Value']
33
- value.nil? || value.is_a?(String) ? value : value['$value']
34
- end
35
-
36
- # Hash of all property names to values
37
- #
38
- # @param include_ids [Boolean] Whether to include the iMIS ID and Ordinal
39
- #
40
- def properties(include_ids: false)
41
- property_values
42
- .map { it['Name'] }
43
- .select { include_ids || !%w[ID Ordinal].include?(it) }
44
- .index_with { self[it] }
45
- end
46
-
47
- private
48
-
49
- def pretty_print_data
50
- { entity:, imis_id:, ordinal: }.compact
51
- end
52
-
53
- def property_values = raw['Properties']['$values']
54
- end
55
- end
56
- end