Skip to content
kristianmandrup edited this page Jan 5, 2011 · 3 revisions

Create default Guest user

  class Guest
    def create
      Guest.new
    end

    def has_role? role
      role == :guest
    end

    def has_roles? *roles
      role == :guest
    end


    def self.roles
      User.roles
    end
  end  

Add to ApplicationController

  # http://stackoverflow.com/questions/4275058/using-devise-with-guest-users

  def current_user
    super || Guest.create # role defaults to 'guest' in the model.
  end

  def user_signed_in?
    current_user && !current_user.new_record?
  end

  def user_session
    user_signed_in? ? super : session
  end    

Control who can create new users

  $ mkdir app/controllers/users
  $ touch app/controllers/users/registrations_controller.rb  
  class Users::RegistrationsController < Devise::RegistrationsController
    before_filter :check_permissions, :only => [:new, :create, :cancel]
    skip_before_filter :require_no_authentication

    def check_permissions
      authorize! :create, resource
    end
  end  

The check permissions method is really simple. It calls the CanCan method, authorize!, and checks if the current user can create users. We use resource here because devise uses resource to refer to the model that can be authenticated. Also notice how I removed the require_no_authentication filter, a Devise filter which allows access to actions without authentication.

  # replace devise_for :users with:
  devise_for :users,  :controllers => { :registrations => "users/registrations" }  

At this point if you hit the users/sign_up page when not logged in, you will notice that a CanCan::AccessDenied is thrown. This exception is thrown anytime permission is denied so you should customize it to your liking. I put the handler in my ApplicationController:

  class ApplicationController < ActionController::Base
    ...
    rescue_from CanCan::AccessDenied do |exception|
      flash[:error] = exception.message
      redirect_to root_url
    end
    ...
  end  
  class UsersController < ApplicationController
    before_filter :get_user, :only => [:index,:new,:edit]
    before_filter :accessible_roles, :only => [:new, :edit, :show, :update, :create]
    load_and_authorize_resource :only => [:show,:new,:destroy,:edit,:update]

    # GET /users
    # GET /users.xml                                                
    # GET /users.json                                       HTML and AJAX
    #-----------------------------------------------------------------------
    def index
      @users = User.accessible_by(current_ability, :index).limit(20)
      respond_to do |format|
        format.json { render :json => @users }
        format.xml  { render :xml => @users }
        format.html
      end
    end

    # GET /users/new
    # GET /users/new.xml                                            
    # GET /users/new.json                                    HTML AND AJAX
    #-------------------------------------------------------------------
    def new
      respond_to do |format|
        format.json { render :json => @user }   
        format.xml  { render :xml => @user }
        format.html
      end
    end

    # GET /users/1
    # GET /users/1.xml                                                       
    # GET /users/1.json                                     HTML AND AJAX
    #-------------------------------------------------------------------
    def show
      respond_to do |format|
        format.json { render :json => @user }
        format.xml  { render :xml => @user }
        format.html      
      end

    rescue ActiveRecord::RecordNotFound
      respond_to_not_found(:json, :xml, :html)
    end

    # GET /users/1/edit                                                      
    # GET /users/1/edit.xml                                                      
    # GET /users/1/edit.json                                HTML AND AJAX
    #-------------------------------------------------------------------
    def edit
      respond_to do |format|
        format.json { render :json => @user }   
        format.xml  { render :xml => @user }
        format.html
      end

    rescue ActiveRecord::RecordNotFound
      respond_to_not_found(:json, :xml, :html)
    end

    # DELETE /users/1     
    # DELETE /users/1.xml
    # DELETE /users/1.json                                  HTML AND AJAX
    #-------------------------------------------------------------------
    def destroy
      @user.destroy!

      respond_to do |format|
        format.json { respond_to_destroy(:ajax) }
        format.xml  { head :ok }
        format.html { respond_to_destroy(:html) }      
      end

    rescue ActiveRecord::RecordNotFound
      respond_to_not_found(:json, :xml, :html)
    end

    # POST /users
    # POST /users.xml         
    # POST /users.json                                      HTML AND AJAX
    #-----------------------------------------------------------------
    def create
      @user = User.new(params[:user])

      if @user.save
        respond_to do |format|
          format.json { render :json => @user.to_json, :status => 200 }
          format.xml  { head :ok }
          format.html { redirect_to :action => :index }
        end
      else
        respond_to do |format|
          format.json { render :text => "Could not create user", :status => :unprocessable_entity } # placeholder
          format.xml  { head :ok }
          format.html { render :action => :new, :status => :unprocessable_entity }
        end
      end
    end
    ...

  # PUT /users/1
   # PUT /users/1.xml
   # PUT /users/1.json                                            HTML AND AJAX
   #----------------------------------------------------------------------------
   def update
     if params[:user][:password].blank?
       [:password,:password_confirmation,:current_password].collect{|p| params[:user].delete(p) }
     else
       @user.errors[:base] << "The password you entered is incorrect" unless @user.valid_password?(params[:user][:current_password])
     end

     respond_to do |format|
       if @user.errors[:base].empty? and @user.update_attributes(params[:user])
         flash[:notice] = "Your account has been updated"
         format.json { render :json => @user.to_json, :status => 200 }
         format.xml  { head :ok }
         format.html { render :action => :edit }
       else
         format.json { render :text => "Could not update user", :status => :unprocessable_entity } #placeholder
         format.xml  { render :xml => @user.errors, :status => :unprocessable_entity }
         format.html { render :action => :edit, :status => :unprocessable_entity }
       end
     end

   rescue
     respond_to_not_found(:js, :xml, :html)
   end
    
    # FILTERS 
    
    
    # Get roles accessible by the current user
    #----------------------------------------------------
    def accessible_roles
      @accessible_roles = Role.accessible_by(current_ability,:read)
    end

    # Make the current user object available to views
    #----------------------------------------
    def get_user
      @current_user = current_user
    end    
  end  

