How can I require user to create profile after he has signed up with Devise?
I am thinking about using Wicked Wizard gem, but it is possible for user to skip all steps and just access the website.
User must setup their profile first, only then they should have access to website.
I am thinking about this implementation:
SomeController.rb
before_action :authenticate_user!
before_action :check_if_profile_created?
private
def check_if_profile_created?
current_user.profile
end
end
But this solution will do this check on all requests made by user, which I think is not good. Is there any other ways to do this? Or how can it be implemented with Wicked Wizard gem? I haven't found how to make it redirect from all pages to current step in wizard.
I have a solution that I just implemented two days ago. There is no much documentation on the Internet about this matter. There might be many ways to do this. This is just the way I did it. So, without more delay, let's get to it.
The Concept:
The whole idea, at least how I conceive it, is to "force" the recently signed up user, after he/she also confirms its email, to additionally confirm his profile. As simple as that. That's because in my case many of the profile fields are obviously not mandatory, except the first_name, last_name, email and obviously the password. The rest (job_title, company, affiliation, etc, etc, etc), are not mandatory. But the thing is that at the same time, I would like to enforce that every single User has a profile as complete as possible. This is absolutely necessary for purposes that are not necessary to mention here.
Note:
This is based in that you already have a SettingsController, that allows to any logged user to update his own profile and to update his password as well (in a separated action method).
Step # 1: Add the related boolean field
Modify your users table by adding a boolean field.
class AddProfileWasConfirmedToUsersTable < ActiveRecord::Migration[6.1]
def change
add_column :users, :profile_was_confirmed, :boolean, null: false, default: false
end
end
The boolean field profile_was_confirmed will ensure that the User's profile has been confirmed. It's false by default, right from creation, and it will be updated once and only once to true, when the user confirms his profile. The system won't enforce a 100% complete profile after that, because in order to do such thing it would have to be mandatory on the User model, and that would mess up with the initial sign up process (using the usual devise).
Step # 2: Add the necessary routes.
This one is for the view where we will include a form (inn our case reuse the settings profile form):
get :confirm_profile, to: 'settings#confirm_profile'
And this one is for the updating of the incoming fields from the previously mentioned form:
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
So at the end you would have something like this (just an example):
get :confirm_profile, to: 'settings#confirm_profile'
resource :settings, only: [:edit, :update] do
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
patch :change_password, to: "settings#change_password"
resource :site_terms, only: [:edit, :update]
end
Note:
Remember to place the additional get route before the resources declaration. Otherwise the framework will confuse your route with the show action method.
Step # 3: Modify your ApplicationsController
before_action :check_profile_was_confirmed
.
.
.
def check_profile_was_confirmed
return unless current_user.present?
unless current_user.profile_was_confirmed? || devise_controller?
redirect_to confirm_profile_path
end
end
Step # 3: Modify your SettingsController
skip_before_action :check_profile_was_confirmed
def confirm_profile
if current_user.profile_was_confirmed?
flash["notice"] = "Your profile has been already confirmed"
redirect_to dashboard_path
end
end
def update_confirmed_profile
if #user.update(update_params) && confirm_params_ready?
#user.update({ profile_was_confirmed: true })
flash["notice"] = "Profile confirmed"
redirect_to dashboard_path
else
flash["alert"] = "All the fields must be filled"
redirect_to confirm_profile_path
end
end
private
def confirm_params_ready?
params_base = params.require(:user).permit(:first_name, :last_name, :email, :company, :job_title)
params_base.values.all?(&:present?)
end
Step 4: Create your view with the form or reuse the same one you are using for updating the profile.
File:
app/views/settings/confirm_profile.html.erb
<% content_for(:title) { " | Profile Confirmation" } %>
<% content_for(:view_header) do %>
<%= render 'shared/view_header', view_title: 'Profile Confirmation' %>
<% end %>
<%#= render partial: 'nav' %>
<div class="w-prose mx-auto space-y-8">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Fill all the fields</h4>
</div>
<div class="card-body">
<%= render partial: "settings/profile_form", locals: { form_url: update_confirmed_profile_settings_path } %>
</div>
</div>
</div>
Step 5: Update your User model's factory
Step 6: Update all your integration tests.
there is no way you can do it without a validation on every request, because you always have to be sure they fill the profile, no matter on what page you enter.
maybe you can add that validation to the application controller with a before action so you don't have to do it in every controller. and just validate if user is logged in and user.profile...
What do you mean ugly? If it's a matter of "the code will be duplicated everywhere", then you can create a controller that inherits from ActionController, and add before_action to that controller specifying that the profile needs to be filled up. Or you can create a concern which you can then plop in
class SomethingController < ApplicationController
before_action :ensure_profile_filled
def ensure_profile_filled
redirect_to 'wherever'
end
end
class ChildController < SomethingController
end
There are also route constraints but I think you want to do this on the controller layer.
Related
I want to send an email to customers that allows them to edit some of the information for their purchase, so my basic set-up is sending them a link to /follow_up_form/purchase.id.
This clearly won't work because we don't want anyone just typing in that URL and changing the user's information, but our site doesn't have any login necessary to make purchases.
Is there a way to autogenerate a secret URL, or pass through some sort of authenticity token? This feels like it should be simple but I don't have any good ideas.
Thanks!
You may use rails 5 has_secure_token and embed it into an URL as a parameter like /follow_up_form/purchase.id?tok='some_toke' And check this parameter when returning the form for edition. This will indeed make stuff more secure.
I would add a token column to the purchase, and set it after creation:
require 'secure_random'
class Purchase < ActiveRecord::Base
before_create :regenerate_token
def regenerate_token
self.token = SecureRandom.urlsafe_base64(24)
end
end
You can then setup follow ups just like you would any other nested resource:
resources :purchases do
resources :follow_ups, only: [:new, :create]
end
To create a link to the form where the user gives feedback use:
<%= link_to("Tell us what you think", new_purchase_follow_up_url(#purchase, token: #purchase.token)) %>
This adds an query string parameter to the URL containing the access token.
To authenticate in the controller we check if the access token matches what we have stored for the purchase:
class FollowUpsController < ApplicationConrtroller
before_action :set_purchase!
before_action :check_token!
# ...
private
def set_purchase!
#purchase = Purchase.find(params[:purchase_id])
end
def check_token!
unless params[:access_token] == #purchase.token
redirect_to root_path, error: 'You are not authorized' and return false
end
end
end
To pass the token between the new and create action add it to the form as a hidden input:
<%= form_for(#follow_up) do |f| %>
<%= hidden_field_tag :access_token, #purchase.token %>
<% end %>
I'm building a rails app that has a lot of things that change based on whether or not the user has completed certain quizzes. Everything works fine when a user is logged in, but I just tried logging out and I got a NoMethodErroron this line of code:
<% if current_user.per_quiz.nil? %>
Basically, if the user has completed per_quiz they go to one page (the edit view), if they haven't they go to a different page (the new view). The only thing that changed was the fact that I logged out, so I can only assume that is what caused the error. Do I need to add some kind of if statement to account for a state in which no user is logged in? How should I fix this error in accordance with Ruby best practices?
You just need to check to see if current_user is set before checking per_quiz. You can handle checking for the login state easily in a view by querying current_user.nil?:
<% if !current_user %>
<p> You must be logged in to do anything useful. Please go login</p>
<% elsif !current_user.per_quiz %>
<p>Cool quiz stuff goes here...</p>
<% else %>
What you probably really want is to have a logged out user go elsewhere, such as the home page or signin page. To do that, you need to do a couple of simple things to your controller. I'm going to assume that the controller is called 'QuizzesController' since you hadn't included your controller code in the question.
Here's how to do it:
class QuizzesController < ApplicationController
# Other devise authentication logic goes here...
before_action :authorize_user!
# Your actions go here...
private
def authorize_user!
if !current_user
redirect_to '/', notice: "You must be logged in to access to this page"
end
end
end
What this does is install a "before_action" handler that will check that the user is logged in before letting them do anything in this controller. If they are not logged in, you can redirect them wherever you wish.
Note that sometimes, only certain actions need this kind of treatment. In that case, you can use an :only or :except option to specify which action(s) are or are not handled. It looks like this:
before_action :authorize_user!, only: [ :new, :edit, :create ]
or like this:
before_action :authorize_user!, except: :list
This will give you greater flexibility in managing the authorization part of the equation, where devise handles the authentication part.
That's because current_user is nil when no user logged in. And if you will cal some method on nil it will throw NoMethodError.
I assume that have code you provided in the view. So, you can check if user is logged in.
<% if user_signed_in? %>
# do smth with user quizzes
<% else %>
# do something else
<% end %>
But the best way is to use before filter in the controller and not allow to anonymous to see that page.
Here is the example application with Rails and devise. https://github.com/RailsApps/rails-devise
Hope that helps.
I'm not sure if this is the "correct" Ruby way to do this, but the way I eventually found was to change it to an if/elsif/else statement:
<% if current_user.nil? %>
...
<% elsif current_user.bal_quiz.nil? %>
...
<% else %>
...
<% end %>
Probably should have figured this out before I posted the question, but I was (and still am) interested to see if there's a better or "more Ruby" way of doing this.
I would like to know how I would take the user's input and use it to redirect them to a new URL. For example if the user entered "556859" into the input field and clicked "Go" it would take them to www.website.com/556859.
I'm using ruby on rails but am not sure how to achieve my goal. Any input would be appreciated.
If you need examples for the view and the rest as well, then ask. But this controller method should be enough to point you in the right direction:
def redirect_action
redirect_to "/#{params[:redirect_path]}"
end
You can send user input to controller method
and in that method you can use redirect_to helper method
For example
in view create form with action="controller/redirect"
and in controller create method redirect
and with params[] use your input
Best regards
You'd do something like this:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def redirect
redirect_to root_path + param
end
end
#config/routes.rb
post "", to: "application#redirect", as: :redirect
#app/views/application/index.html.erb
<%= form_tag redirect_path do %>
<%= f.input :param %>
<%= f.submit "Go" %>
<% end %>
--
The next question becomes "why?"
Rails' routing infrastructure is such that it should give you the ability to access individual views for what you're trying to accomplish.
For example, if you're trying to show users in that redirect, you can do:
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
class UsersController < ActionController::Base
def show
#user = User.find params[:id]
end
end
This will give you the ability to access http://yoururl.com/users/12312321
Alternatively, you could also (if you had nothing else on that part of the app), do the following:
#config/routes.rb
resources :users, path: "" #-> http://yoururl.com/12313123
If you know what type of data you're trying to show, it should give us more to work with in regards how you'd construct routing structure for it.
I'm using Rails 4 and have a Policy model with a field policy_number.
I'd like to create a (search-like) form where you input a policy_number and it redirects you to that Policy's show page. I'm not sure how to go about this, should the form's action be policy_path or something?
Thanks!
The biggest problem here is that the user is inputting the policy number in the search form, so you don't have access to it at the time the form is rendered. Without using JavaScript, you won't be able to go directly to the policy by policy number entered.
Here's a possible starting point, though. Create a PolicySearchController with an index method, add a route for it, and create a simple form.
app/controllers/policy_search_controller.rb
class PolicySearchController < ApplicationController
def index
policy = Policy.where(policy_number: params[:policy_number]).first
if policy.present?
redirect_to policy
else
redirect_to :policies, alert: "No matching policy found."
end
end
end
config/routes.rb
resources :policy_search, only: :index
app/views/policies/index.html.erb
<%= form_tag policy_search_index_path, method: :get do -%>
<%= text_field_tag :policy_number -%>
<% end -%>
Now you can iterate on this to add JavaScript, fuzzy matching, etc. if desired.
Say I have a site like this (generic Q&A site) in Rails and I wanted this "ask" page w/ a text box to be the first page a user sees, even if he's not logged in. He enters a question, and on the 'new' method I check that he's not logged in, and bounced him to /session/new, where he can either log in or create a new account. Question is, how do I (and what is the best way to) preserve that question that he initially asked all through this process?
I'm understanding the flow of action described in the question to be
user is presented with a form
user is redirected to log in page on submit
user is redirected back to form on successful log in
repopulate form on load (Question asks how to do this step)
user finally submits their form.
With steps 2-4 omitted if the user is logged in.
I'm sorry, but I see your question more as a symptom of an underlying UI issue than a rails question.
If only logged in users can post questions, then why display the text box?
If a user is going to have log in any way, why not get that out of the way first. An even better solution is to integrate the log in and form.
Something like this in the view:
<% form_for :question do |form| %>
<% unless logged_in? %>
<% fields_for :session do |session_form|%>
<%= session_form.label :login %>
<%= session_form.text_field :login %>
<%= session_form.label :password %>
<%= session_form.password_field :password %>
<%end%>
<%end%>
<%= form.text_area :question %>
<%end%>
And in the controller
def new
...
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
end
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
if logged_in? && #question.save
.... process successful entry
else
... process unsuccessful entry
end
end
Edit: Mohamad's raises the question of reusing this pattern across multiple controllers and forms. So the answer was updated to address reuse of this pattern.
To simplify this for reuse, you could put this block in a helper function that is referenced in the before_filter for actions that require it.
def login
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
end
end
as in:
before_filter :login => :only [:new , :edit, :update, :delete]
On the view side, it shouldn't be too hard to construct a new variant of form_for that embeds the session parameters. Maybe form_for_with_session?
As for handling an unsuccessful response, I would suggest helper function that takes a block of code. Sorry I don't have time to write out or test one for you.
You keep it in the session. So after logging in, when the user goes back to asking his question, you see there's already something in session.
And you can directly display it.
def create
if current_user # Implement this method in your auth framework
#question = Question.new(params[:question] || session.delete[:question])
# (the usual stuff you'd do to save)
else
session[:question] = params[:question]
redirect_to :controller => :sessions, :action => "new"
end
end
Then, after your user creation and authentication stuff is all done in your login action, just make sure you POST back to this create action if session[:question] is defined.