moesif_rack 1.5.1 → 2.0.2

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: ed6afe67a6d8b2aa3c05b23a704d2ccfaadf9afdb078e9d66998f69fd7ed3f45
4
- data.tar.gz: 3cb56517e37769f179aabf0f1621c0969259558a46fa8594c017f85098f77423
3
+ metadata.gz: ad15d06f64a1f2617468e24773e1be77bda0b774474e8a1f5aa91574b05f5576
4
+ data.tar.gz: 5456b4c5efc5b9239361603192cb0c3d0635cb63ed0be8ebd89fddaca96777b0
5
5
  SHA512:
6
- metadata.gz: 91987307ffe2aaf10763dc39e0dce6acc1d91ed8f614144811b2a0d7c579b06d4f4f112cf6de10a983153aa81f45d5dac3450be5c38658af71728656d4426d5c
7
- data.tar.gz: f6a677544fd723f0ab6842e5496586d93f22b8ddf77fc5ceb1c4fb98f770ce5443461a66f6c744f03b03a428acfa1c55c669d4f4f09841191c99842a40e614f6
6
+ metadata.gz: d162f1712129b7527cb40a7cb3d10387acd677f0e073c3ae8ae543eaff35b1ac72b797d0e5c09726fd725d07ee2b9db841ff173cd63be1f879d3e2bb8bd15f32
7
+ data.tar.gz: 11290586d6235cb8be998b95c43636d8c57b477e0007cb13dd4fb5272432f552c4ad7012f4895b9096b6f8e8761b4e889b3ddb65a43dc68efb0a1499b688e0e9
@@ -7,18 +7,37 @@ require_relative './moesif_helpers'
7
7
  require_relative './regex_config_helper'
8
8
 
9
9
  class AppConfig
10
+ attr_accessor :config, :recent_etag, :last_download_time
11
+
10
12
  def initialize(debug)
11
13
  @debug = debug
12
14
  @moesif_helpers = MoesifHelpers.new(debug)
13
15
  @regex_config_helper = RegexConfigHelper.new(debug)
14
16
  end
15
17
 
18
+ def should_reload(etag_from_create_event)
19
+ if @last_download_time.nil?
20
+ return true
21
+ elsif Time.now.utc > (@last_download_time + 300)
22
+ return true
23
+ elsif !etag_from_create_event.nil? && !@recent_etag.nil?
24
+ @moesif_helpers.log_debug('comparing if etag from event and recent etag match ' + etag_from_create_event + ' ' + @recent_etag)
25
+
26
+ return etag_from_create_event != @recent_etag
27
+ end
28
+ @moesif_helpers.log_debug('should skip reload config')
29
+ return false;
30
+ end
31
+
16
32
  def get_config(api_controller)
17
33
  # Get Application Config
18
- config_api_response = api_controller.get_app_config
34
+ @moesif_helpers.log_debug('try to loading etag')
35
+ config_json, _context = api_controller.get_app_config
36
+ @config = config_json
37
+ @recent_etag = _context.response.headers[:x_moesif_config_etag]
38
+ @last_download_time = Time.now.utc
19
39
  @moesif_helpers.log_debug('new config downloaded')
20
- @moesif_helpers.log_debug(config_api_response.to_s)
21
- config_api_response
40
+ @moesif_helpers.log_debug(config_json.to_s)
22
41
  rescue MoesifApi::APIException => e
23
42
  if e.response_code.between?(401, 403)
24
43
  @moesif_helpers.log_debug 'Unauthorized access getting application configuration. Please check your Appplication Id.'
@@ -29,38 +48,15 @@ class AppConfig
29
48
  @moesif_helpers.log_debug e.to_s
30
49
  end
31
50
 
32
- def parse_configuration(config_api_response)
33
- # Parse configuration object and return Etag, sample rate and last updated time
34
-
35
- # Rails return gzipped compressed response body, so decompressing it and getting JSON response body
36
- response_body = @moesif_helpers.decompress_gzip_body(config_api_response)
37
- @moesif_helpers.log_debug(response_body.to_json)
38
-
39
- # Check if response body is not nil
40
- return response_body, config_api_response.headers[:x_moesif_config_etag], Time.now.utc unless response_body.nil?
41
-
42
- # Return Etag, sample rate and last updated time
43
-
44
- @moesif_helpers.log_debug 'Response body is nil, assuming default behavior'
45
- # Response body is nil, so assuming default behavior
46
- [nil, nil, Time.now.utc]
47
- rescue StandardError => e
48
- @moesif_helpers.log_debug 'Error while parsing the configuration object, assuming default behavior'
49
- @moesif_helpers.log_debug e.to_s
50
- # Assuming default behavior
51
- [nil, nil, Time.now.utc]
52
- end
53
-
54
- def get_sampling_percentage(event_model, config_api_response, user_id, company_id)
51
+ def get_sampling_percentage(event_model, user_id, company_id)
52
+ # if we do not have config for some reason we return 100
53
+ if !@config.nil?
55
54
  # Get sampling percentage
56
-
57
- # Check if response body is not nil
58
- if !config_api_response.nil?
59
55
  @moesif_helpers.log_debug("Getting sample rate for user #{user_id} company #{company_id}")
60
- @moesif_helpers.log_debug(config_api_response.to_s)
56
+ @moesif_helpers.log_debug(@config.to_s)
61
57
 
62
58
  # Get Regex Sampling rate
63
- regex_config = config_api_response.fetch('regex_config', nil)
59
+ regex_config = @config.fetch('regex_config', nil)
64
60
 
65
61
  if !regex_config.nil? and !event_model.nil?
66
62
  config_mapping = @regex_config_helper.prepare_config_mapping(event_model)
@@ -70,10 +66,10 @@ class AppConfig
70
66
  end
71
67
 
72
68
  # Get user sample rate object
73
- user_sample_rate = config_api_response.fetch('user_sample_rate', nil)
69
+ user_sample_rate = @config.fetch('user_sample_rate', nil)
74
70
 
75
71
  # Get company sample rate object
76
- company_sample_rate = config_api_response.fetch('company_sample_rate', nil)
72
+ company_sample_rate = @config.fetch('company_sample_rate', nil)
77
73
 
78
74
  # Get sample rate for the user if exist
79
75
  if !user_id.nil? && !user_sample_rate.nil? && user_sample_rate.key?(user_id)
@@ -85,8 +81,8 @@ class AppConfig
85
81
  return company_sample_rate.fetch(company_id)
86
82
  end
87
83
 
88
- # Return sample rate
89
- config_api_response.fetch('sample_rate', 100)
84
+ # Return overall sample rate
85
+ @config.fetch('sample_rate', 100)
90
86
  else
91
87
  @moesif_helpers.log_debug 'Assuming default behavior as response body is nil - '
92
88
  100
@@ -109,10 +109,7 @@ class GovernanceRules
109
109
  # Get Application Config
110
110
  @last_fetch = Time.now.utc
111
111
  @moesif_helpers.log_debug('starting downlaoding rules')
112
- rules_response = api_controller.get_rules
113
- rules = @moesif_helpers.decompress_gzip_body(rules_response)
114
- @moesif_helpers.log_debug('new rules downloaded')
115
- @moesif_helpers.log_debug(rules.to_json)
112
+ rules, _context = api_controller.get_rules
116
113
 
117
114
  generate_rules_caching(rules)
118
115
  rescue MoesifApi::APIException => e
@@ -12,30 +12,6 @@ class MoesifHelpers
12
12
  puts("#{Time.now} [Moesif Middleware] PID #{Process.pid} TID #{Thread.current.object_id} #{message}")
13
13
  end
14
14
 
