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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Gemfile.lock +36 -30
  4. data/Readme.md +140 -32
  5. data/lib/usps/imis/api.rb +27 -39
  6. data/lib/usps/imis/business_object.rb +87 -47
  7. data/lib/usps/imis/config.rb +14 -3
  8. data/lib/usps/imis/data.rb +72 -0
  9. data/lib/usps/imis/error.rb +51 -0
  10. data/lib/usps/imis/errors/api_error.rb +11 -0
  11. data/lib/usps/imis/errors/config_error.rb +11 -0
  12. data/lib/usps/imis/errors/locked_id_error.rb +15 -0
  13. data/lib/usps/imis/errors/mapper_error.rb +29 -0
  14. data/lib/usps/imis/errors/not_found_error.rb +11 -0
  15. data/lib/usps/imis/errors/panel_unimplemented_error.rb +34 -0
  16. data/lib/usps/imis/{error → errors}/response_error.rb +5 -8
  17. data/lib/usps/imis/errors/unexpected_property_type_error.rb +31 -0
  18. data/lib/usps/imis/mapper.rb +28 -20
  19. data/lib/usps/imis/mocks/business_object.rb +47 -0
  20. data/lib/usps/imis/mocks.rb +11 -0
  21. data/lib/usps/imis/panels/base_panel.rb +144 -0
  22. data/lib/usps/imis/{panel → panels}/education.rb +2 -2
  23. data/lib/usps/imis/{panel → panels}/vsc.rb +2 -2
  24. data/lib/usps/imis/panels.rb +25 -0
  25. data/lib/usps/imis/properties.rb +50 -0
  26. data/lib/usps/imis/query.rb +94 -0
  27. data/lib/usps/imis/requests.rb +27 -3
  28. data/lib/usps/imis/version.rb +1 -1
  29. data/lib/usps/imis.rb +15 -15
  30. data/spec/lib/usps/imis/api_spec.rb +26 -13
  31. data/spec/lib/usps/imis/business_object_spec.rb +44 -20
  32. data/spec/lib/usps/imis/config_spec.rb +2 -2
  33. data/spec/lib/usps/imis/data_spec.rb +66 -0
  34. data/spec/lib/usps/imis/{error/api_error_spec.rb → error_spec.rb} +1 -1
  35. data/spec/lib/usps/imis/{error → errors}/response_error_spec.rb +4 -4
  36. data/spec/lib/usps/imis/mapper_spec.rb +27 -3
  37. data/spec/lib/usps/imis/mocks/business_object_spec.rb +65 -0
  38. data/spec/lib/usps/imis/panels/base_panel_spec.rb +33 -0
  39. data/spec/lib/usps/imis/panels/education_spec.rb +70 -0
  40. data/spec/lib/usps/imis/{panel → panels}/vsc_spec.rb +6 -7
  41. data/spec/lib/usps/imis/properties_spec.rb +19 -0
  42. data/spec/spec_helper.rb +2 -0
  43. data/usps-imis-api.gemspec +1 -1
  44. metadata +28 -16
  45. data/lib/ext/hash.rb +0 -10
  46. data/lib/usps/imis/error/api_error.rb +0 -44
  47. data/lib/usps/imis/error/mapper_error.rb +0 -11
  48. data/lib/usps/imis/panel/base_panel.rb +0 -101
  49. data/lib/usps/imis/panel/panel_properties.rb +0 -52
  50. data/spec/lib/usps/imis/panel/base_panel_spec.rb +0 -32
  51. data/spec/lib/usps/imis/panel/education_spec.rb +0 -55
  52. data/spec/lib/usps/imis/panel/panel_properties_spec.rb +0 -19
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'requests'
4
+ require_relative 'data'
5
+
3
6
  module Usps
4
7
  module Imis
5
8
  # DEV
@@ -18,112 +21,127 @@ module Usps
18
21
  #
19
22
  attr_reader :business_object_name
20
23
 
21
- # Override ID param of the URL (e.g. used for Panels)
24
+ # Ordinal to build override ID param of the URL (e.g. used for Panels)
22
25
  #
