abmeter 0.3.0 → 0.4.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: 2d4ea3bdc7addec399a8644a3d8d2ee8f0329b4d85b117a56f4df51c58e04816
4
- data.tar.gz: b03e52a1fe306fe68b7dd63879ff067e59f358bdf885d4a8905e86d743ba282d
3
+ metadata.gz: f33d4944ddd5acac49f58a672004aa3460a7d8435a651ad6172d88960f02ebdb
4
+ data.tar.gz: b800e19818cc9d408380c14ae7e7fc2bad8d421839f970f14e259b327acb314c
5
5
  SHA512:
6
- metadata.gz: 18f1020d7926f3e7026441877010f730ef7ffc06078ad738a7be0121c9399465d3b056e39b6d0133f97d32fced489bec9a6f26c9d99f4f817e223b8927260eaf
7
- data.tar.gz: 9952d1a56ed63530d25200b21d88b0aaf3aec868752b72c46029e5905af5ff20af1eb45c472323afa20251694949ba9df0ff611d13a1633d0a110546d1aa41a5
6
+ metadata.gz: 83e08361797d68f6367b1651358557b25635e8a8554a9fb3e7dd79169f08b0c7443a52da385535cda59a25b4276cf9424c696dc4130c2a42f75e114412555f8b
7
+ data.tar.gz: e60a8479335b24cc3f57b24a5af553ad77b5a8451c3b8801650ca13721e3281cda29d9c54717f064393f1b6d816157bc6b84a83290e56bb4abcef4edb8c2bbf0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ABMeter Gem
2
2
 
3
- A simple A/B testing client library for Ruby applications.
3
+ [ABMeter](https://abmeter.ai) is a feature-flag and A/B-testing platform. You define parameters, experiments, and feature flags in the ABMeter Lab; this gem reads the value assigned to each user in your Ruby app and reports events back.
4
4
 
5
5
  > **Breaking change in 0.3.0:** `ABMeter.reset!` now discards queued data without any network I/O; use `ABMeter.reset` for the previous drain-on-exit behavior.
6
6
 
@@ -22,24 +22,35 @@ And then execute:
22
22
  $ bundle install
23
23
  ```
24
24
 
25
+ ## Getting an API key
26
+
27
+ Sign up at [abmeter.ai](https://abmeter.ai), then in the **Lab** open **API Keys** and create one. Expose it to your app (e.g. as `ABMETER_API_KEY`).
28
+
25
29
  ## Usage
26
30
 
31
+ Configure the client once at startup:
32
+
27
33
  ```ruby
28
- # configure the client
29
34
  ABMeter.configure do |config|
30
35
  config.api_key = ENV['ABMETER_API_KEY']
31
36
  end
37
+ ```
32
38
 
33
- # Somewhere in the renedring code:
34
- user = ABMeter.user(id: current_user.id, email: current_user.email)
35
- text = ABMeter.param('welcome_text', user)
39
+ The example below assumes a parameter `welcome_text` (in a space, with a variant, controlled by a running experiment or feature flag) and an event type `user_purchases_plan` already exist — create them through the ABMeter API or, more easily, your AI assistant over MCP. Until they do, `resolve_parameter` just returns the parameter's default and `track_event` rejects unknown event types.
36
40
 
37
- # Somewhere in the model code:
38
- current_user.plan = purchased_plan.name
39
- user = ABMeter.user(id: current_user.id, email: current_user.email)
40
- ABMeter.event(`user_purchases_plan`, user, {plan: purchased_plan.name, price: purchased_plan.price})
41
+ ```ruby
42
+ # In request handling: read the value assigned to this user. `user_id` is the
43
+ # only required field; an optional `email:` is available for predicate-based
44
+ # (email-pattern) audience targeting.
45
+ user = ABMeter::User.new(user_id: current_user.id)
46
+ text = ABMeter.resolve_parameter(user: user, parameter_slug: 'welcome_text')
47
+
48
+ # In business logic: record an action that feeds a metric.
49
+ ABMeter.track_event('user_purchases_plan', current_user.id, { plan: purchased_plan.name, price: purchased_plan.price })
41
50
  ```
42
51
 
52
+ The two calls are one loop: the experiment varies `welcome_text`, and the event feeds a metric whose effect the experiment measures across variants. Note that `resolve_parameter` is **not** a pure read — it resolves the value locally and records an exposure in the background; use `get_exposure` for the same resolution without submitting. Full guides live at [abmeter.ai](https://abmeter.ai).
53
+
43
54
  ## Shutdown
44
55
 
45
56
  ```ruby
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Regenerates the cross-language num-utils parity fixture consumed by the
5
+ # Python SDK (sdk-python/tests/test_num_utils_parity.py).
6
+ #
7
+ # Emits 1000 [salt, user_id, expected] triples where `expected` is the integer
8
+ # returned by ABMeter::Core::Utils::NumUtils.to_percentage. The Python
9
+ # implementation must reproduce every row. Run from the abmeter-sdk directory:
10
+ #
11
+ # bundle exec ruby bin/generate_num_utils_parity_fixture.rb
12
+ #
13
+ # Regenerate only when the assignment algorithm changes (it shouldn't).
14
+
15
+ require 'json'
16
+ require_relative '../lib/abmeter/core/utils/num_utils'
17
+
18
+ FIXTURE_PATH = File.expand_path(
19
+ '../../sdk-python/tests/fixtures/num_utils_parity.json', __dir__
20
+ )
21
+ ROW_COUNT = 1000
22
+
23
+ rows = Array.new(ROW_COUNT) do |i|
24
+ # Deterministic, varied inputs — mixed shapes exercise the hashing across
25
+ # salts and user-id formats without depending on RNG.
26
+ salt = "space-salt-#{i}-#{format('%04d', (i * 7) % 9973)}"
27
+ user_id = "user_#{((i * 31) + 5) % 100_003}"
28
+ [salt, user_id, ABMeter::Core::Utils::NumUtils.to_percentage(salt, user_id)]
29
+ end
30
+
31
+ File.write(FIXTURE_PATH, "#{JSON.pretty_generate(rows)}\n")
32
+ puts "Wrote #{rows.size} rows to #{FIXTURE_PATH}"
@@ -1,3 +1,5 @@
1
+ require 'time' # Time#iso8601
2
+
1
3
  module ABMeter
2
4
  class AsyncSubmitter
3
5
  # Private internal constants for async submitter behavior
@@ -64,6 +64,8 @@ module ABMeter
64
64
  end
65
65
 
66
66
  def matches?(user)
67
+ return false if user.email.nil?
68
+
67
69
  user.email.match?(predicate)
68
70
  end
69
71
 
@@ -7,7 +7,11 @@ module ABMeter
7
7
  protected
8
8
 
9
9
  def resolve_parameter_value(parameter, variant)
10
- variant&.parameter_value(parameter.slug) || parameter.default_value
10
+ # Fall back to the default only when the variant does not set this
11
+ # parameter (nil). An explicitly-set falsy value (false, 0, "") is a
12
+ # real override and must win — `||` would wrongly drop a Boolean false.
13
+ value = variant&.parameter_value(parameter.slug)
14
+ value.nil? ? parameter.default_value : value
11
15
  end
12
16
 
13
17
  def validate_expose_parameter_args!(user_id, parameter, audience)
@@ -5,7 +5,7 @@ module ABMeter
5
5
  class User
6
6
  attr_reader :user_id, :email
7
7
 
8
- def initialize(user_id:, email:)
8
+ def initialize(user_id:, email: nil)
9
9
  @user_id = user_id
10
10
  @email = email
11
11
  end
@@ -43,7 +43,6 @@ module ABMeter
43
43
 
44
44
  def validate_user!(user)
45
45
  raise ArgumentError, 'User must have user_id' unless user.respond_to?(:user_id)
46
- raise ArgumentError, 'User must have email' unless user.respond_to?(:email)
47
46
  end
48
47
 
49
48
  def find_matching_feature_flag(user, parameter_slug)
@@ -1,3 +1,3 @@
1
1
  module ABMeter
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
data/lib/abmeter.rb CHANGED
@@ -14,6 +14,12 @@ require_relative 'abmeter/async_submitter'
14
14
  require_relative 'abmeter/error_safety'
15
15
 
16
16
  module ABMeter
17
+ # Top-level alias for the canonical user value object. Lets callers write
18
+ # `ABMeter::User.new(user_id: ...)` instead of reaching into `Core::User`,
19
+ # mirroring `abmeter.User(...)` in the Python SDK. Same class object, so
20
+ # `is_a?`/`===`/`instance_of?` keep working; `Core::User` stays canonical.
21
+ User = Core::User
22
+
17
23
  class << self
18
24
  include ErrorSafety
19
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abmeter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ABMeter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-03 00:00:00.000000000 Z
11
+ date: 2026-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -49,6 +49,7 @@ files:
49
49
  - LICENSE.txt
50
50
  - README.md
51
51
  - bin/console
52
+ - bin/generate_num_utils_parity_fixture.rb
52
53
  - bin/rspec
53
54
  - lib/abmeter.rb
54
55
  - lib/abmeter/api_error.rb
@@ -71,11 +72,11 @@ files:
71
72
  - lib/abmeter/error_safety.rb
72
73
  - lib/abmeter/resolver_provider.rb
73
74
  - lib/abmeter/version.rb
74
- homepage: https://github.com/abmeter/abmeter-ruby
75
+ homepage: https://abmeter.ai
75
76
  licenses:
76
77
  - MIT
77
78
  metadata:
78
- homepage_uri: https://github.com/abmeter/abmeter-ruby
79
+ homepage_uri: https://abmeter.ai
79
80
  source_code_uri: https://github.com/abmeter/abmeter-ruby
80
81
  rubygems_mfa_required: 'true'
81
82
  post_install_message: