Class: Rhino::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/rhino/configuration.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/rhino/configuration.rb', line 14

def initialize
  @models = {}
  @route_groups = {}
  @multi_tenant = {
    organization_identifier_column: "id"
  }
  @invitations = {
    expires_days: 7,
    allowed_roles: nil
  }
  @nested = {
    path: "nested",
    max_operations: 50,
    allowed_models: nil
  }
  @auth = {
    enforce_group_membership: false
  }
  @test_framework = "rspec"
  @client_path = nil
  @mobile_path = nil
  @route_key = nil
end

Instance Attribute Details

#authObject

Returns the value of attribute auth.



12
13
14
# File 'lib/rhino/configuration.rb', line 12

def auth
  @auth
end

#client_pathObject

Returns the value of attribute client_path.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def client_path
  @client_path
end

#invitationsObject

Returns the value of attribute invitations.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def invitations
  @invitations
end

#mobile_pathObject

Returns the value of attribute mobile_path.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def mobile_path
  @mobile_path
end

#modelsObject

Returns the value of attribute models.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def models
  @models
end

#multi_tenantObject

Returns the value of attribute multi_tenant.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def multi_tenant
  @multi_tenant
end

#nestedObject

Returns the value of attribute nested.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def nested
  @nested
end

#route_groupsObject

Returns the value of attribute route_groups.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def route_groups
  @route_groups
end

#route_keyObject

