fino 1.1.4 → 1.3.0

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: a89fc5426c11c8a579d0e95b04ec8d5deb836a35f217f9e321f3f0a0b94eab7d
4
- data.tar.gz: 61140c5d0665b59a2ce86cf723d1f026757e90e8e21cd4cbe6c488f6c8f6d96d
3
+ metadata.gz: c5692873000111c2c668cdf3dd509fb4bdd9d2f3935b467158686739e8e9cc9f
4
+ data.tar.gz: 8416e04110ddf6ef6dd4e110e32806ca3b711dd2a13fa13c7bc18cbece91443d
5
5
  SHA512:
6
- metadata.gz: da686fb13de3413f15900c5b710a76d77ced5cb5a9861df0fe293db51c601cd4975193b9208aff6c1e0dac48ed176255019bcbca4e17340249215a25dc029b59
7
- data.tar.gz: d8bc6812eb4f39493ea31ef986418010ec0d12dc022efa0a38b590769876e19b399f9124527bb47caf553adfe4eac30df660a1c4f1b2d2e19ed6a4fd2c3b2063
6
+ metadata.gz: 99dd798252bd649378504426f0b7c13857a3258501642244b5b110428c0bcfa9abce3266c698be1993722f9643e5897e8e2548fe501d9694fc6f0c205321de12
7
+ data.tar.gz: 9c2f6594f6453f818121e5e273ecc9ed91ff2c85a37c7eedbe82fdd6e2646e36b27f2074b16c5759e6a523003c880aea394785d3c26b599e8178dfd04ad9a57b
@@ -48,6 +48,10 @@ class Fino::Cache::Memory
48
48
  expire_if_needed
49
49
  end
50
50
 
51
+ def delete(key)
52
+ hash.delete(key)
53
+ end
54
+
51
55
  private
52
56
 
53
57
  attr_reader :hash, :expirator
data/lib/fino/library.rb CHANGED
@@ -7,12 +7,12 @@ class Fino::Library
7
7
  @configuration = configuration
8
8
  end
9
9
 
10
- def value(setting_name, at: nil)
11
- setting(setting_name, at: at).value
10
+ def value(setting_name, at: nil, **context)
11
+ setting(setting_name, at: at).value(**context)
12
12
  end
13
13
 
14
- def values(*setting_names, at: nil)
15
- settings(*setting_names, at: at).map(&:value)
14
+ def values(*setting_names, at: nil, **context)
15
+ settings(*setting_names, at: at).map { |s| s.value(**context) }
16
16
  end
17
17
 
18
18
  def setting(setting_name, at: nil)
@@ -34,17 +34,35 @@ class Fino::Library
34
34
  pipeline.read_multi(setting_definitions)
35
35
  end
36
36
 
37
- def set(**setting_names_to_values)
38
- at = setting_names_to_values.delete(:at)
37
+ def variant(setting_name, at: nil, for:)
38
+ setting(setting_name, at: at).variant(
39
+ for: binding.local_variable_get(:for)
40
+ )
41
+ end
42
+
43
+ def set(**data)
44
+ at = data.delete(:at)
45
+ raw_overrides = data.delete(:overrides) || {}
46
+ raw_variants = data.delete(:variants) || {}
39
47
 
40
- setting_names_to_values.each do |setting_name, value|
41
- setting_definition = build_setting_definition(setting_name, at: at)
48
+ setting_name, raw_value = data.first
49
+ setting_definition = build_setting_definition(setting_name, at: at)
50
+ value = setting_definition.type_class.deserialize(raw_value)
42
51
 
43
- pipeline.write(
44
- setting_definition,
45
- setting_definition.type_class.deserialize(value)
46
- )
52
+ variants = raw_variants.map do |percentage, value|
53
+ Fino::Variant.new(percentage, setting_definition.type_class.deserialize(value))
47
54
  end
55
+
56
+ variants.prepend(
57
+ Fino::Variant.new(100.0 - variants.sum(&:percentage), Fino::Variant::CONTROL)
58
+ )
59
+
60
+ pipeline.write(
61
+ setting_definition,
62
+ value,
63
+ raw_overrides.transform_values { |v| setting_definition.type_class.deserialize(v) },
64
+ variants
65
+ )
48
66
  end
