Class: Organizations::Membership

Inherits:
ActiveRecord::Base
  • Object
show all
Extended by:
MetadataFlags
Defined in:
lib/organizations/models/membership.rb

Overview

Membership model representing a user's membership in an organization. Each membership has a role that determines permissions.

The role hierarchy is: owner > admin > member > viewer Each role inherits permissions from lower roles.

⚠️ THE MEMBERSHIP GATE IS NOT ON THIS MODEL. on_member_joining (the strict, vetoing host callback — seat limits, member caps) is dispatched by the SANCTIONED join paths: Organization#add_member!, Invitation#accept!, and JoinRequest#approve! (which covers codes, domains, allowlists, and the account-email shortcut). A direct Membership.create! bypasses the gate entirely — that's deliberate for ops/console/test usage (Organization.create_with_owner!, TestHelpers), but NEVER create memberships directly from request-cycle product code, and any NEW join path added to the gem must dispatch the gate itself (see Configuration#on_member_joining).

Examples:

Checking permissions

membership.has_permission_to?(:invite_members) # => true/false
membership.is_at_least?(:admin) # => true/false

Changing roles

membership.promote_to!(:admin)
membership.demote_to!(:member)

Defined Under Namespace

Classes: CannotDemoteOwner, CannotPromoteToOwner, InvalidRoleChange

Instance Method Summary collapse

Methods included from MetadataFlags

metadata_flag

Instance Method Details

#admin?Boolean

Check if this membership is for an admin (not owner)

Returns:

  • (Boolean)


118
119
120
# File 'lib/organizations/models/membership.rb', line 118

def admin?
  role_sym == :admin
end

#compare_role(other) ⇒ Integer

Compare roles with another membership

Parameters:

Returns:

  • (Integer)

    -1 if higher, 0 if equal, 1 if lower



165
166
167
# File 'lib/organizations/models/membership.rb', line 165

def compare_role(other)
  Roles.compare(role_sym, other.role_sym)
end

#demote_to!(new_role, changed_by: nil) ⇒ self

Demote to a lower role

Parameters:

  • new_role (Symbol, String)

    The new role

  • changed_by (User, nil) (defaults to: nil)

    Who is making the change

Returns:

  • (self)

Raises:



199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/organizations/models/membership.rb', line 199

def demote_to!(new_role, changed_by: nil)
  new_role_sym = new_role.to_sym
  validate_role!(new_role_sym)

  if owner?
    raise CannotDemoteOwner, Organizations.t(:"errors.cannot_demote_owner")
  end

  unless Roles.at_least?(role_sym, new_role_sym)
    raise InvalidRoleChange, Organizations.t(:"errors.demote_not_lower", new_role: new_role, role: role)
  end

  change_role_to!(new_role_sym, changed_by: changed_by)
end

#has_permission_to?(permission) ⇒ Boolean

Check if this membership has a specific permission Uses pre-computed permission sets for O(1) lookup

Parameters:

  • permission (Symbol, String)

    The permission to check

Returns:

  • (Boolean)


140
141
142
# File 'lib/organizations/models/membership.rb', line 140

def has_permission_to?(permission)
  Roles.has_permission?(role_sym, permission)
end

#is_at_least?(minimum_role) ⇒ Boolean

Check if this membership's role is at least as high as the specified role

Examples:

membership.is_at_least?(:admin) # => true if admin or owner
membership.is_at_least?(:owner) # => true only if owner

Parameters:

  • minimum_role (Symbol, String)

    The minimum required role

Returns:

  • (Boolean)


158
159
160
# File 'lib/organizations/models/membership.rb', line 158

def is_at_least?(minimum_role)
  Roles.at_least?(role_sym, minimum_role.to_sym)
end

#member?Boolean

Check if this membership is for a member

Returns:

  • (Boolean)


124
125
126
# File 'lib/organizations/models/membership.rb', line 124

def member?
  role_sym == :member
end

#owner?Boolean

Check if this membership is for an owner

Returns:

  • (Boolean)


112
113
114
# File 'lib/organizations/models/membership.rb', line 112

def owner?
  role_sym == :owner
end

#permissionsArray<Symbol>

Get all permissions for this membership

Returns:

  • (Array<Symbol>)


146
147
148
# File 'lib/organizations/models/membership.rb', line 146

def permissions
  Roles.permissions_for(role_sym)
end

#promote_to!(new_role, changed_by: nil) ⇒ self

Promote to a higher role

Parameters:

  • new_role (Symbol, String)

    The new role

  • changed_by (User, nil) (defaults to: nil)

    Who is making the change

Returns:

  • (self)

Raises:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/organizations/models/membership.rb', line 177

def promote_to!(new_role, changed_by: nil)
  new_role_sym = new_role.to_sym
  validate_role!(new_role_sym)

  # Owner role is only assignable via transfer_ownership_to!
  if new_role_sym == :owner
    raise CannotPromoteToOwner, Organizations.t(:"errors.cannot_promote_to_owner")
  end

  unless Roles.at_least?(new_role_sym, role_sym)
    raise InvalidRoleChange, Organizations.t(:"errors.promote_not_higher", new_role: new_role, role: role)
  end

  change_role_to!(new_role_sym, changed_by: changed_by)
end

#role_symSymbol

Get the role as a symbol

Returns:

  • (Symbol)


106
107
108
# File 'lib/organizations/models/membership.rb', line 106

def role_sym
  role&.to_sym
end

#verified?Boolean

Whether this membership was created with a proven email address (emailed-code challenge, confirmed account email, or accepted invitation)

Returns:

  • (Boolean)


98
99
100
# File 'lib/organizations/models/membership.rb', line 98

def verified?
  verified_at.present?
end

#viewer?Boolean

Check if this membership is for a viewer

Returns:

  • (Boolean)


130
131
132
# File 'lib/organizations/models/membership.rb', line 130

def viewer?
  role_sym == :viewer
end