From b423e2f2c34995b027b0bdde22790767f8a20619 Mon Sep 17 00:00:00 2001 From: Dhruv Paranjape Date: Sun, 25 Aug 2024 19:56:04 +0200 Subject: [PATCH 1/6] Config refactor --- Gemfile.lock | 2 + absmartly.gemspec | 1 + example/example.rb | 2 +- lib/a_b_smartly.rb | 87 ++++++++++---------------------------- lib/a_b_smartly_config.rb | 70 ++++++++++++++++++------------ lib/absmartly.rb | 25 ++++------- lib/client.rb | 67 ++++++++--------------------- lib/client_config.rb | 85 +++++++++++++++++++++++++++++-------- lib/context.rb | 7 ++- lib/context_config.rb | 3 -- lib/default_http_client.rb | 2 +- spec/a_b_smartly_spec.rb | 3 +- spec/context_spec.rb | 5 +-- spec/spec_helper.rb | 1 + 14 files changed, 175 insertions(+), 185 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 55179d3..df28845 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: absmartly-sdk (1.1.2) + bigdecimal faraday (~> 2.0) faraday-retry (~> 2.0) murmurhash3 (~> 0.1.7) @@ -10,6 +11,7 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) + bigdecimal (3.1.8) byebug (11.1.3) diff-lcs (1.5.0) faraday (2.7.4) diff --git a/absmartly.gemspec b/absmartly.gemspec index e931c07..3c7ba10 100644 --- a/absmartly.gemspec +++ b/absmartly.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + spec.add_dependency "bigdecimal" spec.add_dependency "faraday", "~> 2.0" spec.add_dependency "faraday-retry", "~> 2.0" spec.add_dependency "murmurhash3", "~> 0.1.7" diff --git a/example/example.rb b/example/example.rb index 6ff177a..bad1150 100644 --- a/example/example.rb +++ b/example/example.rb @@ -82,4 +82,4 @@ } ctx2.track("payment", properties) -ctx2.close \ No newline at end of file +ctx2.close diff --git a/lib/a_b_smartly.rb b/lib/a_b_smartly.rb index 6328eda..60474db 100644 --- a/lib/a_b_smartly.rb +++ b/lib/a_b_smartly.rb @@ -1,92 +1,51 @@ # frozen_string_literal: true require "time" +require "singleton" +require "forwardable" require_relative "context" require_relative "audience_matcher" -require_relative "default_context_data_provider" -require_relative "default_context_event_handler" -require_relative "default_variable_parser" -require_relative "default_audience_deserializer" -require_relative "scheduled_thread_pool_executor" +require_relative "a_b_smartly_config" +require_relative "absmartly/version" class ABSmartly - attr_accessor :context_data_provider, :context_event_handler, - :variable_parser, :scheduler, :context_event_logger, - :audience_deserializer, :client + extend Forwardable - def self.configure_client(&block) - @@init_http = block - end + attr_reader :config + + def_delegators :@config, :context_data_provider, :context_event_handler, :variable_parser, :context_event_logger, + :audience_deserializer, :client + + def_delegators :@config, :endpoint, :api_key, :application, :environment def self.create(config) - ABSmartly.new(config) + new(config) end def initialize(config) - @@init_http = nil - @context_data_provider = config.context_data_provider - @context_event_handler = config.context_event_handler - @context_event_logger = config.context_event_logger - @variable_parser = config.variable_parser - @audience_deserializer = config.audience_deserializer - @scheduler = config.scheduler - - if @context_data_provider.nil? || @context_event_handler.nil? - @client = config.client - raise ArgumentError.new("Missing Client instance configuration") if @client.nil? - - if @context_data_provider.nil? - @context_data_provider = DefaultContextDataProvider.new(@client) - end - - if @context_event_handler.nil? - @context_event_handler = DefaultContextEventHandler.new(@client) - end - end + config.validate! - if @variable_parser.nil? - @variable_parser = DefaultVariableParser.new - end - - if @audience_deserializer.nil? - @audience_deserializer = DefaultAudienceDeserializer.new - end - if @scheduler.nil? - @scheduler = ScheduledThreadPoolExecutor.new(1) - end + @config = config end - def create_context(config) - validate_params(config) - Context.create(get_utc_format, config, @context_data_provider.context_data, - @context_data_provider, @context_event_handler, @context_event_logger, @variable_parser, - AudienceMatcher.new(@audience_deserializer)) + def create_context(context_config) + Context.create(get_utc_format, context_config, context_data, + context_data_provider, context_event_handler, context_event_logger, variable_parser, + AudienceMatcher.new(audience_deserializer)) end - def create_context_with(config, data) - Context.create(get_utc_format, config, data, - @context_data_provider, @context_event_handler, @context_event_logger, @variable_parser, - AudienceMatcher.new(@audience_deserializer)) + def create_context_with(context_config, data) + Context.create(get_utc_format, context_config, data, + context_data_provider, context_event_handler, context_event_logger, variable_parser, + AudienceMatcher.new(audience_deserializer)) end def context_data - @context_data_provider.context_data + context_data_provider.context_data end private def get_utc_format Time.now.utc.iso8601(3) end - - def validate_params(params) - params.units.each do |key, value| - unless value.is_a?(String) || value.is_a?(Numeric) - raise ArgumentError.new("Unit '#{key}' UID is of unsupported type '#{value.class}'. UID must be one of ['string', 'number']") - end - - if value.to_s.size.zero? - raise ArgumentError.new("Unit '#{key}' UID length must be >= 1") - end - end - end end diff --git a/lib/a_b_smartly_config.rb b/lib/a_b_smartly_config.rb index 42a4034..581659f 100644 --- a/lib/a_b_smartly_config.rb +++ b/lib/a_b_smartly_config.rb @@ -1,49 +1,65 @@ # frozen_string_literal: true +require "forwardable" + +require_relative "client" +require_relative "client_config" +require_relative "default_context_data_provider" +require_relative "default_context_event_handler" +require_relative "default_variable_parser" +require_relative "default_audience_deserializer" + class ABSmartlyConfig - attr_accessor :context_data_provider, :context_event_handler, - :variable_parser, :scheduler, :context_event_logger, - :client, :audience_deserializer + extend Forwardable + + attr_accessor :scheduler + + attr_writer :context_data_provider, :context_event_handler, :audience_deserializer, :variable_parser, :client + + attr_reader :client_config, :context_event_logger + + def_delegators :@client_config, :endpoint, :api_key, :application, :environment + def_delegators :@client_config, :connect_timeout, :connection_request_timeout, :retry_interval, :max_retries + def self.create - ABSmartlyConfig.new + new end - def context_data_provider=(context_data_provider) - @context_data_provider = context_data_provider - self + def initialize + @client_config = ClientConfig.new end - def context_event_handler=(context_event_handler) - @context_event_handler = context_event_handler - self + def validate! + raise ArgumentError.new("event logger not configured") if context_event_logger.nil? + raise ArgumentError.new("failed to initialize client") if client.nil? + raise ArgumentError.new("failed to initialize context_data_provider") if context_data_provider.nil? end - def context_data_provide - @context_event_handler + def context_event_logger=(context_event_logger) + if context_event_logger.is_a?(Proc) + @context_event_logger = ContextEventLoggerCallback.new(context_event_logger) + else + @context_event_logger = context_event_logger + end end - def variable_parser=(variable_parser) - @variable_parser = variable_parser - self + def variable_parser + @variable_parser ||= DefaultVariableParser.new end - def scheduler=(scheduler) - @scheduler = scheduler - self + def audience_deserializer + @audience_deserializer ||= DefaultAudienceDeserializer.new end - def context_event_logger=(context_event_logger) - @context_event_logger = context_event_logger - self + def context_data_provider + @context_data_provider ||= DefaultContextDataProvider.new(client) end - def audience_deserializer=(audience_deserializer) - @audience_deserializer = audience_deserializer - self + def context_event_handler + @context_event_handler ||= DefaultContextEventHandler.new(client) end - def client=(client) - @client = client - self + def client + @client ||= Client.new(client_config) end end diff --git a/lib/absmartly.rb b/lib/absmartly.rb index c2febb2..1fe3680 100644 --- a/lib/absmartly.rb +++ b/lib/absmartly.rb @@ -8,16 +8,16 @@ require_relative "context_config" module Absmartly - @@init_config = nil - class Error < StandardError end class << self - attr_accessor :endpoint, :api_key, :application, :environment + MUTEX = Thread::Mutex.new def configure_client - yield self + yield sdk_config + + sdk_config.validate! end def create @@ -40,24 +40,15 @@ def context_data sdk.context_data end - private - def client_config - @client_config = ClientConfig.create - @client_config.endpoint = @endpoint - @client_config.api_key = @api_key - @client_config.application = @application - @client_config.environment = @environment - @client_config - end + private_constant :MUTEX + private def sdk_config - @sdk_config = ABSmartlyConfig.create - @sdk_config.client = Client.create(client_config) - @sdk_config + MUTEX.synchronize { @sdk_config ||= ABSmartlyConfig.create } end def sdk - @sdk ||= create + MUTEX.synchronize { @sdk ||= create } end end end diff --git a/lib/client.rb b/lib/client.rb index ea6ed7c..79723b1 100644 --- a/lib/client.rb +++ b/lib/client.rb @@ -1,80 +1,49 @@ # frozen_string_literal: true +require "forwardable" require_relative "default_http_client" require_relative "default_http_client_config" require_relative "default_context_data_deserializer" require_relative "default_context_event_serializer" class Client - attr_accessor :url, :query, :headers, :http_client, :executor, :deserializer, :serializer - attr_reader :data_future, :promise, :exception + extend Forwardable - def self.create(config, http_client = nil) - Client.new(config, http_client || DefaultHttpClient.create(DefaultHttpClientConfig.create)) - end - - def initialize(config = nil, http_client = nil) - endpoint = config.endpoint - raise ArgumentError.new("Missing Endpoint configuration") if endpoint.nil? || endpoint.empty? - - api_key = config.api_key - raise ArgumentError.new("Missing APIKey configuration") if api_key.nil? || api_key.empty? - - application = config.application - raise ArgumentError.new("Missing Application configuration") if application.nil? || application.empty? - - environment = config.environment - raise ArgumentError.new("Missing Environment configuration") if environment.nil? || environment.empty? + attr_accessor :http_client + attr_reader :config, :data_future, :promise, :exception - @url = "#{endpoint}/context" - @http_client = http_client - @deserializer = config.context_data_deserializer - @serializer = config.context_event_serializer - @executor = config.executor + def_delegators :@config, :url, :query, :headers, :deserializer, :serializer + def_delegator :@http_client, :close + def_delegator :@promise, :success? - @deserializer = DefaultContextDataDeserializer.new if @deserializer.nil? - @serializer = DefaultContextEventSerializer.new if @serializer.nil? + def self.create(config = nil, http_client = nil) + new(config, http_client) + end - @headers = { - "Content-Type": "application/json", - "X-API-Key": api_key, - "X-Application": application, - "X-Environment": environment, - "X-Application-Version": "0", - "X-Agent": "absmartly-ruby-sdk" - } + def initialize(config = nil, http_client = nil) + @config = config || ClientConfig.new + @config.validate! - @query = { - "application": application, - "environment": environment - } + @http_client = http_client || DefaultHttpClient.create(@config.http_client_config) end def context_data - @promise = @http_client.get(@url, @query, @headers) + @promise = http_client.get(config.url, config.query, config.headers) unless @promise.success? @exception = Exception.new(@promise.body) return self end content = (@promise.body || {}).to_s - @data_future = @deserializer.deserialize(content, 0, content.size) + @data_future = deserializer.deserialize(content, 0, content.size) self end def publish(event) - content = @serializer.serialize(event) - response = @http_client.put(@url, nil, @headers, content) + content = serializer.serialize(event) + response = http_client.put(config.url, nil, config.headers, content) return Exception.new(response.body) unless response.success? response end - - def close - @http_client.close - end - - def success? - @promise.success? - end end diff --git a/lib/client_config.rb b/lib/client_config.rb index 7434da5..113a507 100644 --- a/lib/client_config.rb +++ b/lib/client_config.rb @@ -1,21 +1,33 @@ # frozen_string_literal: true +require "forwardable" +require_relative "default_context_data_deserializer" +require_relative "default_context_event_serializer" +require_relative "default_http_client_config" + class ClientConfig - attr_accessor :endpoint, :api_key, :environment, :application, :deserializer, - :serializer, :executor + extend Forwardable + + attr_accessor :endpoint, :api_key, :environment, :application + + attr_reader :http_client_config + + attr_writer :context_data_deserializer, :context_event_serializer + + def_delegators :@http_client_config, :connect_timeout, :connection_request_timeout, :retry_interval, :max_retries - def self.create - ClientConfig.new + def self.create(endpoint: nil, environment: nil, application: nil, api_key: nil) + new(endpoint:, environment:, application:, api_key:) end def self.create_from_properties(properties, prefix) properties = properties.transform_keys(&:to_sym) - client_config = create - client_config.endpoint = properties["#{prefix}endpoint".to_sym] - client_config.environment = properties["#{prefix}environment".to_sym] - client_config.application = properties["#{prefix}application".to_sym] - client_config.api_key = properties["#{prefix}apikey".to_sym] - client_config + create( + endpoint: properties["#{prefix}endpoint".to_sym], + environment: properties["#{prefix}environment".to_sym], + application: properties["#{prefix}application".to_sym], + api_key: properties["#{prefix}apikey".to_sym] + ) end def initialize(endpoint: nil, environment: nil, application: nil, api_key: nil) @@ -23,21 +35,60 @@ def initialize(endpoint: nil, environment: nil, application: nil, api_key: nil) @environment = environment @application = application @api_key = api_key + + @http_client_config = DefaultHttpClientConfig.new end def context_data_deserializer - @deserializer + @context_data_deserializer ||= DefaultContextDataDeserializer.new end - def context_data_deserializer=(deserializer) - @deserializer = deserializer + def context_event_serializer + @context_event_serializer ||= DefaultContextEventSerializer.new end - def context_event_serializer - @serializer + def deserializer=(deserializer) + @context_data_deserializer = deserializer + end + + def serializer=(serializer) + @context_event_serializer = serializer + end + + def deserializer + context_data_deserializer + end + + def serializer + context_event_serializer + end + + def url + @url ||= "#{endpoint}/context" + end + + def headers + @headers ||= { + "Content-Type": "application/json", + "X-API-Key": api_key, + "X-Application": application, + "X-Environment": environment, + "X-Application-Version": "0", + "X-Agent": "absmartly-ruby-sdk" + } + end + + def query + @query ||= { + "application": application, + "environment": environment + } end - def context_event_serializer=(serializer) - @serializer = serializer + def validate! + raise ArgumentError.new("Missing Endpoint configuration") if endpoint.nil? || endpoint.empty? + raise ArgumentError.new("Missing APIKey configuration") if api_key.nil? || api_key.empty? + raise ArgumentError.new("Missing Application configuration") if application.nil? || application.empty? + raise ArgumentError.new("Missing Environment configuration") if environment.nil? || environment.empty? end end diff --git a/lib/context.rb b/lib/context.rb index 964065d..397abdb 100644 --- a/lib/context.rb +++ b/lib/context.rb @@ -10,7 +10,7 @@ require_relative "json/goal_achievement" class Context - attr_reader :data, :pending_count + attr_reader :pending_count def self.create(clock, config, data_future, data_provider, event_handler, event_logger, variable_parser, audience_matcher) @@ -114,6 +114,10 @@ def custom_assignment(experiment_name) def set_unit(unit_type, uid) check_not_closed? + unless uid.is_a?(String) || uid.is_a?(Numeric) + raise IllegalStateException.new("Unit '#{unit_type}' UID is of unsupported type '#{uid.class}'. UID must be one of ['string', 'number']") + end + previous = @units[unit_type.to_sym] if !previous.nil? && previous != uid raise IllegalStateException.new("Unit '#{unit_type}' already set.") @@ -541,7 +545,6 @@ def assign_data(data) @experimentCustomFieldValues[custom_field_value.name] = value end - end end diff --git a/lib/context_config.rb b/lib/context_config.rb index cbe5c64..156793e 100644 --- a/lib/context_config.rb +++ b/lib/context_config.rb @@ -68,11 +68,8 @@ def custom_assignment(experiment_name) @custom_assignments[experiment_name.to_sym] end - def set_event_logger(event_logger) @event_logger = event_logger self end - - attr_reader :event_logger end diff --git a/lib/default_http_client.rb b/lib/default_http_client.rb index eea0895..ca777a3 100644 --- a/lib/default_http_client.rb +++ b/lib/default_http_client.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "faraday" -require 'faraday/retry' +require "faraday/retry" require "uri" require_relative "http_client" diff --git a/spec/a_b_smartly_spec.rb b/spec/a_b_smartly_spec.rb index 23a8ca9..634001a 100644 --- a/spec/a_b_smartly_spec.rb +++ b/spec/a_b_smartly_spec.rb @@ -17,6 +17,7 @@ it ".create" do config = ABSmartlyConfig.create config.client = client + config.context_event_logger = ContextEventLogger.new absmartly = described_class.create(config) expect(absmartly).not_to be_nil end @@ -25,7 +26,7 @@ expect { config = ABSmartlyConfig.create ABSmartly.create(config) - }.to raise_error(ArgumentError, "Missing Client instance configuration") + }.to raise_error(ArgumentError, "event logger not configured") end it ".create_context" do diff --git a/spec/context_spec.rb b/spec/context_spec.rb index 7fa2bcb..f5f4b42 100644 --- a/spec/context_spec.rb +++ b/spec/context_spec.rb @@ -712,7 +712,7 @@ def faraday_response(content) expect(context.custom_field_value("exp_test_ab", "country")).to eq("US,PT,ES,DE,FR") expect(context.custom_field_type("exp_test_ab", "country")).to eq("string") - data = {"123": 1, "456": 0} + data = { "123": 1, "456": 0 } expect(context.custom_field_value("exp_test_ab", "overrides")).to eq(data) expect(context.custom_field_type("exp_test_ab", "overrides")).to eq("json") @@ -733,7 +733,6 @@ def faraday_response(content) expect(context.custom_field_type("exp_test_no_custom_fields", "languages")).to be_nil expect(context.custom_field_value("exp_test_no_custom_fields", "languages")).to be_nil - end it "peek_treatmentReturnsOverrideVariant" do @@ -1125,7 +1124,7 @@ class MockContextEventLoggerProxy < ContextEventLogger def initialize @called = 0 @events = [] - @logger = Logger.new(STDOUT) + @logger = Logger.new(IO::NULL) end def handle_event(event, data) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b08390..3b55452 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require "absmartly" require "helpers" +require "ostruct" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure From ba89c028d159b2c3e9b17878ef096a6811156d03 Mon Sep 17 00:00:00 2001 From: Dhruv Paranjape Date: Sun, 25 Aug 2024 20:02:23 +0200 Subject: [PATCH 2/6] Add ruby version matrix to the CI --- .github/workflows/main.yml | 10 ++++------ .ruby-version | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b5bf47..6bf8010 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,18 +3,16 @@ on: [push] jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + ruby-version: [ '3.0', '3.1', '3.2', '3.3', '3.4' ] steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.0.0 + ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Bundle install - run: | - gem install bundler - bundle install --jobs 4 --retry 3 - - name: Run RSpec run: COVERAGE=true bundle exec rspec diff --git a/.ruby-version b/.ruby-version index 49cdd66..818bd47 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.6 +3.0.6 From e2ad7493c6f40be8e621735141663caae95971fc Mon Sep 17 00:00:00 2001 From: Dhruv Paranjape Date: Thu, 29 Aug 2024 17:09:37 +0200 Subject: [PATCH 3/6] remove bigdecimal as a dependency --- Gemfile.lock | 4 ---- absmartly.gemspec | 1 - lib/json_expr/expr_evaluator.rb | 1 - 3 files changed, 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index df28845..3cc9307 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,6 @@ PATH remote: . specs: absmartly-sdk (1.1.2) - bigdecimal faraday (~> 2.0) faraday-retry (~> 2.0) murmurhash3 (~> 0.1.7) @@ -11,7 +10,6 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) - bigdecimal (3.1.8) byebug (11.1.3) diff-lcs (1.5.0) faraday (2.7.4) @@ -34,7 +32,6 @@ GEM reline (0.1.5) io-console (~> 0.5) rexml (3.2.8) - strscan (>= 3.0.9) rspec (3.11.0) rspec-core (~> 3.11.0) rspec-expectations (~> 3.11.0) @@ -62,7 +59,6 @@ GEM parser (>= 3.1.1.0) ruby-progressbar (1.11.0) ruby2_keywords (0.0.5) - strscan (3.1.0) unicode-display_width (2.2.0) PLATFORMS diff --git a/absmartly.gemspec b/absmartly.gemspec index 3c7ba10..e931c07 100644 --- a/absmartly.gemspec +++ b/absmartly.gemspec @@ -32,7 +32,6 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "bigdecimal" spec.add_dependency "faraday", "~> 2.0" spec.add_dependency "faraday-retry", "~> 2.0" spec.add_dependency "murmurhash3", "~> 0.1.7" diff --git a/lib/json_expr/expr_evaluator.rb b/lib/json_expr/expr_evaluator.rb index 788ea92..8e73197 100644 --- a/lib/json_expr/expr_evaluator.rb +++ b/lib/json_expr/expr_evaluator.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "bigdecimal" require_relative "../string" require_relative "./evaluator" EMPTY_MAP = {} From 7ea206685141026d3fd5294c7a881ea88d0f3d10 Mon Sep 17 00:00:00 2001 From: yektt Date: Thu, 29 Aug 2024 15:28:59 +0200 Subject: [PATCH 4/6] Fix relative path in DefaultContextEventHandler. --- lib/default_context_event_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/default_context_event_handler.rb b/lib/default_context_event_handler.rb index a238836..ae13f21 100644 --- a/lib/default_context_event_handler.rb +++ b/lib/default_context_event_handler.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "context_data_provider" +require_relative "context_event_handler" class DefaultContextEventHandler < ContextDataProvider attr_accessor :client From 0789616e31b55b04064b0cd8c53e453130781642 Mon Sep 17 00:00:00 2001 From: yektt Date: Thu, 29 Aug 2024 15:35:16 +0200 Subject: [PATCH 5/6] Fix class inheritance in DefaultContextEventHandler. --- lib/default_context_event_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/default_context_event_handler.rb b/lib/default_context_event_handler.rb index ae13f21..caecf68 100644 --- a/lib/default_context_event_handler.rb +++ b/lib/default_context_event_handler.rb @@ -2,7 +2,7 @@ require_relative "context_event_handler" -class DefaultContextEventHandler < ContextDataProvider +class DefaultContextEventHandler < ContextEventHandler attr_accessor :client def initialize(client) From 3f946e13df4d340c8aca5d7a8f0417cbf0e0d1a6 Mon Sep 17 00:00:00 2001 From: Dhruv Paranjape Date: Tue, 3 Sep 2024 16:11:16 +0200 Subject: [PATCH 6/6] Update version --- lib/absmartly/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/absmartly/version.rb b/lib/absmartly/version.rb index a6539a3..4e387d3 100644 --- a/lib/absmartly/version.rb +++ b/lib/absmartly/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Absmartly - VERSION = "1.1.2" + VERSION = "1.2.0" end