Class: Organizations::JoinState
- Inherits:
-
Object
- Object
- Organizations::JoinState
- Defined in:
- lib/organizations/join_state.rb
Overview
The headless state machine behind any "join this organization" screen — BYO-UI in its truest form: the gem ships the STATE, the host ships the pixels. One object answers "which state is this screen in" so a page body, a pinned CTA, and a Turbo Stream response can never disagree (splitting these predicates across partials is exactly how a submit button ends up pointing at a form that no longer exists — a bug the first production host paid for before extracting this).
:member — the viewer belongs; show the success/hub state
:verifying — an emailed-code challenge is in flight; show the 6-digit
input (+ resend with #resend_seconds cooldown)
:pending — a request awaits manual approval; show the waiting state
:entry — no relationship yet; show the join form(s) the org's
instruments allow (organization.accepts_domain_joining? /
accepts_code_joining? / accepts_join_requests?)
Constant Summary collapse
- STATUSES =
%i[member verifying pending entry].freeze
Instance Attribute Summary collapse
-
#join_request ⇒ Object
readonly
Returns the value of attribute join_request.
-
#membership ⇒ Object
readonly
Returns the value of attribute membership.
-
#organization ⇒ Object
readonly
Returns the value of attribute organization.
-
#result ⇒ Object
readonly
Returns the value of attribute result.
Class Method Summary collapse
-
.for(user:, organization:, result: nil) ⇒ JoinState
Build the state for a viewer.
Instance Method Summary collapse
-
#challenge ⇒ Organizations::JoinRequest?
The join request whose emailed challenge is being answered (feeds the verify form and #resend_seconds).
- #entry? ⇒ Boolean
-
#error_message ⇒ String?
Localized message of a just-failed action, nil otherwise.
-
#initialize(organization:, membership:, join_request:, result: nil) ⇒ JoinState
constructor
A new instance of JoinState.
- #member? ⇒ Boolean
- #pending? ⇒ Boolean
-
#resend_seconds ⇒ Integer
Seconds until the gem will accept another code send for this challenge — drive a client-side resend cooldown from this instead of mirroring config.verification_resend_interval as a magic number in the host (mirrored constants desync the day someone tunes the config).
-
#status ⇒ Symbol
One of STATUSES.
- #verifying? ⇒ Boolean
Constructor Details
#initialize(organization:, membership:, join_request:, result: nil) ⇒ JoinState
Returns a new instance of JoinState.
54 55 56 57 58 59 |
# File 'lib/organizations/join_state.rb', line 54 def initialize(organization:, membership:, join_request:, result: nil) @organization = organization @membership = membership @join_request = join_request @result = result end |
Instance Attribute Details
#join_request ⇒ Object (readonly)
Returns the value of attribute join_request.
34 35 36 |
# File 'lib/organizations/join_state.rb', line 34 def join_request @join_request end |
#membership ⇒ Object (readonly)
Returns the value of attribute membership.
34 35 36 |
# File 'lib/organizations/join_state.rb', line 34 def membership @membership end |
#organization ⇒ Object (readonly)
Returns the value of attribute organization.
34 35 36 |
# File 'lib/organizations/join_state.rb', line 34 def organization @organization end |
#result ⇒ Object (readonly)
Returns the value of attribute result.
34 35 36 |
# File 'lib/organizations/join_state.rb', line 34 def result @result end |
Class Method Details
.for(user:, organization:, result: nil) ⇒ JoinState
Build the state for a viewer. Pass the JoinFlow::Result of the action that JUST ran (nil on a plain GET): its records are fresher than any association cache — right after a successful verify, the new membership lives on the result, not necessarily in a reloaded association.
45 46 47 48 49 50 51 52 |
# File 'lib/organizations/join_state.rb', line 45 def self.for(user:, organization:, result: nil) new( organization: organization, membership: result&.membership || organization.memberships.find_by(user_id: user.id), join_request: result&.join_request || user.pending_join_request_for(organization), result: result ) end |
Instance Method Details
#challenge ⇒ Organizations::JoinRequest?
The join request whose emailed challenge is being answered (feeds the verify form and #resend_seconds).
94 95 96 |
# File 'lib/organizations/join_state.rb', line 94 def challenge result&.join_request || join_request end |
#entry? ⇒ Boolean
87 88 89 |
# File 'lib/organizations/join_state.rb', line 87 def entry? status == :entry end |
#error_message ⇒ String?
Localized message of a just-failed action, nil otherwise.
113 114 115 |
# File 'lib/organizations/join_state.rb', line 113 def result&. if result&.failed? end |
#member? ⇒ Boolean
70 71 72 |
# File 'lib/organizations/join_state.rb', line 70 def member? membership.present? || result&.member? end |
#pending? ⇒ Boolean
81 82 83 84 85 |
# File 'lib/organizations/join_state.rb', line 81 def pending? return false if member? || join_request.present? || result&.pending? end |
#resend_seconds ⇒ Integer
Seconds until the gem will accept another code send for this challenge — drive a client-side resend cooldown from this instead of mirroring config.verification_resend_interval as a magic number in the host (mirrored constants desync the day someone tunes the config).
103 104 105 106 107 108 109 |
# File 'lib/organizations/join_state.rb', line 103 def resend_seconds sent_at = challenge&.verification_sent_at return 0 if sent_at.blank? interval = Organizations.configuration.verification_resend_interval.to_i [interval - (Time.current - sent_at).to_i, 0].max end |
#status ⇒ Symbol
Returns one of STATUSES.
62 63 64 65 66 67 68 |
# File 'lib/organizations/join_state.rb', line 62 def status return :member if member? return :verifying if return :pending if pending? :entry end |
#verifying? ⇒ Boolean
74 75 76 77 78 79 |
# File 'lib/organizations/join_state.rb', line 74 def return false if member? return true if result&.challenge_sent? challenge.present? && challenge.verification_sent_at.present? && !challenge.email_verified? end |