Search code examples
ruby-on-railsvalidationauthenticationdevisebefore-filter

Devise - Authenticate user (after validations) on a create action


Using Devise, I know how to protect controller actions from non-signed-in users through:

before_filter :authenticate_user!

In order to illustrate what I am trying to achieve, please see an example:

I have the following controller: (a project belongs to a user)

projects_controller.rb

def create
  @project = current_user.projects.new(params[:project])
  if @project.save
    redirect_to @project
  else
    render :action => 'new'
  end
end

What I am looking for is a way that users can interact more with the website before having to sign up/sign in. Something like:

after_validation :authenticate_user!

if the user is not signed in, and redirect him after success (sign up/sign in) to the "project" show page.

Things I thought:

1.) Change the controller in order to accept a project object without user_id, ask for authentication if the user is not signed in, then update attributes with the user_id

I try to do it like this first and it results to a very ugly code. (Moreover authenticate_user! doesn't redirect to the @project which lead to more customization)

2.) Create a wizard with nested_attributes (project form and nested new registration form and session form)

3.) Something better? (a custom method?)

It seems authologic manages this more easily. I'm not sure it is a reason to switch so I would like to have your idea/answer on this. Thanks!

EDIT

references: Peter Ehrlich answer comment

CONTROLLER WITH VALIDATIONS LOGIC

projects_controller.rb

def create
  unless current_user
    @project = Project.new(params[:project]) # create a project variable used only for testing validation (this variable will change in resume project method just before being saved)
    if @project.valid? # test if validations pass
      session['new_project'] = params[:project]
      redirect_to '/users/sign_up'
    else
      render :action => 'new'
    end
  else
    @project = current_user.projects.new(params[:project])
    if @project.save
      redirect_to @project
    else
      render :action => 'new'
    end
  end
end

def resume_project
  @project = current_user.projects.new(session.delete('new_project')) # changes the @project variable
  @project.save
  redirect_to @project
end

routes

  get "/resume_project", :controller => 'projects', :action => 'resume_project'

application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery

  def after_sign_in_path_for(resource)
    return '/resume_project' if session['new_project'].present?
  super
end

Solution

  • Something like this should work:

    def create
        unless current_user
            session['new_project'] = params[:project]
            redirect_to '/register'
            return
        end
        # and on to normal stuff
    
    # in your devise controller 
    def after_sign_in_path
        return '/resume_project' if session['new_project'].present?
        super
    end
    
    # back in projects_controller now   
    def resume_project
        @project.create(session.delete('new_project'))
        # you know the drill from here
        # I'd also put in a check to make an error if the session is not set- in case they reload or some such
    

    Keep in mind that session is a cookie in the browser, and thus has a size limit (4kb). If you're posting images or other media, you'll have to store them temporarily server-side.

    Another option would be to create a userless project, and use a similar technique to allow them to claim it as their own. This would be nice if you wanted unclaimed projects displayed to all to be available as a flow.