lm_rest 1.0.6 → 1.0.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.
@@ -3,10 +3,23 @@ require 'base64'
3
3
  require 'openssl'
4
4
  require 'rest-client'
5
5
  require 'json'
6
+ require 'uri'
6
7
  require 'lm_rest/resource'
7
8
  require 'lm_rest/request_params'
8
9
 
9
10
  module LMRest
11
+ class APIError < StandardError
12
+ attr_reader :status, :body, :headers
13
+
14
+ def initialize(response)
15
+ @status = response.code
16
+ @body = response.body
17
+ @headers = response.headers
18
+
19
+ super("LogicMonitor API request failed with status #{@status}")
20
+ end
21
+ end
22
+
10
23
  class APIClient
11
24
  include RequestParams
12
25
 
@@ -39,19 +52,14 @@ module LMRest
39
52
  uri.split("?")[0].split("/").join("/")
40
53
  end
41
54
 
42
- def sign(method, uri, data = nil)
43
-
55
+ def sign(method, uri, data = nil, content_type = 'application/json')
44
56
  resource_uri = uri_to_resource_uri(uri)
45
57
 
46
58
  time = DateTime.now.strftime('%Q')
47
59
 
48
60
  http_method = method.to_s.upcase
49
61
 
50
- if data.nil? || data.empty?
51
- data = ''
52
- else
53
- data = data.to_json.to_s
54
- end
62
+ data = data_for_signature(data, content_type)
55
63
 
56
64
  message = "#{http_method}#{time}#{data}#{resource_uri}"
57
65
 
@@ -66,45 +74,67 @@ module LMRest
66
74
  "LMv1 #{access_id}:#{signature}:#{time}"
67
75
  end
68
76
 
69
- def request(method, uri, params={})
70
- headers = {}
71
- headers['Authorization'] = sign(method, uri, params)
72
- headers['Content-Type'] = 'application/json'
73
- headers['Accept'] = 'application/json, text/javascript'
74
- headers['X-version'] = '3'
75
-
77
+ def request(method, uri, params = nil, content_type: 'application/json')
78
+ headers = build_headers(method, uri, params, content_type)
76
79
  url = api_url + uri
77
- #puts "URL: " + url
78
- #puts headers
79
-
80
- json_params = params.to_json
81
-
82
- begin
83
- case method
84
- when :get
85
- response = RestClient.get(url, headers)
86
- when :post
87
- response = RestClient.post(url, json_params, headers)
88
- when :put
89
- response = RestClient.put(url, json_params, headers)
90
- when :delete
91
- response = RestClient.delete(url, headers: headers)
80
+ payload = serialize_payload(params, content_type)
81
+
82
+ response = execute_request(method.to_sym, url, payload, headers)
83
+ handle_response(response)
84
+
85
+ parse_response(response)
86
+ end
87
+
88
+ def build_headers(method, uri, params, content_type)
89
+ headers = {
90
+ 'Authorization' => sign(method, uri, params, content_type),
91
+ 'Accept' => 'application/json, text/javascript',
92
+ 'X-version' => '3'
93
+ }
94
+
95
+ headers['Content-Type'] = content_type unless multipart_form?(content_type)
96
+ headers
97
+ end
98
+
99
+ def execute_request(method, url, payload, headers)
100
+ case method
101
+ when :get
102
+ RestClient.get(url, headers)
103
+ when :post
104
+ RestClient.post(url, payload, headers)
105
+ when :put
106
+ RestClient.put(url, payload, headers)
107
+ when :patch
108
+ RestClient.patch(url, payload, headers)
109
+ when :delete
110
+ if payload.nil?
111
+ RestClient.delete(url, headers)
112
+ else
113
+ RestClient::Request.execute(method: :delete, url: url, payload: payload, headers: headers)
92
114
  end
93
- rescue => e
94
- puts e.http_body
95
- raise
115
+ else
116
+ raise ArgumentError, "unsupported HTTP method: #{method}"
96
117
  end
118
+ end
97
119
 
98
- if response.code != 200
99
- puts response.code.to_s + ":" + response.body.to_s
100
- raise
101
- end
120
+ def handle_response(response)
121
+ raise APIError.new(response) unless response.code.between?(200, 299)
122
+
123
+ @limit = response.headers[:x_rate_limit_limit] || response.headers['x_rate_limit_limit']
124
+ @remaining = response.headers[:x_rate_limit_remaining] || response.headers['x_rate_limit_remaining']
125
+ @window = response.headers[:x_rate_limit_window] || response.headers['x_rate_limit_window']
126
+ end
102
127
 