23
- attr_reader :url_id
26
+ attr_reader :ordinal
24
27
 
25
28
  # A new instance of +BusinessObject+
26
29
  #
27
- def initialize(api, business_object_name, url_id: nil)
30
+ def initialize(api, business_object_name, ordinal: nil)
28
31
  @api = api
29
32
  @business_object_name = business_object_name
30
- @url_id = url_id
33
+ @ordinal = ordinal
31
34
  end
32
35
 
33
36
  # Get a business object for the current member
34
37
  #
35
- # @return [Hash] Response data from the API
38
+ # If +fields+ is provided, will return only those field values
36
39
  #
37
- def get
38
- request = Net::HTTP::Get.new(uri)
39
- result = submit(uri, authorize(request))
40
- JSON.parse(result.body)
41
- end
40
+ # @param fields [String] Field names to return
41
+ #
42
+ # @return [Usps::Imis::Data, Array<Usps::Imis::Data>] Response data from the API
43
+ #
44
+ def get(*fields) = fields.any? ? get_fields(*fields) : raw_object
45
+ alias read get
42
46
 
43
47
  # Get a single named field from a business object for the current member
44
48
  #
45
- # @param name [String] Field name to return
49
+ # @param field [String] Field name to return
46
50
  #
47
- # @return [Hash] Response data from the API
51
+ # @return Response data field value from the API
48
52
  #
49
- def get_field(name)
50
- values = get['Properties']['$values']
51
- value = values.find { |hash| hash['Name'] == name }['Value']
53
+ def get_field(field) = raw_object[field]
54
+ alias fetch get_field
55
+ alias [] get_field
52
56
 
53
- value.is_a?(String) ? value : value['$value']
57
+ # Get named fields from a business object for the current member
58
+ #
59
+ # @param names [Array<String>] Field names to return
60
+ #
61
+ # @return [Array] Response data from the API
62
+ #
63
+ def get_fields(*fields)
64
+ values = raw_object
65
+ fields.map { values[it] }
54
66
  end
67
+ alias fetch_all get_fields
55
68
 
56
69
  # Update only specific fields on a business object for the current member
57
70
  #
58
71
  # @param fields [Hash] Conforms to pattern +{ field_key => value }+
59
72
  #
60
- # @return [Hash] Response data from the API
73
+ # @return [Usps::Imis::Data] Response data from the API
61
74
  #
62
- def put_fields(fields)
63
- updated = filter_fields(fields)
64
- put(updated)
65
- end
75
+ def put_fields(fields) = put(filter_fields(fields))
76
+ alias patch put_fields
66
77
 
67
78
  # Update a business object for the current member
68
79
  #
80
+ # Any properties not included will be left unmodified
81
+ #
69
82
  # @param body [Hash] Full raw API object data
70
83
  #
71
- # @return [Hash] Response data from the API
84
+ # @return [Usps::Imis::Data] Response data from the API
72
85
  #
73
- def put(body)
74
- request = Net::HTTP::Put.new(uri)
75
- request.body = JSON.dump(body)
76
- result = submit(uri, authorize(request))
77
- JSON.parse(result.body)
78
- end
86
+ def put(body) = put_object(Net::HTTP::Put.new(uri), body)
87
+ alias update put
79
88
 
80
89
  # Create a business object for the current member
81
90
  #
82
91
  # @param body [Hash] Full raw API object data
83
92
  #
84
- # @return [Hash] Response data from the API
93
+ # @return [Usps::Imis::Data] Response data from the API
85
94
  #
86
- def post(body)
87
- request = Net::HTTP::Post.new(uri)
88
- request.body = JSON.dump(body)
89
- result = submit(uri, authorize(request))
90
- JSON.parse(result.body)
91
- end
95
+ def post(body) = put_object(Net::HTTP::Post.new(uri(id: '')), body)
96
+ alias create post
92
97
 
93
98
  # Remove a business object for the current member
94
99
  #
95
- # @return [String] Error response body from the API, or empty string on success
100
+ # @return [true] Only on success response (i.e. blank string from the API)
96
101
  #
