Module: RailsOnboarding::ControllerHelpers

Extended by:
ActiveSupport::Concern
Defined in:
lib/rails_onboarding/controller_helpers.rb

Instance Method Summary collapse

Instance Method Details

#advance_onboarding!(step_name) ⇒ Boolean

Complete the current onboarding step from a host-app controller.

Call this from the action that performs the step's real work (e.g. ProfilesController#create after a successful save), then redirect to onboarding_path to advance the user:

if advance_onboarding!(:profile)
redirect_to onboarding_path
else
redirect_to @profile
end

Deliberately a no-op unless the named step is the user's current step - so when the same action runs outside onboarding (the user edits their profile again next week), the controller behaves normally.

Parameters:

  • step_name (Symbol, String)

    the step this action fulfills

Returns:

  • (Boolean)

    true if the step was completed and the user advanced



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rails_onboarding/controller_helpers.rb', line 85

def advance_onboarding!(step_name)
  return false unless user_signed_in?
  return false unless current_user.respond_to?(:needs_onboarding?)
  return false unless current_user.needs_onboarding?

  step = current_user.current_onboarding_step
  return false unless step && step[:name].to_sym == step_name.to_sym

  current_user.complete_onboarding_step!(step[:name])
  true
end

#needs_onboarding?Boolean

Returns:

  • (Boolean)


29
30
31
32
33
34
35
36
# File 'lib/rails_onboarding/controller_helpers.rb', line 29

def needs_onboarding?
  return false unless user_signed_in?
  return false if on_onboarding_page?
  return false if skip_onboarding_request?
  return false if self.class.skip_onboarding_for_action?(action_name)

  current_user.needs_onboarding?
end

#on_onboarding_page?Boolean

Returns:

  • (Boolean)


38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/rails_onboarding/controller_helpers.rb', line 38

def on_onboarding_page?
  # Deliberately reads the engine proxy, not the onboarding_path method -
  # in engine controllers the engine's own url_helper shadows ours, so
  # relying on method resolution here would be ambiguous. Chomping makes
  # the match segment-aware: the engine root (with or without its
  # trailing slash) and anything under it count, but sibling host paths
  # that merely share the prefix (/onboarding_help when mounted at
  # /onboarding) do not.
  root = rails_onboarding.onboarding_path.chomp("/")
  return true if request.path == root || request.path.start_with?("#{root}/")

  on_current_step_page?
end

#onboarding_continue_available?Boolean

Would following the banner's "Continue" link actually move the user?

Anywhere else in the app, yes: /onboarding routes them to the current step. Standing on the step's own page it depends - /onboarding re-checks :complete_if and only advances once it passes. Until then it resolves the step's path and redirects straight back to the page the user is already on, so the link renders but the click does nothing visible. The banner asks this before offering it.

The comparison is deliberately an exact path match rather than on_current_step_page?, which also treats any other action on the same controller as "on the step page". That breadth is right for the loop guard - it keeps the guard off the PATCH that completes the step - but wrong here: /profiles/123 is a different page from a step pointing at /profiles/123/edit, and Continue really does move the user between them.

Returns:

  • (Boolean)


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rails_onboarding/controller_helpers.rb', line 138

def onboarding_continue_available?
  return false unless user_signed_in?
  return false unless current_user.respond_to?(:current_onboarding_step)

  step = current_user.current_onboarding_step
  # No :path means the step renders a gem template at /onboarding, which is
  # always somewhere other than the host page showing this banner.
  return true unless step.is_a?(Hash) && step[:path]

  # The resolved route may carry a query string or be a full URL;
  # request.path never does.
  resolved_path = URI.parse(resolve_onboarding_step_path(step[:path]).to_s).path
  return true unless request.path == resolved_path

  onboarding_step_criteria_met?(step)
rescue StandardError => e
  # An unresolvable :path means the engine can't redirect to it either - it
  # falls back to rendering a gem template, so Continue still goes
  # somewhere. Offer it.
  Rails.logger.warn("RailsOnboarding: could not resolve step path for the banner link: #{e.class} - #{e.message}")
  true
end

#onboarding_pathObject

The onboarding flow lives at the engine's mount root, so the raw engine helper returns it with a trailing slash ("/onboarding/") like any mounted engine root. Serve host apps the canonical slash-less form for links and redirects.



62
63
64
65
# File 'lib/rails_onboarding/controller_helpers.rb', line 62

def onboarding_path
  path = rails_onboarding.onboarding_path.chomp("/")
  path.empty? ? "/" : path
end

#onboarding_step_criteria_met?(step) ⇒ Boolean

Has this step's :complete_if been satisfied?

A buggy :complete_if must not brick onboarding - if it raises, treat the step as not yet complete instead of letting the engine's shared StandardError handler redirect back to /onboarding (which would re-raise on arrival, redirecting again in an endless browser loop).

Returns:

  • (Boolean)


114
115
116
117
118
119
120
121
# File 'lib/rails_onboarding/controller_helpers.rb', line 114

def onboarding_step_criteria_met?(step)
  return false unless step.is_a?(Hash) && step[:complete_if].is_a?(Proc)

  step[:complete_if].call(current_user)
rescue StandardError => e
  Rails.logger.error("RailsOnboarding: complete_if for step '#{step[:name]}' raised #{e.class} - #{e.message}")
  false
end

#resolve_onboarding_step_path(path) ⇒ Object

Resolve a step's :path option against the host application's routes. Symbols/Strings are sent to the main_app route proxy; Procs are instance_exec'd in the controller context, so a zero-arg lambda can use main_app, current_user, params, etc.:

path: :new_profile_path
path: -> { main_app.new_post_path(from: "onboarding") }


104
105
106
# File 'lib/rails_onboarding/controller_helpers.rb', line 104

def resolve_onboarding_step_path(path)
  path.is_a?(Proc) ? instance_exec(&path) : main_app.public_send(path)
end

#skip_onboarding_request?Boolean

Returns:

  • (Boolean)


52
53
54
55
56
# File 'lib/rails_onboarding/controller_helpers.rb', line 52

def skip_onboarding_request?
  request.xhr? ||
    request.format.json? ||
    request.path.start_with?("/api")
end