103
- @limit = response.headers['x_rate_limit_limit']
104
- @remaining = response.headers['x_rate_limit_remaining']
105
- @window = response.headers['x_rate_limit_window']
128
+ def parse_response(response)
129
+ return nil if response.body.nil? || response.body.empty?
106
130
 
107
- JSON.parse(response.body)
131
+ content_type = response.headers[:content_type] || response.headers['content_type']
132
+
133
+ if content_type.to_s.include?('application/json')
134
+ JSON.parse(response.body)
135
+ else
136
+ response.body
137
+ end
108
138
  end
109
139
 
110
140
  # Handles making multiple requests to the API if pagination is necessary.
@@ -114,70 +144,59 @@ module LMRest
114
144
  # If you need to walk through resources page-by-page manullay, use the
115
145
  # request() method with the 'offset' and 'size' params
116
146
  #
117
- def paginate(uri, params)
118
-
119
- # Hooray for pagination logic!
120
- if (params[:size] == 0 || params[:size].nil? || params[:size] > ITEMS_SIZE_LIMIT)
121
- # save user-entered size in a param for use later
122
- user_size = params[:size]
123
-
124
- # set our size param to the max
125
- params[:size] = ITEMS_SIZE_LIMIT
126
-
127
- # Set our offset to grab the first page of results
128
- params[:offset] ||= 0
129
-
130
- # make the initial request
131
- body = request(:get, uri.call(params), nil)
132
-
133
- # pull the actual items out of the request body and into our
134
- # item_collector while we build up the items list
135
- item_collector = body['items']
136
-
137
- # The API sends the total number of objects back in the first request.
138
- # We need this to determine how many more pages to pull
139
- total = body['total']
140
-
141
- # If user didn't pass size param, set it to total
142
- # This just means you'll get all items if not specifying a size
143
- user_size ||= total
144
-
145
- # If the user passed a size larger than what's available, set it to
146
- # total to retrieve all items
147
- if user_size > total
148
- user_size = total
149
- end
147
+ def paginate(uri, params, method = :get, payload = nil, content_type = 'application/json')
148
+ params = normalize_params_hash(params)
149
+ user_size = params[:size]&.to_i
150
+ params[:size] = user_size && user_size < ITEMS_SIZE_LIMIT ? user_size : ITEMS_SIZE_LIMIT
151
+ params[:offset] = (params[:offset] || 0).to_i
152
+
153
+ body = request(method, uri.call(params), payload, content_type: content_type)
154
+ return body unless body.is_a?(Hash) && body.key?('items')
155
+
156
+ total = (body['total'] || body['items'].length).to_i
157
+ user_size = determine_user_size(user_size, total)
158
+ item_collector = body['items'].first(user_size)
159
+
160
+ while item_collector.length < user_size
161
+ params[:offset] += params[:size].to_i
162
+ remaining = user_size - item_collector.length
163
+ params[:size] = [remaining, ITEMS_SIZE_LIMIT].min
164
+ body = request(method, uri.call(params), payload, content_type: content_type)
165
+ break unless body.is_a?(Hash) && body.key?('items')
166
+ break if body['items'].empty?
167
+
168
+ item_collector.concat(body['items'].first(remaining))
169
+ end
150
170
 
151
- # calculate the remaining number of items (after first request)
152
- # then use that to figure out how many more times we need to call
153
- # request() to get all the items, then do it
154
- pages_remaining = ((user_size - ITEMS_SIZE_LIMIT).to_f/ITEMS_SIZE_LIMIT).ceil
171
+ body['items'] = item_collector
172
+ body
173
+ end
155
174
 
156
- pages_remaining.times do |page|
175
+ def determine_user_size(user_size, total)
176
+ user_size ||= total
177
+ user_size > total ? total : user_size
178
+ end
157
179
 
158
- # Increment the offset by the limit to get the next page
159
- params[:offset] += ITEMS_SIZE_LIMIT
180
+ def self.define_operation_methods(operations)
181
+ operations.each do |method_name, operation|
182
+ define_operation_method(method_name, operation)
183
+ end
184
+ end
160
185
 