49
67
 
50
68
  def slice(**mapping)
@@ -57,6 +75,11 @@ class Fino::Library
57
75
  pipeline.read_multi(setting_definitions)
58
76
  end
59
77
 
78
+ def set_variants(setting_name, at: nil, variants:)
79
+ setting_definition = build_setting_definition(setting_name, at: at)
80
+ pipeline.write_variants(setting_definition, variants)
81
+ end
82
+
60
83
  private
61
84
 
62
85
  attr_reader :configuration
@@ -22,13 +22,10 @@ class Fino::Pipe::Cache
22
22
  end
23
23
  end
24
24
 
25
- def write(setting_definition, value)
26
- pipe.write(setting_definition, value)
25
+ def write(setting_definition, value, **context)
26
+ pipe.write(setting_definition, value, **context)
27
27
 
28
- cache.write(
29
- setting_definition.key,
30
- setting_definition.type_class.build(setting_definition, value)
31
- )
28
+ cache.delete(setting_definition.key)
32
29
  end
33
30
 
34
31
  private
@@ -17,8 +17,8 @@ class Fino::Pipe::Storage
17
17
  end
18
18
  end
19
19
 
20
- def write(setting_definition, value)
21
- adapter.write(setting_definition, value)
20
+ def write(setting_definition, value, overrides, variants)
21
+ adapter.write(setting_definition, value, overrides, variants)
22
22
  end
23
23
 
24
24
  private
@@ -27,11 +27,14 @@ class Fino::Pipe::Storage
27
27
 
28
28
  def to_setting(setting_definition, raw_adapter_data)
29
29
  raw_value = adapter.fetch_value_from(raw_adapter_data)
30
+ scoped_raw_values = adapter.fetch_scoped_values_from(raw_adapter_data)
31
+ raw_variants = adapter.fetch_raw_variants_from(raw_adapter_data)
30
32
 
31
33
  setting_definition.type_class.build(
32
34
  setting_definition,
33
35
  raw_value,
34
- **raw_adapter_data
36
+ scoped_raw_values,
37
+ raw_variants
35
38
  )
36
39
  end
37
40
  end
data/lib/fino/pipe.rb CHANGED
@@ -13,7 +13,7 @@ module Fino::Pipe
13
13
  raise NotImplementedError
14
14
  end
15
15
 
16
- def write(setting_definition, value)
16
+ def write(setting_definition, value, overrides = {}, variants = {})
17
17
  raise NotImplementedError
18
18
  end
19
19
 
data/lib/fino/setting.rb CHANGED
@@ -14,22 +14,37 @@ module Fino::Setting
14
14
  raise NotImplementedError
15
15
  end
16
16
 
17
- def build(setting_definition, raw_value, *options)
17
+ def build(setting_definition, raw_value, scoped_raw_values, raw_variants)
18
+ value = raw_value.equal?(Fino::EMPTINESS) ? setting_definition.options[:default] : deserialize(raw_value)
19
+ scoped_values = scoped_raw_values.transform_values { |v| deserialize(v) }
20
+
21
+ variants = raw_variants.map do |raw_variant|
22
+ Fino::Variant.new(
23
+ raw_variant.fetch(:percentage),
24
+ deserialize(raw_variant.fetch(:value))
25
+ )
26
+ end
27
+
28
+ variants.prepend(
29
+ Fino::Variant.new(percentage: 100.0 - variants.sum(&:percentage), value: Fino::Variant::CONTROL)
30
+ )
31
+
18
32
  new(
19
33
  setting_definition,
20
- raw_value.equal?(Fino::EMPTINESS) ? setting_definition.options[:default] : deserialize(raw_value),
21
- *options
34
+ value,
35
+ scoped_values,
36
+ variants
22
37
  )
23
38
  end
24
39
  end
25
40
 
26
- attr_reader :definition, :value
41
+ attr_reader :definition, :variants
27
42
 
