Module: RailsOnboarding::Onboardable
- Extended by:
- ActiveSupport::Concern
- Defined in:
- app/models/concerns/rails_onboarding/onboardable.rb
Constant Summary collapse
- MAX_TOOLTIPS_SHOWN =
Maximum sizes for JSON fields to prevent memory issues
1000- MAX_MILESTONES_ACHIEVED =
500- MAX_JSON_SIZE_BYTES =
~64KB for TEXT columns
65_535
Instance Method Summary collapse
- #achieve_milestone!(milestone_key, session_id: nil) ⇒ Object
-
#achieved_milestones ⇒ Object
Milestones.
-
#available_tooltips ⇒ Object
Get available tooltips that haven't been shown.
-
#can_go_back? ⇒ Boolean
Rollback & Navigation Methods.
- #complete_onboarding!(session_id: nil, completion_time: nil) ⇒ Object
-
#complete_onboarding_step!(step_name, session_id: nil, time_spent: nil) ⇒ void
(also: #complete_step)
Complete the current onboarding step and advance to the next.
- #current_onboarding_step ⇒ Object (also: #current_step_info)
-
#dismiss_tooltip(tooltip_id, session_id: nil) ⇒ Object
Dismiss a tooltip (marks as shown and tracks as dismissed).
- #go_back!(session_id: nil) ⇒ Object
- #go_to_step!(step_name, session_id: nil) ⇒ Object
- #mark_tooltip_shown!(feature, session_id: nil) ⇒ Object (also: #mark_tooltip_shown)
- #milestone_achieved?(milestone_key) ⇒ Boolean
-
#milestone_achieved_at(milestone_key) ⇒ Time?
Get the timestamp when a specific milestone was achieved.
- #milestones_available ⇒ Object
-
#needs_onboarding? ⇒ Boolean
Determines if the user needs to go through onboarding.
- #next_onboarding_step ⇒ Object (also: #next_step)
-
#onboarding_progress ⇒ Integer
(also: #onboarding_progress_percentage)
Calculate onboarding progress as a percentage.
-
#onboarding_skipped? ⇒ Boolean
Onboarding skipped check.
-
#onboarding_steps ⇒ Array<Hash>
Returns the onboarding steps for this user.
- #previous_onboarding_step ⇒ Object (also: #previous_step)
- #recent_milestones(limit: 5) ⇒ Object
- #reset_onboarding! ⇒ Object (also: #reset_onboarding)
-
#reset_tooltips! ⇒ Object
Reset all tooltips (mark all as not shown).
- #restart_onboarding!(session_id: nil) ⇒ Object
-
#show_feature_tooltip?(feature) ⇒ Boolean
Feature tooltips.
- #skip_onboarding!(session_id: nil) ⇒ Object
- #skip_onboarding_step!(step_name, session_id: nil) ⇒ Object (also: #skip_step)
- #start_onboarding!(session_id: nil) ⇒ Object
-
#step_completed?(step_name) ⇒ Boolean
Check if a specific step has been completed.
-
#tooltip_shown?(tooltip_id) ⇒ Boolean
Check if a specific tooltip has been shown to this user.
- #total_milestone_points ⇒ Object
- #track_tooltip_interaction!(feature, action, session_id: nil) ⇒ Object
Instance Method Details
#achieve_milestone!(milestone_key, session_id: nil) ⇒ Object
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 303 def achieve_milestone!(milestone_key, session_id: nil) return false unless RailsOnboarding.configuration.enable_milestones return false if milestone_achieved?(milestone_key) milestone_config = RailsOnboarding.configuration.milestone_by_key(milestone_key) return false unless milestone_config points_earned = milestone_config[:points] || 0 achieved_at = Time.current self.milestones_achieved ||= [] # Store as hash with timestamp for new achievements self.milestones_achieved << { "key" => milestone_key.to_s, "achieved_at" => achieved_at.iso8601 } self.milestone_points = (milestone_points || 0) + points_earned self.last_milestone_at = achieved_at persist_and_track! do AnalyticsEvent.track_milestone_achieved( user: self, milestone_key: milestone_key, points_earned: points_earned, session_id: session_id ) end milestone_config end |
#achieved_milestones ⇒ Object
Milestones
251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 251 def achieved_milestones milestones = milestones_achieved || [] # Warn about old format if any string entries are found if milestones.any? { |m| m.is_a?(String) } RailsOnboarding::Deprecation.deprecate_format( "Storing milestones as array of strings", new_format: "array of hashes with 'key' and 'achieved_at' fields", version: "2.0.0" ) end milestones.map { |m| m.is_a?(Hash) ? m["key"] : m.to_s } end |
#available_tooltips ⇒ Object
Get available tooltips that haven't been shown
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 493 def available_tooltips return [] unless RailsOnboarding.configuration.enable_tooltips RailsOnboarding.configuration.feature_tooltips.select do |key, _config| show_feature_tooltip?(key) end.map do |key, config| { id: key.to_s, title: config[:title], content: config[:content], target: config[:target], position: config[:position] || "top" } end end |
#can_go_back? ⇒ Boolean
Rollback & Navigation Methods
387 388 389 390 391 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 387 def can_go_back? return false unless onboarding_current_step current_index = RailsOnboarding.configuration.step_index(onboarding_current_step) current_index && current_index > 0 end |
#complete_onboarding!(session_id: nil, completion_time: nil) ⇒ Object
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 164 def complete_onboarding!(session_id: nil, completion_time: nil) persist_and_track!( onboarding_completed: true, onboarding_completed_at: Time.current, onboarding_current_step: nil ) do AnalyticsEvent.track_onboarding_completed( user: self, completion_time: completion_time, was_skipped: false, session_id: session_id ) check_and_achieve_completion_milestones(session_id: session_id) end end |
#complete_onboarding_step!(step_name, session_id: nil, time_spent: nil) ⇒ void Also known as: complete_step
This method returns an undefined value.
Complete the current onboarding step and advance to the next
This method handles the core onboarding flow logic:
- Validates the step exists in the configuration
- Tracks step completion for analytics
- Checks and awards any relevant milestones
- Advances to the next step or completes onboarding if on the last step
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 133 def complete_onboarding_step!(step_name, session_id: nil, time_spent: nil) current_index = RailsOnboarding.configuration.step_index(step_name) if current_index.nil? # If step not found, complete onboarding complete_onboarding!(session_id: session_id) return end next_step = RailsOnboarding.configuration.steps[current_index + 1] if next_step update!(onboarding_current_step: next_step[:name]) else complete_onboarding!(session_id: session_id) end # Track step completion and check for milestones only after the step # change above has actually persisted - otherwise a failed save would # still leave behind an analytics event (and possibly an awarded # milestone) for a step completion that never happened. AnalyticsEvent.track_step_completed( user: self, step_name: step_name, step_index: current_index, time_spent: time_spent, session_id: session_id ) check_and_achieve_step_milestones(step_name, session_id: session_id) end |
#current_onboarding_step ⇒ Object Also known as: current_step_info
98 99 100 101 102 103 104 105 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 98 def current_onboarding_step return nil if onboarding_completed? step_name = onboarding_current_step || RailsOnboarding.configuration.steps.first[:name] step = RailsOnboarding.configuration.step_by_name(step_name) step || RailsOnboarding.configuration.steps.first end |
#dismiss_tooltip(tooltip_id, session_id: nil) ⇒ Object
Dismiss a tooltip (marks as shown and tracks as dismissed)
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 523 def dismiss_tooltip(tooltip_id, session_id: nil) # Mark as shown without tracking (to avoid duplicate event) self.feature_tooltips_shown ||= {} self.feature_tooltips_shown[tooltip_id.to_s] = Time.current.iso8601 save! # Track as dismissed, not shown AnalyticsEvent.track_tooltip_interaction( user: self, tooltip_feature: tooltip_id, action: "dismissed", session_id: session_id ) true rescue StandardError => e Rails.logger.error("Failed to dismiss tooltip: #{e.}") false end |
#go_back!(session_id: nil) ⇒ Object
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 399 def go_back!(session_id: nil) return false unless can_go_back? prev_step = previous_onboarding_step return false unless prev_step from_step = onboarding_current_step persist_and_track!(onboarding_current_step: prev_step[:name]) do AnalyticsEvent.track_custom_event( user: self, event_name: "onboarding_step_back", event_data: { from_step: from_step, to_step: prev_step[:name] }, session_id: session_id ) end true end |
#go_to_step!(step_name, session_id: nil) ⇒ Object
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 418 def go_to_step!(step_name, session_id: nil) step = RailsOnboarding.configuration.step_by_name(step_name) return false unless step from_step = onboarding_current_step persist_and_track!(onboarding_current_step: step_name) do AnalyticsEvent.track_custom_event( user: self, event_name: "onboarding_step_jump", event_data: { from_step: from_step, to_step: step_name }, session_id: session_id ) end true end |
#mark_tooltip_shown!(feature, session_id: nil) ⇒ Object Also known as: mark_tooltip_shown
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 215 def mark_tooltip_shown!(feature, session_id: nil) self.feature_tooltips_shown ||= {} # Trim old entries if approaching limit if feature_tooltips_shown.size >= MAX_TOOLTIPS_SHOWN trim_oldest_tooltips end self.feature_tooltips_shown[feature.to_s] = Time.current.iso8601 persist_and_track! do AnalyticsEvent.track_tooltip_interaction( user: self, tooltip_feature: feature, action: "shown", session_id: session_id ) end end |
#milestone_achieved?(milestone_key) ⇒ Boolean
266 267 268 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 266 def milestone_achieved?(milestone_key) achieved_milestones.include?(milestone_key.to_s) end |
#milestone_achieved_at(milestone_key) ⇒ Time?
Get the timestamp when a specific milestone was achieved
This method supports both old (string array) and new (hash array) formats for backwards compatibility. For old format data, it returns last_milestone_at as a fallback since per-milestone timestamps weren't stored.
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 280 def milestone_achieved_at(milestone_key) return nil unless milestone_achieved?(milestone_key) # Support both old array format and new hash format milestones = milestones_achieved || [] milestone = milestones.find do |m| if m.is_a?(Hash) m["key"].to_s == milestone_key.to_s else m.to_s == milestone_key.to_s end end # Return timestamp if available, otherwise return last_milestone_at as fallback if milestone.is_a?(Hash) && milestone["achieved_at"] Time.parse(milestone["achieved_at"]) else last_milestone_at end rescue StandardError last_milestone_at end |
#milestones_available ⇒ Object
338 339 340 341 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 338 def milestones_available return [] unless RailsOnboarding.configuration.enable_milestones RailsOnboarding.configuration.milestones.reject { |m| milestone_achieved?(m[:key]) } end |
#needs_onboarding? ⇒ Boolean
Determines if the user needs to go through onboarding
This method checks the configuration setting and user state to determine if onboarding should be shown. It supports different strategies:
- :new_users - Only users created within the last hour
- :all_users - All users who haven't completed onboarding
- Proc - Custom logic defined in configuration
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 55 def needs_onboarding? return false if onboarding_completed? return false if onboarding_skipped? case RailsOnboarding.configuration.onboarding_required_for when :new_users # Users imported before the gem was installed can have a NULL # created_at. An unknown signup time is not a recent one, so they're # treated as existing users rather than crashing on every request. created_at.present? && created_at > 1.hour.ago when :all_users true when Proc RailsOnboarding.configuration.onboarding_required_for.call(self) else true end end |
#next_onboarding_step ⇒ Object Also known as: next_step
110 111 112 113 114 115 116 117 118 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 110 def next_onboarding_step return nil if onboarding_completed? current_index = RailsOnboarding.configuration.step_index(onboarding_current_step) return RailsOnboarding.configuration.steps.first unless current_index next_step = RailsOnboarding.configuration.steps[current_index + 1] next_step end |
#onboarding_progress ⇒ Integer Also known as: onboarding_progress_percentage
Calculate onboarding progress as a percentage
Returns a value between 0 and 100 representing how far the user has progressed through the onboarding flow. Calculation is based on current step position divided by total steps.
83 84 85 86 87 88 89 90 91 92 93 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 83 def onboarding_progress return 100 if onboarding_completed? return 0 unless onboarding_current_step current_index = RailsOnboarding.configuration.step_index(onboarding_current_step) return 0 if current_index.nil? total_steps = RailsOnboarding.configuration.total_steps ((current_index + 1).to_f / total_steps * 100).round end |
#onboarding_skipped? ⇒ Boolean
Onboarding skipped check
543 544 545 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 543 def onboarding_skipped? onboarding_skipped == true end |
#onboarding_steps ⇒ Array<Hash>
Returns the onboarding steps for this user
If multi-tenant support is enabled and the user has an organization, returns organization-specific steps. Otherwise returns the default steps.
464 465 466 467 468 469 470 471 472 473 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 464 def onboarding_steps # Check for organization-specific steps via MultiTenant if respond_to?(:current_organization_id) && current_organization_id MultiTenant.steps_for(current_organization_id) elsif respond_to?(:organization_id) && organization_id MultiTenant.steps_for(organization_id) else RailsOnboarding.configuration.steps end end |
#previous_onboarding_step ⇒ Object Also known as: previous_step
393 394 395 396 397 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 393 def previous_onboarding_step return nil unless can_go_back? current_index = RailsOnboarding.configuration.step_index(onboarding_current_step) RailsOnboarding.configuration.steps[current_index - 1] end |
#recent_milestones(limit: 5) ⇒ Object
343 344 345 346 347 348 349 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 343 def recent_milestones(limit: 5) return [] unless RailsOnboarding.configuration.enable_milestones achieved_milestones.last(limit).map do |key| RailsOnboarding.configuration.milestone_by_key(key) end.compact end |
#reset_onboarding! ⇒ Object Also known as: reset_onboarding
196 197 198 199 200 201 202 203 204 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 196 def reset_onboarding! update!( onboarding_completed: false, onboarding_completed_at: nil, onboarding_skipped: false, onboarding_current_step: nil, feature_tooltips_shown: {} ) end |
#reset_tooltips! ⇒ Object
Reset all tooltips (mark all as not shown)
236 237 238 239 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 236 def reset_tooltips! self.feature_tooltips_shown = {} save! end |
#restart_onboarding!(session_id: nil) ⇒ Object
435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 435 def restart_onboarding!(session_id: nil) previous_step = onboarding_current_step was_completed = onboarding_completed reset_onboarding! start_onboarding!(session_id: session_id) AnalyticsEvent.track_custom_event( user: self, event_name: "onboarding_restarted", event_data: { previous_step: previous_step, was_completed: was_completed }, session_id: session_id ) end |
#show_feature_tooltip?(feature) ⇒ Boolean
Feature tooltips
207 208 209 210 211 212 213 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 207 def show_feature_tooltip?(feature) return false unless RailsOnboarding.configuration.enable_tooltips return false unless RailsOnboarding.configuration.feature_tooltips[feature.to_s] shown_tooltips = (feature_tooltips_shown || {}) !shown_tooltips[feature.to_s] end |
#skip_onboarding!(session_id: nil) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 180 def skip_onboarding!(session_id: nil) persist_and_track!( onboarding_completed: true, onboarding_completed_at: Time.current, onboarding_skipped: true, onboarding_current_step: nil ) do AnalyticsEvent.track_onboarding_completed( user: self, completion_time: nil, was_skipped: true, session_id: session_id ) end end |
#skip_onboarding_step!(step_name, session_id: nil) ⇒ Object Also known as: skip_step
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 365 def skip_onboarding_step!(step_name, session_id: nil) current_index = RailsOnboarding.configuration.step_index(step_name) return unless current_index next_step = RailsOnboarding.configuration.steps[current_index + 1] if next_step update!(onboarding_current_step: next_step[:name]) else complete_onboarding!(session_id: session_id) end AnalyticsEvent.track_step_skipped( user: self, step_name: step_name, step_index: current_index, session_id: session_id ) end |
#start_onboarding!(session_id: nil) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 351 def start_onboarding!(session_id: nil) return if onboarding_completed? # Set initial step if not already set if onboarding_current_step.nil? && RailsOnboarding.configuration.steps.any? update!(onboarding_current_step: RailsOnboarding.configuration.steps.first[:name]) end AnalyticsEvent.track_onboarding_started( user: self, session_id: session_id ) end |
#step_completed?(step_name) ⇒ Boolean
Check if a specific step has been completed
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 476 def step_completed?(step_name) return false unless step_name step_index = RailsOnboarding.configuration.step_index(step_name) return false if step_index.nil? # If onboarding is completed, all steps are completed return true if onboarding_completed? # Check if current step is past the requested step current_index = RailsOnboarding.configuration.step_index(onboarding_current_step) return false if current_index.nil? current_index > step_index end |
#tooltip_shown?(tooltip_id) ⇒ Boolean
Check if a specific tooltip has been shown to this user
This method directly checks the user's feature_tooltips_shown hash to determine if a tooltip was marked as shown, regardless of whether the tooltip is configured in the application settings.
517 518 519 520 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 517 def tooltip_shown?(tooltip_id) shown_tooltips = feature_tooltips_shown || {} shown_tooltips.key?(tooltip_id.to_s) end |
#total_milestone_points ⇒ Object
334 335 336 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 334 def total_milestone_points milestone_points || 0 end |
#track_tooltip_interaction!(feature, action, session_id: nil) ⇒ Object
241 242 243 244 245 246 247 248 |
# File 'app/models/concerns/rails_onboarding/onboardable.rb', line 241 def track_tooltip_interaction!(feature, action, session_id: nil) AnalyticsEvent.track_tooltip_interaction( user: self, tooltip_feature: feature, action: action, session_id: session_id ) end |