161
- # if this is the last page, get the remainder
162
- if page == pages_remaining - 1
163
- params[:size] = user_size%ITEMS_SIZE_LIMIT
164
- else
165
- # else, get a whole page
166
- params[:size] = ITEMS_SIZE_LIMIT
167
- end
186
+ def self.define_operation_method(method_name, operation)
187
+ define_method(method_name) do |*args|
188
+ perform_operation(operation, args)
189
+ end
190
+ end
168
191
 
169
- # make a subsequent request with modified params
170
- body = request(:get, uri.call(params), nil)
192
+ def self.define_alias_methods(aliases)
193
+ aliases.each do |alias_name, target_name|
194
+ next if alias_name == target_name
195
+ next unless method_defined?(target_name)
171
196
 
172
- # add these items to our item_collector
173
- item_collector += body['items']
197
+ define_method(alias_name) do |*args|
198
+ public_send(target_name, *args)
174
199
  end
175
-
176
- body['items'] = item_collector
177
- body
178
- else
179
- # No pagination required, just request the page
180
- request(:get, uri.call(params), nil)
181
200
  end
182
201
  end
183
202
 
@@ -189,78 +208,74 @@ module LMRest
189
208
  attributes['actions'].each do |action|
190
209
  case action
191
210
  when 'get'
211
+ define_get_methods(resource_uri, singular, plural)
212
+ when 'add'
213
+ define_add_method(resource_uri, singular)
214
+ when 'update'
215
+ define_update_method(resource_uri, singular)
216
+ when 'delete'
217
+ define_delete_method(resource_uri, singular)
218
+ end
219
+ end
220
+ end
192
221
 
193
- uri = lambda { |params| "#{resource_uri}#{RequestParams.parameterize(params)}"}
222
+ def self.define_get_methods(resource_uri, singular, plural)
223
+ uri = lambda { |params| "#{resource_uri}#{RequestParams.parameterize(params)}" }
194
224
 
195
- unless plural.nil?
196
- # Define a method to fetch multiple resources with optional params
197
- define_method("get_#{plural}") do |params = {}|
198
- Resource.parse paginate(uri, params)
199
- end
200
- end
201
-
202
- # Define a method to get one resource by it's id number, with optional
203
- # params, thought now that I think about it I'm not sure why you'd pass
204
- # params when grabbing just one resource.
205
-
206
- # Some resources are Singletons
207
- unless singular.nil?
208
- define_method("get_#{singular}") do |*args|
209
- case args.size
210
- when 0
211
- Resource.parse request(:get, "#{resource_uri}", nil)
212
- when 1
213
- Resource.parse request(:get, "#{resource_uri}/#{args[0]}", nil)
214
- when 2
215
- Resource.parse request(:get, "#{resource_uri}/#{args[0]}#{RequestParams.parameterize(args[1])}", nil)
216
- else
217
- raise ArgumentError.new("wrong number for arguments (#{args.count} for 1..2)")
218
- end
219
- end
220
- end
225
+ define_plural_get_method(uri, plural) unless plural.nil?
226
+ define_singular_get_method(resource_uri, singular) unless singular.nil?
227
+ end
221
228
 
222
- when 'add'
229
+ def self.define_plural_get_method(uri, plural)
230
+ define_method("get_#{plural}") do |params = {}|
231
+ Resource.parse paginate(uri, params)
232
+ end
233
+ end
223
234
 
224
- # Define a method to add a new resource to the account
225
- define_method("add_#{singular}") do |properties|
226
- if properties.class == LMRest::Resource
227
- Resource.parse request(:post, "#{resource_uri}", properties.to_h)
228
- else
229
- Resource.parse request(:post, "#{resource_uri}", properties)
230
- end
231
- end
235
+ def self.define_singular_get_method(resource_uri, singular)
236
+ define_method("get_#{singular}") do |*args|
237
+ case args.size
238
+ when 0
239
+ Resource.parse request(:get, "#{resource_uri}", nil)
240
+ when 1
241
+ Resource.parse request(:get, "#{resource_uri}/#{args[0]}", nil)
242
+ when 2
243
+ Resource.parse request(:get, "#{resource_uri}/#{args[0]}#{RequestParams.parameterize(args[1])}", nil)
244
+ else
245
+ raise ArgumentError.new("wrong number for arguments (#{args.count} for 1..2)")
246
+ end
247
+ end
248
+ end
232
249
 