97
- def delete
98
- request = Net::HTTP::Delete.new(uri)
99
- result = submit(uri, authorize(request))
100
- result.body
101
- end
102
+ def delete = submit(uri, authorize(Net::HTTP::Delete.new(uri))).body == '' # rubocop:disable Naming/PredicateMethod
103
+ alias destroy delete
104
+
105
+ # Ruby 3.5 instance variable filter
106
+ #
107
+ def instance_variables_to_inspect = instance_variables - %i[@api]
102
108
 
103
109
  private
104
110
 
105
111
  def token = api.token
106
112
  def token_expiration = api.token_expiration
107
113
 
114
+ def logger = Imis.logger('BusinessObject')
115
+
108
116
  # Construct a business object API endpoint address
109
117
  #
110
- def uri
111
- id_for_url = url_id ? CGI.escape(url_id) : api.imis_id
112
- full_path = "#{API_PATH}/#{business_object_name}/#{id_for_url}"
118
+ def uri(id: nil)
119
+ full_path = "#{API_PATH}/#{business_object_name}/#{id_for_uri(id)}"
113
120
  URI(File.join(Imis.configuration.hostname, full_path))
114
121
  end
115
122
 
123
+ # Select the correct ID to use in the URI
124
+ #
125
+ def id_for_uri(id = nil)
126
+ return CGI.escape(id) unless id.nil?
127
+ return CGI.escape("~#{api.imis_id}|#{ordinal}") if ordinal
128
+
129
+ api.imis_id.to_s
130
+ end
131
+
116
132
  # Manually assemble the matching data structure, with fields in the correct order
117
133
  #
118
134
  def filter_fields(fields)
119
135
  existing = get
120
136
 
121
137
  JSON.parse(JSON.dump(existing)).tap do |updated|
122
- # The first property is always the iMIS ID again
123
- updated['Properties']['$values'] = [existing['Properties']['$values'][0]]
138
+ # Preserve the iMIS ID, as well as the Ordinal (if present)
139
+ updated['Properties']['$values'], properties =
140
+ existing.raw['Properties']['$values'].partition { %w[ID Ordinal].include?(it['Name']) }
124
141
 
125
142
  # Iterate through all existing fields
126
- existing['Properties']['$values'].each do |value|
143
+ properties.each do |value|
144
+ # Skip unmodified fields
127
145
  next unless fields.keys.include?(value['Name'])
128
146
 
129
147
  # Strings are not wrapped in the type definition structure
@@ -139,6 +157,28 @@ module Usps
139
157
  end
140
158
  end
141
159
  end
160
+
161
+ # Get a raw object response from the API
162
+ #
163
+ # Useful for stubbing data in tests
164
+ #
165
+ def raw_object
166
+ request = Net::HTTP::Get.new(uri)
167
+ result = submit(uri, authorize(request))
168
+ result = Data.from_json(result.body)
169
+ JSON.pretty_generate(result).split("\n").each { logger.debug " -> #{it}" }
170
+ result
171
+ end
172
+
173
+ # Upload an object to the API
174
+ #
175
+ def put_object(request, body)
176
+ request.body = JSON.dump(body)
177
+ result = submit(uri, authorize(request))
178
+ result = Data.from_json(result.body)
179
+ JSON.pretty_generate(result).split("\n").each { logger.debug " -> #{it}" }
180
+ result
181
+ end
142
182
  end
143
183
  end
144
184
  end
@@ -9,21 +9,28 @@ module Usps
9
9
  IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
10
10
 
11
11
  attr_accessor :imis_id_query_name, :username, :password
12
- attr_reader :environment
12
+ attr_reader :environment, :logger, :logger_level
13
13
 
14
14
  def initialize
15
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)
19
+ @logger = ActiveSupport::TaggedLogging.new(Logger.new($stdout, level: :info))
19
20
 
20
21
  yield self if block_given?
22
+
23
+ @logger_level = logger.class::SEV_LABEL[logger.level].downcase.to_sym
21
24
  end
22
25
 
23
26
  def environment=(env)
24
27
  @environment = ActiveSupport::StringInquirer.new(env.to_s)
25
28
  end
26
29
 
