User Context
Build a new context from the authenticated user, request, and authorized entity before the first corresponding evaluation. The shared client fetches app/environment definitions at startup; context changes are local and do not cause another fetch.
Creating Context
Basic Context
context = Toggly::Context.new(
identity: 'user-123',
groups: ['beta-testers', 'premium'],
claims: {
'role' => 'admin'
},
request: Toggly::RequestContext.new(
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
accept_language: 'en-US,en;q=0.9',
country: 'US'
),
traits: {
plan: 'enterprise',
created_at: '2024-01-15'
},
entity: Toggly::EntityContext.new(
kind: 'Order', key: 'order-42',
attributes: { 'Vip' => true, 'Total' => 149.95 }
)
)
| Field | Used by |
|---|---|
identity | Percentage, Targeting, segment percentage gates |
groups | Targeting group rules |
claims | UserClaims (Claim + Value exact match) |
request | HTTP segment filters (browser / language / country / device / OS) |
traits | ContextualTargeting and custom filters |
entity | ContextProperty rules for one concrete object |
UserClaims never reads HTTP headers — set claims from your authenticated principal / JWT.
Mapping HTTP headers
Toggly::HttpRequestMapper maps common headers into RequestContext (same
precedence as Node fromHttpRequest). It does not invent identity, groups,
or claims:
In a Rails controller with an authenticated current_user and the shared client, normalize Rack header names explicitly. Accept proxy country values only from a proxy your application trusts:
base = Toggly::Context.new(
identity: current_user.id.to_s,
claims: { 'role' => current_user.role }
)
headers = {
'user-agent' => request.user_agent,
'accept-language' => request.headers['Accept-Language'],
'cf-ipcountry' => request.headers['CF-IPCountry']
}
context = Toggly::HttpRequestMapper.merge_into(headers, base)
client.enabled?(:MobileCheckout, context: context)
Built-in segment filters (BrowserFamily, BrowserLanguage, Country /
CountryFamily, DeviceType, OS / OperatingSystem) plus UserClaims are
Local in the Ruby SDK. ContextualTargeting (trait rules) remains a
Ruby/Rust-specific filter outside the shared dashboard catalog. See
SDK × filter matrix.
Factory Methods
# With just identity
context = Toggly::Context.with_identity('user-123')
# Anonymous context
context = Toggly::Context.anonymous
Using Context
With Client
# Use the shared client from startup and the context constructed above.
puts client.enabled?(:premium_feature, context: context)
With Global API
if Toggly.enabled?(:premium_feature, context: context)
show_premium_content
end
Context Properties
Identity
Unique identifier for the user (used for percentage rollouts):
context = Toggly::Context.new(identity: current_user.id.to_s)
# Check if identity is present
context.identity? # => true
Groups
Arrays of group memberships for group targeting:
context = Toggly::Context.new(
identity: 'user-123',
groups: ['admin', 'beta-testers', 'premium']
)
# Check group membership
context.in_group?('beta-testers') # => true
context.in_group?(:admin) # => true (symbols work too)
Traits
Key-value pairs for ContextualTargeting (Ruby/Rust-specific) and custom filters.
Do not put UserClaims or HTTP segment data here — use claims / request:
context = Toggly::Context.new(
traits: {
plan: 'enterprise',
age: 25,
}
)
Modifying Context
Use the with_* methods to create new contexts instead of mutating shared data. Context groups, hashes and entities are not deeply frozen; keep them owned by the request.
Adding Traits
base_context = Toggly::Context.new(identity: 'user-123')
# Create new context with additional traits
enhanced_context = base_context.with_traits(
plan: 'enterprise',
country: 'US'
)
# Original unchanged
base_context.trait('plan') # => nil
enhanced_context.trait('plan') # => 'enterprise'
Adding Groups
base_context = Toggly::Context.new(identity: 'user-123', groups: ['users'])
# Add more groups
enhanced_context = base_context.with_groups('beta-testers', 'premium')
enhanced_context.groups # => ['users', 'beta-testers', 'premium']
Rails Context Building
Automatic Context
The default builder reads the authenticated user's ID, configured groups, custom traits and an entity from request.env['toggly.entity'] or user.toggly_entity. It stores IP, user agent and locale as traits. It does not create claims or a structured RequestContext.
Add these settings inside your existing Toggly::Rails.configure initializer (whose app key or nonempty defaults are already configured):
config.identity_method = :id
config.groups_method = :role_names # Application method returning group names
Custom Trait Extractors
Inside that same configuration block, custom traits can use your application model:
config.add_trait(:plan) do |request, user|
user&.subscription&.plan
end
config.add_trait(:locale) do |_request, _user|
I18n.locale.to_s
end
Custom Context Builder
Use this builder when filters need claims and HTTP fields. Your authentication layer must establish the user before the SDK's first context build. Claims come from verified application state, never directly from user-supplied headers or parameters. The mapper maps fields; it does not authenticate them.
# Add inside the existing Toggly::Rails.configure block, before it ends.
config.context_builder = ->(request, user) do
# role/role_names are application methods on the authenticated principal.
Toggly::Context.new(
identity: user&.id&.to_s,
groups: user ? user.role_names : [],
claims: user ? { 'role' => user.role } : {},
request: Toggly::HttpRequestMapper.from_http_headers(
'user-agent' => request.user_agent,
'accept-language' => request.headers['Accept-Language'],
'cf-ipcountry' => request.headers['CF-IPCountry']
),
entity: request.env['toggly.entity']
)
end
Attach the authorized Order in a prepend_before_action before the SDK caches context; see Rails custom context. If authentication itself runs in a callback, order that callback before entity loading. For another Order later in the action, use with_entity and pass context: explicitly.
Targeting Rules
Context is used by various targeting rules:
Percentage Rollouts
Uses identity for consistent bucketing:
# Stable identity keeps percentage bucketing consistent
context = Toggly::Context.with_identity('user-123')
# Stable boolean for this identity/flag and unchanged percentage definition
client.enabled?(:new_feature, context: context) # consistent result
User Targeting
Target specific users:
# In Toggly dashboard, configure: users: ["user-123", "user-456"]
context = Toggly::Context.with_identity('user-123')
client.enabled?(:beta_feature, context: context) # Uses the loaded definition
Group Targeting
Target user groups:
# In Toggly dashboard, configure: groups: ["beta-testers"]
context = Toggly::Context.new(
identity: 'user-789',
groups: ['beta-testers']
)
client.enabled?(:beta_feature, context: context) # Uses the loaded definition
Contextual Targeting
ContextualTargeting consumes traits in custom definitions; it is outside the shared dashboard filter catalog. For dashboard Country rules, populate request.country. For Order rules, populate entity.attributes and use ContextProperty. Merely supplying a trait does not create a dashboard rule or enable a flag.
Entity context
Identity, groups and claims describe the user. entity describes the one Order currently being evaluated. Its key is not the user's identity. See Entity & page context.
The Basic Context example constructs an entity directly. For application model mapping, register a mapper once before client construction. This example is self-contained apart from the shared client, which you create after registration:
Order = Struct.new(:id, :vip, :total, keyword_init: true)
Toggly.register_context('Order') do |order|
Toggly::EntityContext.new(
kind: 'Order', key: order.id.to_s,
attributes: { 'Vip' => order.vip, 'Total' => order.total }
)
end
# Construct your shared client after registration, as in Quick Start.
user_context = Toggly::Context.new(
identity: 'user-123', groups: ['premium'],
claims: { 'role' => 'customer' },
request: Toggly::RequestContext.new(country: 'US')
)
order = Order.new(id: 'order-42', vip: true, total: 149.95)
other_order = Order.new(id: 'order-43', vip: false, total: 20.0)
order_context = user_context.with_entity(Toggly.map_entity('Order', order))
other_context = user_context.with_entity(Toggly.map_entity('Order', other_order))
client.enabled?('ExpressCheckout', context: order_context)
client.enabled?('ExpressCheckout', context: other_context)
Both calls retain the same user and request fields but inspect different Orders. For a ContextProperty Vip eq true boolean rule, only the VIP Order matches that rule. An unknown mapper returns nil; a ContextProperty check without an entity fails. This does not disable unrelated user-only flags.
An optional schema: Toggly::EntityContextSchemaRegistration.new(...) argument declares dashboard catalog metadata for startup registration. disable_entity_context_registration disables that upload on the core Config; local mapping remains available. No schema upload is needed for the direct entity example.
Cache Key
cache_key describes identity/groups/traits/claims/request but excludes entity and definition revision. Do not use it alone to cache evaluation results. Contexts for two different Orders can have the same key.
context = Toggly::Context.new(
identity: 'user-123',
groups: ['admin', 'beta'],
traits: { plan: 'enterprise' }
)
context.cache_key # => "user-123:admin,beta:plan=enterprise::"
Serialization
Convert context to/from hash:
context = Toggly::Context.new(
identity: 'user-123',
groups: ['beta'],
traits: { plan: 'enterprise' }
)
# To hash
hash = context.to_h
# => { identity: 'user-123', groups: ['beta'],
# traits: { 'plan' => 'enterprise' }, claims: {}, request: nil, entity: nil }
restored = Toggly::Context.from_hash(hash)
restored == context # => true