233
- when 'update'
250
+ def self.define_add_method(resource_uri, singular)
251
+ define_method("add_#{singular}") do |properties|
252
+ properties_hash = properties.is_a?(LMRest::Resource) ? properties.to_h : properties
253
+ Resource.parse request(:post, "#{resource_uri}", properties_hash)
254
+ end
255
+ end
234
256
 
235
- # Define a method to update a resource
236
- define_method("update_#{singular}") do |id, properties = {}|
237
- if id.class == LMRest::Resource
238
- Resource.parse request(:put, "#{resource_uri}/#{id.id}", id.to_h)
239
- else
240
- Resource.parse request(:put, "#{resource_uri}/#{id}", properties)
241
- end
242
- end
257
+ def self.define_update_method(resource_uri, singular)
258
+ define_method("update_#{singular}") do |id, properties = {}|
259
+ if id.is_a?(LMRest::Resource)
260
+ properties = id.to_h if properties.empty?
261
+ id = id.id
262
+ end
243
263
 
244
- when 'delete'
264
+ Resource.parse request(:put, "#{resource_uri}/#{id}", properties)
265
+ end
266
+ end
245
267
 
246
- # Define a method to delete the resource
247
- define_method("delete_#{singular}") do |id|
248
- if id.class == LMRest::Resource
249
- id = id.id
250
- Resource.parse request(:delete, "#{resource_uri}/#{id}", nil)
251
- else
252
- Resource.parse request(:delete, "#{resource_uri}/#{id}", nil)
253
- end
254
- end
255
- end
268
+ def self.define_delete_method(resource_uri, singular)
269
+ define_method("delete_#{singular}") do |id, params = {}|
270
+ id = id.id if id.is_a?(LMRest::Resource)
271
+ uri = "#{resource_uri}/#{id}#{RequestParams.parameterize(params)}"
272
+ Resource.parse request(:delete, uri, nil)
256
273
  end
257
274
  end
258
275
 
259
276
  def self.define_child_methods(resource_type, attributes)
260
277
  parent_singular = attributes['method_names']['singular']
261
- parent_plural = attributes['method_names']['plural']
262
278
  parent_resource_uri = attributes['url']
263
- parent_id = attributes['parent_id_key']
264
279
  children = attributes['children']
265
280
 
266
281
  children.each do |child_name|
@@ -275,39 +290,56 @@ module LMRest
275
290
  child_resource_uri = attributes['url'].split("/").last
276
291
 
277
292
  child['actions'].each do |action|
278
- case action
279
- when 'get'
280
-
293
+ case action
294
+ when 'get'
281
295
  define_method("get_#{parent_singular}_#{child_plural}") do |id, params = {}, &block|
282
296
  uri = lambda { |params| "#{parent_resource_uri}/#{id}/#{child['method_names']['plural']}#{RequestParams.parameterize(params)}" }
283
297
  Resource.parse paginate(uri, params)
284
298
  end
285
299
 
286
- when 'add'
287
- when 'update'
288
- when 'delete'
289
- end
300
+ when 'add'
301
+ define_method("add_#{parent_singular}_#{child_singular}") do |parent_id, properties|
302
+ properties_hash = properties.is_a?(LMRest::Resource) ? properties.to_h : properties
303
+ Resource.parse request(:post, "#{parent_resource_uri}/#{parent_id}/#{child_resource_uri}", properties_hash)
304
+ end
305
+
306
+ when 'update'
307
+ define_method("update_#{parent_singular}_#{child_singular}") do |parent_id, child_id, properties = {}|
308
+ Resource.parse request(:put, "#{parent_resource_uri}/#{parent_id}/#{child_resource_uri}/#{child_id}", properties)
309
+ end
310
+
311
+ when 'delete'
312
+ define_method("delete_#{parent_singular}_#{child_singular}") do |parent_id, child_id|
313
+ Resource.parse request(:delete, "#{parent_resource_uri}/#{parent_id}/#{child_resource_uri}/#{child_id}", nil)
314
+ end
315
+ end
290
316
  end
291
317
  end
292
318
  end
293
319
 
294
320
  # Define methods based on the JSON structure
295
321
  def self.setup
322
+ return if @api_methods_defined
323
+
296
324
  @@api_definition_path = File.expand_path(File.join(File.dirname(__FILE__), "../../api.json"))
297
325
  @@api_json = JSON.parse(File.read(@@api_definition_path))
298
- @@api_json.each do |resource_type, attributes|
299
- define_action_methods(resource_type, attributes) if attributes['actions']
300
- define_child_methods(resource_type, attributes) if attributes['children']
326
+
327
+ if @@api_json['operations']
328
+ define_operation_methods(@@api_json['operations'])
329
+ define_alias_methods(@@api_json['aliases'] || {})
330
+ else
331
+ @@api_json.each do |resource_type, attributes|
332
+ define_action_methods(resource_type, attributes) if attributes['actions']
333
+ define_child_methods(resource_type, attributes) if attributes['children']
334
+ end
301
335
  end
336
+
337
+ @api_methods_defined = true
302
338
  end
303
339
 
304
340
  # Ack a down collector, pass the ID and a comment
305
341
  def ack_collector_down(id, comment)
306
- if id.class == LMRest::Resource
307
- Resource.parse request(:post, "/setting/collectors/#{id.id}/ackdown", {comment: comment})
308
- else
309
- Resource.parse request(:post, "/setting/collectors/#{id}/ackdown", {comment: comment})
310
- end
342
+ ack_collector_down_alert_by_id(resource_id(id), comment: comment)
311
343
  end
312
344
 
313
345
  # run a report
@@ -319,8 +351,215 @@ module LMRest
319
351
  end
320
352
  end
321
353
 
354
+ # Helper to return execution counts for all websites across a time window.
355
+ # Assumes website interval is in minutes and locations/checkpoints is an array
356
+ # or comma-separated list.
357
+ def website_execution_stats(days = 30)
358
+ raise ArgumentError, "days must be positive" if days <= 0
359
+
360
+ websites = get_websites
361
+
362
+ per_site = websites.map do |site|
363
+ interval = website_interval_minutes(site)
364
+ location_count = website_location_count(site)
365
+ next if interval.nil? || interval <= 0
366
+
367
+ executions = (days * 24 * 60.0 / interval * location_count).ceil
368
+
369
+ {
370
+ id: site.respond_to?(:id) ? site.id : nil,
371
+ name: site.respond_to?(:name) ? site.name : nil,
372
+ interval_minutes: interval,
373
+ location_count: location_count,
374
+ executions: executions
375
+ }
376
+ end.compact
377
+
378
+ {
379
+ days: days,
380
+ website_count: per_site.count,
381
+ total_executions: per_site.sum { |row| row[:executions] },
382
+ websites: per_site
383
+ }
384
+ end
385
+
322
386
  private
323
387
 
324
388
  attr_accessor :access_key