15
- def decompress_gzip_body(moesif_api_response)
16
- # Decompress gzip response body
17
-
18
- # Check if the content-encoding header exist and is of type zip
19
- if moesif_api_response.headers.key?(:content_encoding) && moesif_api_response.headers[:content_encoding].eql?('gzip')
20
-
21
- # Create a GZipReader object to read data
22
- gzip_reader = Zlib::GzipReader.new(StringIO.new(moesif_api_response.raw_body.to_s))
23
-
24
- # Read the body
25
- uncompressed_string = gzip_reader.read
26
-
27
- # Return the parsed body
28
- JSON.parse(uncompressed_string)
29
- else
30
- @moesif_helpers.log_debug 'Content Encoding is of type other than gzip, returning nil'
31
- nil
32
- end
33
- rescue StandardError => e
34
- @moesif_helpers.log_debug 'Error while decompressing the response body'
35
- @moesif_helpers.log_debug e.to_s
36
- nil
37
- end
38
-
39
15
  def format_replacement_body(replacement_body, original_body)
40
16
  # replacement_body is an hash or array json in this case.
41
17
  # but original body could be in chunks already. we want to follow suit.
@@ -18,7 +18,7 @@ module MoesifRack
18
18
  @app = app
19
19
  raise 'application_id required for Moesif Middleware' unless options['application_id']
20
20
 
21
- @api_client = MoesifApi::MoesifAPIClient.new(options['application_id'])
21
+ @api_client = MoesifApi::MoesifAPIClient.new(options['application_id'], 'moesif-rack/2.0.2')
22
22
  @api_controller = @api_client.api
23
23
 
24
24
  @api_version = options['api_version']
@@ -32,9 +32,6 @@ module MoesifRack
32
32
  @app_config = AppConfig.new(@debug)
33
33
  @moesif_helpers = MoesifHelpers.new(@debug)
34
34
 
35
- @config_etag = nil
36
- @last_config_download_time = Time.now.utc
37
- @config_dict = {}
38
35
  @disable_transaction_id = options['disable_transaction_id'] || false
39
36
  @log_body = options.fetch('log_body', true)
40
37
  @batch_size = options['batch_size'] || 200
@@ -49,22 +46,20 @@ module MoesifRack
49
46
  start_worker
50
47
 
51
48
  begin
52
- new_config = @app_config.get_config(@api_controller)
53
- unless new_config.nil?
54
- @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
55
- end
49
+ @app_config.get_config(@api_controller)
56
50
  @governance_manager.load_rules(@api_controller)
57
51
  rescue StandardError => e
58
52
  @moesif_helpers.log_debug 'Error while parsing application configuration on initialization'
59
53
  @moesif_helpers.log_debug e.to_s
60
54
  end
55
+ # backwards compatibility for a typo in docs
61
56
  @capture_outoing_requests = options['capture_outoing_requests']
62
57
  @capture_outgoing_requests = options['capture_outgoing_requests']
63
58
  return unless @capture_outoing_requests || @capture_outgoing_requests
64
59
 
65
60
  @moesif_helpers.log_debug 'Start Capturing outgoing requests'
66
61
  require_relative '../../moesif_capture_outgoing/httplog'
67
- MoesifCaptureOutgoing.start_capture_outgoing(options)
62
+ MoesifCaptureOutgoing.start_capture_outgoing(options, @app_config, @events_queue)
68
63
  end
69
64
 
70
65
  def update_user(user_profile)
@@ -167,20 +162,10 @@ module MoesifRack
167
162
  end
168
163
 
169
164
  @moesif_helpers.log_debug('No events to read from the queue') if @events_queue.empty?
170
- if (!@event_response_config_etag.nil? && !@config_etag.nil? && @config_etag != @event_response_config_etag) || (Time.now.utc > (@last_config_download_time + 300))
171
- begin
172
- @moesif_helpers.log_debug('try to reload config and rules again')
173
- new_config = @app_config.get_config(@api_controller)
174
- unless new_config.nil?
175
- @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
176
- end
177
- # since logic to reload config is already here for every 5 minutes,
178
- # reload rules here also.
179
- @governance_manager.load_rules(@api_controller)
180
- rescue StandardError => e
181
- @moesif_helpers.log_debug 'Error while updating the application configuration'
182
- @moesif_helpers.log_debug e.to_s
183
- end
165
+
166
+ if @app_config.should_reload(@event_response_config_etag)
167
+ @app_config.get_config(@api_controller)
168
+ @governance_manager.load_rules(@api_controller)
184
169
  end
