ncore 2.1.2 → 3.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5e3084a6206184b0d08f617ea39e25afa49f6f1021470b8a770031fd6b76616
4
- data.tar.gz: 7c44c6ec3d83c4055369244c0d31c81a4dfa51ec26f1db681fdba9ad73561c98
3
+ metadata.gz: 5dc98407fe99f0886bc4462862fa5141d91be2a92ce6e00845e3770d9d10ffa4
4
+ data.tar.gz: 756f94ca80ffed714f51ba6822bb30b27e34a270e7bffd1e33c3ac84d2b4d011
5
5
  SHA512:
6
- metadata.gz: 5b2d70b6819652ae21e562342429f1879c9f27d2d96fa0c8825a778f47a588be641839a2c54ca3f97a4afc6f79be4737e7d6562c0922fe89d11ed2eae9c56546
7
- data.tar.gz: 3d2b6121d5f9e2fbb37160667e82146ef3849ddbed2c728f1fb351f10d3bb1507b026a6f4d04bfe8d55e9dff24df8ab2373147fb778a73aaa5ccca3f84eeb9bf
6
+ metadata.gz: 54c04edbf55eaeb2a4100e4dd6fdaf0ce4a4485aa6c0d144bf09f4284bd926cb257e0e1ae9b04a93ead527173a7b5a7f263a36159f7013af5fa8a05dc5032ae6
7
+ data.tar.gz: ae8e883bb03b9d1d1c18874e69c4719f9ab9420a448f347bd4203ffbce31996fe7a94e0f31714e505cbd06d52177af5a450b36825b23aa79a3bffe15ce053a28
@@ -1,3 +1,57 @@
1
+ #### 3.1.0
2
+
3
+ - Add .bulk_update and bulk_update!
4
+
5
+ #### 3.0.0
6
+
7
+ BREAKING CHANGES
8
+ - Update has_many, belongs_to signatures
9
+ - Rename Base#url -> #resource_path
10
+ - Drop ActiveModel <= 4.1
11
+ - `#errors` is now always an ActiveModel::Errors instance
12
+
13
+ DEPRECATION NOTICE
14
+ - ValidationError is deprecated and will be removed in 3.1.
15
+
16
+ Other changes
17
+ - Add :cache option for requests
18
+ Set default store at MyApi::Api.cache_store=
19
+ See example railtie.rb for auto-config
20
+ Examples:
21
+ SomeResource.all(cache: true)
22
+ uses MyApi::Api.cache_store
23
+ SomeResource.find(id, cache: {expires_in: 5.minutes})
24
+ uses MyApi::Api.cache_store with specified options
25
+ SomeResource.find(id, cache: Dalli::Store.new(...))
26
+ uses provided cache store (with its default options)
27
+ - Make bearer_credential_key allow strings or symbols
28
+ - Warn on attr name collision
29
+ - Update CA certificates
30
+ - Better default output for #as_json
31
+ - Allow ActiveModel/Support 6.0
32
+ - Resolve deprecation messages on Ruby 2.6
33
+ - Add #factory
34
+ - API response :errors may be hash or array
35
+ - Add RecordInvalid#errors
36
+ - Better Ruby and ActiveModel integration
37
+ - #eql?, #==, #hash
38
+ - #model_name
39
+ - #i18n_scope, config via Api.i18n_scope=
40
+ - #cache_key, #cache_version, #cache_key_with_version
41
+
42
+ #### 2.2.2
43
+
44
+ - Update certs
45
+
46
+ #### 2.2.1
47
+
48
+ - Fix decimal attributes on Ruby <= 2.5.x
49
+
50
+ #### 2.2.0
51
+
52
+ - Allow ActiveSupport 6.0
53
+ - Resolve deprecation messages on Ruby 2.6
54
+
1
55
  #### 2.1.2
2
56
 
3
57
  - Fix URL processing when frozen
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2018 Notioneer, Inc.
1
+ Copyright (c) 2014-2020 Notioneer, Inc.
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  NCore is a Ruby gem designed to help build REST API clients. It is not an API
4
4
  client by itself, but provides several useful building blocks to build one.
5
5
 
6
- It relies on `excon` for HTTP handling and `activesupport`.
6
+ It relies on `excon` for HTTP handling and `activemodel`.
7
7
 
8
8
  If present, uses `multi_json`. Otherwise, the stdlib 'json' is used.
9
9
  'multi_json' with an accelerated json gem is recommended.
@@ -21,6 +21,8 @@ module MyApi
21
21
 
22
22
  self.strict_attributes = false
23
23
 
24
+ self.i18n_scope = :my_api
25
+
24
26
  self.instrument_key = 'request.my_api'
25
27
 
26
28
  self.status_page = 'http://my.api.status.page'
@@ -30,6 +32,12 @@ module MyApi
30
32
  # self.bearer_credential_key = :api_key
31
33
 
32
34
  self.credentials_error_message = %Q{Missing API credentials. Set default credentials using "MyApi.credentials = {api_user: YOUR_API_USER, api_key: YOUR_API_KEY}"}
35
+
36
+ # self.verify_ssl = true
37
+
38
+ # self.ssl_cert_bundle = 'path/to/bundle.crt'
39
+
40
+ # self.logger = Logger.new(STDOUT)
33
41
  end
34
42
 
35
43
  end
@@ -1,6 +1,10 @@
1
1
  module MyApi
2
2
  class Railtie < Rails::Railtie
3
3
 
4
+ config.after_initialize do
5
+ MyApi::Api.cache_store = Rails.cache
6
+ end
7
+
4
8
  initializer "my_api.log_runtime" do |app|
5
9
  require 'my_api/rails/log_subscriber'
6
10
  ActiveSupport.on_load(:action_controller) do
@@ -1,14 +1,43 @@
1
1
  require 'active_support/all'
2
+ require 'active_model'
2
3
  require 'excon'
3
4
  require 'pp'
4
5
 
5
- %w(version builder configuration associations attributes client collection exceptions identity lifecycle util base singleton_base).each do |f|
6
+ %w(
7
+ version
8
+ builder
9
+ configuration
10
+ associations
11
+ attributes
12
+ client
13
+ client_cache
14
+ collection
15
+ exceptions
16
+ identity
17
+ lifecycle
18
+ util
19
+ base
20
+ singleton_base
21
+ ).each do |f|
6
22
  require "ncore/#{f}"
7
23
  end
8
24
 
9
- %w(all build count create delete delete_bulk delete_single find find_single update).each do |f|
25
+ %w(
26
+ all
27
+ build
28
+ count
29
+ create
30
+ delete
31
+ delete_bulk
32
+ delete_single
33
+ find
34
+ find_single
35
+ update
36
+ update_bulk
37
+ ).each do |f|
10
38
  require "ncore/methods/#{f}"
11
39
  end
12
40
 
13
41
  require 'ncore/rails/action_controller' if defined?(::ActionController)
14
- require 'ncore/rails/active_model' if defined?(::ActiveModel)
42
+ require 'ncore/rails/active_model'
43
+ require 'ncore/rails/module_fix'
@@ -1,17 +1,17 @@
1
1
  module NCore
2
2
  module Associations
3
3
 
4
- def has_many(assoc, klass=nil)
4
+ def has_many(assoc, class_name: nil)
5
5
  assoc = assoc.to_s
6
- klass ||= "#{module_name}::#{assoc.camelize.singularize}"
6
+ klass = class_name || "#{module_name}::#{assoc.camelize.singularize}"
7
7
  key = "#{attrib_name}_id"
8
8
  class_eval <<-M1, __FILE__, __LINE__+1
9
9
  def #{assoc}(params={})
10
10
  return [] unless id
11
11
  reload = params.delete :reload
12
- params = parse_request_params(params).reverse_merge credentials: api_creds
13
12
  cacheable = params.except(:credentials, :request).empty?
14
- params.merge! #{key}: id
13
+ params = parse_request_params(params).reverse_merge credentials: api_creds
14
+ params[:#{key}] = id
15
15
  if cacheable
16
16
  # only cache unfiltered, default api call
17
17
  @attribs[:#{assoc}] = (!reload && @attribs[:#{assoc}]) || #{klass}.all(params)
@@ -24,7 +24,7 @@ module NCore
24
24
  def find_#{assoc.singularize}(aid, params={})
25
25
  raise UnsavedObjectError unless id
26
26
  params = parse_request_params(params).reverse_merge credentials: api_creds
27
- params.merge! #{key}: id
27
+ params[:#{key}] = id
28
28
  #{klass}.find(aid, params)
29
29
  end
30
30
  M2
@@ -33,7 +33,7 @@ module NCore
33
33
  def create_#{assoc.singularize}(params={})
34
34
  raise UnsavedObjectError unless id
35
35
  params = parse_request_params(params).reverse_merge credentials: api_creds
36
- params.merge! #{key}: id
36
+ params[:#{key}] = id
37
37
  #{klass}.create(params)
38
38
  end
39
39
  M3
@@ -42,7 +42,7 @@ module NCore
42
42
  def update_#{assoc.singularize}(aid, params={})
43
43
  raise UnsavedObjectError unless id
44
44
  params = parse_request_params(params).reverse_merge credentials: api_creds
45
- params.merge! #{key}: id
45
+ params[:#{key}] = id
46
46
  #{klass}.update(aid, params)
47
47
  end
48
48
  M4
@@ -50,7 +50,7 @@ module NCore
50
50
  def create_#{assoc.singularize}!(params={})
51
51
  raise UnsavedObjectError unless id
52
52
  params = parse_request_params(params).reverse_merge credentials: api_creds
53
- params.merge! #{key}: id
53
+ params[:#{key}] = id
54
54
  #{klass}.create!(params)
55
55
  end
56
56
  M5
@@ -58,7 +58,7 @@ module NCore
58
58
  def update_#{assoc.singularize}!(aid, params={})
59
59
  raise UnsavedObjectError unless id
60
60
  params = parse_request_params(params).reverse_merge credentials: api_creds
61
- params.merge! #{key}: id
61
+ params[:#{key}] = id
62
62
  #{klass}.update!(aid, params)
63
63
  end
64
64
  M6
@@ -67,7 +67,7 @@ module NCore
67
67
  def delete_#{assoc.singularize}(aid, params={})
68
68
  raise UnsavedObjectError unless id
69
69
  params = parse_request_params(params).reverse_merge credentials: api_creds
70
- params.merge! #{key}: id
70
+ params[:#{key}] = id
71
71
  #{klass}.delete(aid, params)
72
72
  end
73
73
  M7
@@ -75,15 +75,15 @@ module NCore
75
75
  def delete_#{assoc.singularize}!(aid, params={})
76
76
  raise UnsavedObjectError unless id
77
77
  params = parse_request_params(params).reverse_merge credentials: api_creds
78
- params.merge! #{key}: id
78
+ params[:#{key}] = id
79
79
  #{klass}.delete!(aid, params)
80
80
  end
81
81
  M8
82
82
  end
83
83
 
84
- def belongs_to(assoc, klass=nil)
84
+ def belongs_to(assoc, class_name: nil)
85
85
  assoc = assoc.to_s
86
- klass ||= "#{module_name}::#{assoc.camelize}"
86
+ klass = class_name || "#{module_name}::#{assoc.camelize}"
87
87
  class_eval <<-M1, __FILE__, __LINE__+1
88
88
  attr :#{assoc}_id
89
89
  def #{assoc}(params={})
@@ -10,6 +10,7 @@ module NCore
10
10
  module ClassMethods
11
11
  def attr(*attrs)
12
12
  attrs.each do |attr|
13
+ check_existing_method(attr)
13
14
  class_eval <<-AR, __FILE__, __LINE__+1
14
15
  def #{attr}
15
16
  self[:#{attr}]
@@ -20,6 +21,7 @@ module NCore
20
21
 
21
22
  def attr_datetime(*attrs)
22
23
  attrs.each do |attr|
24
+ check_existing_method(attr)
23
25
  class_eval <<-AD, __FILE__, __LINE__+1
24
26
  def #{attr}
25
27
  case self[:#{attr}]
@@ -39,6 +41,7 @@ module NCore
39
41
 
40
42
  def attr_decimal(*attrs)
41
43
  attrs.each do |attr|
44
+ check_existing_method(attr)
42
45
  class_eval <<-AD, __FILE__, __LINE__+1
43
46
  def #{attr}
44
47
  case self[:#{attr}]
@@ -52,10 +55,19 @@ module NCore
52
55
  end
53
56
  end
54
57
 
58
+ def check_existing_method(attr)
59
+ if method_defined?(attr) || private_method_defined?(attr)
60
+ sc = self
61
+ sc = sc.superclass while sc.superclass != Object
62
+ warn "Warning: Existing method #{sc.name}##{attr} being overwritten at #{caller[3]}"
63
+ end
64
+ end
65
+
55
66
  def parse_request_params(params={}, opts={})
56
67
  params = params.with_indifferent_access
57
68
  req = params.delete(:request)
58
69
  creds = params.delete(:credentials)
70
+ cache = params.delete(:cache)
59
71
  if opts[:json_root]
60
72
  if params.key?(opts[:json_root])
61
73
  o = params
@@ -67,6 +79,7 @@ module NCore
67
79
  end
68
80
  o[:request] = req if req
69
81
  o[:credentials] = creds if creds
82
+ o[:cache] = cache if cache
70
83
  o
71
84
  end
72
85
  end
@@ -86,23 +99,18 @@ module NCore
86
99
  creds_attr = attribs.delete(:credentials)
87
100
  @api_creds = api_creds || creds_attr
88
101
 
89
- if attribs.keys.sort == %w(data error metadata)
90
- load_attrs = attribs
91
- else
92
- load_attrs = {
93
- metadata: attribs.delete(:metadata),
94
- errors: attribs.delete(:errors),
95
- data: attribs.delete(:data) || attribs
96
- }
97
- end
98
- load(load_attrs)
102
+ load(
103
+ metadata: attribs.delete(:metadata),
104
+ errors: attribs.delete(:errors),
105
+ data: attribs.delete(:data) || attribs
106
+ )
99
107
  end
100
108
 
101
109
 
102
110
  def attributes
103
111
  Util.deep_clone(@attribs)
104
112
  end
105
-
113
+ alias_method :as_json, :attributes
106
114
 
107
115
  def [](attr)
108
116
  @attribs[attr]
@@ -150,10 +158,10 @@ module NCore
150
158
  end
151
159
 
152
160
 
153
- def load(parsed)
154
- self.metadata = parsed[:metadata] || {}.with_indifferent_access
155
- self.errors = parsed[:errors] || {}.with_indifferent_access
156
- parsed[:data].each do |k,v|
161
+ def load(data:, errors: nil, metadata: nil)
162
+ self.metadata = metadata || {}.with_indifferent_access
163
+ self.errors = parse_errors(errors)
164
+ data.each do |k,v|
157
165
  if respond_to? "#{k}="
158
166
  send "#{k}=", self.class.interpret_type(v, api_creds)
159
167
  else
@@ -163,13 +171,28 @@ module NCore
163
171
  self
164
172
  end
165
173
 
174
+ def parse_errors(errors)
175
+ errors ||= []
176
+ if errors.is_a?(::ActiveModel::Errors)
177
+ errors
178
+ else
179
+ ::ActiveModel::Errors.new(self).tap do |e0|
180
+ errors.each{|msg| e0.add :base, msg }
181
+ end
182
+ end
183
+ end
184
+
166
185
  end
167
186
 
168
187
 
169
- class BigMoney < BigDecimal
188
+ class BigMoney < SimpleDelegator
189
+
190
+ def initialize(*args)
191
+ __setobj__(BigDecimal(*args))
192
+ end
170
193
 
171
194
  def to_s
172
- if (self % BigDecimal.new('0.01')) == 0
195
+ if (self % BigDecimal('0.01')) == 0
173
196
  '%.2f' % self
174
197
  else
175
198
  super
@@ -4,8 +4,10 @@ module NCore
4
4
 
5
5
  included do
6
6
  extend Associations
7
+ include ActiveModel
7
8
  include Attributes
8
9
  include Client
10
+ include Client::Cache
9
11
  include Identity
10
12
  include Lifecycle
11
13
  include Util
@@ -21,15 +23,16 @@ module NCore
21
23
  include DeleteBulk if types.include? :delete_bulk
22
24
  include Find if types.include? :find
23
25
  include Update if types.include? :update
26
+ include UpdateBulk if types.include? :update_bulk
24
27
  end
25
28
 
26
- def url
29
+ def resource_path
27
30
  class_name.underscore.pluralize
28
31
  end
29
32
  end
30
33
 
31
- def url
32
- "#{self.class.url}/#{CGI.escape((id||'-').to_s)}"
34
+ def resource_path
35
+ "#{self.class.resource_path}/#{CGI.escape((id||'-').to_s)}"
33
36
  end
34
37
 
35
38
  end
@@ -6,11 +6,12 @@ module NCore
6
6
 
7
7
  module ClassMethods
8
8
 
9
- # opts - {params: {}, headers: {}, credentials: {}}
9
+ # opts - {params: {}, headers: {}, credentials: {}, cache: {}}
10
10
  # unknown keys assumed to be :params if :params is missing
11
11
  def request(method, url, opts={})
12
12
  opts = opts.with_indifferent_access
13
13
  request_credentials = opts.delete 'credentials'
14
+ cache_opts = opts.delete 'cache'
14
15
  headers = opts.delete('headers') || {}
15
16
  params = opts['params'] || opts
16
17
 
@@ -48,7 +49,7 @@ module NCore
48
49
  write_timeout: 50,
49
50
  }
50
51
 
51
- response = execute_request(rest_opts)
52
+ response = execute_request(rest_opts, cache_opts)
52
53
  parsed = parse_response(response)
53
54
  [parsed, request_credentials]
54
55
  end
@@ -58,7 +59,7 @@ module NCore
58
59
 
59
60
  def retrieve_credentials
60
61
  if credentials.blank?
61
- raise parent::Error, credentials_error_message
62
+ raise module_parent::Error, credentials_error_message
62
63
  end
63
64
  credentials
64
65
  end
@@ -69,7 +70,7 @@ module NCore
69
70
 
70
71
  def retrieve_default_url
71
72
  if default_url.blank?
72
- raise parent::Error, credentials_error_message
73
+ raise module_parent::Error, credentials_error_message
73
74
  end
74
75
  default_url
75
76
  end
@@ -87,12 +88,12 @@ module NCore
87
88
 
88
89
  def build_query_string(params)
89
90
  if params.any?
90
- query_string = params.map do |k,v|
91
+ query_string = params.sort.map do |k,v|
91
92
  if v.is_a?(Array)
92
93
  if v.empty?
93
94
  "#{k.to_s}[]="
94
95
  else
95
- v.map do |v2|
96
+ v.sort.map do |v2|
96
97
  "#{k.to_s}[]=#{CGI::escape(v2.to_s)}"
97
98
  end.join('&')
98
99
  end
@@ -163,7 +164,7 @@ module NCore
163
164
  end
164
165
 
165
166
 
166
- def execute_request(rest_opts)
167
+ def execute_request(rest_opts, _)
167
168
  debug_request rest_opts if debug
168
169
 
169
170
  tries = 0
@@ -175,7 +176,8 @@ module NCore
175
176
  begin
176
177
  tries += 1
177
178
  response = connection.request rest_opts.except(:url)
178
- rescue Excon::Errors::SocketError, Excon::Error::Timeout, Errno::EADDRNOTAVAIL => e
179
+ rescue Excon::Error::Socket, Excon::Errors::SocketError, Excon::Error::Timeout,
180
+ Errno::EADDRNOTAVAIL => e
179
181
  # retry when keepalive was closed
180
182
  if tries <= 1 #&& e.message =~ /end of file reached/
181
183
  retry
@@ -187,30 +189,30 @@ module NCore
187
189
  debug_response response if debug
188
190
  end
189
191
  rescue Errno::ECONNRESET
190
- raise parent::ConnectionError, "Connection reset for #{host_for_error rest_opts[:url]} : check network or visit #{status_page}."
192
+ raise module_parent::ConnectionError, "Connection reset for #{host_for_error rest_opts[:url]} : check network or visit #{status_page}."
191
193
  rescue Errno::ECONNREFUSED
192
- raise parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
194
+ raise module_parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
193
195
  rescue Excon::Error::Timeout => e
194
196
  case e.message
195
197
  when /timeout reached/
196
- raise parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
198
+ raise module_parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
197
199
  else
198
200
  raise e
199
201
  end
200
202
  rescue Excon::Errors::SocketError => e
201
203
  case e.message
202
204
  when /Unable to verify certificate/
203
- raise parent::ConnectionError, "Unable to verify certificate for #{host_for_error rest_opts[:url]} : verify URL or disable SSL certificate verification (insecure)."
205
+ raise module_parent::ConnectionError, "Unable to verify certificate for #{host_for_error rest_opts[:url]} : verify URL or disable SSL certificate verification (insecure)."
204
206
  when /Name or service not known/, /No address associated with hostname/
205
- raise parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
207
+ raise module_parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
206
208
  when /Errno::ECONNREFUSED/
207
- raise parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
209
+ raise module_parent::ConnectionError, "Connection error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
208
210
  else
209
211
  raise e
210
212
  end
211
213
  rescue SocketError => e
212
214
  if e.message =~ /nodename nor servname provided/
213
- raise parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
215
+ raise module_parent::ConnectionError, "DNS error for #{host_for_error rest_opts[:url]} : check network and DNS or visit #{status_page}."
214
216
  else
215
217
  raise e
216
218
  end
@@ -218,21 +220,21 @@ module NCore
218
220
 
219
221
  case response.status
220
222
  when 401 # API auth valid; API call itself is an auth-related call and failed
221
- raise parent::AuthenticationFailed
223
+ raise module_parent::AuthenticationFailed
222
224
  when 402
223
- raise parent::AccountInactive, "Account inactive; login to portal to check account status."
225
+ raise module_parent::AccountInactive, "Account inactive; login to portal to check account status."
224
226
  when 403 # API auth failed or insufficient permissions
225
- raise parent::AccessDenied, "Access denied; check your API credentials and permissions."
227
+ raise module_parent::AccessDenied, "Access denied; check your API credentials and permissions."
226
228
  when 404
227
- raise parent::RecordNotFound
229
+ raise module_parent::RecordNotFound
228
230
  when 409, 422
229
231
  # pass through
230
232
  when 429
231
- raise parent::RateLimited
233
+ raise module_parent::RateLimited
232
234
  when 400..499
233
- raise parent::Error, "Client error: #{response.status}\n #{response.body}"
235
+ raise module_parent::Error, "Client error: #{response.status}\n #{response.body}"
234
236
  when 500..599
235
- raise parent::Error, "Server error: #{response.status}\n #{response.body}"
237
+ raise module_parent::Error, "Server error: #{response.status}\n #{response.body}"
236
238
  end
237
239
  response
238
240
  end
@@ -245,20 +247,20 @@ module NCore
245
247
  begin
246
248
  json = MultiJson.load(response.body, symbolize_keys: false) || {}
247
249
  rescue MultiJson::ParseError
248
- raise parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
250
+ raise module_parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
249
251
  end
250
252
  else
251
253
  begin
252
254
  json = JSON.parse(response.body, symbolize_names: false) || {}
253
255
  rescue JSON::ParserError
254
- raise parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
256
+ raise module_parent::Error, "Unable to parse API response; HTTP status: #{response.status}; body: #{response.body.inspect}"
255
257
  end
256
258
  end
257
259
  end
258
260
  json = json.with_indifferent_access
259
261
  errors = json.delete(:errors) || []
260
262
  if errors.any?
261
- errors = errors.values.flatten
263
+ errors = errors.values.flatten if errors.is_a?(Hash)
262
264
  metadata, json = json, {}
263
265
  else
264
266
  errors = []
@@ -301,7 +303,7 @@ module NCore
301
303
  else
302
304
  m = 'WARNNG: SSL cert verification is disabled.'
303
305
  unless verify_ssl
304
- m += " Enable verification with: #{parent}::Api.verify_ssl = true."
306
+ m += " Enable verification with: #{module_parent}::Api.verify_ssl = true."
305
307
  end
306
308
  unless bundle_readable
307
309
  m += " Unable to read CA bundle #{ssl_cert_bundle}."