kameleoon-client-ruby 3.2.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kameleoon/configuration/custom_data_info.rb +16 -8
  3. data/lib/kameleoon/configuration/data_file.rb +40 -17
  4. data/lib/kameleoon/configuration/feature_flag.rb +10 -0
  5. data/lib/kameleoon/configuration/rule.rb +4 -0
  6. data/lib/kameleoon/configuration/settings.rb +13 -8
  7. data/lib/kameleoon/configuration/variation_exposition.rb +4 -0
  8. data/lib/kameleoon/data/browser.rb +4 -0
  9. data/lib/kameleoon/data/conversion.rb +4 -0
  10. data/lib/kameleoon/data/cookie.rb +4 -0
  11. data/lib/kameleoon/data/custom_data.rb +11 -3
  12. data/lib/kameleoon/data/data.rb +30 -4
  13. data/lib/kameleoon/data/device.rb +4 -0
  14. data/lib/kameleoon/data/geolocation.rb +5 -0
  15. data/lib/kameleoon/data/kcs_heat.rb +16 -0
  16. data/lib/kameleoon/data/manager/assigned_variation.rb +5 -0
  17. data/lib/kameleoon/data/manager/data_array_storage.rb +7 -0
  18. data/lib/kameleoon/data/manager/data_map_storage.rb +7 -0
  19. data/lib/kameleoon/data/manager/page_view_visit.rb +4 -0
  20. data/lib/kameleoon/data/manager/visitor.rb +198 -67
  21. data/lib/kameleoon/data/manager/visitor_manager.rb +54 -17
  22. data/lib/kameleoon/data/mapping_identifier.rb +33 -0
  23. data/lib/kameleoon/data/operating_system.rb +4 -0
  24. data/lib/kameleoon/data/page_view.rb +6 -1
  25. data/lib/kameleoon/data/unique_identifier.rb +11 -0
  26. data/lib/kameleoon/data/user_agent.rb +4 -0
  27. data/lib/kameleoon/data/visitor_visits.rb +12 -1
  28. data/lib/kameleoon/hybrid/manager.rb +13 -4
  29. data/lib/kameleoon/kameleoon_client.rb +348 -146
  30. data/lib/kameleoon/kameleoon_client_config.rb +64 -17
  31. data/lib/kameleoon/kameleoon_client_factory.rb +15 -2
  32. data/lib/kameleoon/logging/default_logger.rb +20 -0
  33. data/lib/kameleoon/logging/kameleoon_logger.rb +77 -0
  34. data/lib/kameleoon/logging/logger.rb +12 -0
  35. data/lib/kameleoon/managers/data/data_manager.rb +36 -0
  36. data/lib/kameleoon/managers/remote_data/remote_data_manager.rb +33 -18
  37. data/lib/kameleoon/managers/remote_data/remote_visitor_data.rb +42 -16
  38. data/lib/kameleoon/managers/tracking/tracking_builder.rb +149 -0
  39. data/lib/kameleoon/managers/tracking/tracking_manager.rb +97 -0
  40. data/lib/kameleoon/managers/tracking/visitor_tracking_registry.rb +94 -0
  41. data/lib/kameleoon/managers/warehouse/warehouse_manager.rb +22 -5
  42. data/lib/kameleoon/network/access_token_source.rb +46 -14
  43. data/lib/kameleoon/network/cookie/cookie_manager.rb +45 -7
  44. data/lib/kameleoon/network/net_provider.rb +2 -3
  45. data/lib/kameleoon/network/network_manager.rb +16 -21
  46. data/lib/kameleoon/network/request.rb +14 -3
  47. data/lib/kameleoon/network/response.rb +4 -0
  48. data/lib/kameleoon/network/url_provider.rb +11 -10
  49. data/lib/kameleoon/real_time/real_time_configuration_service.rb +10 -11
  50. data/lib/kameleoon/sdk_version.rb +31 -0
  51. data/lib/kameleoon/targeting/condition.rb +6 -2
  52. data/lib/kameleoon/targeting/condition_factory.rb +3 -0
  53. data/lib/kameleoon/targeting/conditions/browser_condition.rb +3 -3
  54. data/lib/kameleoon/targeting/conditions/cookie_condition.rb +10 -10
  55. data/lib/kameleoon/targeting/conditions/geolocation_condition.rb +0 -1
  56. data/lib/kameleoon/targeting/conditions/kcs_heat_range_condition.rb +37 -0
  57. data/lib/kameleoon/targeting/conditions/number_condition.rb +4 -4
  58. data/lib/kameleoon/targeting/conditions/operating_system_condition.rb +1 -2
  59. data/lib/kameleoon/targeting/conditions/sdk_language_condition.rb +2 -1
  60. data/lib/kameleoon/targeting/conditions/segment_condition.rb +3 -3
  61. data/lib/kameleoon/targeting/conditions/string_value_condition.rb +2 -1
  62. data/lib/kameleoon/targeting/conditions/target_feature_flag_condition.rb +7 -11
  63. data/lib/kameleoon/targeting/conditions/time_elapsed_since_visit_condition.rb +1 -2
  64. data/lib/kameleoon/targeting/conditions/visit_number_today_condition.rb +4 -4
  65. data/lib/kameleoon/targeting/conditions/visit_number_total_condition.rb +5 -3
  66. data/lib/kameleoon/targeting/conditions/visitor_new_return_condition.rb +7 -6
  67. data/lib/kameleoon/targeting/models.rb +0 -14
  68. data/lib/kameleoon/targeting/targeting_manager.rb +37 -7
  69. data/lib/kameleoon/targeting/tree_builder.rb +10 -5
  70. data/lib/kameleoon/types/remote_visitor_data_filter.rb +21 -3
  71. data/lib/kameleoon/types/variable.rb +21 -0
  72. data/lib/kameleoon/types/variation.rb +22 -0
  73. data/lib/kameleoon/utils.rb +18 -0
  74. data/lib/kameleoon/version.rb +1 -27
  75. metadata +16 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8dea61e52fdfa38f0fc3d2161711fe169940de6053d9d372fef06b30e70975f
