Why?
Students of the #pivorak Ruby Summer Courses 2021 have been working on their practical part project "HoldMyDog" (a dog sitting service) and there was a registration form for users. Since we have split the information about user between User
and Profile
models we need to configure devise to save data from one form to both of them.
What we had?
Database structure
Users migration
db/migrations/20210810072523_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.1] def change create_table :users do |t| ## Database authenticatable t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at t.string :role t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true end end
User model
app/models/user.rb
class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_one :profile end
Profiles migration
db/migrations/20210810073644_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration[6.1] def change create_table :profiles do |t| t.string :first_name t.string :last_name t.string :phone t.text :description t.references :user, null: false, foreign_key: true t.timestamps end end end
Profile model
app/models/profile.rb
class Profile < ApplicationRecord belongs_to :user validates :first_name, presence: true validates :last_name, presence: true validates :phone, presence: true validates :description, length: { maximum: 300 } end
What to do?
First we need to generate devise views and controllers for registration and then modify them accordingly to allow form params for profile to pass.
Devise generators
We can use devise generators:
-
rails generate devise:views
- to generate all devise views -
rails generate devise:controllers
- to generate all devise controllers
1. Generating devise views for registration
bundle exec rails g devise:controllers users -c registrations
This will generate only registrations controller for us.
2. Editing routes.rb
to use our customised controller
config/routes.rb
Rails.application.routes.draw do devise_for :users, controllers: { registrations: 'users/registrations' } end
This part is important because without explicit routing you will end up using default devise controller.
3. Generating views
bundle exec rails g devise:views users
This will generate all devise views in scope of users.
4. Editing form view
app/views/users/registrations/new.html.erb
<div class="container"> <h2 class="form-header">Sign up now!</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "users/shared/error_messages", resource: resource %> <%= f.fields_for :profile do |pf| %> <div> <%= pf.text_field :first_name, placeholder: 'First name *' %> </div> <div> <%= pf.text_field :last_name, placeholder: 'Last name *' %> </div> <div> <%= pf.text_field :phone, placeholder: 'Phone' %> </div> <% end %> <div> <%= f.email_field :email, autocomplete: "email", placeholder: 'Email *' %> </div> <div> <%= f.password_field :password, autocomplete: "new-password", placeholder: 'Password *' %> </div> <div> <%= f.password_field :password_confirmation, autocomplete: "new-password", placeholder: 'Repeat password *' %> </div> <%= f.fields_for :profile do |profile_form| %> <%= profile_form.text_area :description, cols: 40, rows: 3, placeholder: 'Tell us about yourself ;)' %> <% end %> <div>* How would you like to use the service?</div> <div> <%= f.radio_button :role, 'sitter', css: 'form-check-input' %> <%= label :role_sitter, 'I want to hold someone`s pet', css: 'form-check-label' %> </div> <div> <%= f.radio_button :role, 'owner', css: 'form-check-input' %> <%= label :role_owner, 'I want to give my pet to sitter', css: 'form-check-label' %> </div> <%= f.submit "Sign up", class: "btn btn-light sign-up-btn mt-4" %> <% end %> </div>
You may notice that the form looks different and we don't have all the fields that we described in our view, that's because we need to modify the new
action for registration and build profile object for form before rendering.
5. Editing new
action: building profile
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController def new build_resource({}) resource.build_profile respond_with resource end end
After we built a profile in new action, after reload form will look like this:
It may seem that we're done, but we need to save the data from the form to the database.
6. Permitting profile saving
app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_sign_up_params, only: [:create] def new build_resource({}) resource.build_profile respond_with resource end protected def sign_up_params devise_parameter_sanitizer.sanitize(:sign_up) { |user| user.permit(permitted_attributes) } end def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up, keys: permitted_attributes) end def permitted_attributes [ :email, :password, :password_confirmation, :remember_me, :role, profile_attributes: %i[first_name last_name phone description] ] end end
This way we allow a user to pass params from form to database, but there is one more step we need to do - since we are passing all params together and we haven't modified the create
action for Users::RegistrationsController
we need to allow User
model to accept attributes for Profile
.
app/models/user.rb
class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_one :profile accepts_nested_attributes_for :profile end
Top comments (0)