Views

  <h2>Register User</h2>

  <%= form_for(@user) do |f| %>
    <%= error_messages(@user,"Could not register user") %>

    <%= render :partial => 'user_fields', :locals => { :f => f } %>

    <p><%= f.label :password %></p>
    <p><%= f.password_field :password %></p>

    <p><%= f.label :password_confirmation %></p>
    <p><%= f.password_field :password_confirmation %></p>

    <p><%= f.submit "Register" %></p>
  <% end %>  

partial: user_fields.html.erb

<%= f.label :first_name >


<= f.text_field :first

name %>

<%= f.label :last_name %>

<%= f.text_field :last_name %>

<%= f.label :email %>

<%= f.text_field :email %>

<% if can? :read, Role %>

<%= f.label :role %>

    <%= habtm_checkboxes(@user, :role_ids, @accessible_roles, :name) %>
<% end %>

Only allow editing of role attribute if allowed to!

Edit existing user

  <h3><%= @user == @current_user ? "Your Account Settings" : "Edit User" %></h3>

  <%= form_for(@user, :html => { :method => :put }) do |f| %>
  	<%= error_messages(@user,"Could not update user") %>
  	<%= render :partial => 'user_fields', :locals => { :f => f } %>

  	<p><%= f.label :password %> <i>(leave blank if you don't want to change it)</i></p>
  	<p><%= f.password_field :password %></p>

  	<p><%= f.label :password_confirmation %></p>
  	<p><%= f.password_field :password_confirmation %></p>

  	<p><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i></p>
  	<p><%= f.password_field :current_password %></p>

    <p><%= f.submit "Update" %></p>
  <% end %>
  <%= link_to "Back", :back %>  

Show User

  <h3><%= @user.name %></h3>

  <%= link_to_if(can?(:update,@user), "Edit", edit_user_path(@user)) %> |
  <%= link_to_if(can?(:delete, @user), "Delete", user_path(@user), :confirm => "Are you sure?", :method => :delete) {} %>

  <table class="one-column-emphasis">
  	<tbody>
  		<tr>
  			<td class="oce-first">Email:</td>
  			<td><%= @user.email %></td>
  		</tr>
  		<tr>
  			<td class="oce-first">Role:</td>
  			<td><%= @user.roles.first.name %></td>
  		</tr>
  	<% if can?(:see_timestamps,User) %>
  		<tr>
  			<td class="oce-first">Created at:</td>
  			<td><%= @user.created_at %></td>
  		</tr>
  		<tr>
  			<td class="oce-first">Last Sign In:</td>
  			<td><%= @user.last_sign_in_at %></td>
  		</tr>
  		<tr>
  			<td class="oce-first">Sign In Count:</td>
  			<td><%= @user.sign_in_count %></td>
  		</tr>
  	<% end %>
  	</tbody>
  </table>  

Custom can see timestamps!

  if user.role? :admin
    can :see_timestamps, User
  elsif user.role? :normal
    can :see_timestamps, User, :id => user.id
  end  

Recaptcha integration on signup

  class RegistrationsController < Devise::RegistrationsController

    def create
      if verify_recaptcha
        super
      else
        build_resource
        clean_up_passwords(resource)
        flash[:error] = "There was an error with the recaptcha code below. Please re-enter the code and click submit."
        render_with_scope :new
      end
    end
    ...
  end  

Recaptcha with Omniauth

  class RegistrationsController < Devise::RegistrationsController

    def create
      if session[:omniauth] == nil #OmniAuth
        if verify_recaptcha
          super
          session[:omniauth] = nil unless @user.new_record? #OmniAuth
        else
          build_resource
          clean_up_passwords(resource)
          flash[:error] = "There was an error with the recaptcha code below. Please re-enter the code and click submit."
          render_with_scope :new
        end
      else
        super
        session[:omniauth] = nil unless @user.new_record? #OmniAuth
      end
    end