4
- data.tar.gz: 201acbdb7b3597d4637207c6f599c04dc9bbf0984de60fb93904569193152230
3
+ metadata.gz: 2fb2eefaf6fa7008e5e475044e2f3c20baa088332657f0e19e17c73abffdfa3b
4
+ data.tar.gz: ef078e7a9d1ac4fd2fef3a74edf4ee5328043b6989c59eafc29559f79ab535d4
5
5
  SHA512:
6
- metadata.gz: d3ecf83261db7d1ed52d231c141521df16ea5f2d0e42240c80db684537dd5ab32f5047430446fe1779f40485357b37ea455416369d5dce0f748c76ed7b132103
7
- data.tar.gz: f5e1641dd1311bac6620c6db63f396fbe7d46f96eecab69713eef49e9e81f67a24c5644f68b7fdff4dd99455a5c480be57ab26e7372947b02eefec3033d310fc
6
+ metadata.gz: 8df54dcee89dc21a3565948e96b330b655d780ca7cbd4955385b12c5ad64ccdbc8318b17043acb02eddcd2d8dd45191eaeb001d6957e250573e8b9cc606ffbf0
7
+ data.tar.gz: 3fe4dfe732310de088f45dcc2002c2504b03e45e2e101bb7199b46783cac46aed80a7e35fc0cc6995ddcb7ba0202629d367922e58d8a8e90b974679f91363086
@@ -1,25 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kameleoon/logging/kameleoon_logger'
4
+
3
5
  module Kameleoon
4
6
  # Module which contains all internal data of SDK
5
7
  module Configuration
6
-
7
8
  class CustomDataInfo
8
9
  attr_reader :local_only, :visitor_scope, :mapping_identifier_index
10
+
9
11
  SCOPE_VISITOR = 'VISITOR'
10
12
 
11
- def initialize(hashes, log_func = nil)
13
+ def initialize(hashes)
12
14
  @local_only = Set[]
13
15
  @visitor_scope = Set[]
14
- @log_func = log_func
15
16
  unless hashes.nil?
16
17
  for hash in hashes
17
18
  index = hash['index']
18
19
  @local_only.add(index) if hash['localOnly']
19
20
  @visitor_scope.add(index) if hash['scope'] == SCOPE_VISITOR
20
21
  if hash['isMappingIdentifier']
