Module: Organizations::Models::Concerns::HasOrganizations::InstanceMethods
- Defined in:
- lib/organizations/models/concerns/has_organizations.rb
Overview
Instance methods included in the user model
Instance Attribute Summary collapse
-
#_current_organization_id ⇒ Object
Store for current organization ID (set by controller).
Instance Method Summary collapse
-
#belongs_to_any_organization? ⇒ Boolean
Check if user belongs to any organization Uses efficient EXISTS query Falls back to DB query when loaded association is empty to avoid stale false negatives.
-
#clear_organization_cache! ⇒ Object
Clear cached organization data (called when switching orgs).
-
#create_organization!(name_or_attributes) ⇒ Organizations::Organization
Creates a new organization with this user as owner Sets the new organization as the current organization.
-
#current_membership ⇒ Organizations::Membership?
Returns the membership in the current organization Keyed by org_id to handle org switches correctly.
-
#current_organization ⇒ Organizations::Organization?
Returns the current organization for this session.
-
#current_organization_role ⇒ Symbol?
Returns the role in the current organization.
-
#has_organization_permission_to?(permission) ⇒ Boolean
Check if user has a specific permission in current organization Uses pre-computed permission sets for O(1) lookup.
-
#has_organization_role?(role) ⇒ Boolean
Check if user has at least the specified role.
-
#has_pending_organization_invitations? ⇒ Boolean
Check if user has pending invitations Uses efficient EXISTS query.
-
#is_admin_of?(org) ⇒ Boolean
Check if user is admin (or owner) of specific organization.
-
#is_at_least?(role, in: nil) ⇒ Boolean
Check if user is at least the specified role in an organization.
-
#is_member_of?(org) ⇒ Boolean
Check if user is a member of specific organization Uses efficient EXISTS query Falls back to DB query when loaded association misses to avoid stale false negatives (e.g., membership created via organization.memberships.create! bypasses user cache).
-
#is_organization_admin? ⇒ Boolean
Check if user is admin (or owner) of current organization.
-
#is_organization_member? ⇒ Boolean
Check if user is member (or higher) of current organization.
-
#is_organization_owner? ⇒ Boolean
Check if user is owner of current organization.
-
#is_organization_viewer? ⇒ Boolean
Check if user is viewer (or higher) of current organization.
-
#is_owner_of?(org) ⇒ Boolean
Check if user is owner of specific organization.
-
#is_viewer_of?(org) ⇒ Boolean
Check if user is viewer (or higher) of specific organization.
-
#leave_current_organization! ⇒ Object
Leave current organization.
-
#leave_organization!(org) ⇒ Object
Leave an organization.
-
#organization ⇒ Object
Alias for current_organization (convenience as per README).
-
#pending_join_request_for(organization) ⇒ Organizations::JoinRequest?
This user's open request for an organization, if any.
-
#pending_organization_invitations ⇒ ActiveRecord::Relation
Get pending invitations for this user.
-
#request_to_join!(organization, message: nil) ⇒ Organizations::JoinRequest
Petition to join an organization (request-to-join workflow).
-
#role_in(org) ⇒ Symbol?
Get user's role in a specific organization Smart about reusing loaded associations, with DB fallback for stale caches.
-
#send_organization_invite_to!(email, organization: nil, role: nil) ⇒ Organizations::Invitation
Send invitation to join organization.
-
#should_create_personal_organization? ⇒ Boolean
Extension seam for personal organization creation.
Instance Attribute Details
#_current_organization_id ⇒ Object
Store for current organization ID (set by controller)
201 202 203 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 201 def _current_organization_id @_current_organization_id end |
Instance Method Details
#belongs_to_any_organization? ⇒ Boolean
Check if user belongs to any organization Uses efficient EXISTS query Falls back to DB query when loaded association is empty to avoid stale false negatives
253 254 255 256 257 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 253 def belongs_to_any_organization? return true if memberships.loaded? && memberships.any? memberships.exists? end |
#clear_organization_cache! ⇒ Object
Clear cached organization data (called when switching orgs)
239 240 241 242 243 244 245 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 239 def clear_organization_cache! @_current_organization = nil @_current_organization_id_cached = nil @_current_membership = nil @_current_membership_org_id = nil @_current_organization_id = nil end |
#create_organization!(name_or_attributes) ⇒ Organizations::Organization
Creates a new organization with this user as owner Sets the new organization as the current organization
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 432 def create_organization!(name_or_attributes) attributes = name_or_attributes.is_a?(Hash) ? name_or_attributes : { name: name_or_attributes } # Check max organizations limit settings = self.class.organization_settings max = settings[:max_organizations] if max && owned_organizations.count >= max raise OrganizationLimitReached, Organizations.t(:"errors.organization_limit_reached", max: max) end org = nil ActiveRecord::Base.transaction do org = Organizations::Organization.create!(attributes) Organizations::Membership.create!( user: self, organization: org, role: "owner" ) end # Set as current organization context # Order matters: set the ID first, then set the cached values # (don't call clear_organization_cache! which would clear the ID) @_current_organization = org @_current_organization_id_cached = org.id @_current_membership = nil @_current_membership_org_id = nil self._current_organization_id = org.id Callbacks.dispatch(:organization_created, organization: org, user: self) org end |
#current_membership ⇒ Organizations::Membership?
Returns the membership in the current organization Keyed by org_id to handle org switches correctly
220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 220 def current_membership return nil unless current_organization # Key memoization by org_id to avoid staleness after org switch if @_current_membership_org_id != current_organization.id @_current_membership = nil @_current_membership_org_id = current_organization.id end @_current_membership ||= memberships.find_by(organization_id: current_organization.id) end |
#current_organization ⇒ Organizations::Organization?
Returns the current organization for this session
205 206 207 208 209 210 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 205 def current_organization return @_current_organization if defined?(@_current_organization) && @_current_organization_id_cached == _current_organization_id @_current_organization_id_cached = _current_organization_id @_current_organization = _current_organization_id ? organizations.find_by(id: _current_organization_id) : nil end |
#current_organization_role ⇒ Symbol?
Returns the role in the current organization
234 235 236 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 234 def current_organization_role current_membership&.role&.to_sym end |
#has_organization_permission_to?(permission) ⇒ Boolean
Check if user has a specific permission in current organization Uses pre-computed permission sets for O(1) lookup
412 413 414 415 416 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 412 def () return false unless current_organization_role Roles.(current_organization_role, ) end |
#has_organization_role?(role) ⇒ Boolean
Check if user has at least the specified role
311 312 313 314 315 316 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 311 def has_organization_role?(role) current_role = current_organization_role return false unless current_role Roles.at_least?(current_role, role.to_sym) end |
#has_pending_organization_invitations? ⇒ Boolean
Check if user has pending invitations Uses efficient EXISTS query
262 263 264 265 266 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 262 def has_pending_organization_invitations? return false unless respond_to?(:email) && email.present? Organizations::Invitation.pending.for_email(email).exists? end |
#is_admin_of?(org) ⇒ Boolean
Check if user is admin (or owner) of specific organization
363 364 365 366 367 368 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 363 def is_admin_of?(org) role = role_in(org) return false unless role Roles.at_least?(role, :admin) end |
#is_at_least?(role, in: nil) ⇒ Boolean
Check if user is at least the specified role in an organization
398 399 400 401 402 403 404 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 398 def is_at_least?(role, in: nil) org = binding.local_variable_get(:in) user_role = org ? role_in(org) : current_organization_role return false unless user_role Roles.at_least?(user_role, role.to_sym) end |
#is_member_of?(org) ⇒ Boolean
Check if user is a member of specific organization Uses efficient EXISTS query Falls back to DB query when loaded association misses to avoid stale false negatives (e.g., membership created via organization.memberships.create! bypasses user cache)
376 377 378 379 380 381 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 376 def is_member_of?(org) return false unless org return true if memberships.loaded? && memberships.any? { |m| m.organization_id == org.id } memberships.exists?(organization_id: org.id) end |
#is_organization_admin? ⇒ Boolean
Check if user is admin (or owner) of current organization
286 287 288 289 290 291 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 286 def is_organization_admin? role = current_organization_role return false unless role Roles.at_least?(role, :admin) end |
#is_organization_member? ⇒ Boolean
Check if user is member (or higher) of current organization
295 296 297 298 299 300 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 295 def is_organization_member? role = current_organization_role return false unless role Roles.at_least?(role, :member) end |
#is_organization_owner? ⇒ Boolean
Check if user is owner of current organization
280 281 282 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 280 def is_organization_owner? current_organization_role == :owner end |
#is_organization_viewer? ⇒ Boolean
Check if user is viewer (or higher) of current organization
304 305 306 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 304 def is_organization_viewer? current_organization_role.present? end |
#is_owner_of?(org) ⇒ Boolean
Check if user is owner of specific organization
356 357 358 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 356 def is_owner_of?(org) role_in(org) == :owner end |
#is_viewer_of?(org) ⇒ Boolean
Check if user is viewer (or higher) of specific organization
386 387 388 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 386 def is_viewer_of?(org) role_in(org).present? end |
#leave_current_organization! ⇒ Object
Leave current organization
510 511 512 513 514 515 516 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 510 def leave_current_organization! unless current_organization raise NoCurrentOrganization, Organizations.t(:"errors.no_current_organization_to_leave") end leave_organization!(current_organization) end |
#leave_organization!(org) ⇒ Object
Leave an organization
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 471 def leave_organization!(org) membership = memberships.find_by(organization_id: org.id) return unless membership ActiveRecord::Base.transaction do # Lock organization to prevent race condition with other leave operations org.lock! # Check if this is the only owner if membership.role.to_sym == :owner owner_count = org.memberships.where(role: "owner").count if owner_count == 1 raise CannotLeaveAsLastOwner, Organizations.t(:"errors.cannot_leave_as_last_owner") end end # Check require_organization setting settings = self.class.organization_settings if settings[:require_organization] && organizations.count == 1 raise CannotLeaveLastOrganization, Organizations.t(:"errors.cannot_leave_last_organization") end membership.destroy! end # Clear cache if leaving current organization clear_organization_cache! if _current_organization_id == org.id Callbacks.dispatch( :member_removed, organization: org, user: self, membership: membership, removed_by: self ) end |
#organization ⇒ Object
Alias for current_organization (convenience as per README)
213 214 215 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 213 def organization current_organization end |
#pending_join_request_for(organization) ⇒ Organizations::JoinRequest?
This user's open request for an organization, if any
595 596 597 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 595 def pending_join_request_for(organization) organization_join_requests.pending.find_by(organization_id: organization.id) end |
#pending_organization_invitations ⇒ ActiveRecord::Relation
Get pending invitations for this user
270 271 272 273 274 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 270 def pending_organization_invitations return Organizations::Invitation.none unless respond_to?(:email) && email.present? Organizations::Invitation.pending.for_email(email) end |
#request_to_join!(organization, message: nil) ⇒ Organizations::JoinRequest
Petition to join an organization (request-to-join workflow). Idempotent: returns the existing open request when one exists. Fires the :join_request_created callback for new requests.
NOTE: max_organizations deliberately does NOT apply here — that
setting caps orgs a user can OWN. Hosts wanting to cap plain
memberships should check before approving (see README).
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 559 def request_to_join!(organization, message: nil) existing = pending_join_request_for(organization) return existing if existing # A member doesn't need to request — surface their (approved) # state instead of creating a nonsense pending row. existing_membership = memberships.find_by(organization_id: organization.id) if existing_membership raise Organizations::JoinRequestAlreadyDecided, Organizations.t(:"errors.join_request_already_member") end # SAVEPOINT: the RecordNotUnique rescue below must stay reachable # when a HOST wraps this call in its own transaction (PostgreSQL # aborted-transaction semantics — see JoinRequest#create_membership!). request = ActiveRecord::Base.transaction(requires_new: true) do organization.join_requests.create!(user: self, message: ) end Callbacks.dispatch( :join_request_created, organization: organization, user: self, join_request: request ) request rescue ActiveRecord::RecordNotUnique # Race: a concurrent request slipped past the pre-check. pending_join_request_for(organization) || raise(Organizations::JoinRequestError, Organizations.t(:"errors.join_request_create_failed")) end |
#role_in(org) ⇒ Symbol?
Get user's role in a specific organization Smart about reusing loaded associations, with DB fallback for stale caches
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 324 def role_in(org) return nil unless org # Try org's loaded memberships first if org.respond_to?(:association) && org.association(:memberships).loaded? membership = org.memberships.find { |m| m.user_id == id } return membership.role.to_sym if membership end # Try user's loaded memberships if memberships.loaded? membership = memberships.find { |m| m.organization_id == org.id } return membership.role.to_sym if membership end # Try loaded organizations' memberships if organizations.loaded? loaded_org = organizations.find { |candidate| candidate.id == org.id } if loaded_org && loaded_org.respond_to?(:association) && loaded_org.association(:memberships).loaded? membership = loaded_org.memberships.find { |m| m.user_id == id } return membership.role.to_sym if membership end end # DB fallback - loaded associations may be stale if membership was created # via another association path (e.g., organization.memberships.create!) memberships.find_by(organization_id: org.id)&.role&.to_sym end |
#send_organization_invite_to!(email, organization: nil, role: nil) ⇒ Organizations::Invitation
Send invitation to join organization
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 524 def send_organization_invite_to!(email, organization: nil, role: nil) org = organization || current_organization unless org raise NoCurrentOrganization, "No organization specified and no current organization set" end # Check permission (permission-based, not role-based) # This respects custom role configurations user_role = role_in(org) unless user_role && Roles.(user_role, :invite_members) raise Organizations::NotAuthorized.new( Organizations.t(:"errors.invite_not_authorized"), permission: :invite_members, organization: org, user: self ) end org.send_invite_to!(email, invited_by: self, role: role) end |
#should_create_personal_organization? ⇒ Boolean
Extension seam for personal organization creation. Override this method in your User model to implement custom logic.
615 616 617 618 619 620 621 622 623 624 |
# File 'lib/organizations/models/concerns/has_organizations.rb', line 615 def should_create_personal_organization? setting = self.class.organization_settings[:create_personal_org] case setting when Proc setting.call(self) else !!setting end end |