185
170
 
186
171
  sleep @batch_max_time
@@ -326,7 +311,7 @@ module MoesifRack
326
311
  random_percentage = Random.rand(0.00..100.00)
327
312
 
328
313
  begin
329
- sampling_percentage = @app_config.get_sampling_percentage(_event_model, @config, _event_model.user_id,
314
+ sampling_percentage = @app_config.get_sampling_percentage(_event_model, _event_model.user_id,
330
315
  _event_model.company_id)
331
316
  @moesif_helpers.log_debug "Using sample rate #{sampling_percentage}"
332
317
  rescue StandardError => e
@@ -367,7 +352,7 @@ module MoesifRack
367
352
  # now we can do govern based on
368
353
  # override_response = govern(env, event_model)
369
354
  # return override_response if override_response
370
- new_response = @governance_manager.govern_request(@config, env, event_model, status, headers, body)
355
+ new_response = @governance_manager.govern_request(@app_config.config, env, event_model, status, headers, body)
371
356
 
372
357
  # update the event model
373
358
  if new_response
@@ -7,7 +7,7 @@ require_relative '../../lib/moesif_rack/app_config'
7
7
 
8
8
  module MoesifCaptureOutgoing
9
9
  class << self
10
- def start_capture_outgoing(options)
10
+ def start_capture_outgoing(options, app_config_manager, events_queue)
11
11
  @moesif_options = options
12
12
  raise 'application_id required for Moesif Middleware' unless @moesif_options['application_id']
13
13
 
@@ -21,22 +21,13 @@ module MoesifCaptureOutgoing
21
21
  @skip_outgoing = options['skip_outgoing']
22
22
  @mask_data_outgoing = options['mask_data_outgoing']
23
23
  @log_body_outgoing = options.fetch('log_body_outgoing', true)
24
- @app_config = AppConfig.new(@debug)
25
- @config_etag = nil
24
+
25
+ @app_config = app_config_manager
26
+ # @app_config and @events_queue should be shared instance from the middleware
27
+ # so that we can use the same queue and same loaded @app_config
28
+ @events_queue = events_queue
26
29
  @sampling_percentage = 100
27
30
  @last_updated_time = Time.now.utc
28
- @config_dict = {}
29
- begin
30
- new_config = @app_config.get_config(@api_controller)
31
- unless new_config.nil?
32
- @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
33
- end
34
- rescue StandardError => e
35
- if @debug
36
- puts 'Error while parsing application configuration on initialization'
37
- puts e
38
- end
39
- end
40
31
  end
41
32
 
42
33
  def call(url, request, request_time, response, response_time)
@@ -152,7 +143,7 @@ module MoesifCaptureOutgoing
152
143
  begin
153
144
  @random_percentage = Random.rand(0.00..100.00)
154
145
  begin
155
- @sampling_percentage = @app_config.get_sampling_percentage(event_model, @config, event_model.user_id,
146
+ @sampling_percentage = @app_config.get_sampling_percentage(event_model, event_model.user_id,
156
147
  event_model.company_id)
157
148
  rescue StandardError => e
158
149
  if @debug
@@ -168,23 +159,13 @@ module MoesifCaptureOutgoing
168
159
  puts 'Sending Outgoing Request Data to Moesif'
169
160
  puts event_model.to_json
170
161
  end
171
- event_api_response = @api_controller.create_event(event_model)
172
- event_response_config_etag = event_api_response[:x_moesif_config_etag]
173
-
174
- if !event_response_config_etag.nil? && !@config_etag.nil? && @config_etag != event_response_config_etag && Time.now.utc > @last_updated_time + 300
175
- begin
176
- new_config = @app_config.get_config(@api_controller)
177
- unless new_config.nil?
178
- @config, @config_etag, @last_config_download_time = @app_config.parse_configuration(new_config)
179
- end
180
- rescue StandardError => e
181
- if @debug
182
- puts 'Error while updating the application configuration'
183
- puts e
184
- end
185
- end
162
+
163
+ # we put in the queue and format abot it.
164
+ unless @events_queue.nil?
165
+ @events_queue << event_model
166
+ puts('Outgoing Event successfully added to event queue') if @debug
167
+ return
186
168
  end
187
- puts('Event successfully sent to Moesif') if @debug
188
169
  elsif @debug
189
170
  puts('Skipped outgoing Event due to sampling percentage: ' + @sampling_percentage.to_s + ' and random percentage: ' + @random_percentage.to_s)
190
171
  end
@@ -1469,9 +1469,5 @@
1469
1469
  ]
1470
1470
  },