21
- if @mapping_identifier_index != nil
22
- @log_func&.call('More than one mapping identifier is set. Undefined behavior may occur on cross-device reconciliation.')
22
+ unless @mapping_identifier_index.nil?
23
+ Logging::KameleoonLogger.warning('More than one mapping identifier is set. Undefined behavior ' \
24
+ 'may occur on cross-device reconciliation.')
25
+
23
26
  end
24
27
  @mapping_identifier_index = index
25
28
  end
@@ -27,17 +30,22 @@ module Kameleoon
27
30
  end
28
31
  end
29
32
 
30
- def local_only? (index)
33
+ def local_only?(index)
31
34
  @local_only.include?(index)
32
35
  end
33
36
 
34
- def mapping_identifier? (index)
37
+ def mapping_identifier?(index)
35
38
  index == @mapping_identifier_index
36
39
  end
37
40
 
38
- def visitor_scope? (index)
41
+ def visitor_scope?(index)
39
42
  @visitor_scope.include?(index)
40
43
  end
44
+
45
+ def self.mapping_identifier?(custom_data_info, custom_data)
46
+ !custom_data_info.nil? && (custom_data.id == custom_data_info.mapping_identifier_index) && \
47
+ !(custom_data.values.empty? || custom_data.values[0].empty?)
48
+ end
41
49
  end
42
50
  end
43
51
  end
@@ -3,31 +3,32 @@
3
3
  require 'kameleoon/configuration/settings'
4
4
  require 'kameleoon/configuration/feature_flag'
5
5
  require 'kameleoon/configuration/custom_data_info'
6
+ require 'kameleoon/logging/kameleoon_logger'
6
7
 
7
8
  module Kameleoon
8
9
  module Configuration
9
10
  class DataFile
10
- attr_reader :settings, :feature_flags, :has_any_targeted_delivery_rule, :feature_flag_by_id, :rule_by_segment_id, :variation_by_id, :custom_data_info
11
+ attr_reader :settings, :feature_flags, :has_any_targeted_delivery_rule, :feature_flag_by_id, :rule_by_segment_id,
12
+ :variation_by_id, :custom_data_info
11
13
 
12
- def initialize(environment, log_func = nil)
13
- @settings = Settings.new
14
- @feature_flags = {}
15
- @environment = environment
16
- @log_func = log_func
17
- collect_indices
18
- @custom_data_info = Kameleoon::Configuration::CustomDataInfo.new(nil, @log_func)
14
+ def to_s
15
+ 'DataFile{' \
16
+ "environment:#{@environment}," \
17
+ "feature_flags:#{@feature_flags.size}," \
18
+ "settings:#{@settings}" \
19
+ '}'
19
20
  end
20
21
 
21
- def init(configuration)
22
- @settings.update(configuration['configuration'])
23
- configuration['featureFlags'].each do |raw|
24
- ff = FeatureFlag.new(raw)
25
- @feature_flags[ff.feature_key] = ff
22
+ def initialize(environment, configuration = nil)
23
+ Logging::KameleoonLogger.debug('CALL: DataFile.new(environment: %s)', environment)
24
+ @environment = environment
25
+ if configuration.nil?
26
+ init_default
27
+ else
28
+ init(configuration)
26
29
  end
27
- @has_any_targeted_delivery_rule = any_targeted_delivery_rule?
28
30
  collect_indices
29
- @custom_data_info = CustomDataInfo.new(configuration['customData'], @log_func)
30
- self
31
+ Logging::KameleoonLogger.debug('RETURN: DataFile.new(environment: %s)', environment)
31
32
  end
32
33
 
33
34
  def get_feature_flag(feature_key)
@@ -40,6 +41,28 @@ module Kameleoon
40
41
 
41
42
  private
42
43
 
44
+ def init_default
45
+ Logging::KameleoonLogger.debug('CALL: DataFile.init_default')
46
+ @settings = Settings.new
47
+ @feature_flags = {}
48
+ @has_any_targeted_delivery_rule = false
49
+ @custom_data_info = Kameleoon::Configuration::CustomDataInfo.new(nil)
50
+ Logging::KameleoonLogger.debug('RETURN: DataFile.init_default')
51
+ end
52
+
53
+ def init(configuration)
54
+ Logging::KameleoonLogger.debug('CALL: DataFile.init(configuration: %s)', configuration)
55
+ @settings = Settings.new(configuration['configuration'])
56
+ @feature_flags = {}
57
+ configuration['featureFlags'].each do |raw|
58
+ ff = FeatureFlag.new(raw)
59
+ @feature_flags[ff.feature_key] = ff
60
+ end
61
+ @has_any_targeted_delivery_rule = any_targeted_delivery_rule?
62
+ @custom_data_info = CustomDataInfo.new(configuration['customData'])
63
+ Logging::KameleoonLogger.debug('RETURN: DataFile.init(configuration: %s)', configuration)
64
+ end
65
+
43
66
  def any_targeted_delivery_rule?