30
+ def logger=(logger)
31
+ @logger = ActiveSupport::TaggedLogging.new(logger)
32
+ end
33
+
27
34
  # Environment-specific API endpoint hostname
28
35
  #
29
36
  # @return The API hostname for the current environment
@@ -32,12 +39,16 @@ module Usps
32
39
  return IMIS_ROOT_URL_PROD if environment.production?
33
40
  return IMIS_ROOT_URL_DEV if environment.development?
34
41
 
35
- raise Error::ApiError, "Unexpected API environment: #{environment}"
42
+ raise Errors::ConfigError, "Unexpected API environment: #{environment}"
36
43
  end
37
44
 
38
45
  # Ruby 3.5 instance variable filter
39
46
  #
40
- def instance_variables_to_inspect = %i[@environment @imis_id_query_name @username]
47
+ def instance_variables_to_inspect = %i[@environment @imis_id_query_name @username @logger_level]
48
+
49
+ # Parameters to filter out of logging
50
+ #
51
+ def filtered_parameters = %i[password]
41
52
  end
42
53
  end
43
54
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pp'
4
+ require 'stringio'
5
+
6
+ module Usps
7
+ module Imis
8
+ # Convenience wrapper for accessing specific properties within an API data response
9
+ #
10
+ class Data < Hash
11
+ # Load raw API response JSON to access properties
12
+ #
13
+ # @param json [String] Raw API response JSON
14
+ #
15
+ def self.from_json(json) = self[JSON.parse(json)]
16
+
17
+ alias raw to_h
18
+
19
+ # The Business Object or Panel name
20
+ #
21
+ def entity = raw['EntityTypeName']
22
+
23
+ # Access the iMIS ID property
24
+ #
25
+ def imis_id = self['ID'].to_i
26
+ alias id imis_id
27
+
28
+ # Access the Ordinal identifier property (if present)
29
+ #
30
+ def ordinal = self['Ordinal']&.to_i
31
+
32
+ # Access an individual property value by name
33
+ #
34
+ def [](property_name)
35
+ property = raw['Properties']['$values'].find { it['Name'] == property_name }
36
+ return if property.nil?
37
+
38
+ value = property['Value']
39
+ value.is_a?(String) ? value : value['$value']
40
+ end
41
+
42
+ # Hash of all property names to values
43
+ #
44
+ # @param include_ids [Boolean] Whether to include the iMIS ID and Ordinal
45
+ #
46
+ def properties(include_ids: false)
47
+ raw['Properties']['$values']
48
+ .map { it['Name'] }
49
+ .select { include_ids || !%w[ID Ordinal].include?(it) }
50
+ .index_with { self[it] }
51
+ end
52
+
53
+ def inspect
54
+ stringio = StringIO.new
55
+ PP.pp(self, stringio)
56
+ stringio.string.delete("\n")
57
+ end
58
+
59
+ def pretty_print(pp)
60
+ data = { entity:, imis_id:, ordinal: }.compact
61
+
62
+ pp.group(1, "#<#{self.class}", '>') do
63
+ data.each do |key, value|
64
+ pp.breakable
65
+ pp.text "#{key}="
66
+ pp.pp value
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ # Base error class for all internal exceptions
6
+ #
7
+ class Error < StandardError
8
+ # Additional call-specific metadata to pass through to Bugsnag
9
+ #
10
+ attr_accessor :metadata
11
+
12
+ # A new instance of +ApiError+
13
+ #
14
+ # @param message [String] The base exception message
15
+ # @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
16
+ #
17
+ def initialize(message, metadata = {})
18
+ super(message)
19
+ @metadata = metadata
20
+ end
21
+
22
+ # Additional metadata to include in Bugsnag reports
23
+ #
24
+ # Can include fields at the top level, which will be shows on the custom tab
25
+ #
26
+ # Can include fields nested under a top-level key, which will be shown on a tab with the
27
+ # top-level key as its name
28
+ #
29
+ # @return [Hash]
30
+ #
31
+ def bugsnag_meta_data
32
+ metadata == {} ? {} : base_metadata
33
+ end
34
+
35
+ private
36
+
37
+ def base_metadata
38
+ { api: metadata }
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ require_relative 'errors/api_error'
45
+ require_relative 'errors/config_error'
46
+ require_relative 'errors/locked_id_error'
47
+ require_relative 'errors/mapper_error'
48
+ require_relative 'errors/not_found_error'
49
+ require_relative 'errors/response_error'
50
+ require_relative 'errors/panel_unimplemented_error'
51
+ require_relative 'errors/unexpected_property_type_error'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Generic exception raised from within the gem
7
+ #
8
+ class ApiError < Error; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Exception raised for invalid configuration
7
+ #
8
+ class ConfigError < Error; end
9
+ end
10
+ end
11
+ end
@@ -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 change the iMIS ID while it is locked
7
+ #
8
+ class LockedIdError < Error
9
+ def initialize = super(message)
10
+
11
+ def message = 'Cannot change iMIS ID while locked'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Exception raised by a +Mapper+
7
+ #
8
+ class MapperError < Error
9
+ # Create a new instance of +MapperError+
10
+ #
11
+ # @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
12
+ #
13
+ def initialize(metadata = {})
14
+ @metadata = metadata
15
+ super(message, metadata)
16
+ end
17
+
18
+ # Exception message including the unrecognized field
19
+ #
20
+ def message
21
+ <<~MESSAGE.chomp
22
+ Mapper does not recognize field: "#{metadata[:field_key]}".
23
+ Please report what data you are attempting to work with to ITCom leadership.
24
+ MESSAGE
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Exception raised when the requested object cannot be found
7
+ #
8
+ class NotFoundError < Error; end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Exception raised when a panel is missing required method definitions
7
+ #
8
+ class PanelUnimplementedError < Error
9
+ # Create a new instance of +PanelUnimplementedError+ from the class name and missing method
10
+ #
11
+ # @param class_name [String] Name of the Panel class that is missing a method definition
12
+ # @param method [String] Method definition that is not defined on the Panel
13
+ #
14
+ def self.from(class_name, method) = new(class_name, method)
15
+
16
+ # Create a new instance of +PanelUnimplementedError+
17
+ #
18
+ # @param class_name [String] Name of the Panel class that is missing a method definition
19
+ # @param method [String] Method definition that is not defined on the Panel
20
+ # @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
21
+ #
22
+ def initialize(class_name, method, metadata = {})
23
+ @class_name = class_name
24
+ @method = method
25
+ super(message, metadata)
26
+ end
27
+
28
+ # Exception message including the undefined method
29
+ #
30
+ def message = "#{@class_name} must implement ##{@method}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -2,10 +2,10 @@
2
2
 
3
3
  module Usps
4
4
  module Imis
5
- module Error
5
+ module Errors
6
6
  # Exception raised due to receiving an error response from the API
7
7
  #
8
- class ResponseError < ApiError
8
+ class ResponseError < Error
9
9
  # [Net::HTTPResponse] The response received from the API
10
10
  #
11
11
  attr_reader :response
@@ -18,17 +18,14 @@ module Usps
18
18
  #
19
19
  # @param response [Net::HTTPResponse] The response received from the API
20
20
  #
21
- def self.from(response)
22
- new(nil, response)
23
- end
21
+ def self.from(response) = new(response)
24
22
 
25
23
  # Create a new instance of +ResponseError+
26
24
  #
27
- # @param _message Ignored
28
25
  # @param response [Net::HTTPResponse] The response received from the API
29
26
  # @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
30
27
  #
31
- def initialize(_message, response, metadata = {})
28
+ def initialize(response, metadata = {})
32
29
  @response = response
33
30
  super(message, metadata)
34
31
  end
@@ -43,7 +40,7 @@ module Usps
43
40
  # @return [Hash]
44
41
  #
45
42
  def bugsnag_meta_data
46
- base_metadata.tap { |m| m[:api].merge!(metadata) }
43
+ base_metadata.tap { it[:api].merge!(metadata) }
47
44
  end
48
45
 
49
46
  # Auto-formatted exception message, based on the provided API response
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Errors
6
+ # Exception raised when attempting to wrap an unexpected property type
7
+ #
8
+ class UnexpectedPropertyTypeError < Error
9
+ # Create a new instance of +UnexpectedPropertyTypeError+ from an unexpected value
10
+ #
11
+ # @param value Unexpected value
12
+ #
13
+ def self.from(value) = new(value)
14
+
15
+ # Create a new instance of +UnexpectedPropertyTypeError+
16
+ #
17
+ # @param value Unexpected value
18
+ # @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
19
+ #
20
+ def initialize(value, metadata = {})
21
+ @value = value
22
+ super(message, metadata)
23
+ end
24
+
25
+ # Exception message including the undefined method
26
+ #
27
+ def message = "Unexpected property type: #{@value.inspect}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -28,6 +28,14 @@ module Usps
28
28
  @api.imis_id = imis_id if imis_id
29
29
  end
30
30
 
31
+ def fetch(field_key)
32
+ missing_mapping!(field_key) unless FIELD_MAPPING.key?(field_key.to_sym)
33
+
34
+ business_object_name, field = FIELD_MAPPING[field_key]
35
+ api.on(business_object_name)[field]
36
+ end
37
+ alias [] fetch
38
+
31
39
  # Update a member's data on multiple affected business objects by arbitrary field names
32
40
  #
33
41
  # Does not require knowing which business object / iMIS-specific field name to use
@@ -36,7 +44,7 @@ module Usps
36
44
  #
37
45
  # @param data [Hash] Conforms to pattern +{ field_key => value }+
38
46
  #
39
- # @return [Array<Hash>] Response data from the API for each internal update request
47
+ # @return [Array<Usps::Imis::Data>] Response data from the API for each internal update request
40
48
  #
41
49
  def update(data)
42
50
  updates = data.each_with_object({}) do |(field_key, value), hash|
@@ -47,37 +55,37 @@ module Usps
47
55
  end
48
56
 
49
57
  updates.map do |business_object_name, field_updates|
50
- api.business_object(business_object_name).put_fields(field_updates)
58
+ api.on(business_object_name).put_fields(field_updates)
51
59
  end
52
60
  end
53
61
 
62
+ # Ruby 3.5 instance variable filter
63
+ #
64
+ def instance_variables_to_inspect = instance_variables - %i[@api]
65
+
54
66
  private
55
67
 
56
- def map_update(field_name)
57
- if FIELD_MAPPING.key?(field_name.to_sym)
58
- business_object_name, field = FIELD_MAPPING[field_name.to_sym]
59
- yield(business_object_name, field)
60
- else
61
- missing_mapping(field_name)
62
- end
68
+ def logger = Imis.logger('Mapper')
69
+
70
+ def map_update(field_key)
71
+ missing_mapping!(field_key) unless FIELD_MAPPING.key?(field_key.to_sym)
72
+
73
+ business_object_name, field = FIELD_MAPPING[field_key.to_sym]
74
+ yield(business_object_name, field)
63
75
  end
64
76
 
65
- def missing_mapping(field_name)
77
+ def missing_mapping!(field_key)
66
78
  unless ENV['TESTING']
67
79
  # :nocov:
68
- warn(
69
- "Mapper does not know how to handle field \"#{field_name}\".\n\n" \
70
- 'You can use api.put_fields(business_object_name, { field_name => value }) ' \
71
- "if you know the business object and iMIS-specific field name.\n\n"
72
- )
80
+ "Mapper does not know how to handle field \"#{field_key}\".\n\n" \
81
+ 'You can use api.put_fields(business_object_name, { field_name => value }) ' \
82
+ "if you know the business object and iMIS-specific field name.\n\n"
83
+ .split("\n")
84
+ .each { logger.warn(it) }
73
85
  # :nocov:
74
86
  end
75
87
 
76
- raise(
77
- Error::MapperError,
78
- "Unrecognized field: \"#{field_name}\". " \
79
- 'Please report what data you are attempting to work with to ITCom leadership.'
80
- )
88
+ raise Errors::MapperError.new({ field_key: })
81
89
  end
82
90
  end
83
91
  end