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
@@ -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,99 +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)
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
46
+
47
+ # Get a single named field from a business object for the current member
48
+ #
49
+ # @param field [String] Field name to return
50
+ #
51
+ # @return Response data field value from the API
52
+ #
53
+ def get_field(field) = raw_object[field]
54
+ alias fetch get_field
55
+ alias [] get_field
56
+
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] }
41
66
  end
67
+ alias fetch_all get_fields
42
68
 
43
69
  # Update only specific fields on a business object for the current member
44
70
  #
45
71
  # @param fields [Hash] Conforms to pattern +{ field_key => value }+
46
72
  #
47
- # @return [Hash] Response data from the API
73
+ # @return [Usps::Imis::Data] Response data from the API
48
74
  #
49
- def put_fields(fields)
50
- updated = filter_fields(fields)
51
- put(updated)
52
- end
75
+ def put_fields(fields) = put(filter_fields(fields))
76
+ alias patch put_fields
53
77
 
54
78
  # Update a business object for the current member
55
79
  #
80
+ # Any properties not included will be left unmodified
81
+ #
56
82
  # @param body [Hash] Full raw API object data
57
83
  #
58
- # @return [Hash] Response data from the API
84
+ # @return [Usps::Imis::Data] Response data from the API
59
85
  #
60
- def put(body)
61
- request = Net::HTTP::Put.new(uri)
62
- request.body = JSON.dump(body)
63
- result = submit(uri, authorize(request))
64
- JSON.parse(result.body)
65
- end
86
+ def put(body) = put_object(Net::HTTP::Put.new(uri), body)
87
+ alias update put
66
88
 
67
89
  # Create a business object for the current member
68
90
  #
69
91
  # @param body [Hash] Full raw API object data
70
92
  #
71
- # @return [Hash] Response data from the API
93
+ # @return [Usps::Imis::Data] Response data from the API
72
94
  #
73
- def post(body)
74
- request = Net::HTTP::Post.new(uri)
75
- request.body = JSON.dump(body)
76
- result = submit(uri, authorize(request))
77
- JSON.parse(result.body)
78
- end
95
+ def post(body) = put_object(Net::HTTP::Post.new(uri(id: '')), body)
96
+ alias create post
79
97
 
80
98
  # Remove a business object for the current member
81
99
  #
82
- # @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)
83
101
  #
84
- def delete
85
- request = Net::HTTP::Delete.new(uri)
86
- result = submit(uri, authorize(request))
87
- result.body
88
- 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]
89
108
 
90
109
  private
91
110
 
92
111
  def token = api.token
93
112
  def token_expiration = api.token_expiration
94
113
 
114
+ def logger = Imis.logger('BusinessObject')
115
+
95
116
  # Construct a business object API endpoint address
96
117
  #
97
- def uri
98
- id_for_url = url_id ? CGI.escape(url_id) : api.imis_id
99
- 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)}"
100
120
  URI(File.join(Imis.configuration.hostname, full_path))
101
121
  end
102
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
+
103
132
  # Manually assemble the matching data structure, with fields in the correct order
104
133
  #
105
134
  def filter_fields(fields)
106
135
  existing = get
107
136
 
108
137
  JSON.parse(JSON.dump(existing)).tap do |updated|
109
- # The first property is always the iMIS ID again
110
- 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']) }
111
141
 
112
142
  # Iterate through all existing fields
113
- existing['Properties']['$values'].each do |value|
143
+ properties.each do |value|
144
+ # Skip unmodified fields
114
145
  next unless fields.keys.include?(value['Name'])
115
146
 
116
147
  # Strings are not wrapped in the type definition structure
@@ -126,6 +157,28 @@ module Usps
126
157
  end
127
158
  end
128
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
129
182
  end
130
183
  end
131
184
  end
@@ -8,10 +8,27 @@ module Usps
8
8
  IMIS_ROOT_URL_PROD = 'https://portal.americasboatingclub.org'
9
9
  IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
10
10
 
11
- attr_accessor :environment, :imis_id_query_name, :username, :password
11
+ attr_accessor :imis_id_query_name, :username, :password
12
+ attr_reader :environment, :logger, :logger_level
12
13
 
13
14
  def initialize
15
+ @environment = defined?(Rails) ? Rails.env : ActiveSupport::StringInquirer.new('development')
16
+ @imis_id_query_name = ENV.fetch('IMIS_ID_QUERY_NAME', nil)
17
+ @username = ENV.fetch('IMIS_USERNAME', nil)
18
+ @password = ENV.fetch('IMIS_PASSWORD', nil)
19
+ @logger = ActiveSupport::TaggedLogging.new(Logger.new($stdout, level: :info))
20
+
14
21
  yield self if block_given?
22
+
23
+ @logger_level = logger.class::SEV_LABEL[logger.level].downcase.to_sym
24
+ end
25
+
26
+ def environment=(env)
27
+ @environment = ActiveSupport::StringInquirer.new(env.to_s)
28
+ end
29
+
30
+ def logger=(logger)
31
+ @logger = ActiveSupport::TaggedLogging.new(logger)
15
32
  end
16
33
 
17
34
  # Environment-specific API endpoint hostname
@@ -19,19 +36,19 @@ module Usps
19
36
  # @return The API hostname for the current environment
20
37
  #
21
38
  def hostname
22
- case environment.to_sym
23
- when :production
24
- IMIS_ROOT_URL_PROD
25
- when :development
26
- IMIS_ROOT_URL_DEV
27
- else
28
- raise Error::ApiError, "Unexpected API environment: #{environment}"
29
- end
39
+ return IMIS_ROOT_URL_PROD if environment.production?
40
+ return IMIS_ROOT_URL_DEV if environment.development?
41
+
42
+ raise Errors::ConfigError, "Unexpected API environment: #{environment}"
30
43
  end
31
44
 
32
45
  # Ruby 3.5 instance variable filter
33
46
  #
34
- 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]
35
52
  end
36
53
  end
37
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