absmartly-sdk 1.2.1 → 1.2.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: 7cc6b910c683119e99ad8800165f98b47b280ccd5f9e44a915d1043e1256888c
4
- data.tar.gz: 8d8d61b7f4f8ed27280a4c3aa4ea7cf9bf8519b5b6743a809c3a799bd38e6c5f
3
+ metadata.gz: 4a2b6f96e452fc60de76e49bc1166b6df3030b1ab424e99cdaf72554493ac0b3
4
+ data.tar.gz: aae37038713a693935209e387dee6ac5a33f84d4c74a27edec5d2ed69152e1e7
5
5
  SHA512:
6
- metadata.gz: c6a4e6f5e52ffeb1f29a60d0a9a08474c460c28355dfd6c1471c3c756254248cac33c2c7fe0760e8b415e1b699cbee882217177f32a9aeffdbfe5fa2e65a0b45
7
- data.tar.gz: 1d714b5d6288e2a87982fec2e45f76ff2ef3aaeb2d227e43c3dbaaeafb419f9a33bff7acdd615326454067bee3dcce7682caf11e7ba3a921229d065eb4d51ee0
6
+ metadata.gz: 6c5feb92c44db0ea6b24d33f40812626d4fdeaa6339c258c5da5926e522382f0981a0550b2508ae7a79ca6b0b1d6c18d54ed2a0f1b24c4241b45cad3d3a39edb
7
+ data.tar.gz: 113ba3c0c2594537dd8ea6c02021bc447f7918783b7175106317d60893013a843f0f93f4eb4411deab9b502f97b539120593d66a0d9c4b50e2fc201cf1d2e531
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- absmartly-sdk (1.2.1)
4
+ absmartly-sdk (1.2.2)
5
5
  base64 (~> 0.2)
6
6
  faraday (~> 2.0)
7
7
  faraday-retry (~> 2.0)
data/absmartly.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # $:.push File.expand_path("../lib", __FILE__)
4
+ require File.expand_path("lib/absmartly/version", __dir__)
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "absmartly-sdk"
7
+ spec.version = Absmartly::VERSION
8
+ spec.authors = ["absmartly"]
9
+ spec.email = ["sdks@absmartly.com"]
10
+
11
+ spec.summary = "Absmartly gem"
12
+ spec.description = "Absmartly gem"
13
+
14
+ spec.homepage = "https://github.com/absmartly/ruby-sdk"
15
+
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 2.7.0"
18
+ spec.extra_rdoc_files = ["README.md"]
19
+
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = "https://github.com/absmartly/ruby-sdk"
22
+ spec.metadata["changelog_uri"] = "https://github.com/absmartly/ruby-sdk"
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(__dir__) do
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Required for Ruby 3.4+ where base64 is no longer a default gem.
36
+ # Faraday 2.7.x uses base64 but doesn't declare it as a dependency.
37
+ spec.add_dependency "base64", "~> 0.2"
38
+ spec.add_dependency "faraday", "~> 2.0"
39
+ spec.add_dependency "faraday-retry", "~> 2.0"
40
+ spec.add_dependency "murmurhash3", "~> 0.1.7"
41
+
42
+ # For more information and examples about making a new gem, check out our
43
+ # guide at: https://bundler.io/guides/creating_gem.html
44
+ end
data/lib/a_b_smartly.rb CHANGED
@@ -1,51 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "time"
4
- require "singleton"
5
- require "forwardable"
6
4
  require_relative "context"
7
5
  require_relative "audience_matcher"
8
- require_relative "a_b_smartly_config"
9
- require_relative "absmartly/version"
6
+ require_relative "default_context_data_provider"
7
+ require_relative "default_context_event_handler"
8
+ require_relative "default_variable_parser"
9
+ require_relative "default_audience_deserializer"
10
+ require_relative "scheduled_thread_pool_executor"
10
11
 
11
12
  class ABSmartly
12
- extend Forwardable
13
-
14
- attr_reader :config
15
-
16
- def_delegators :@config, :context_data_provider, :context_event_handler, :variable_parser, :context_event_logger,
17
- :audience_deserializer, :client
18
-
19
- def_delegators :@config, :endpoint, :api_key, :application, :environment
13
+ attr_accessor :context_data_provider, :context_event_handler,
14
+ :variable_parser, :scheduler, :context_event_logger,
15
+ :audience_deserializer, :client
20
16
 
21
17
  def self.create(config)
22
- new(config)
18
+ ABSmartly.new(config)
23
19
  end
