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 +4 -4
- data/README.md +20 -9
- data/bin/generate_num_utils_parity_fixture.rb +32 -0
- data/lib/abmeter/async_submitter.rb +2 -0
- data/lib/abmeter/core/assignment_config/audience.rb +2 -0
- data/lib/abmeter/core/assignment_config/exposable.rb +5 -1
- data/lib/abmeter/core/user.rb +1 -1
- data/lib/abmeter/core/user_parameter_resolver.rb +0 -1
- data/lib/abmeter/version.rb +1 -1
- data/lib/abmeter.rb +6 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f33d4944ddd5acac49f58a672004aa3460a7d8435a651ad6172d88960f02ebdb
|
|
4
|
+
data.tar.gz: b800e19818cc9d408380c14ae7e7fc2bad8d421839f970f14e259b327acb314c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 83e08361797d68f6367b1651358557b25635e8a8554a9fb3e7dd79169f08b0c7443a52da385535cda59a25b4276cf9424c696dc4130c2a42f75e114412555f8b
|
|
7
|
+
data.tar.gz: e60a8479335b24cc3f57b24a5af553ad77b5a8451c3b8801650ca13721e3281cda29d9c54717f064393f1b6d816157bc6b84a83290e56bb4abcef4edb8c2bbf0
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ABMeter Gem
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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}"
|
|
@@ -7,7 +7,11 @@ module ABMeter
|
|
|
7
7
|
protected
|
|
8
8
|
|
|
9
9
|
def resolve_parameter_value(parameter, variant)
|
|
10
|
-
variant
|
|
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)
|
data/lib/abmeter/core/user.rb
CHANGED
|
@@ -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)
|
data/lib/abmeter/version.rb
CHANGED
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.
|
|
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-
|
|
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://
|
|
75
|
+
homepage: https://abmeter.ai
|
|
75
76
|
licenses:
|
|
76
77
|
- MIT
|
|
77
78
|
metadata:
|
|
78
|
-
homepage_uri: https://
|
|
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:
|