Global default route key: the column matched against the :id URL segment on member endpoints (show/update/destroy/restore/force_delete) for every model that does not declare its own rhino_route_key. Default nil = primary key (today's behavior, fully backward compatible).



11
12
13
# File 'lib/rhino/configuration.rb', line 11

def route_key
  @route_key
end

#test_frameworkObject

Returns the value of attribute test_framework.



5
6
7
# File 'lib/rhino/configuration.rb', line 5

def test_framework
  @test_framework
end

Instance Method Details

#auth_enabled_groupsObject

Names of all groups (except :public) that opted into per-group auth.



174
175
176
177
# File 'lib/rhino/configuration.rb', line 174

def auth_enabled_groups
  @route_groups.keys.reject { |name| name.to_s == "public" }
               .select { |name| group_auth_enabled?(name) }
end

#auth_enabled_legacy_groupsObject

Names of auth-enabled groups that have an empty prefix AND no domain, i.e. groups whose auth routes would be byte-for-byte identical to the legacy unprefixed /api/auth/* set (GROUP_AUTH_DESIGN.md ยง11.1). Such a group IS the default/legacy auth: the legacy routes adopt its route_group/hooks instead of registering a colliding second set. Two or more is a conflict (raised by the route-group validator).



185
186
187
188
189
190
191
192
# File 'lib/rhino/configuration.rb', line 185

def auth_enabled_legacy_groups
  auth_enabled_groups.select do |name|
    group = @route_groups[name.to_sym]
    prefix = group[:prefix].to_s
    domain = group[:domain]
    prefix.empty? && (domain.nil? || domain.to_s.strip.empty?)
  end
end

#enforce_group_membership?Boolean

Master flag (default off). When off, behavior is byte-for-byte today's: no group-membership enforcement.

Returns:

  • (Boolean)


46
47
48
# File 'lib/rhino/configuration.rb', line 46

def enforce_group_membership?
  !!@auth[:enforce_group_membership]
end

#group_auth_enabled?(group_name) ⇒ Boolean

Whether a group has per-group auth routes enabled (auth: true). The public group is never auth-enabled.

Returns:

  • (Boolean)


166
167
168
169
170
171
# File 'lib/rhino/configuration.rb', line 166

def group_auth_enabled?(group_name)
  return false if group_name.to_s == "public"

  group = @route_groups[group_name.to_sym]
  !!(group && group[:auth])
end

#group_is_tenant?(group_name) ⇒ Boolean

Whether a group is a tenant group (organization-scoped). Only the reserved :tenant group is treated as a tenant group, matching has_tenant_group?.

Returns:

  • (Boolean)


220
221
222
# File 'lib/rhino/configuration.rb', line 220

def group_is_tenant?(group_name)
  group_name.to_s == "tenant"
end

#group_tenant?(name) ⇒ Boolean

Whether the named route group has a tenant boundary, i.e. whether Rhino.query must fail closed inside it. Only a group that explicitly declares tenant: false does not; every other answer โ€” an unknown group, an untagged route, or no group at all (jobs, rake tasks, console) โ€” is true, so the resolver keeps failing closed wherever the group is not provably non-tenant.

Returns:

  • (Boolean)


94
95
96
97
98
99
100
101
# File 'lib/rhino/configuration.rb', line 94

def group_tenant?(name)
  return true if name.nil? || name.to_s.empty?

  group = @route_groups[name.to_sym]
  return true unless group

  group.fetch(:tenant, true) != false
end

#has_public_group?Boolean

Whether a 'public' route group is configured

Returns:

  • (Boolean)


131
132
133
# File 'lib/rhino/configuration.rb', line 131

def has_public_group?
  @route_groups.key?(:public)
end

#has_tenant_group?Boolean

Whether a 'tenant' route group is configured

Returns:

  • (Boolean)


126
127
128
# File 'lib/rhino/configuration.rb', line 126

def has_tenant_group?
  @route_groups.key?(:tenant)
end

#hooks_for_group(group_name) ⇒ Object

Resolve the configured lifecycle-hooks class for a group, instantiated. Returns nil when the group has no hooks configured. Accepts a class, a class name string, or an instance.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/rhino/configuration.rb', line 197

def hooks_for_group(group_name)
  return nil if group_name.nil?

  group = @route_groups[group_name.to_sym]
  return nil unless group

  hooks = group[:hooks]
  return nil if hooks.nil?

  case hooks
  when String
    klass = hooks.safe_constantize
    klass&.new
  when Class
    hooks.new
  else
    hooks
  end
end

#model(slug, klass_name) ⇒ Object

Register a model with its slug Usage: config.model :posts, 'Post'



52
53
54
# File 'lib/rhino/configuration.rb', line 52

def model(slug, klass_name)
  @models[slug.to_sym] = klass_name.to_s
end

#model_in_group?(slug, group_name) ⇒ Boolean

Check if a specific slug belongs to a specific group

Returns:

  • (Boolean)


156
157
158
# File 'lib/rhino/configuration.rb', line 156

def model_in_group?(slug, group_name)
  models_for_group(group_name).include?(slug.to_sym)
end

#models_for_group(group_name) ⇒ Object

Resolve the model slugs for a given route group



136
137
138
139
140
141
142
143
144
145
146
# File 'lib/rhino/configuration.rb', line 136

def models_for_group(group_name)
  group = @route_groups[group_name.to_sym]
  return [] unless group

  group_models = group[:models]
  if group_models == :all || group_models == "*"
    @models.keys
  else
    Array(group_models).map(&:to_sym) & @models.keys
  end
end

#public_model?(slug) ⇒ Boolean

Check if a model belongs to the 'public' route group

Returns:

  • (Boolean)


149
150
151
152
153
# File 'lib/rhino/configuration.rb', line 149

def public_model?(slug)
  return false unless has_public_group?

  models_for_group(:public).include?(slug.to_sym)
end

#resolve_model(slug) ⇒ Object

Resolve a model class from its slug



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rhino/configuration.rb', line 104

def resolve_model(slug)
  klass_name = @models[slug.to_sym]
  raise ActiveRecord::RecordNotFound, "The #{slug} model does not exist" unless klass_name

  klass = klass_name.constantize
  raise ActiveRecord::RecordNotFound, "The #{slug} model does not exist" unless klass

  klass
rescue NameError
  raise ActiveRecord::RecordNotFound, "The #{slug} model does not exist"
end

#route_group(name, prefix: "", domain: nil, middleware: [], models: :all, auth: false, hooks: nil, tenant: true) ⇒ Object

Register a route group with its configuration Usage: config.route_group :tenant, prefix: ':organization', middleware: [Rhino::Middleware::ResolveOrganizationFromRoute], models: :all

The optional domain: keyword constrains the group's routes to a specific host. Two groups can then share the same prefix: but live on different domains. A parameterized domain such as "organization.example.com" captures the subdomain and feeds organization resolution exactly like the path-prefix ":organization" does. Groups without a domain (nil/blank) match any host (default, fully backward compatible).

The optional tenant: keyword declares whether the group has a tenant boundary. It defaults to true: Rhino.query inside the group fails closed, raising Rhino::MissingTenantContext when an organization-scopable model is queried with no organization resolved. Pass tenant: false for a group that legitimately spans every organization โ€” a back office or admin group whose operators see all tenants' rows.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/rhino/configuration.rb', line 72

def route_group(name, prefix: "", domain: nil, middleware: [], models: :all, auth: false, hooks: nil,
                tenant: true)
  normalized_domain = domain.to_s.strip
  normalized_domain = nil if normalized_domain.empty?

  @route_groups[name.to_sym] = {
    prefix: prefix.to_s,
    domain: normalized_domain,
    middleware: Array(middleware),
    models: models,
    auth: !!auth,
    hooks: hooks,
    tenant: tenant != false
  }
end

#slug_for(model_class) ⇒ Object

Find the slug for a given model class



117
118
119
120
121
122
123
# File 'lib/rhino/configuration.rb', line 117

def slug_for(model_class)
  class_name = model_class.is_a?(Class) ? model_class.name : model_class.class.name
  @models.each do |slug, klass_name|
    return slug if klass_name == class_name
  end
  nil
end