1471
1471
  "ip_addresses_blocked_by_name": {},
1472
- "regex_config": [],
1473
- "billing_config_jsons": {
1474
- "stripe": "{\"user_id_field\":\"id\",\"company_id_field\":\"metadata.uuid\"}",
1475
- "recurly": "{\"user_id_field\":\"account_code\",\"company_id_field\":\"uuid\"}"
1476
- }
1472
+ "regex_config": []
1477
1473
  }
@@ -1,6 +1,6 @@
1
1
  require 'moesif_api'
2
2
  require 'test/unit'
3
- require 'rack'
3
+
4
4
  require 'net/http'
5
5
  require_relative '../lib/moesif_rack/app_config'
6
6
  require_relative '../lib/moesif_rack'
@@ -61,10 +61,10 @@ class GovernanceRulesTest < Test::Unit::TestCase
61
61
  #for user id matched rules it depends on getting from config_rules_values
62
62
  #for that particular user id.
63
63
  # for this test case I will use this rule as fake input
64
- #https://www.moesif.com/wrap/app/88:210-1051:5/governance-rule/64a5b8f9aca3042266d36ebc
64
+ #https://www.moesif.com/wrap/app/88:210-660:387/governance-rule/64a783a3e7d62b036d16006e
65
65
  config_user_rules_values = [
66
66
  {
67
- "rules" => "64a5b8f9aca3042266d36ebc",
67
+ "rules" => "64a783a3e7d62b036d16006e",
68
68
  "values" => {
69
69
  "0" => "rome",
70
70
  "1" => "some value for 1",
@@ -105,9 +105,10 @@ class GovernanceRulesTest < Test::Unit::TestCase
105
105
  }
106
106
  user_id = 'vancouver1'
107
107
 
108
+ # https://www.moesif.com/wrap/app/88:210-660:387/governance-rule/64a783a43660b60f7c766a06
108
109
  config_user_rules_values = [
109
110
  {
110
- "rules" => "64a5b8fa3660b60f7c7662fc",
111
+ "rules" => "64a783a43660b60f7c766a06",
111
112
  "values" => {
112
113
  "0" => "city",
113
114
  "1" => "some value for 1",
@@ -45,6 +45,7 @@ class MoesifRackTest < Test::Unit::TestCase
45
45
  event_model
46
46
  }
47
47
  }
48
+
48
49
  @moesif_rack_app = MoesifRack::MoesifMiddleware.new(@app, @options)
49
50
  @app_config = AppConfig.new(true)
50
51
  end
@@ -72,9 +73,9 @@ class MoesifRackTest < Test::Unit::TestCase
72
73
  campaign_model = {"utm_source" => "Newsletter",
73
74
  "utm_medium" => "Email"}
74
75
 
75
- user_model = { "user_id" => "12345",
76
+ user_model = { "user_id" => "12345",
76
77
  "company_id" => "67890",
77
- "modified_time" => Time.now.utc.iso8601,
78
+ "modified_time" => Time.now.utc.iso8601,
78
79
  "metadata" => metadata,
79
80
  "campaign" => campaign_model}
80
81
 
@@ -91,14 +92,14 @@ class MoesifRackTest < Test::Unit::TestCase
91
92
 
92
93
  user_models = []
93
94
 
94
- user_model_A = { "user_id" => "12345",
95
+ user_model_A = { "user_id" => "12345",
95
96
  "company_id" => "67890",
96
- "modified_time" => Time.now.utc.iso8601,
97
+ "modified_time" => Time.now.utc.iso8601,
97
98
  "metadata" => metadata }
98
-
99
+
99
100
  user_model_B = { "user_id" => "1234",
100
- "company_id" => "6789",
101
- "modified_time" => Time.now.utc.iso8601,
101
+ "company_id" => "6789",
102
+ "modified_time" => Time.now.utc.iso8601,
102
103
  "metadata" => metadata }
103
104
 
104
105
  user_models << user_model_A << user_model_B
@@ -116,9 +117,10 @@ class MoesifRackTest < Test::Unit::TestCase
116
117
  def test_get_config
117
118
  @api_client = MoesifApi::MoesifAPIClient.new(@options['application_id'])
118
119
  @api_controller = @api_client.api
119
- @config = @app_config.get_config(@api_controller)
120
- @config_body, @config_etag, @last_updated_time = @app_config.parse_configuration(@config)
121
- @sampling_percentage = @app_config.get_sampling_percentage(nil, @config_body, nil, nil)
120
+ @app_config.get_config(@api_controller)
121
+ @sampling_percentage = @app_config.get_sampling_percentage(nil, nil, nil)
122
+ assert_instance_of Hash, @app_config.config
123
+ print "app config from test " + @app_config.config.to_s
122
124
  assert_operator 100, :>=, @sampling_percentage
123
125
  end
124
126
 
@@ -132,8 +134,8 @@ class MoesifRackTest < Test::Unit::TestCase
132
134
  campaign_model = {"utm_source" => "Adwords",
133
135
  "utm_medium" => "Twitter"}
134
136
 
135
- company_model = { "company_id" => "12345",
136
- "company_domain" => "acmeinc.com",
137
+ company_model = { "company_id" => "12345",
138
+ "company_domain" => "acmeinc.com",
137
139
  "metadata" => metadata,
138
140
  "campaign" => campaign_model }
139
141
 
@@ -151,11 +153,11 @@ class MoesifRackTest < Test::Unit::TestCase
151
153
  company_models = []
152
154
 
153
155
  company_model_A = { "company_id" => "12345",
154
- "company_domain" => "nowhere.com",
156
+ "company_domain" => "nowhere.com",
155
157
  "metadata" => metadata }
156
-
158
+
157
159
  company_model_B = { "company_id" => "1234",
158
- "company_domain" => "acmeinc.com",
160
+ "company_domain" => "acmeinc.com",
159
161
  "metadata" => metadata }
160
162
 
161
163
  company_models << company_model_A << company_model_B
@@ -163,4 +165,4 @@ class MoesifRackTest < Test::Unit::TestCase
163
165
  assert_equal response, nil
164
166
  end
165
167
 
166
- end
168
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moesif_rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Moesif, Inc
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-07-10 00:00:00.000000000 Z
12
+ date: 2023-08-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit
@@ -37,14 +37,20 @@ dependencies:
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.2.17
40
+ version: '2'
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.0.2
41
44
  type: :runtime
42
45
  prerelease: false
43
46
  version_requirements: !ruby/object:Gem::Requirement
44
47
  requirements:
45
48
  - - "~>"
46
49
  - !ruby/object:Gem::Version
47
- version: 1.2.17
50
+ version: '2'
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.0.2
48
54
  description: Rack/Rails middleware to log API calls to Moesif API analytics and monitoring
49
55
  email: xing@moesif.com
50
56
  executables: []
@@ -67,8 +73,8 @@ files:
67
73
  - moesif_capture_outgoing/httplog/http_log.rb
68
74
  - test/config_example.json
69
75
  - test/govrule_example.json
70
- - test/moesif_rack_test.rb
71
76
  - test/test_governance_rules.rb
77
+ - test/test_moesif_rack.rb
72
78
  homepage: https://moesif.com
73
79
  licenses:
74
80
  - Apache-2.0
@@ -88,7 +94,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
94
  requirements:
89
95
  - - ">="
90
96
  - !ruby/object:Gem::Version
91
- version: '2.0'
97
+ version: '2.6'
92
98
  required_rubygems_version: !ruby/object:Gem::Requirement
93
99
  requirements:
94
100
  - - ">="