Olivier Girardot
Olivier Girardot

Reputation: 409

Using devise in rails, trying to add a second step after Signup to update user with name, bio and photo

I am new to coding and to stackoverflow, so I hope the question I am about to ask is no too stupid What I am trying to do is in the title: After a user signs in(email, password), they are redirected to a new form where they can fill in their name, bio and pic. Everything goes as planned until they hit submit and bump into 2 issues:

1/ The new data is not taken into account 2/ The redirection is wrong

After sign_up the user is redirected to "profiles/:id/edit". After submit form, it redirects to "user/:id", which doesn't exist.

Shouldn't it redirect to profiles#update in the controller?

Here are my different codes:

1/ routes

Rails.application.routes.draw do
  devise_for :users
  root to: "pages#home"
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  resources :jam_sessions, only: [:show, :index, :new, :create] do
    resources :spots, only: [:show, :create, :new]
    resources :messages, only: [:create]
  end
  resources :participations, only: [:create, :update]

  resources :profiles, only: [:show, :edit, :update]
  resources :dashboards, only: [:index, :show]


  resources :reviews, only: [:create]


  mount ActionCable.server => "/cable"

  get 'users/:id', :to => 'profiles#edit', :as => :user

  patch 'profiles#edit', :to => 'profiles#update'
end 

2/ profiles_controller

class ProfilesController < ApplicationController
  def show

    @user = User.find(params[:id])
    @reviews = Review.where(receiver_id: @user.id)
    @instruments = UserInstrument.where(user_id: @user.id)
    @participations = Participation.where(user_id: @user.id)
    date = Time.now

    if @participations != nil
      @jam_sessions = []

      @participations. each do |participation|
        spot = Spot.find_by(id: participation.spot_id)
        @jam_sessions << JamSession.find_by(id: spot.jam_session_id)

      end
      @future_jam_sessions = []
      @past_jam_sessions = []
      @jam_sessions.each do |jam_session|
        if jam_session.starts_at > date
          @future_jam_sessions << jam_session
        else
          @past_jam_sessions << jam_session
        end
      end
    else
      puts "no jam"
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    @user.update(user_params)
    raise
    if @user.save
      redirect_to profiles_path(@user)
    else
      render "new"
    end
  end
  private

    def user_params
      params.require(:user).permit(:first_name, :last_name, :bio)
    end
end

3/ application_controller

class ApplicationController < ActionController::Base
  before_action :authenticate_user!, except: [:home, :index, :show]

  before_action :configure_permitted_parameters, if: :devise_controller?
  def configure_permitted_parameters
    # For additional fields in app/views/devise/registrations/new.html.erb
    devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :bio])
    # For additional in app/views/devise/registrations/edit.html.erb
    devise_parameter_sanitizer.permit(:account_update, keys: [:username])
  end

  def default_url_options
    { host: ENV["DOMAIN"] || "localhost:3000" }
  end

end

4/ user model

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :user_instruments, dependent: :destroy
  has_many :instruments, through: :user_instruments
  has_many :messages, dependent: :destroy
  has_one_attached :photo, dependent: :destroy
  has_many :reviews_written, class_name: "Review", foreign_key: :writer_id
  has_many :reviews_received, class_name: "Review", foreign_key: :receiver_id
  has_many :jam_sessions, dependent: :destroy

  def profile_picture
    if photo.attached?
      photo.key
    else
      "avatar-unknown.png"
    end
  end

  def full_name
    "#{first_name} #{last_name}"
  end
end

5/ edit view

<%= simple_form_for(@user) do |f| %>
  <%= f.input :first_name %>
  <%= f.input :last_name %>
  <%= f.input :bio %>
  <%= f.input :photo, as: :file %>
    <%= f.submit 'Update profile' %>
<% end %>

Thank you! Olivier

Upvotes: 1

Views: 472

Answers (1)

Clara
Clara

Reputation: 2715

it's Clara here :)

So I see some issues with your code, but it also seems like some stuff is already working, which is good. So it is creating a new user and after that redirecting to the right form where you edit the user, am I right? (Just asking because I don't see the redirection from the devise registration form to the user#edit).

In your applications controller, you do have this line, which is unecessary: devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name, :last_name, :bio]) You would only need to permit additional parameters for the devise form, if you would add them into the devise registrations form directly. But here you are adding a new form.

So now, how can we handle the new form? The problem in your code is very subtle. You are changing a user object, but you chose to use a profiles controller including the profiles routes (But you also have some user routes). The thing is, in the edit user form the following line decides where the HTTP requests goes as soon as someone hits submit.

<%= simple_form_for(@user) do |f| %>

Open the browser and look in the inspect mode which html is generated, it will be something like this (it will have more stuff but this is the interesting part for us)

<form action="/users" accept-charset="UTF-8" method="patch">

This means, when someone hits submit a HTTP PATCH request is made to /users Now, the way your routes are build currently, they don't accommodate for this.

So you can add a new routes

resources :users, only [:update]

An into the users_controller#update you can put the code you have in profiles_controller#update at the moment.

Now this would leave us with a weird situation where the edit part is in the profiles controller and the update one is in the users controller. You can sove this in two ways

  1. Move everything to the users controller. So that you have edit und update routes for the users controller, edit and update actions (has the same as the profile_controller#edit and #update in your code) and the view. Don't forget to delete the stuff in profiles or in two weeks it will be very confusing).
  2. Tell the simple form, not to go to the users_controller but the profiles_controller and you can keep the set up as you have it. You can do it by adding this line
 simple_form_for :user, url: user_path, method: "PATCH" ```

Some more remarks now:

In routes.rb the last line is not correct. And, instead of defining the normal CRUD actions by hand, you should use this syntax:

...
resources :users, only: [:edit, :update]
...

And in the profiles controller in the update action and there you need to change the render. (No matter if you leave it there or move it to the users_controller). It should be:

 if @user.save
   redirect_to profiles_path(@user)
 else
   render "edit"
 end

When the user doesn't get saved, you want to render edit not new.

Lastly, there is one downside to creating two different forms but I think it is not a very big problem here: validations.

If you want to have a validation on let's say :bio it won't work with this setup that you have right now, because the user object already get's created when the devise registration form is submitted. And at this first point the validations would be tested - so you could not test if a bio was there already or not. There are gems to handle this and I also found this article for further research. https://www.honeybadger.io/blog/multi-step-forms-in-rails/

Upvotes: 2

Related Questions