28
- def initialize(definition, value, **options)
43
+ def initialize(definition, value, scoped_values = {}, variants = [])
29
44
  @definition = definition
30
45
  @value = value
31
-
32
- @options = options
46
+ @scoped_values = scoped_values
47
+ @variants = variants
33
48
  end
34
49
 
35
50
  def name
@@ -40,6 +55,41 @@ module Fino::Setting
40
55
  definition.key
41
56
  end
42
57
 
58
+ def value(**context)
59
+ return @value unless (scope = context[:for])
60
+
61
+ scoped_values.fetch(scope.to_s) do
62
+ return @value if variants.empty?
63
+
64
+ variant = variant(for: scope)
65
+ result = variant_id_to_value.fetch(variant.id, @value)
66
+
67
+ return @value if result == Fino::Variant::CONTROL
68
+
69
+ result
70
+ end
71
+ end
72
+
73
+ def variant(for:)
74
+ Fino::VariantPicker.new(self).call(
75
+ binding.local_variable_get(:for)
76
+ )
77
+ end
78
+
79
+ def variant_id_to_value
80
+ @variant_id_to_value ||= variants.each_with_object({}) do |variant, memo|
81
+ memo[variant.id] = variant.value
82
+ end
83
+ end
84
+
85
+ def overriden_scopes
86
+ scoped_values.keys
87
+ end
88
+
89
+ def scope_overrides
90
+ scoped_values
91
+ end
92
+
43
93
  def type
44
94
  definition.type
45
95
  end
@@ -69,9 +119,15 @@ module Fino::Setting
69
119
  "key=#{key.inspect}",
70
120
  "type=#{type_class.inspect}",
71
121
  "value=#{value.inspect}",
122
+ "overrides=#{@scoped_values.inspect}",
123
+ "variants=#{variants.inspect}",
72
124
  "default=#{default.inspect}"
73
125
  ]
74
126
 
75
127
  "#<#{self.class.name} #{attributes.join(', ')}>"
76
128
  end
129
+
130
+ private
131
+
132
+ attr_reader :scoped_values
77
133
  end
@@ -0,0 +1,5 @@
1
+
2
+ Fino::Variant = Struct.new(:percentage, :value) do
3
+ def id = @id ||= SecureRandom.uuid
4
+ end
5
+ Fino::Variant::CONTROL = Object.new
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fino::VariantPicker
4
+ SCALING_FACTOR = 1_000
5
+
6
+ attr_reader :setting
7
+
8
+ def initialize(setting)
9
+ @setting = setting
10
+ end
11
+
12
+ def call(scope)
13
+ return nil if setting.variants.empty?
14
+
15
+ random = Zlib.crc32("#{setting.key}#{scope}") % (100 * SCALING_FACTOR)
16
+ cumulative = 0
17
+
18
+ picked_variant = setting.variants.sort_by(&:percentage).find do |variant|
19
+ cumulative += variant.percentage * SCALING_FACTOR
20
+ random <= cumulative
21
+ end
22
+
23
+ Fino.logger.debug { "Variant picked: #{picked_variant}" }
24
+
25
+ picked_variant
26
+ end
27
+ end
data/lib/fino/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fino
4
- VERSION = "1.1.4"
4
+ VERSION = "1.3.0"
5
5
  REQUIRED_RUBY_VERSION = ">= 3.0.0"
6
6
  end
data/lib/fino.rb CHANGED
@@ -25,7 +25,9 @@ module Fino
25
25
  :setting,
26
26
  :settings,
27
27
  :slice,
28
- :set
28
+ :set,
29
+ :define_variants,
30
+ :variant
29
31
 
30
32
  module_function
31
33
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fino
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Egor Iskrenkov
@@ -54,6 +54,8 @@ files:
54
54
  - lib/fino/settings/integer.rb
55
55
  - lib/fino/settings/section.rb
56
56
  - lib/fino/settings/string.rb
57
+ - lib/fino/variant.rb
58
+ - lib/fino/variant_picker.rb
57
59
  - lib/fino/version.rb
58
60
  homepage: https://github.com/eiskrenkov/fino
59
61
  licenses: