shipeasy-sdk 2.0.0 → 2.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 +4 -4
- data/README.md +87 -294
- data/bin/shipeasy-skill +8 -0
- data/docs/skill/SKILL.md +152 -0
- data/lib/generators/shipeasy/install/USAGE +24 -0
- data/lib/generators/shipeasy/install/install_generator.rb +86 -0
- data/lib/generators/shipeasy/install/templates/initializer.rb.tt +41 -0
- data/lib/shipeasy/admin.rb +85 -0
- data/lib/shipeasy/client.rb +9 -0
- data/lib/shipeasy/config.rb +192 -7
- data/lib/shipeasy/engine.rb +16 -8
- data/lib/shipeasy/sdk/openfeature.rb +33 -6
- data/lib/shipeasy/sdk/skill.rb +76 -0
- data/lib/shipeasy/sdk/version.rb +1 -1
- data/lib/shipeasy_admin/api/alert_rules_api.rb +292 -0
- data/lib/shipeasy_admin/api/attributes_api.rb +82 -0
- data/lib/shipeasy_admin/api/configs_api.rb +680 -0
- data/lib/shipeasy_admin/api/events_api.rb +438 -0
- data/lib/shipeasy_admin/api/experiments_api.rb +727 -0
- data/lib/shipeasy_admin/api/gates_api.rb +438 -0
- data/lib/shipeasy_admin/api/i18n_api.rb +507 -0
- data/lib/shipeasy_admin/api/killswitches_api.rb +526 -0
- data/lib/shipeasy_admin/api/metrics_api.rb +281 -0
- data/lib/shipeasy_admin/api/ops_api.rb +525 -0
- data/lib/shipeasy_admin/api/projects_api.rb +153 -0
- data/lib/shipeasy_admin/api/universes_api.rb +306 -0
- data/lib/shipeasy_admin/api_client.rb +441 -0
- data/lib/shipeasy_admin/api_error.rb +58 -0
- data/lib/shipeasy_admin/api_model_base.rb +88 -0
- data/lib/shipeasy_admin/configuration.rb +397 -0
- data/lib/shipeasy_admin/models/approve_event_request.rb +198 -0
- data/lib/shipeasy_admin/models/approve_event_response.rb +166 -0
- data/lib/shipeasy_admin/models/create_alert_rule_request.rb +408 -0
- data/lib/shipeasy_admin/models/create_alert_rule_response.rb +165 -0
- data/lib/shipeasy_admin/models/create_config_request.rb +280 -0
- data/lib/shipeasy_admin/models/create_config_response.rb +201 -0
- data/lib/shipeasy_admin/models/create_event_request.rb +253 -0
- data/lib/shipeasy_admin/models/create_event_response.rb +193 -0
- data/lib/shipeasy_admin/models/create_experiment_request.rb +869 -0
- data/lib/shipeasy_admin/models/create_experiment_request_goal_metric.rb +336 -0
- data/lib/shipeasy_admin/models/create_experiment_response.rb +212 -0
- data/lib/shipeasy_admin/models/create_gate_request.rb +529 -0
- data/lib/shipeasy_admin/models/create_gate_response.rb +212 -0
- data/lib/shipeasy_admin/models/create_i18n_profile_request.rb +188 -0
- data/lib/shipeasy_admin/models/create_i18n_profile_response.rb +193 -0
- data/lib/shipeasy_admin/models/create_killswitch_request.rb +263 -0
- data/lib/shipeasy_admin/models/create_killswitch_response.rb +201 -0
- data/lib/shipeasy_admin/models/create_metric_request.rb +415 -0
- data/lib/shipeasy_admin/models/create_metric_response.rb +193 -0
- data/lib/shipeasy_admin/models/create_ops_item_request.rb +287 -0
- data/lib/shipeasy_admin/models/create_ops_item_response.rb +177 -0
- data/lib/shipeasy_admin/models/create_universe_request.rb +289 -0
- data/lib/shipeasy_admin/models/create_universe_response.rb +212 -0
- data/lib/shipeasy_admin/models/delete_alert_rule_response.rb +188 -0
- data/lib/shipeasy_admin/models/delete_config_response.rb +188 -0
- data/lib/shipeasy_admin/models/delete_event_response.rb +189 -0
- data/lib/shipeasy_admin/models/delete_experiment_response.rb +188 -0
- data/lib/shipeasy_admin/models/delete_gate_response.rb +188 -0
- data/lib/shipeasy_admin/models/delete_killswitch_response.rb +188 -0
- data/lib/shipeasy_admin/models/delete_metric_response.rb +189 -0
- data/lib/shipeasy_admin/models/delete_universe_response.rb +188 -0
- data/lib/shipeasy_admin/models/disable_gate_response.rb +190 -0
- data/lib/shipeasy_admin/models/discard_config_draft_request.rb +190 -0
- data/lib/shipeasy_admin/models/discard_config_draft_response.rb +188 -0
- data/lib/shipeasy_admin/models/enable_gate_response.rb +190 -0
- data/lib/shipeasy_admin/models/error_response.rb +185 -0
- data/lib/shipeasy_admin/models/get_config_response.rb +351 -0
- data/lib/shipeasy_admin/models/get_current_project_response.rb +606 -0
- data/lib/shipeasy_admin/models/get_current_project_response_module_translations.rb +104 -0
- data/lib/shipeasy_admin/models/get_event_response.rb +344 -0
- data/lib/shipeasy_admin/models/get_experiment_response.rb +688 -0
- data/lib/shipeasy_admin/models/get_experiment_results_response.rb +243 -0
- data/lib/shipeasy_admin/models/get_experiment_results_response_experiment.rb +248 -0
- data/lib/shipeasy_admin/models/get_experiment_results_response_results_inner.rb +252 -0
- data/lib/shipeasy_admin/models/get_experiment_timeseries_response.rb +192 -0
- data/lib/shipeasy_admin/models/get_experiment_timeseries_response_experiment.rb +261 -0
- data/lib/shipeasy_admin/models/get_experiment_timeseries_response_series_inner.rb +278 -0
- data/lib/shipeasy_admin/models/get_killswitch_response.rb +270 -0
- data/lib/shipeasy_admin/models/get_metric_response.rb +378 -0
- data/lib/shipeasy_admin/models/get_ops_item_response.rb +349 -0
- data/lib/shipeasy_admin/models/link_pr_to_ops_item_request.rb +186 -0
- data/lib/shipeasy_admin/models/link_pr_to_ops_item_response.rb +166 -0
- data/lib/shipeasy_admin/models/list_alert_rules_response_inner.rb +478 -0
- data/lib/shipeasy_admin/models/list_alert_rules_response_inner_notify.rb +177 -0
- data/lib/shipeasy_admin/models/list_alert_rules_response_inner_notify_slack_channel.rb +211 -0
- data/lib/shipeasy_admin/models/list_attributes_response_inner.rb +176 -0
- data/lib/shipeasy_admin/models/list_config_activity_response_inner.rb +280 -0
- data/lib/shipeasy_admin/models/list_configs_response.rb +178 -0
- data/lib/shipeasy_admin/models/list_configs_response_data_inner.rb +351 -0
- data/lib/shipeasy_admin/models/list_configs_response_data_inner_drafts_value.rb +237 -0
- data/lib/shipeasy_admin/models/list_configs_response_data_inner_envs_value.rb +237 -0
- data/lib/shipeasy_admin/models/list_events_response_inner.rb +344 -0
- data/lib/shipeasy_admin/models/list_events_response_inner_properties_inner.rb +289 -0
- data/lib/shipeasy_admin/models/list_experiments_response.rb +178 -0
- data/lib/shipeasy_admin/models/list_experiments_response_data_inner.rb +688 -0
- data/lib/shipeasy_admin/models/list_experiments_response_data_inner_groups_inner.rb +258 -0
- data/lib/shipeasy_admin/models/list_gates_response.rb +178 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner.rb +395 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner_enabled.rb +104 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner_rules_inner.rb +238 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner.rb +105 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner_one_of.rb +375 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner_one_of1.rb +331 -0
- data/lib/shipeasy_admin/models/list_gates_response_data_inner_stack_inner_one_of_ramp.rb +319 -0
- data/lib/shipeasy_admin/models/list_i18n_drafts_response_inner.rb +272 -0
- data/lib/shipeasy_admin/models/list_i18n_keys_response.rb +195 -0
- data/lib/shipeasy_admin/models/list_i18n_keys_response_keys_inner.rb +284 -0
- data/lib/shipeasy_admin/models/list_i18n_profiles_response_inner.rb +224 -0
- data/lib/shipeasy_admin/models/list_killswitches_response.rb +178 -0
- data/lib/shipeasy_admin/models/list_killswitches_response_data_inner.rb +270 -0
- data/lib/shipeasy_admin/models/list_killswitches_response_data_inner_envs_value.rb +248 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner.rb +378 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir.rb +295 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg.rb +113 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of1.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of2.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of3.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of4.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of5.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of6.rb +188 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of7.rb +217 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of8.rb +233 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of9.rb +240 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of9_numerator.rb +266 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_agg_one_of9_numerator_filters_inner.rb +264 -0
- data/lib/shipeasy_admin/models/list_metrics_response_inner_query_ir_group_by.rb +228 -0
- data/lib/shipeasy_admin/models/list_ops_items_response_inner.rb +349 -0
- data/lib/shipeasy_admin/models/list_slack_channels_response.rb +195 -0
- data/lib/shipeasy_admin/models/list_slack_channels_response_channels_inner.rb +203 -0
- data/lib/shipeasy_admin/models/list_universes_response.rb +178 -0
- data/lib/shipeasy_admin/models/list_universes_response_data_inner.rb +305 -0
- data/lib/shipeasy_admin/models/notify_ops_request.rb +261 -0
- data/lib/shipeasy_admin/models/notify_ops_response.rb +193 -0
- data/lib/shipeasy_admin/models/publish_config_draft_request.rb +190 -0
- data/lib/shipeasy_admin/models/publish_config_draft_response.rb +260 -0
- data/lib/shipeasy_admin/models/publish_i18n_profile_request.rb +149 -0
- data/lib/shipeasy_admin/models/publish_i18n_profile_response.rb +404 -0
- data/lib/shipeasy_admin/models/push_i18n_keys_request.rb +205 -0
- data/lib/shipeasy_admin/models/push_i18n_keys_request_keys_inner.rb +192 -0
- data/lib/shipeasy_admin/models/push_i18n_keys_response.rb +261 -0
- data/lib/shipeasy_admin/models/reanalyze_experiment_response.rb +214 -0
- data/lib/shipeasy_admin/models/save_config_draft_request.rb +202 -0
- data/lib/shipeasy_admin/models/save_config_draft_response.rb +286 -0
- data/lib/shipeasy_admin/models/set_experiment_metrics_request.rb +168 -0
- data/lib/shipeasy_admin/models/set_experiment_metrics_request_metrics_inner.rb +216 -0
- data/lib/shipeasy_admin/models/set_experiment_metrics_response.rb +179 -0
- data/lib/shipeasy_admin/models/set_experiment_metrics_response_metrics_inner.rb +201 -0
- data/lib/shipeasy_admin/models/set_experiment_status_request.rb +190 -0
- data/lib/shipeasy_admin/models/set_experiment_status_response.rb +214 -0
- data/lib/shipeasy_admin/models/set_killswitch_switch_request.rb +264 -0
- data/lib/shipeasy_admin/models/set_killswitch_switch_response.rb +288 -0
- data/lib/shipeasy_admin/models/unset_killswitch_switch_request.rb +237 -0
- data/lib/shipeasy_admin/models/unset_killswitch_switch_response.rb +289 -0
- data/lib/shipeasy_admin/models/update_alert_rule_request.rb +306 -0
- data/lib/shipeasy_admin/models/update_alert_rule_response.rb +165 -0
- data/lib/shipeasy_admin/models/update_config_request.rb +198 -0
- data/lib/shipeasy_admin/models/update_config_response.rb +165 -0
- data/lib/shipeasy_admin/models/update_event_request.rb +198 -0
- data/lib/shipeasy_admin/models/update_event_response.rb +166 -0
- data/lib/shipeasy_admin/models/update_experiment_request.rb +746 -0
- data/lib/shipeasy_admin/models/update_experiment_response.rb +165 -0
- data/lib/shipeasy_admin/models/update_gate_request.rb +403 -0
- data/lib/shipeasy_admin/models/update_gate_response.rb +166 -0
- data/lib/shipeasy_admin/models/update_i18n_key_request.rb +176 -0
- data/lib/shipeasy_admin/models/update_i18n_key_response.rb +166 -0
- data/lib/shipeasy_admin/models/update_killswitch_request.rb +224 -0
- data/lib/shipeasy_admin/models/update_killswitch_response.rb +165 -0
- data/lib/shipeasy_admin/models/update_ops_item_request.rb +205 -0
- data/lib/shipeasy_admin/models/update_ops_item_response.rb +166 -0
- data/lib/shipeasy_admin/models/update_universe_request.rb +213 -0
- data/lib/shipeasy_admin/models/update_universe_response.rb +165 -0
- data/lib/shipeasy_admin/models/upsert_project_request.rb +222 -0
- data/lib/shipeasy_admin/models/upsert_project_response.rb +260 -0
- data/lib/shipeasy_admin/version.rb +15 -0
- data/lib/shipeasy_admin.rb +196 -0
- metadata +235 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 434e056f9f0849c73316e812f9ff8918ad838d9161283db0b3a8c9c7450b1077
|
|
4
|
+
data.tar.gz: beb6a893abc08ec7463a64fb2ec22f7df014f4da147b824a5f7b8d70ed3f34a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17339d55c4ed50599d3cc2cfa04f2f5a53e57a5478fb626bbf999ac6297da3a35a47e9a139150b4cfcce209c6e29f1b033180c9f5cc64923f106f5c1379adff8
|
|
7
|
+
data.tar.gz: 57fb7a408683111fd2f0aee2b389c97f5de038b22b2b6bbf0ff757c193220740a766951a7cda8baa59c58fc83bc26ea7e1103a07944e9206555a22970d071b19
|
data/README.md
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
This file is GENERATED by scripts/gen_readme.rb from docs/.
|
|
3
|
+
Do NOT edit by hand — edit the docs, then run: ruby scripts/gen_readme.rb
|
|
4
|
+
-->
|
|
5
|
+
|
|
1
6
|
# shipeasy-sdk (Ruby)
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
8
|
+
[](https://github.com/shipeasy-ai/sdk-ruby/actions/workflows/test.yml)
|
|
9
|
+
[](https://rubygems.org/gems/shipeasy-sdk)
|
|
10
|
+
[](https://www.ruby-lang.org/)
|
|
11
|
+
|
|
12
|
+
Server SDK for [Shipeasy](https://shipeasy.ai) — **feature flags, dynamic
|
|
13
|
+
configs, kill switches, A/B experiments, and metric tracking**, with Rails i18n
|
|
14
|
+
view helpers. Server-key only; never embed in a browser.
|
|
15
|
+
|
|
16
|
+
> 📚 **Full documentation:** **<https://shipeasy-ai.github.io/sdk-ruby/>** — also browsable under
|
|
17
|
+
> [`docs/`](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs). This README is generated from those docs.
|
|
5
18
|
|
|
6
19
|
> Source-available under the [Shipeasy-SAL 1.0](./LICENSE).
|
|
7
20
|
|
|
21
|
+
## 🤖 Using an AI agent?
|
|
22
|
+
|
|
23
|
+
This SDK ships an installable **agent skill** — a copy-paste-ready guide to
|
|
24
|
+
`Shipeasy.configure` + `Client.new(user)`, testing, experiments, error
|
|
25
|
+
reporting, and more, with links the agent can pull for deeper docs:
|
|
26
|
+
|
|
27
|
+
- **Skill:** [`docs/skill/SKILL.md`](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/skill/SKILL.md) · raw:
|
|
28
|
+
`https://shipeasy-ai.github.io/sdk-ruby/skill/SKILL.md`
|
|
29
|
+
- **Install it** (ships with the gem — no network):
|
|
30
|
+
`shipeasy-skill install` → `.claude/skills/shipeasy-ruby/SKILL.md`
|
|
31
|
+
(or via the Shipeasy CLI: `shipeasy docs skill --sdk ruby --install`)
|
|
32
|
+
|
|
33
|
+
**Humans:** you can copy that skill straight into your own project's agent skills
|
|
34
|
+
directory (e.g. `.claude/skills/shipeasy-ruby/SKILL.md`) so your coding agent
|
|
35
|
+
always uses the correct Shipeasy patterns. Every doc page and snippet is also
|
|
36
|
+
fetchable by URL — start from the manifest at `https://shipeasy-ai.github.io/sdk-ruby/manifest.json`.
|
|
37
|
+
|
|
8
38
|
## Install
|
|
9
39
|
|
|
10
40
|
```ruby
|
|
@@ -12,324 +42,87 @@ gate evaluation, runtime configs, experiments, and metric ingestion.
|
|
|
12
42
|
gem "shipeasy-sdk"
|
|
13
43
|
```
|
|
14
44
|
|
|
15
|
-
|
|
45
|
+
Requires Ruby 3.0+. Per-framework setup (Rails / Sinatra / serverless) and the
|
|
46
|
+
anon-id middleware are on the [Installation](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/installation.md) page.
|
|
16
47
|
|
|
17
|
-
|
|
18
|
-
`Shipeasy::Client`** per request via its constructor.
|
|
19
|
-
|
|
20
|
-
`config/initializers/shipeasy.rb`:
|
|
48
|
+
## Quickstart — `configure` once, then `Client.new(user)` per request
|
|
21
49
|
|
|
22
50
|
```ruby
|
|
51
|
+
# boot (config/initializers/shipeasy.rb)
|
|
23
52
|
Shipeasy.configure do |c|
|
|
24
53
|
c.api_key = ENV.fetch("SHIPEASY_SERVER_KEY")
|
|
25
|
-
|
|
26
|
-
# Optional: map YOUR user object → the Shipeasy attribute hash. Runs once,
|
|
27
|
-
# in the Shipeasy::Client constructor. Omit it and the object you pass to
|
|
28
|
-
# Shipeasy::Client.new IS the attribute hash (identity).
|
|
29
54
|
c.attributes = ->(u) { { "user_id" => u.id, "plan" => u.plan } }
|
|
30
|
-
|
|
31
|
-
c.public_key = ENV.fetch("SHIPEASY_CLIENT_KEY") # for i18n view helpers
|
|
32
|
-
c.profile = "default"
|
|
33
|
-
end
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`configure` builds the single global engine for you and kicks off a one-shot
|
|
37
|
-
fetch (fire-and-forget). Anywhere in your app, construct a bound client and call
|
|
38
|
-
the getters with **no user argument** — the user is bound at construction:
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
flags = Shipeasy::Client.new(current_user) # runs the attributes transform once
|
|
42
|
-
|
|
43
|
-
if flags.get_flag("new_checkout") # NO user arg
|
|
44
|
-
# ship it
|
|
45
55
|
end
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
panic = flags.get_killswitch("payments")
|
|
50
|
-
```
|
|
57
|
+
# per request — construct once per callsite (cheap; binds the user)
|
|
58
|
+
flags = Shipeasy::Client.new(current_user)
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
Shipeasy.engine.track(current_user.id.to_s, "checkout_completed", { revenue: 49.99 })
|
|
60
|
+
flags.get_flag("new_checkout") # NO user arg — bound at construction
|
|
61
|
+
flags.get_config("button_color")
|
|
62
|
+
result = flags.get_experiment("checkout_cta", { label: "Buy" })
|
|
63
|
+
flags.log_exposure("checkout_cta") # at the decision point
|
|
64
|
+
flags.track("purchase", { revenue: 49 }) # on conversion
|
|
65
|
+
flags.get_killswitch("payments")
|
|
61
66
|
```
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
> now the lightweight user-bound handle. The legacy `Shipeasy.flags.get_flag(name, user)`
|
|
66
|
-
> singleton still works.
|
|
68
|
+
Constructing `Shipeasy::Client.new(user)` before `Shipeasy.configure` raises
|
|
69
|
+
`Shipeasy::Error`.
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```erb
|
|
71
|
-
<%= i18n_head_tags %>
|
|
72
|
-
<h1><%= i18n_t("hero.title", name: current_user.name) %></h1>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Anonymous visitors (zero-config bucketing)
|
|
76
|
-
|
|
77
|
-
For logged-out traffic you need a *stable* unit so a fractional rollout buckets
|
|
78
|
-
the same on the server and in the browser. In Rails this is automatic: a Railtie
|
|
79
|
-
mounts `Shipeasy::SDK::RackMiddleware`, which mints the shared `__se_anon_id`
|
|
80
|
-
first-party cookie (read + written by every Shipeasy SDK, including the browser)
|
|
81
|
-
for any request without one. Evaluations then default to it with **no per-call
|
|
82
|
-
wiring** — `get_flag` on an anonymous request just works:
|
|
83
|
-
|
|
84
|
-
```ruby
|
|
85
|
-
# current_user is nil → buckets on the __se_anon_id cookie automatically
|
|
86
|
-
Shipeasy::Client.new({}).get_flag("new_checkout")
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
An explicit `user_id` / `anonymous_id` always wins. If you prefer to read the id
|
|
90
|
-
yourself it's also on the Rack env as `request.env["shipeasy.anon_id"]`. The
|
|
91
|
-
cookie is non-`HttpOnly` by design so the browser SDK can bucket identically. A
|
|
92
|
-
request with **no** unit still resolves a fully-rolled (100%) gate as on; only
|
|
93
|
-
fractional gates need the id. Cookie name + format are a cross-SDK contract —
|
|
94
|
-
see `18-identity-bucketing.md`.
|
|
95
|
-
|
|
96
|
-
For **Sinatra / Hanami / bare Rack** (no Railtie), mount it yourself:
|
|
97
|
-
|
|
98
|
-
```ruby
|
|
99
|
-
use Shipeasy::SDK::RackMiddleware
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Quickstart (plain Ruby / Sinatra / Hanami / scripts)
|
|
103
|
-
|
|
104
|
-
Same pattern, just without `config/initializers`:
|
|
105
|
-
|
|
106
|
-
```ruby
|
|
107
|
-
require "shipeasy-sdk"
|
|
108
|
-
|
|
109
|
-
Shipeasy.configure { |c| c.api_key = ENV.fetch("SHIPEASY_SERVER_KEY") }
|
|
110
|
-
|
|
111
|
-
# With no `attributes` transform, the hash you pass IS the attribute map.
|
|
112
|
-
Shipeasy::Client.new({ "user_id" => "u_1" }).get_flag("new_checkout")
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
The Rails view helpers (`i18n_*`) are not loaded outside Rails, so the
|
|
116
|
-
gem doesn't pull Rails into Sinatra/Hanami apps.
|
|
117
|
-
|
|
118
|
-
## Lambda / Cloud Run / serverless
|
|
119
|
-
|
|
120
|
-
Skip the auto-init facade — it spawns a poll thread you don't want in a
|
|
121
|
-
short-lived function. Build the client explicitly and call `init_once`
|
|
122
|
-
for a single synchronous fetch:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
engine = Shipeasy::Engine.new(api_key: ENV.fetch("SHIPEASY_SERVER_KEY"))
|
|
126
|
-
engine.init_once
|
|
127
|
-
engine.get_flag("new_checkout", user)
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Lifecycle escape hatch
|
|
131
|
-
|
|
132
|
-
If you want explicit shutdown control in a long-running worker, build the
|
|
133
|
-
client yourself and skip the singleton:
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
client = Shipeasy::SDK.new_client # reads api_key + base_url from Shipeasy.config
|
|
137
|
-
client.init
|
|
138
|
-
at_exit { client.destroy }
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Server-side rendering (SSR)
|
|
142
|
-
|
|
143
|
-
Emit the request's evaluated flags as a declarative `<script>` tag so the
|
|
144
|
-
browser SDK has them on first paint. `bootstrap_script_tag` carries the payload
|
|
145
|
-
in `data-*` attributes (**no key**); the static `se-bootstrap.js` loader
|
|
146
|
-
hydrates `window.__SE_BOOTSTRAP` and writes the `__se_anon_id` cookie so the
|
|
147
|
-
browser buckets identically to the server.
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
user = { "user_id" => "u_123" }
|
|
151
|
-
|
|
152
|
-
# Two tags for the document <head>. The PUBLIC client key (not the server
|
|
153
|
-
# key) goes on the i18n loader tag.
|
|
154
|
-
head = client.bootstrap_script_tag(user, anon_id: anon_id) +
|
|
155
|
-
client.i18n_script_tag(client_key, profile: "en:prod")
|
|
156
|
-
|
|
157
|
-
# …or get the raw payload ({ "flags", "configs", "experiments", "killswitches" }):
|
|
158
|
-
boot = client.evaluate(user)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
`bootstrap_script_tag` also accepts `i18n_profile:` and `base_url:` (defaults to
|
|
162
|
-
`https://cdn.shipeasy.ai`). In **Rails**, the existing
|
|
163
|
-
`Shipeasy::I18n::ViewHelpers#i18n_script_tag` view helper still renders the i18n
|
|
164
|
-
loader tag from your app config.
|
|
165
|
-
|
|
166
|
-
## Default values
|
|
167
|
-
|
|
168
|
-
`get_flag` and `get_config` take an optional `default:` returned **only when the
|
|
169
|
-
value cannot be resolved** — never when a flag genuinely evaluates to `false`.
|
|
170
|
-
|
|
171
|
-
```ruby
|
|
172
|
-
# Flag: default is returned only when the client isn't ready yet (no blob
|
|
173
|
-
# fetched) or the gate doesn't exist. A gate that evaluates to false (disabled,
|
|
174
|
-
# or outside its rollout) returns false, NOT the default.
|
|
175
|
-
Shipeasy.flags.get_flag("new_checkout", user, default: true)
|
|
176
|
-
|
|
177
|
-
# Config: default is returned when the config key is absent. A decode proc still
|
|
178
|
-
# runs on a present value.
|
|
179
|
-
Shipeasy.flags.get_config("button_color", default: "blue")
|
|
180
|
-
Shipeasy.flags.get_config("limits", ->(v) { v["max"] }, default: 0)
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
## Evaluation detail
|
|
184
|
-
|
|
185
|
-
`get_flag_detail(name, user)` returns the boolean **and the reason** it was
|
|
186
|
-
reached, as a `FlagDetail` struct (`.value`, `.reason`). `get_flag` is built on
|
|
187
|
-
top of it. The reason is one of the `REASON_*` constants:
|
|
188
|
-
|
|
189
|
-
| Reason | Meaning |
|
|
190
|
-
| ------------------ | --------------------------------------------------- |
|
|
191
|
-
| `OVERRIDE` | answered by a local `override_flag` (no telemetry) |
|
|
192
|
-
| `CLIENT_NOT_READY` | no flag blob fetched/loaded yet |
|
|
193
|
-
| `FLAG_NOT_FOUND` | blob present, but this gate isn't in it |
|
|
194
|
-
| `OFF` | gate present but disabled or killswitched |
|
|
195
|
-
| `RULE_MATCH` | evaluated to `true` |
|
|
196
|
-
| `DEFAULT` | evaluated to `false` (rollout/rule) |
|
|
197
|
-
|
|
198
|
-
```ruby
|
|
199
|
-
detail = Shipeasy.flags.get_flag_detail("new_checkout", user)
|
|
200
|
-
detail.value # => true / false
|
|
201
|
-
detail.reason # => "RULE_MATCH" / "DEFAULT" / "OFF" / ...
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
The `gate` usage beacon fires exactly once per `get_flag_detail` call (never on
|
|
205
|
-
the `OVERRIDE` short-circuit).
|
|
206
|
-
|
|
207
|
-
## Change listeners
|
|
208
|
-
|
|
209
|
-
`on_change` registers a callback fired after a background poll fetches **new**
|
|
210
|
-
flag/config data (HTTP 200, not a 304). It accepts a block or any callable and
|
|
211
|
-
returns an unsubscribe proc. Listeners never fire in test/offline mode (there is
|
|
212
|
-
no poll thread). A raising listener is isolated and logged, not propagated.
|
|
213
|
-
|
|
214
|
-
```ruby
|
|
215
|
-
unsubscribe = Shipeasy.flags.on_change { reload_local_cache! }
|
|
216
|
-
# ... later
|
|
217
|
-
unsubscribe.call
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
## Offline snapshot
|
|
221
|
-
|
|
222
|
-
For CI, air-gapped runs, or reproducing a production decision from a captured
|
|
223
|
-
blob, build a **no-network** client that still runs the real evaluator against a
|
|
224
|
-
snapshot. The snapshot JSON holds the raw response bodies of the two SDK
|
|
225
|
-
endpoints:
|
|
226
|
-
|
|
227
|
-
```json
|
|
228
|
-
{ "flags": <body of /sdk/flags>, "experiments": <body of /sdk/experiments> }
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
```ruby
|
|
232
|
-
client = Shipeasy::Engine.from_file("snapshot.json")
|
|
233
|
-
# or, from already-parsed blobs:
|
|
234
|
-
client = Shipeasy::Engine.from_snapshot(flags: flags_body, experiments: exps_body)
|
|
235
|
-
|
|
236
|
-
client.get_flag("new_checkout", user) # real evaluation, no network
|
|
237
|
-
client.get_experiment("checkout_cta", user, {})
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
`init` / `init_once` / `track` are no-ops and telemetry is off (it reuses the
|
|
241
|
-
`for_testing` plumbing). Local `override_*` setters still apply on top of the
|
|
242
|
-
snapshot.
|
|
243
|
-
|
|
244
|
-
## Evaluation details
|
|
71
|
+
## Documentation
|
|
245
72
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
-
|
|
255
|
-
|
|
73
|
+
| Page | What |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| [Overview](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/overview.md) | The `Shipeasy.configure` + `Client.new(user)` model. |
|
|
76
|
+
| [Installation](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/installation.md) | Install, frameworks (Rails / Sinatra / serverless), `configure` wiring. |
|
|
77
|
+
| [Configuration](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/configuration.md) | Keys, `attributes`, one-shot vs poll, every option. |
|
|
78
|
+
| [Feature flags](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/flags.md) | `get_flag`, `get_flag_detail`, defaults. |
|
|
79
|
+
| [Dynamic configs](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/configs.md) | `get_config`, typed decode, defaults. |
|
|
80
|
+
| [Kill switches](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/killswitches.md) | `get_killswitch`, named switches. |
|
|
81
|
+
| [Experiments](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/experiments.md) | `get_experiment`, `log_exposure`, `track`. |
|
|
82
|
+
| [Internationalization](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/i18n.md) | Rails view helpers + the SSR loader tag. |
|
|
83
|
+
| [Error reporting](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/error-reporting.md) | `see()` structured error reporting. |
|
|
84
|
+
| [Testing](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/testing.md) | `configure_for_testing` / `configure_for_offline`, overrides. |
|
|
85
|
+
| [OpenFeature](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/openfeature.md) | `Shipeasy::OpenFeature::Provider` (OpenFeature server provider). |
|
|
86
|
+
| [Advanced](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/advanced.md) | Anon-id middleware, private attributes, sticky bucketing, SSR. |
|
|
87
|
+
| [Admin API client](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/admin-api.md) | Optional `Shipeasy::Admin::Client` for administering resources (generated from the Open… |
|
|
88
|
+
|
|
89
|
+
Copy-paste snippets live under [`docs/snippets/`](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/snippets)
|
|
90
|
+
(release · metrics · i18n · ops); an installable agent skill is at
|
|
91
|
+
[`docs/skill/SKILL.md`](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/skill/SKILL.md).
|
|
256
92
|
|
|
257
93
|
## Testing
|
|
258
94
|
|
|
259
|
-
|
|
260
|
-
returns exactly the values you seed — no api_key, no fetch, no poll thread, no
|
|
261
|
-
telemetry, no metric ingestion. Build one with `Shipeasy::Engine.for_testing` and
|
|
262
|
-
seed each entity with the `override_*` setters (Statsig-style local overrides).
|
|
263
|
-
An override always wins over the fetched blob, so the getters answer
|
|
264
|
-
deterministically:
|
|
95
|
+
Use **`Shipeasy.configure_for_testing`** — the test-mode sibling of [`Shipeasy.configure`](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/configuration.md). It does **zero network**, needs no api key, and seeds the values your code under test should see via override arguments. Then read through the ordinary `Shipeasy::Client.new(user)` — the *same* call your production code uses.
|
|
265
96
|
|
|
266
97
|
```ruby
|
|
267
98
|
require "shipeasy-sdk"
|
|
268
99
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
client.get_flag("new_checkout", { user_id: "u_1" }) # => true
|
|
100
|
+
Shipeasy.configure_for_testing(
|
|
101
|
+
flags: { "new_checkout" => true },
|
|
102
|
+
configs: { "billing_copy" => { "title" => "Welcome" } },
|
|
103
|
+
experiments: { "checkout_button" => ["treatment", { "color" => "green" }] },
|
|
104
|
+
)
|
|
275
105
|
|
|
276
|
-
#
|
|
277
|
-
client.
|
|
278
|
-
client.get_config("button_color") # => "blue"
|
|
279
|
-
client.override_config("limits", { "max" => 10 })
|
|
280
|
-
client.get_config("limits", ->(v) { v["max"] }) # => 10
|
|
106
|
+
# construct once per callsite (cheap; binds the user)
|
|
107
|
+
client = Shipeasy::Client.new({ "user_id" => "u_123" })
|
|
281
108
|
|
|
282
|
-
#
|
|
283
|
-
client.
|
|
284
|
-
r = client.get_experiment("checkout_cta", { user_id: "u_1" }, { label: "default" })
|
|
285
|
-
r.in_experiment # => true
|
|
286
|
-
r.group # => "treatment"
|
|
287
|
-
r.params # => { label: "Buy now" }
|
|
109
|
+
client.get_flag("new_checkout") # => true
|
|
110
|
+
client.get_config("billing_copy") # => { "title" => "Welcome" }
|
|
288
111
|
|
|
289
|
-
|
|
290
|
-
|
|
112
|
+
result = client.get_experiment("checkout_button", { "color" => "blue" })
|
|
113
|
+
result.in_experiment # => true
|
|
114
|
+
result.group # => "treatment"
|
|
115
|
+
result.params # => { "color" => "green" }
|
|
291
116
|
|
|
292
|
-
#
|
|
293
|
-
client.
|
|
117
|
+
# track / log_exposure are no-ops in test mode — safe to call, send nothing
|
|
118
|
+
client.track("purchase", { amount: 49 })
|
|
294
119
|
```
|
|
295
120
|
|
|
296
|
-
|
|
297
|
-
`
|
|
298
|
-
`Shipeasy::Engine.new(...)`), so you can pin one value in local development while the
|
|
299
|
-
rest comes from the fetched blob.
|
|
300
|
-
|
|
301
|
-
### Global engine / bound client
|
|
302
|
-
|
|
303
|
-
`Shipeasy.engine` (registered by `configure`) and `Shipeasy.flags` (legacy
|
|
304
|
-
singleton) both fetch over the network, so in tests stub them to a
|
|
305
|
-
`for_testing` engine. `Shipeasy::Client.new(user)` reads `Shipeasy.engine`, so
|
|
306
|
-
stubbing the engine also covers the bound-client path:
|
|
307
|
-
|
|
308
|
-
```ruby
|
|
309
|
-
# RSpec
|
|
310
|
-
before do
|
|
311
|
-
test_engine = Shipeasy::Engine.for_testing
|
|
312
|
-
test_engine.override_flag("new_checkout", true)
|
|
313
|
-
allow(Shipeasy).to receive(:engine).and_return(test_engine)
|
|
314
|
-
allow(Shipeasy).to receive(:flags).and_return(test_engine) # legacy path
|
|
315
|
-
|
|
316
|
-
# Shipeasy::Client.new(user).get_flag("new_checkout") now => true
|
|
317
|
-
end
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## Configuration
|
|
321
|
-
|
|
322
|
-
| Parameter | Default | Description |
|
|
323
|
-
| ------------ | ----------------------------- | ------------------------------------------------------------------- |
|
|
324
|
-
| `api_key` | (required) | SDK key from the Shipeasy dashboard |
|
|
325
|
-
| `base_url` | `https://edge.shipeasy.dev` | Override for local dev / staging |
|
|
326
|
-
| `attributes` | identity (`->(u) { u }`) | Callable mapping your user object → the Shipeasy attribute hash |
|
|
327
|
-
|
|
328
|
-
## Documentation
|
|
329
|
-
|
|
330
|
-
[docs.shipeasy.ai](https://docs.shipeasy.ai)
|
|
121
|
+
More — the on-the-spot override helpers and a working example
|
|
122
|
+
`shipeasy-snapshot.json` — on the [Testing](https://github.com/shipeasy-ai/sdk-ruby/blob/main/docs/pages/testing.md) page.
|
|
331
123
|
|
|
332
124
|
## License
|
|
333
125
|
|
|
334
|
-
[Shipeasy-SAL 1.0](./LICENSE) — source-available, non-commercial-use,
|
|
335
|
-
|
|
126
|
+
[Shipeasy-SAL 1.0](./LICENSE) — source-available, non-commercial-use, permitted
|
|
127
|
+
as a Shipeasy client. Evaluation is tested against the cross-language
|
|
128
|
+
MurmurHash3 vectors in `experiment-platform/04-evaluation.md`.
|
data/bin/shipeasy-skill
ADDED
data/docs/skill/SKILL.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shipeasy-ruby
|
|
3
|
+
description: Use Shipeasy (feature flags, configs, kill switches, A/B experiments, i18n) from Ruby. Covers Shipeasy.configure + Client.new(user), get_flag/get_config/get_experiment/get_killswitch, track, testing, OpenFeature.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Shipeasy Ruby SDK
|
|
7
|
+
|
|
8
|
+
Server-side Ruby gem (`shipeasy-sdk`) for Shipeasy: feature gates, dynamic
|
|
9
|
+
configs, kill switches, A/B experiments, metrics, `see()` error reporting, and
|
|
10
|
+
Rails i18n view helpers. Server-key only — never embed in a browser. Ruby 3.0+.
|
|
11
|
+
|
|
12
|
+
Two things only: **`Shipeasy.configure`** once at boot, then
|
|
13
|
+
**`Shipeasy::Client.new(user)`** per request.
|
|
14
|
+
|
|
15
|
+
> **Pulling deeper docs.** Each section below links its full reference page and
|
|
16
|
+
> copy-paste snippets — fetch any of them as raw Markdown when you need more than
|
|
17
|
+
> this summary. Discover the whole tree from the manifest:
|
|
18
|
+
> `https://shipeasy-ai.github.io/sdk-ruby/manifest.json` (lists every
|
|
19
|
+
> `pages/<key>.md` and `snippets/<group>/<leaf>.md`). All URLs below are
|
|
20
|
+
> `https://shipeasy-ai.github.io/sdk-ruby/…`.
|
|
21
|
+
|
|
22
|
+
## Configure once (boot)
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# config/initializers/shipeasy.rb
|
|
26
|
+
Shipeasy.configure do |c|
|
|
27
|
+
c.api_key = ENV.fetch("SHIPEASY_SERVER_KEY")
|
|
28
|
+
# Optional: map your user object → the attribute hash (runs once in Client.new).
|
|
29
|
+
c.attributes = ->(u) { { "user_id" => u.id, "plan" => u.plan } }
|
|
30
|
+
# i18n only (public client key + profile):
|
|
31
|
+
c.public_key = ENV.fetch("SHIPEASY_CLIENT_KEY")
|
|
32
|
+
c.profile = "default"
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Omit `c.attributes` if your user object is already the attribute hash. For a
|
|
37
|
+
long-running server set `c.poll = true` to keep the blob fresh in the background;
|
|
38
|
+
the default (one-shot fetch, no thread) is serverless-friendly.
|
|
39
|
+
|
|
40
|
+
**Rails:** don't write the initializer by hand — run the generator:
|
|
41
|
+
`bin/rails generate shipeasy:install` (add `--i18n` to also inject
|
|
42
|
+
`<%= i18n_head_tags %>` into the app layout, `--no-poll` for serverless).
|
|
43
|
+
|
|
44
|
+
→ More: `pages/installation.md` (per-framework setup + generator),
|
|
45
|
+
`pages/configuration.md` (every option).
|
|
46
|
+
|
|
47
|
+
## Evaluate (bound `Client.new(user)` — NO user arg)
|
|
48
|
+
|
|
49
|
+
Bind the user once per request, then call without re-passing it — `track` and
|
|
50
|
+
`log_exposure` are on the bound client too, so experiments are end-to-end here:
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
flags = Shipeasy::Client.new(current_user) # runs the attributes transform once
|
|
54
|
+
|
|
55
|
+
flags.get_flag("new_checkout") # bool; default: only when unresolved
|
|
56
|
+
flags.get_config("button_color", default: "blue")
|
|
57
|
+
flags.get_killswitch("payments") # true = killed; optional switch_key
|
|
58
|
+
result = flags.get_experiment("checkout_cta", { label: "Buy now" })
|
|
59
|
+
# result.in_experiment / result.group / result.params
|
|
60
|
+
|
|
61
|
+
flags.log_exposure("checkout_cta") # at the decision point
|
|
62
|
+
flags.track("purchase", { revenue: 49 }) # conversion / metric event
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`get_flag_detail` returns a `FlagDetail` (`.value`, `.reason`: `RULE_MATCH`,
|
|
66
|
+
`DEFAULT`, `OFF`, `OVERRIDE`, `FLAG_NOT_FOUND`, `CLIENT_NOT_READY`).
|
|
67
|
+
|
|
68
|
+
→ More: pages `pages/flags.md` · `pages/configs.md` · `pages/killswitches.md`
|
|
69
|
+
(incl. named switches) · `pages/experiments.md`. Snippets
|
|
70
|
+
`snippets/release/{flags,configs,killswitches,experiments}.md` and
|
|
71
|
+
`snippets/metrics/track.md`.
|
|
72
|
+
|
|
73
|
+
## Testing (no network)
|
|
74
|
+
|
|
75
|
+
Use the `configure` siblings — seed overrides, read through the same `Client`:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
Shipeasy.configure_for_testing(
|
|
79
|
+
flags: { "new_checkout" => true },
|
|
80
|
+
configs: { "billing_copy" => { "title" => "Welcome" } },
|
|
81
|
+
experiments: { "checkout_button" => ["treatment", { "color" => "green" }] },
|
|
82
|
+
)
|
|
83
|
+
Shipeasy::Client.new({ "user_id" => "u_123" }).get_flag("new_checkout") # => true
|
|
84
|
+
|
|
85
|
+
# flip a value on the spot, mid-test:
|
|
86
|
+
Shipeasy.override_flag("new_checkout", false)
|
|
87
|
+
Shipeasy.clear_overrides
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Offline (real rules from a snapshot / file):
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
Shipeasy.configure_for_offline(path: "snapshot.json")
|
|
94
|
+
# or snapshot: { "flags" => {...}, "experiments" => {...} }, plus optional overrides
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
→ More: `pages/testing.md` (override helpers + a working example
|
|
98
|
+
`shipeasy-snapshot.json`).
|
|
99
|
+
|
|
100
|
+
## OpenFeature
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
require "open_feature/sdk" # optional dep: gem "openfeature-sdk" (Ruby ≥ 3.4)
|
|
104
|
+
require "shipeasy/sdk/openfeature"
|
|
105
|
+
|
|
106
|
+
Shipeasy.configure { |c| c.api_key = ENV.fetch("SHIPEASY_SERVER_KEY"); c.poll = true }
|
|
107
|
+
OpenFeature::SDK.configure { |c| c.set_provider(Shipeasy::OpenFeature::Provider.new) } # uses the global
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Boolean → gate; string/number/object → config.
|
|
111
|
+
|
|
112
|
+
→ More: `pages/openfeature.md` (reason mapping, type routing).
|
|
113
|
+
|
|
114
|
+
## Error reporting — see()
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
begin
|
|
118
|
+
charge_card(order)
|
|
119
|
+
rescue => e
|
|
120
|
+
Shipeasy.see(e).causes_the("checkout").to("use the backup processor")
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`Shipeasy.see_violation(name)` for non-exception problems;
|
|
125
|
+
`Shipeasy.control_flow_exception(e).because(...)` marks expected control flow
|
|
126
|
+
(reports nothing).
|
|
127
|
+
|
|
128
|
+
→ More: `pages/error-reporting.md` · snippets `snippets/ops/see.md`
|
|
129
|
+
(`.extras`, violations, control-flow exceptions).
|
|
130
|
+
|
|
131
|
+
## i18n (Rails)
|
|
132
|
+
|
|
133
|
+
```erb
|
|
134
|
+
<%= i18n_head_tags %>
|
|
135
|
+
<h1><%= i18n_t("hero.title", name: current_user.name) %></h1>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Outside Rails: `Shipeasy.i18n_script_tag(client_key, profile: "en:prod")` emits
|
|
139
|
+
the loader tag (public client key).
|
|
140
|
+
|
|
141
|
+
→ More: `pages/i18n.md` · snippets `snippets/i18n/{setup,render}.md`.
|
|
142
|
+
|
|
143
|
+
## Other surfaces
|
|
144
|
+
|
|
145
|
+
- Anon bucketing: `Shipeasy::SDK::RackMiddleware` mints the shared `__se_anon_id`
|
|
146
|
+
cookie (Rails Railtie auto-mounts it); anonymous `get_flag` then just works.
|
|
147
|
+
- `c.private_attributes = ["email"]` strips keys from outbound events.
|
|
148
|
+
- `c.sticky_store = Shipeasy::SDK::InMemoryStickyStore.new` pins experiment assignment.
|
|
149
|
+
- SSR: `Shipeasy.bootstrap_script_tag(user)` + `Shipeasy.i18n_script_tag(client_key, "en:prod")`.
|
|
150
|
+
- `Shipeasy.on_change { ... }` (requires `c.poll = true`) fires after a poll fetches new data.
|
|
151
|
+
|
|
152
|
+
→ More: `pages/advanced.md`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Install Shipeasy into a Rails app. Creates the config/initializers/shipeasy.rb
|
|
3
|
+
initializer that calls `Shipeasy.configure` once at boot. With --i18n it also
|
|
4
|
+
sets the public client key and injects `<%= i18n_head_tags %>` into your
|
|
5
|
+
application layout's <head>.
|
|
6
|
+
|
|
7
|
+
The gem's Railties already auto-mount the anon-id Rack middleware and the i18n
|
|
8
|
+
view helpers, so this generator only creates what your app must own — the
|
|
9
|
+
initializer, the (optional) layout tag, and a reminder to set your keys.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
rails generate shipeasy:install
|
|
13
|
+
|
|
14
|
+
Creates:
|
|
15
|
+
config/initializers/shipeasy.rb (server key + background poll)
|
|
16
|
+
|
|
17
|
+
rails generate shipeasy:install --i18n
|
|
18
|
+
|
|
19
|
+
Also sets the public client key in the initializer and injects
|
|
20
|
+
<%= i18n_head_tags %> into app/views/layouts/application.html.erb
|
|
21
|
+
|
|
22
|
+
rails generate shipeasy:install --no-poll
|
|
23
|
+
|
|
24
|
+
One-shot fetch instead of the background poll (serverless / Lambda).
|