Integrating Wicked and Devise

July 10, 2018

Like Merlin's adventures in "The Sword in The Stone", Rails often seems like a magical place. Take devise, for instance. When you bundle install the devise gem, everything works flawlessly. You let devise work its wonders, and users are authenticated and can log in without encountering numerous issues. You also don't have to spend hours writing your authorization by hand (yes!- thank you to all of the devise contributors). And then, to make the user's life easier, you decide that you need to split up your long registration form into multiple pages. Enter the wicked gem (https://github.com/schneems/wicked). You add the gem to your gemfile, bundle install, rails g controller a registration_steps or after_sign_up controller (whatever name you would like for your controller), add the resources to your routes file, and add 'include Wicked::Wizard' to the top of your new controller file. class RegistrationStepsController < ApplicationController include Wicked::Wizard end Wicked comes with a steps method that is similar to ActiveRecord::Enum, but instead of coupling values to integers, it takes page names as its arguments. If you were opening an e-commerce site, your steps might include: steps :company_profile, :add_listings, :get_paid (you also need to create these .html.erb files in your views/registration_steps directory). The wicked wizard will call the steps in order, using the show action in our new registration_steps controller. def show render_wizard end *** Calling render_wizard at the end of your show action will render the appropriate view = views/registration_steps/company_profile.html.erb ********************************************* This is where devise comes back into the picture. After your new user signs up, you would like them to continue registering on the company_profile page. To do this, you need to redirect the devise default action, which normally directs the user to the root_url. On the devise gem page, there is a section about configuring controllers. To configure a modified devise controller, you need to rails g devise:controllers [scope]. If the scope is users, it will create a users folder in the controller directory = app/controllers/users/ (https://github.com/plataformatec/devise). That 'users' folder will include all of the possible devise controllers, and you can alter the desired controller from there. 1. In the terminal, rails g devise:controllers users The top path of your controller will look like this: class Users::RegistrationsController < Devise::RegistrationsController 2. In the users/registrations controller, uncomment def new super end def create super end def update super end 3. At the bottom of your registrations controller, uncomment these two definitions and replace super(resource), with registration_steps_path. protected def after_sign_up_path_for(resource) registration_steps_path end def after_update_path_for(resource) registration_steps_path end end *** By reading through the devise documentation, you can see that the after_sign_up_path_for(resource) and the after_update_path_for(resource) are the paths that devise calls once the user's information is authenticated. So, we can redirect these paths using the registration_steps_path (this is the index path for the registration_steps_controller- and it will redirect us to the next page, designated in our wicked steps). 4. Devise then asks that you change your routes.rb file. devise_for :users, controllers: {:registrations => "users/registrations"} *** This bit of code tells devise to use the user/registrations controller for the registration (new + edit) actions. 5. Also, remember to copy/paste your new and edit views into the views/users/registrations folder, as devise will no longer look for it in its default location. There! That's all we have to do to update our devise default routes. Now let's go make sure that the wicked gem works properly. ********************************************* In order for the edited info to save to the user's profile, you need to add @user = current_user to the show action in your registration_steps controller. def show @user = current_user render_wizard end Then add the desired form fields to your company_profile page. You can have the user continue to the next page or skip this step by replacing the 'submit' code in the class="actions" div at the bottom of your form. <%= f.submit "Save and Next" %> # will move to the next page <%= link_to 'Skip this Page', next_wizard_path %> # will skip this step or if you would like to create a manual skip: <%= link_to 'Skip this Page', wizard_path(:add_listings) %> *** You will need to add these links to the bottom of your add_listings.html.erb and your get_paid.html.erb as well. If the user clicks on the "Save and Next" button, wicked will call upon the update action in your controller, so let's create that next in our registration_steps controller. def update @user = current_user @user.attributes(user_params) render_wizard @user end This action will update the user attributes, and by passing the current_user to render_wizard, it will not render the next page unless the user is saved. Lastly, add a private section to the bottom of your controller to define your user_params. private def user_params params.require(:user).permit(:first_name, :last_name, :email, :password...) end end Make sure that you have added the desired form fields to your new .html.erb pages. And kablam! It should work!!! The user should be able to sign up, continue onto the company_profile page, then to the add_listings page, and finish signing up on the get_paid page. The wicked gem will complete the cycle by routing the fully registered user to the root_url. Congrats. You are brilliant. *** A huge thank you to Richard Schneeman, Ryan Bates and all other contributors. xox http://railscasts.com/episodes/346-wizard-forms-with-wicked?view=asciicast https://github.com/plataformatec/devise https://github.com/schneems/wicked https://github.com/schneems/wicked/wiki/Building-Partial-Objects-Step-by-Step http://rubylearning.com/satishtalim/ruby_overriding_methods.html