Documentation
¶
Overview ¶
Package rolify is a pure-Go (CGO-free) reimplementation of the deterministic core of Ruby's rolify gem — role management with global, class-scoped, and instance-scoped roles. It reproduces the role model, the user-side API (add_role / has_role? / remove_role / roles / has_all_roles? / has_any_role?), the resource-side helpers (applied_roles / roles_to_administrate), the faithful scope-matching semantics, the :any wildcard, and the strict_rolify / role_cache options — without any Ruby runtime.
It is the role engine for [go-embedded-ruby](https://github.com/go-embedded-ruby/ruby), but a standalone, reusable module.
What it is — and isn't ¶
Everything rolify does above the database is deterministic and needs no interpreter, so it lives here as pure Go: creating roles, joining them to users, and — crucially — the scope-matching rules that decide whether a stored role satisfies a query. Persistence itself is a host seam: the Store interface (FindRole / CreateRole / AssignRole / RemoveRole / RolesFor / AllRoles) is the only piece that touches storage. The default in-repo store is MemoryStore, an allocation-only implementation used by the tests; a future rbgo binding wires the seam to ActiveRecord, mirroring the gem, whose only persistent concern is the roles table and the users↔roles join.
Scope semantics ¶
A Role carries a Name and an optional scope expressed as a ResourceType and ResourceID:
- Global role: ResourceType == "" and ResourceID == "". Matches any query.
- Class role: ResourceType set, ResourceID == "". Matches any instance of that class and the class itself.
- Instance role: ResourceType and ResourceID set. Matches exactly.
A query names a role and a Scope: Global, ClassScope, InstanceScope, or the Any wildcard (Ruby's has_role? :admin, :any) which matches a role of any scope. The strict_rolify option (Strict) disables scope inheritance so a global or class role no longer satisfies a narrower class/instance query.
Flow ¶
rf := rolify.New(rolify.NewMemoryStore())
user := rf.User("42")
user.AddRole("admin", rolify.Global()) // add_role :admin
user.AddRole("moderator", rolify.ClassScope("Forum")) // add_role :moderator, Forum
user.AddRole("owner", rolify.InstanceScope("Forum", "7"))
user.HasRole("admin", rolify.Global()) // true
user.HasRole("moderator", rolify.InstanceScope("Forum", "1")) // true (class role)
user.HasRole("owner", rolify.Any()) // true
user.RemoveRole("admin", rolify.Global())
Value model ¶
A host (go-embedded-ruby / rbgo) maps its Ruby Role / user / resource objects to and from these shapes:
user.add_role :admin -> User.AddRole("admin", Global())
user.add_role :mod, Forum -> User.AddRole("mod", ClassScope("Forum"))
user.add_role :mod, @forum -> User.AddRole("mod", InstanceScope("Forum", id))
user.has_role? :admin -> User.HasRole("admin", Global())
user.has_role? :admin, :any -> User.HasRole("admin", Any())
user.has_cached_role? :admin -> User.HasCachedRole("admin", Global())
user.remove_role :admin -> User.RemoveRole("admin", Global())
user.roles -> User.Roles()
user.has_all_roles?(...) -> User.HasAllRoles(...)
user.has_any_role?(...) -> User.HasAnyRole(...)
@forum.applied_roles -> Rolify.AppliedRoles("Forum", id)
@forum.roles_to_administrate -> Rolify.RolesToAdministrate("Forum", id)
Index ¶
- type MemoryStore
- func (m *MemoryStore) AllRoles() []*Role
- func (m *MemoryStore) AssignRole(userID string, role *Role)
- func (m *MemoryStore) CreateRole(name, resourceType, resourceID string) *Role
- func (m *MemoryStore) FindRole(name, resourceType, resourceID string) (*Role, bool)
- func (m *MemoryStore) RemoveRole(userID string, role *Role) bool
- func (m *MemoryStore) RolesFor(userID string) []*Role
- type Option
- type Query
- type Role
- type Rolify
- type Scope
- type Store
- type User
- func (u *User) AddRole(name string, s Scope) *Role
- func (u *User) HasAllRoles(queries ...Query) bool
- func (u *User) HasAnyRole(queries ...Query) bool
- func (u *User) HasCachedRole(name string, s Scope) bool
- func (u *User) HasRole(name string, s Scope) bool
- func (u *User) RemoveRole(name string, s Scope) bool
- func (u *User) Roles() []*Role
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an allocation-only Store: a slice of roles plus a userID→role-ID join set. It is deterministic (insertion order is preserved on every read), so it behaves identically under qemu emulation and wasm.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty in-memory Store.
func (*MemoryStore) AssignRole ¶
func (m *MemoryStore) AssignRole(userID string, role *Role)
AssignRole implements Store.
func (*MemoryStore) CreateRole ¶
func (m *MemoryStore) CreateRole(name, resourceType, resourceID string) *Role
CreateRole implements Store.
func (*MemoryStore) FindRole ¶
func (m *MemoryStore) FindRole(name, resourceType, resourceID string) (*Role, bool)
FindRole implements Store.
func (*MemoryStore) RemoveRole ¶
func (m *MemoryStore) RemoveRole(userID string, role *Role) bool
RemoveRole implements Store.
type Option ¶
type Option func(*Rolify)
Option configures a Rolify engine.
type Role ¶
Role is rolify's role value model: a Name plus an optional scope expressed as a ResourceType and ResourceID. The empty string denotes an absent scope component (rolify stores SQL NULL). ID is the store-assigned identity used for the users↔roles join; the value the gem cares about is the (Name, ResourceType, ResourceID) triple.
- Global role: ResourceType == "" and ResourceID == "".
- Class role: ResourceType set, ResourceID == "".
- Instance role: ResourceType and ResourceID set.
type Rolify ¶
type Rolify struct {
// contains filtered or unexported fields
}
Rolify is the role engine bound to a Store and a set of options.
func (*Rolify) AppliedRoles ¶
AppliedRoles returns the roles that apply to the resource instance identified by (resourceType, resourceID): instance-scoped roles on it, class-scoped roles on its class, and global roles (Ruby: @resource.applied_roles).
func (*Rolify) RolesToAdministrate ¶
RolesToAdministrate returns the roles that administrate the resource instance identified by (resourceType, resourceID): the roles scoped to this resource or to its class, excluding global roles (Ruby: @resource.roles_to_administrate).
type Scope ¶
Scope names the target of a role query or assignment. Build one with Global, ClassScope, InstanceScope, or Any. The zero Scope is Global.
func Any ¶
func Any() Scope
Any is the wildcard query (Ruby: has_role? :admin, :any); it matches a role of any scope. Using it for an assignment is treated as Global.
func ClassScope ¶
ClassScope scopes to a resource class (Ruby: add_role :mod, Forum).
func Global ¶
func Global() Scope
Global is the unscoped query/assignment (Ruby: add_role :admin / has_role? :admin).
func InstanceScope ¶
InstanceScope scopes to a single resource instance (Ruby: add_role :mod, @forum).
type Store ¶
type Store interface {
// FindRole returns the role matching the exact (name, resourceType,
// resourceID) triple, and whether it was found.
FindRole(name, resourceType, resourceID string) (*Role, bool)
// CreateRole persists a new role for the triple and returns it.
CreateRole(name, resourceType, resourceID string) *Role
// AssignRole joins userID to role. It is idempotent: joining an
// already-joined role is a no-op.
AssignRole(userID string, role *Role)
// RemoveRole unjoins userID from role, reporting whether a join existed.
RemoveRole(userID string, role *Role) bool
// RolesFor returns every role joined to userID, in a deterministic order.
RolesFor(userID string) []*Role
// AllRoles returns every persisted role, in a deterministic order. It backs
// the resource-side helpers (applied_roles / roles_to_administrate).
AllRoles() []*Role
}
Store is the persistence seam. It models rolify's roles table and its users↔roles join over the Role value model. The in-repo MemoryStore backs the tests; a host binding wires it to ActiveRecord.
type User ¶
type User struct {
// contains filtered or unexported fields
}
User is a rolify role holder: the object the gem mixes add_role / has_role? / remove_role into.
func (*User) AddRole ¶
AddRole grants name in scope s (Ruby: add_role). It is idempotent: an existing role is reused and re-granting it changes nothing. It returns the role.
func (*User) HasAllRoles ¶
HasAllRoles reports whether the user has every listed role (Ruby: has_all_roles?). With no queries it reports false.
func (*User) HasAnyRole ¶
HasAnyRole reports whether the user has at least one listed role (Ruby: has_any_role?).
func (*User) HasCachedRole ¶
HasCachedRole reports whether the user has name in scope s using only the cached role snapshot (Ruby: has_cached_role?). The snapshot is loaded once on first use and never re-queries the store until a mutation invalidates it, so it answers without touching persistence even when the Cache option is off.
func (*User) HasRole ¶
HasRole reports whether the user has name in scope s (Ruby: has_role?). Under strict_rolify a class/instance query is matched exactly; otherwise scope inheritance applies (a global role satisfies any query, a class role satisfies its instances).
func (*User) RemoveRole ¶
RemoveRole revokes name in scope s (Ruby: remove_role). Removing a role the user does not have — or one that does not exist — is a no-op; the returned bool reports whether a grant was removed.