44
67
  @feature_flags.any? { |_, ff| ff.environment_enabled && ff.rules.any?(&:targeted_delivery_type?) }
45
68
  end
@@ -50,10 +73,10 @@ module Kameleoon
50
73
  @variation_by_id = {}
51
74
 
52
75
  @feature_flags.each_value do |feature_flag|
76
+ @feature_flag_by_id[feature_flag.id] = feature_flag
53
77
  next if feature_flag.rules.nil?
54
78
 
55
79
  feature_flag.rules.each do |rule|
56
- @feature_flag_by_id[feature_flag.id] = feature_flag
57
80
  @rule_by_segment_id[rule.segment_id] = rule
58
81
  rule.variation_by_exposition.each do |variation|
59
82
  @variation_by_id[variation.variation_id] = variation
@@ -14,6 +14,16 @@ module Kameleoon
14
14
  array&.map { |it| FeatureFlag.new(it) }
15
15
  end
16
16
 
17
+ def to_s
18
+ 'FeatureFlag{' \
19
+ "id:#{@id}, " \
20
+ "feature_key:#{@feature_key}, " \
21
+ "environment_enabled:#{@environment_enabled}, " \
22
+ "default_variation_key:#{@default_variation_key}, " \
23
+ "rules:#{@rules.size}" \
24
+ '}'
25
+ end
26
+
17
27
  def initialize(hash)
18
28
  @id = hash['id']
19
29
  @feature_key = hash['featureKey']
@@ -36,6 +36,10 @@ module Kameleoon
36
36
  array&.map { |it| Rule.new(it) }
37
37
  end
38
38
 
39
+ def to_s
40
+ "Rule{id:#{@id}}"
41
+ end
42
+
39
43
  def initialize(hash)
40
44
  @id = hash['id']
41
45
  @order = hash['order']
@@ -9,16 +9,21 @@ module Kameleoon
9
9
  attr_accessor :real_time_update
10
10
  attr_reader :is_consent_required, :data_api_domain
11
11
 
12
- def initialize
13
- @real_time_update = false
14
- @is_consent_required = false
15
- @data_api_domain = nil
12
+ def to_s
13
+ "Settings{real_time_update:#{@real_time_update},is_consent_required:#{@is_consent_required}," \
14
+ "data_api_domain:#{@data_api_domain}}"
16
15
  end
17
16
 
18
- def update(configuration)
19
- @real_time_update = configuration['realTimeUpdate'] || false
20
- @is_consent_required = configuration['consentType'] == 'REQUIRED'
21
- @data_api_domain = configuration['dataApiDomain']
17
+ def initialize(configuration = nil)
18
+ if configuration.nil?
19
+ @real_time_update = false
20
+ @is_consent_required = false
21
+ @data_api_domain = nil
22
+ else
23
+ @real_time_update = configuration['realTimeUpdate'] || false
24
+ @is_consent_required = configuration['consentType'] == 'REQUIRED'
25
+ @data_api_domain = configuration['dataApiDomain']
26
+ end
22
27
  end
23
28
  end
24
29
  end
@@ -11,6 +11,10 @@ module Kameleoon
11
11
  array&.map { |it| VariationByExposition.new(it) }
12
12
  end
13
13
 
14
+ def to_s
15
+ "VariationByExposition{exposition:#{@exposition},variation_key:#{@variation_key},variation_id:#{@variation_id}}"
16
+ end
17
+
14
18
  def initialize(hash)
15
19
  @variation_key = hash['variationKey']
16
20
  @variation_id = hash['variationId']
@@ -37,6 +37,10 @@ module Kameleoon
37
37
  class Browser < DuplicationUnsafeData
38
38
  attr_reader :type, :version
39
39
 
40
+ def to_s
41
+ "Browser{type:#{@type},version:#{@version}}"
42
+ end
43
+
40
44
  # @param [BrowserType] browser_type Browser type, can be: CHROME, INTERNET_EXPLORER, FIREFOX, SAFARI, OPERA, OTHER
41
45
  # @param [float] version Version of browser
42
46
  def initialize(browser_type, version = Float::NAN)
@@ -9,6 +9,10 @@ module Kameleoon
9
9
  class Conversion < DuplicationSafeData
10
10
  attr_reader :goal_id, :revenue, :negative
11
11
 
12
+ def to_s
13
+ "Conversion{goal_id:#{@goal_id},revenue:#{@revenue},negative:#{@negative}}"
14
+ end
15
+
12
16
  # @param [Integer] goal_id Id of the goal associated to the conversion
13
17
  # @param [Float] revenue Optional field - Revenue associated to the conversion.
14
18
  # @param [Boolean] negative Optional field - If the revenue is negative. By default it's positive.
@@ -9,6 +9,10 @@ module Kameleoon
9
9
  def initialize(cookies)
10
10
  @cookies = cookies
11
11
  end
12
+
13
+ def to_s
14
+ "Cookie{cookies:#{@cookies}}"
15
+ end
12
16
  end
13
17
  end
14
18
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'kameleoon/logging/kameleoon_logger'
4
5
  require 'kameleoon/network/uri_helper'
5
6
  require_relative 'data'
6
7
 
@@ -8,7 +9,10 @@ module Kameleoon
8
9
  # Represents any custom data for targeting conditions
9
10
  class CustomData < DuplicationUnsafeData
10
11
  attr_reader :id, :values
11
- attr_accessor :is_mapping_identifier
12
+
13
+ def to_s
14
+ "CustomData{id:#{@id},values:#{@values}}"
15
+ end
12
16
 
13
17
  # @param [Integer] id Id of the custom data
14
18
  # @param [String] value Value of the custom data
@@ -33,9 +37,14 @@ module Kameleoon
33
37
  @id = arg0
34
38
  @values = args
35
39
  end
40
+
41
+ if @values.empty?
42
+ Logging::KameleoonLogger.error('Created a custom data %s with no values. It will not be tracked.', @id)
43
+ end
44
+
36
45
  return if @id.is_a?(Integer)
37
46
 
38
- warn "CustomData field 'id' must be of 'Integer' type"
47
+ Logging::KameleoonLogger.warning("CustomData field 'id' must be of 'Integer' type")
39
48
  @id = @id.is_a?(String) ? @id.to_i : -1
40
49
  end
41
50
  # rubocop:enable Metrics/MethodLength
@@ -51,7 +60,6 @@ module Kameleoon
51
60
  overwrite: 'true',
52
61
  nonce: nonce
53
62
  }
54
- params[:mappingIdentifier] = is_mapping_identifier if is_mapping_identifier
55
63
  Kameleoon::Network::UriHelper.encode_query(params)
56
64
  end
57
65
  end
@@ -18,21 +18,47 @@ module Kameleoon
18
18
  GEOLOCATION = 'GEOLOCATION'
19
19
  end
20
20
 
21
+ module DataState
22
+ UNSENT = 0
23
+ TRANSMITTING = 1
24
+ SENT = 2
25
+ end
26
+
21
27
  # Represents base class for any Kameleoon data
22
28
  class Data
23
- attr_reader :instance, :sent
29
+ attr_reader :instance
24
30
 
25
31
  def initialize(data_type)
26
32
  @instance = data_type
27
- @sent = false
33
+ @state = DataState::UNSENT
28
34
  end
29
35
 
30
36
  def obtain_full_post_text_line
31
37
  raise KameleoonError.new('ToDo: implement this method.'), 'ToDo: implement this method.'
32
38
  end
33
39
 
40
+ def unsent
41
+ @state == DataState::UNSENT
42
+ end
43
+
44
+ def transmitting
45
+ @state == DataState::TRANSMITTING
46
+ end
47
+
48
+ def sent
49
+ @state == DataState::SENT
50
+ end
51
+
52
+ def mark_as_unsent
53
+ @state = DataState::UNSENT if transmitting
54
+ end
55
+
56
+ def mark_as_transmitting
57
+ @state = DataState::TRANSMITTING if unsent
58
+ end
59
+
34
60
  def mark_as_sent
35
- @sent = true
61
+ @state = DataState::SENT
36
62
  @nonce = nil
37
63
  end
38
64
  end
@@ -52,7 +78,7 @@ module Kameleoon
52
78
  private
53
79
 
54
80
  def nonce
55
- @nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if !@sent && @nonce.nil?
81
+ @nonce = Kameleoon::Utils.generate_random_string(NONCE_LENGTH) if !sent && @nonce.nil?
56
82
  @nonce
57
83
  end
58
84
  end
@@ -15,6 +15,10 @@ module Kameleoon
15
15
  class Device < DuplicationUnsafeData
16
16
  attr_reader :device_type
17
17
 
18
+ def to_s
19
+ "Device{device_type:#{@device_type}}"
20
+ end
21
+
18
22
  def initialize(device_type)
19
23
  super(DataType::DEVICE)
20
24
  @device_type = device_type
@@ -5,6 +5,11 @@ module Kameleoon
5
5
  class Geolocation < DuplicationUnsafeData
6
6
  attr_reader :country, :region, :city, :postal_code, :latitude, :longitude
7
7
 
8
+ def to_s
9
+ "Geolocation{country:'#{@country}',region:'#{@region}',city:'#{@city}',postal_code:'#{@postal_code}'," \
10
+ "latitude:#{@latitude},longitude:#{@longitude}}"
11
+ end
12
+
8
13
  # @param [String] country Country of visitor's geolocation. Required
9
14
  # @param [String] region Region of visitor's geolocation. Optional
10
15
  # @param [String] city City of visitor's geolocation. Optional
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kameleoon
4
+ class KcsHeat
5
+ attr_reader :values
6
+
7
+ def initialize(values)
8
+ @values = values
9
+ @values.freeze
10
+ end
11
+ end
12
+
13
+ def to_s
14
+ "KcsHeat{values:#{@values}}"
15
+ end
16
+ end
@@ -13,6 +13,11 @@ module Kameleoon
13
13
 
14
14
  attr_reader :experiment_id, :variation_id, :rule_type, :assignment_time
15
15
 
16
+ def to_s
17
+ "AssignedVariation{experiment_id:#{@experiment_id},variation_id:#{@variation_id}," \
18
+ "assignment_time:#{@assignment_time},rule_type:#{@rule_type}}"
19
+ end
20
+
16
21
  def initialize(experiment_id, variation_id, rule_type = Kameleoon::Configuration::RuleType::UNKNOWN, assignment_time: nil)
17
22
  super(DataType::ASSIGNED_VARIATION)
18
23
  @experiment_id = experiment_id
@@ -6,6 +6,13 @@ module Kameleoon
6
6
  # DataArrayStorage is a readonly accessor to a sequntial storage of specific visitor data.
7
7
  # It is thread-safe
8
8
  class DataArrayStorage
9
+
10
+ def to_s
11
+ values = []
12
+ enumerate { |v| values << v }
13
+ "DataArrayStorage{values:#{values.join(',')}}"
14
+ end
15
+
9
16
  def initialize(mutex, array)
10
17
  @mutex = mutex
11
18
  @array = array
@@ -6,6 +6,13 @@ module Kameleoon
6
6
  # DataMapStorage is a readonly accessor to a key-value based storage of specific visitor data.
7
7
  # It is thread-safe
8
8
  class DataMapStorage
9
+
10
+ def to_s
11
+ values = []
12
+ enumerate { |v| values << v }
13
+ "DataMapStorage{values:#{values.join(',')}}"
14
+ end
15
+
9
16
  def initialize(mutex, map)
10
17
  @mutex = mutex
11
18
  @map = map
@@ -5,6 +5,10 @@ module Kameleoon
5
5
  class PageViewVisit
6
6
  attr_reader :page_view, :count, :last_timestamp
7
7
 
8
+ def to_s
9
+ "PageViewVisit{last_timestamp:#{@last_timestamp},count:#{@count},page_view:#{@page_view}}"
10
+ end
11
+
8
12
  def initialize(page_view, count = 1, timestamp = nil)
9
13
  @page_view = page_view
10
14
  @count = count