24
20
 
25
21
  def initialize(config)
26
- config.validate!
22
+ @context_data_provider = config.context_data_provider
23
+ @context_event_handler = config.context_event_handler
24
+ @context_event_logger = config.context_event_logger
25
+ @variable_parser = config.variable_parser
26
+ @audience_deserializer = config.audience_deserializer
27
+ @scheduler = config.scheduler
28
+
29
+ if @context_data_provider.nil? || @context_event_handler.nil?
30
+ @client = config.client
31
+ raise ArgumentError.new("Missing Client instance configuration") if @client.nil?
32
+
33
+ if @context_data_provider.nil?
34
+ @context_data_provider = DefaultContextDataProvider.new(@client)
35
+ end
36
+
37
+ if @context_event_handler.nil?
38
+ @context_event_handler = DefaultContextEventHandler.new(@client)
39
+ end
40
+ end
27
41
 
28
- @config = config
42
+ if @variable_parser.nil?
43
+ @variable_parser = DefaultVariableParser.new
44
+ end
45
+
46
+ if @audience_deserializer.nil?
47
+ @audience_deserializer = DefaultAudienceDeserializer.new
48
+ end
49
+ if @scheduler.nil?
50
+ @scheduler = ScheduledThreadPoolExecutor.new(1)
51
+ end
29
52
  end
30
53
 
31
- def create_context(context_config)
32
- Context.create(get_utc_format, context_config, context_data,
33
- context_data_provider, context_event_handler, context_event_logger, variable_parser,
34
- AudienceMatcher.new(audience_deserializer))
54
+ def create_context(config)
55
+ validate_params(config)
56
+ Context.create(get_utc_format, config, @context_data_provider.context_data,
57
+ @context_data_provider, @context_event_handler, @context_event_logger, @variable_parser,
58
+ AudienceMatcher.new(@audience_deserializer))
35
59
  end
36
60
 
37
- def create_context_with(context_config, data)
38
- Context.create(get_utc_format, context_config, data,
39
- context_data_provider, context_event_handler, context_event_logger, variable_parser,
40
- AudienceMatcher.new(audience_deserializer))
61
+ def create_context_with(config, data)
62
+ validate_params(config)
63
+ Context.create(get_utc_format, config, data,
64
+ @context_data_provider, @context_event_handler, @context_event_logger, @variable_parser,
65
+ AudienceMatcher.new(@audience_deserializer))
41
66
  end
42
67
 
43
68
  def context_data
44
- context_data_provider.context_data
69
+ @context_data_provider.context_data
45
70
  end
46
71
 
47
72
  private
48
73
  def get_utc_format
49
74
  Time.now.utc.iso8601(3)
50
75
  end
76
+
77
+ def validate_params(params)
78
+ params.units.each do |key, value|
79
+ unless value.is_a?(String) || value.is_a?(Numeric)
80
+ raise ArgumentError.new("Unit '#{key}' UID is of unsupported type '#{value.class}'. UID must be one of ['string', 'number']")
81
+ end
82
+
83
+ if value.to_s.size.zero?
84
+ raise ArgumentError.new("Unit '#{key}' UID length must be >= 1")
85
+ end
86
+ end
87
+ end
51
88
  end
@@ -1,65 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
4
-
5
- require_relative "client"
6
- require_relative "client_config"
7
- require_relative "default_context_data_provider"
8
- require_relative "default_context_event_handler"
9
- require_relative "default_variable_parser"
10
- require_relative "default_audience_deserializer"
11
-
12
3
  class ABSmartlyConfig
13
- extend Forwardable
14
-
15
- attr_accessor :scheduler
16
-
17
- attr_writer :context_data_provider, :context_event_handler, :audience_deserializer, :variable_parser, :client
18
-
19
- attr_reader :client_config, :context_event_logger
20
-
21
- def_delegators :@client_config, :endpoint, :api_key, :application, :environment
22
- def_delegators :@client_config, :connect_timeout, :connection_request_timeout, :retry_interval, :max_retries
23
-
4
+ attr_accessor :context_data_provider, :context_event_handler,
5
+ :variable_parser, :scheduler, :context_event_logger,
6
+ :client, :audience_deserializer
24
7
  def self.create
25
- new
8
+ ABSmartlyConfig.new
26
9
  end
27
10
 
28
- def initialize
29
- @client_config = ClientConfig.new
11
+ def context_data_provider=(context_data_provider)
12
+ @context_data_provider = context_data_provider
13
+ self
30
14
  end
31
15
 
32
- def validate!
33
- raise ArgumentError.new("event logger not configured") if context_event_logger.nil?
34
- raise ArgumentError.new("failed to initialize client") if client.nil?
35
- raise ArgumentError.new("failed to initialize context_data_provider") if context_data_provider.nil?
16
+ def context_event_handler=(context_event_handler)
17
+ @context_event_handler = context_event_handler
18
+ self
36
19
  end
37
20
 
38
- def context_event_logger=(context_event_logger)
39
- if context_event_logger.is_a?(Proc)
40
- @context_event_logger = ContextEventLoggerCallback.new(context_event_logger)
41
- else
42
- @context_event_logger = context_event_logger
43
- end
21
+ def variable_parser=(variable_parser)
22
+ @variable_parser = variable_parser
23
+ self
44
24
  end
45
25
 
46
- def variable_parser
47
- @variable_parser ||= DefaultVariableParser.new
26
+ def scheduler=(scheduler)
27
+ @scheduler = scheduler
28
+ self
48
29
  end
49
30
 
50
- def audience_deserializer
51
- @audience_deserializer ||= DefaultAudienceDeserializer.new
52
- end
53
-
54
- def context_data_provider
55
- @context_data_provider ||= DefaultContextDataProvider.new(client)
31
+ def context_event_logger=(context_event_logger)
32
+ @context_event_logger = context_event_logger
33
+ self
56
34
  end
57
35
 
58
- def context_event_handler
59
- @context_event_handler ||= DefaultContextEventHandler.new(client)
36
+ def audience_deserializer=(audience_deserializer)
37
+ @audience_deserializer = audience_deserializer
38
+ self
60
39
  end
61
40
 
62
- def client
63
- @client ||= Client.new(client_config)
41
+ def client=(client)
42
+ @client = client
43
+ self
64
44
  end
65
45
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Absmartly
4
- VERSION = "1.2.1"
4
+ VERSION = "1.2.2"
5
5
  end
data/lib/absmartly.rb CHANGED
@@ -12,12 +12,10 @@ module Absmartly
12
12
  end
13
13
 
14
14
  class << self
15
- MUTEX = Thread::Mutex.new
15
+ attr_accessor :endpoint, :api_key, :application, :environment
16
16
 
17
17
  def configure_client
18
- yield sdk_config
19
-
20
- sdk_config.validate!
18
+ yield self
21
19
  end
22
20
 
23
21
  def create
@@ -40,15 +38,24 @@ module Absmartly
40
38
  sdk.context_data
41
39
  end
42
40
 
43
- private_constant :MUTEX
44
-
45
41
  private
42
+ def client_config
43
+ @client_config = ClientConfig.create
44
+ @client_config.endpoint = @endpoint
45
+ @client_config.api_key = @api_key
46
+ @client_config.application = @application
47
+ @client_config.environment = @environment
48
+ @client_config
49
+ end
50
+
46
51
  def sdk_config
47
- MUTEX.synchronize { @sdk_config ||= ABSmartlyConfig.create }
52
+ @sdk_config = ABSmartlyConfig.create
53
+ @sdk_config.client = Client.create(client_config)
54
+ @sdk_config
48
55
  end
49
56
 
50
57
  def sdk
51
- MUTEX.synchronize { @sdk ||= create }
58
+ @sdk ||= create
52
59
  end
53
60
  end
54
61
  end
data/lib/client.rb CHANGED
@@ -1,49 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
4
3
  require_relative "default_http_client"
5
4
  require_relative "default_http_client_config"
6
5
  require_relative "default_context_data_deserializer"
7
6
  require_relative "default_context_event_serializer"
8
7
 
9
8
  class Client
10
- extend Forwardable
9
+ attr_accessor :url, :query, :headers, :http_client, :executor, :deserializer, :serializer
10
+ attr_reader :data_future, :promise, :exception
11
11
 
12
- attr_accessor :http_client
13
- attr_reader :config, :data_future, :promise, :exception
12
+ def self.create(config, http_client = nil)
13
+ Client.new(config, http_client || DefaultHttpClient.create(DefaultHttpClientConfig.create))
14
+ end
14
15
 
15
- def_delegators :@config, :url, :query, :headers, :deserializer, :serializer
16
- def_delegator :@http_client, :close
17
- def_delegator :@promise, :success?
16
+ def initialize(config, http_client = nil)
17
+ endpoint = config.endpoint
18
+ raise ArgumentError.new("Missing Endpoint configuration") if endpoint.nil? || endpoint.empty?
18
19
 
19
- def self.create(config = nil, http_client = nil)
20
- new(config, http_client)
21
- end
20
+ api_key = config.api_key
21
+ raise ArgumentError.new("Missing APIKey configuration") if api_key.nil? || api_key.empty?
22
+
23
+ application = config.application
24
+ raise ArgumentError.new("Missing Application configuration") if application.nil? || application.empty?
25
+
26
+ environment = config.environment
27
+ raise ArgumentError.new("Missing Environment configuration") if environment.nil? || environment.empty?
28
+
29
+ @url = "#{endpoint}/context"
30
+ @http_client = http_client
31
+ @deserializer = config.context_data_deserializer
32
+ @serializer = config.context_event_serializer
33
+ @executor = config.executor
22
34
 
23
- def initialize(config = nil, http_client = nil)
24
- @config = config || ClientConfig.new
25
- @config.validate!
35
+ @deserializer = DefaultContextDataDeserializer.new if @deserializer.nil?
36
+ @serializer = DefaultContextEventSerializer.new if @serializer.nil?
26
37
 
27
- @http_client = http_client || DefaultHttpClient.create(@config.http_client_config)
38
+ @headers = {
39
+ "Content-Type": "application/json",
40
+ "X-API-Key": api_key,
41
+ "X-Application": application,
42
+ "X-Environment": environment,
43
+ "X-Application-Version": "0",
44
+ "X-Agent": "absmartly-ruby-sdk"
45
+ }
46
+
47
+ @query = {
48
+ "application": application,
49
+ "environment": environment
50
+ }
28
51
  end
29
52
 
30
53
  def context_data
31
- @promise = http_client.get(config.url, config.query, config.headers)
54
+ @promise = @http_client.get(@url, @query, @headers)
32
55
  unless @promise.success?
33
56
  @exception = Exception.new(@promise.body)
34
57
  return self
35
58
  end
36
59
 
37
60
  content = (@promise.body || {}).to_s
38
- @data_future = deserializer.deserialize(content, 0, content.size)
61
+ @data_future = @deserializer.deserialize(content, 0, content.size)
39
62
  self
40
63
  end
41
64
 
42
65
  def publish(event)
43
- content = serializer.serialize(event)
44
- response = http_client.put(config.url, nil, config.headers, content)
66
+ content = @serializer.serialize(event)
67
+ response = @http_client.put(@url, nil, @headers, content)
45
68
  return Exception.new(response.body) unless response.success?
46
69
 
47
70
  response
48
71
  end
72
+
73
+ def close
74
+ @http_client.close
75
+ end
76
+
77
+ def success?
78
+ @promise&.success? || false
79
+ end
49
80
  end
data/lib/client_config.rb CHANGED
@@ -1,33 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
4
- require_relative "default_context_data_deserializer"
5
- require_relative "default_context_event_serializer"
6
- require_relative "default_http_client_config"
7
-
8
3
  class ClientConfig
9
- extend Forwardable
10
-
11
- attr_accessor :endpoint, :api_key, :environment, :application
12
-
13
- attr_reader :http_client_config
14
-
15
- attr_writer :context_data_deserializer, :context_event_serializer
16
-
17
- def_delegators :@http_client_config, :connect_timeout, :connection_request_timeout, :retry_interval, :max_retries
4
+ attr_accessor :endpoint, :api_key, :environment, :application, :deserializer,
5
+ :serializer, :executor
18
6
 
19
- def self.create(endpoint: nil, environment: nil, application: nil, api_key: nil)
20
- new(endpoint: endpoint, environment: environment, application: application, api_key: api_key)
7
+ def self.create
8
+ ClientConfig.new
21
9
  end
22
10
 
23
11
  def self.create_from_properties(properties, prefix)
24
12
  properties = properties.transform_keys(&:to_sym)
25
- create(
26
- endpoint: properties["#{prefix}endpoint".to_sym],
27
- environment: properties["#{prefix}environment".to_sym],
28
- application: properties["#{prefix}application".to_sym],
29
- api_key: properties["#{prefix}apikey".to_sym]
30
- )
13
+ client_config = create
14
+ client_config.endpoint = properties["#{prefix}endpoint".to_sym]
15
+ client_config.environment = properties["#{prefix}environment".to_sym]
16
+ client_config.application = properties["#{prefix}application".to_sym]
17
+ client_config.api_key = properties["#{prefix}apikey".to_sym]
18
+ client_config
31
19
  end