389
+
390
+ def perform_operation(operation, args)
391
+ path_values, body, query_params = coerce_operation_arguments(operation, args)
392
+ content_type = request_content_type(operation)
393
+ method = operation['method'].to_sym
394
+ uri = lambda { |params| operation_uri(operation, path_values, params) }
395
+
396
+ response =
397
+ if operation_paginated?(operation)
398
+ paginate(uri, query_params, method, body, content_type)
399
+ else
400
+ request(method, uri.call(query_params), body, content_type: content_type)
401
+ end
402
+
403
+ Resource.parse(response)
404
+ end
405
+
406
+ def coerce_operation_arguments(operation, args)
407
+ args = args.dup
408
+ path_param_names = operation['path_params'] || []
409
+ has_body = !!operation['body_param']
410
+ query_params = {}
411
+ inferred_body = nil
412
+
413
+ if has_body
414
+ if args.length > path_param_names.length + 1 && args.last.is_a?(Hash)
415
+ query_params = args.pop
416
+ elsif !operation['body_required'] &&
417
+ args.length == path_param_names.length + 1 &&
418
+ args.last.is_a?(Hash) &&
419
+ query_params_only?(args.last, operation)
420
+ query_params = args.pop
421
+ end
422
+
423
+ if path_param_names.length == 1 &&
424
+ args.length == 1 &&
425
+ resource_like?(args.first) &&
426
+ %w[put patch].include?(operation['method'])
427
+ inferred_body = args.first
428
+ end
429
+ elsif args.length > path_param_names.length && args.last.is_a?(Hash)
430
+ query_params = args.pop
431
+ end
432
+
433
+ path_values = path_param_names.map do
434
+ value = args.shift
435
+ value = inferred_body if value.nil?
436
+ resource_id(value)
437
+ end
438
+
439
+ if path_values.any?(&:nil?)
440
+ raise ArgumentError, "#{operation['name']} requires path params: #{path_param_names.join(', ')}"
441
+ end
442
+
443
+ body = nil
444
+ if has_body
445
+ body_arg = args.shift || inferred_body
446
+ if body_arg.nil? && operation['body_required']
447
+ raise ArgumentError, "#{operation['name']} requires a request body"
448
+ end
449
+ body = resource_to_hash(body_arg) unless body_arg.nil?
450
+ end
451
+
452
+ unless args.empty?
453
+ raise ArgumentError, "wrong number of arguments for #{operation['name']}"
454
+ end
455
+
456
+ [path_values, body, normalize_params_hash(query_params)]
457
+ end
458
+
459
+ def operation_uri(operation, path_values, query_params)
460
+ uri = operation['path'].dup
461
+
462
+ (operation['path_params'] || []).zip(path_values).each do |name, value|
463
+ uri.gsub!("{#{name}}", URI.encode_www_form_component(value.to_s))
464
+ end
465
+
466
+ "#{uri}#{RequestParams.parameterize(query_params)}"
467
+ end
468
+
469
+ def request_content_type(operation)
470
+ Array(operation['consumes']).find { |type| type && !type.empty? } || 'application/json'
471
+ end
472
+
473
+ def operation_paginated?(operation)
474
+ operation['paginated'] || (operation['method'] == 'get' && operation['paginated_response'])
475
+ end
476
+
477
+ def normalize_params_hash(params)
478
+ return {} if params.nil?
479
+
480
+ params.each_with_object({}) do |(key, value), normalized|
481
+ normalized[key.to_sym] = value
482
+ end
483
+ end
484
+
485
+ def query_params_only?(params, operation)
486
+ query_param_names = operation['query_params'] || []
487
+ return false if query_param_names.empty?
488
+
489
+ params.keys.all? { |key| query_param_names.include?(key.to_s) }
490
+ end
491
+
492
+ def resource_like?(value)
493
+ value.is_a?(LMRest::Resource) || value.is_a?(Hash)
494
+ end
495
+
496
+ def resource_id(value)
497
+ case value
498
+ when LMRest::Resource
499
+ value.id if value.respond_to?(:id)
500
+ when Hash
501
+ value[:id] || value['id']
502
+ else
503
+ value
504
+ end
505
+ end
506
+
507
+ def resource_to_hash(value)
508
+ value.is_a?(LMRest::Resource) ? value.to_h : value
509
+ end
510
+
511
+ def data_for_signature(data, content_type)
512
+ return '' if data.nil? || (data.respond_to?(:empty?) && data.empty?)
513
+ return '' if multipart_form?(content_type)
514
+ return data if data.is_a?(String)
515
+
516
+ data.to_json.to_s
517
+ end
518
+
519
+ def serialize_payload(payload, content_type)
520
+ return nil if payload.nil?
521
+ return payload if multipart_form?(content_type)
522
+ return payload if payload.is_a?(String)
523
+
524
+ payload.to_json
525
+ end
526
+
527
+ def multipart_form?(content_type)
528
+ content_type.to_s.include?('multipart/form-data')
529
+ end
530
+
531
+ def website_interval_minutes(site)
532
+ interval_attrs = %i[checkInterval interval pollingInterval testInterval]
533
+ interval_attrs.each do |attr|
534
+ next unless site.respond_to?(attr)
535
+ value = site.send(attr)
536
+ return value.to_f if value
537
+ end
538
+ nil
539
+ end
540
+
541
+ def website_location_count(site)
542
+ location_attrs = %i[checkpoints checkpointIds checkpointId locations locationIds]
543
+ location_attrs.each do |attr|
544
+ next unless site.respond_to?(attr)
545
+ value = site.send(attr)
546
+ count = count_locations(value)
547
+ return count unless count.nil?
548
+ end
549
+ 0
550
+ end
551
+
552
+ def count_locations(value)
553
+ case value
554
+ when Array
555
+ value.compact.length
556
+ when String
557
+ value.split(",").map(&:strip).reject(&:empty?).length
558
+ when Numeric
559
+ 1
560
+ else
561
+ nil
562
+ end
563
+ end
325
564
  end
326
565
  end