32
20
 
33
21
  def initialize(endpoint: nil, environment: nil, application: nil, api_key: nil)
@@ -35,60 +23,21 @@ class ClientConfig
35
23
  @environment = environment
36
24
  @application = application
37
25
  @api_key = api_key
38
-
39
- @http_client_config = DefaultHttpClientConfig.new
40
26
  end
41
27
 
42
28
  def context_data_deserializer
43
- @context_data_deserializer ||= DefaultContextDataDeserializer.new
44
- end
45
-
46
- def context_event_serializer
47
- @context_event_serializer ||= DefaultContextEventSerializer.new
48
- end
49
-
50
- def deserializer=(deserializer)
51
- @context_data_deserializer = deserializer
52
- end
53
-
54
- def serializer=(serializer)
55
- @context_event_serializer = serializer
56
- end
57
-
58
- def deserializer
59
- context_data_deserializer
29
+ @deserializer
60
30
  end
61
31
 
62
- def serializer
63
- context_event_serializer
32
+ def context_data_deserializer=(deserializer)
33
+ @deserializer = deserializer
64
34
  end
65
35
 
66
- def url
67
- @url ||= "#{endpoint}/context"
68
- end
69
-
70
- def headers
71
- @headers ||= {
72
- "Content-Type": "application/json",
73
- "X-API-Key": api_key,
74
- "X-Application": application,
75
- "X-Environment": environment,
76
- "X-Application-Version": "0",
77
- "X-Agent": "absmartly-ruby-sdk"
78
- }
79
- end
80
-
81
- def query
82
- @query ||= {
83
- "application": application,
84
- "environment": environment
85
- }
36
+ def context_event_serializer
37
+ @serializer
86
38
  end
87
39
 
88
- def validate!
89
- raise ArgumentError.new("Missing Endpoint configuration") if endpoint.nil? || endpoint.empty?
90
- raise ArgumentError.new("Missing APIKey configuration") if api_key.nil? || api_key.empty?
91
- raise ArgumentError.new("Missing Application configuration") if application.nil? || application.empty?
92
- raise ArgumentError.new("Missing Environment configuration") if environment.nil? || environment.empty?
40
+ def context_event_serializer=(serializer)
41
+ @serializer = serializer
93
42
  end
94
43
  end
data/lib/context.rb CHANGED
@@ -10,7 +10,7 @@ require_relative "json/publish_event"
10
10
  require_relative "json/goal_achievement"
11
11
 
12
12
  class Context
13
- attr_reader :pending_count
13
+ attr_reader :data, :pending_count
14
14
 
15
15
  def self.create(clock, config, data_future, data_provider,
16
16
  event_handler, event_logger, variable_parser, audience_matcher)
@@ -114,10 +114,6 @@ class Context
114
114
  def set_unit(unit_type, uid)
115
115
  check_not_closed?
116
116
 
117
- unless uid.is_a?(String) || uid.is_a?(Numeric)
118
- raise IllegalStateException.new("Unit '#{unit_type}' UID is of unsupported type '#{uid.class}'. UID must be one of ['string', 'number']")
119
- end
120
-
121
117
  previous = @units[unit_type.to_sym]
122
118
  if !previous.nil? && previous != uid
123
119
  raise IllegalStateException.new("Unit '#{unit_type}' already set.")
@@ -545,6 +541,7 @@ class Context
545
541
  @experimentCustomFieldValues[custom_field_value.name] = value
546
542
 
547
543
  end
544
+
548
545
  end
549
546
  end
550
547
 
@@ -69,8 +69,11 @@ class ContextConfig
69
69
  @custom_assignments[experiment_name.to_sym]
70
70
  end
71
71
 
72
+
72
73
  def set_event_logger(event_logger)
73
74
  @event_logger = event_logger
74
75
  self
75
76
  end
77
+
78
+ attr_reader :event_logger
76
79
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "faraday"
4
- require "faraday/retry"
4
+ require 'faraday/retry'
5
5
  require "uri"
6
6
  require_relative "http_client"
7
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: absmartly-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - absmartly
@@ -83,6 +83,7 @@ files:
83
83
  - LICENSE.txt
84
84
  - README.md
85
85
  - Rakefile
86
+ - absmartly.gemspec
86
87
  - example/example.rb
87
88
  - lib/a_b_smartly.rb
88
89
  - lib/a_b_smartly_config.rb