Sunday, March 23, 2014

Create User Profile Rails App

Part 1: Setting Up the Rails App

$ rails _3.2.13_ new user_profile -d mysql

$ cd user_profile

Part 2: Devise Users

In /Gemfile,

gem 'devise'

$ bundle install

$ rails generate devise:install

In /config/environment/development.rb,

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

In /config/routes.rb,

root :to => "home#index"

In /app/views/layours/application.html.erb,

<body>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>

<%= yield %>

In /config/application.rb,

# Enable the asset pipeline
config.assets.enabled = true
config.assets.initialize_on_precompile = false

$ rm public/index.html

Part 3: User Model

$ rails generate devise user

$ rake db:migrate

Part 4: Home Index Page

$ rails g controller home

In /app/controllers/home_controller.rb,

class HomeController < ApplicationController
  def index
    
  end
end

Create your own index page at /app/views/home/index.html.erb.

Part 5: Sign In, Sign Out and Register Links

Create the new file on /app/views/common/_session.html.erb and include the code below into the file:

<%- if user_signed_in? %>
  <p>Hello <%= current_user.email %><br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Register', new_user_registration_path %> |
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

In /app/views/home/index.html.erb,

  <div id="secondaryContent">
    <%= render 'common/session' %>
  </div>

$ rails g devise:views

Part 6: Authorization

In /app/controllers/application_controller.rb,

class ApplicationController < ActionController::Base
  protect_from_forgery

  protected
  def authorize_user!
    if user_signed_in? 
      return
    else
      flash[:notice] = 'You need to sign in first'
      redirect_to new_user_session_path
    end
  end
end

In /app/controllers/home_controller.rb,

class HomeController < ApplicationController
  before_filter :authorize_user!
  
  def index
    
  end
end

Part 7: Username

$ rails g migration add_username_to_users username:string

$ rake db:migrate

Add the code below into the 3 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb
  • /app/views/devise/sessions/new.html.erb

  <div><%= f.label :username %><br />
  <%= f.text_field :username %></div>

In /config/initializers/devise.rb,

  # ==> Configuration for any authentication mechanism
  # Configure which keys are used when authenticating a user. The default is
  # just :email. You can configure it to use [:username, :subdomain], so for
  # authenticating a user, both parameters are required. Remember that those
  # parameters are used only when authenticating and not when retrieving from
  # session. If you need permissions, you should implement that in a before filter.
  # You can also supply a hash where the value is a boolean determining whether
  # or not authentication should be aborted when the value is not present.
  config.authentication_keys = [ :username ]

In /app/models/user.rb,

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model

  attr_accessible :username, :email, :password, :password_confirmation, :remember_me
end

Part 8: Name & Nickname

$ rails g migration add_name_to_users name:string

$ rails g migration add_nickname_to_users nickname:string

$ rake db:migrate

Add the code below into the 2 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb

  <div><%= f.label :name %><br />
  <%= f.text_field :name %></div>
  
  <div><%= f.label :nickname %><br />
  <%= f.text_field :nickname %></div>

In /app/models/user.rb,

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :remember_me
end

In /app/common/_session.html.erb,

<%- if user_signed_in? %>
  <p>Welcome, <%= current_user.nickname %>!<br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Register', new_user_registration_path %> |
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

Part 9: Carrierwave File Uploads

In /Gemfile,

gem 'carrierwave'

$ bundle install

$ rails g uploader photo

$ rails g migration add_photo_to_users photo:string

$ rake db:migrate

In /app/models/user.rb,

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
 
  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :photo, :remote_photo_url, :remember_me

  mount_uploader :photo, PhotoUploader
end

In /app/uploaders/photo_uploader.rb,

class PhotoUploader < CarrierWave::Uploader::Base
  def extension_white_list
    %w(jpg jpeg gif png)
  end
end

Add the code below into the 2 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb

  <div><%= f.label :photo %><br />
  <%= f.file_field :photo %><br />
  <%= f.label :remote_photo_url, "or photo URL" %>
  <%= f.text_field :remote_photo_url %> </div>

Part 10: User Authorization with Rolify and Cancan

In /Gemfile,

gem 'rolify'
gem 'cancan'

$ bundle install

$ rails g rolify Role User

$ rake db:migrate

In /app/models/user.rb,

class User < ActiveRecord::Base
  rolify
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  
  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :photo, :remote_photo_url, :remember_me
  
  attr_accessor :current_role

  mount_uploader :photo, PhotoUploader
end

In /app/models/role.rb,

class Role < ActiveRecord::Base
  has_and_belongs_to_many :users, :join_table => :users_roles
  belongs_to :resource, :polymorphic => true
  
  scopify
  
  attr_accessible :name
end

$ rails g cancan:ability

In /app/models/ability.rb,

class Ability
  include CanCan::Ability

  def initialize(user)
    
    user ||= User.new
    
    if user.role? :admin
      can :manage, :all
    else
      cannot :create, User
      can :read, User
      can :update, User
      cannot :destroy, User
      cannot :index, User
    end
  end
end

Part 11: User with Roles

Add the code below into the 2 files as belows:
  • /app/views/devise/registrations/edit.html.erb
  • /app/views/devise/registrations/new.html.erb

  <div><%= f.label :role %><br />
  <% for role in Role.find(:all) %>
    <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
    <%= role.name %><br/>
  <% end %></div>

In /app/models/user.rb,

class User < ActiveRecord::Base
  rolify

  has_and_belongs_to_many :roles, :join_table => :users_roles
  
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  
  # Setup accessible (or protected) attributes for your model
  attr_accessible :username, :email, :password, :password_confirmation, :name, :nickname, :photo, :remote_photo_url, :role_ids, :remember_me
  
  attr_accessor :current_role

  mount_uploader :photo, PhotoUploader

  def role?(role_sym)
    roles.any? { |r| r.name.underscore.to_sym == role_sym }
  end
end

Part 12: User Management via CRUD

In order to manage our users through a CRUD interface we have to create a users controller and its associated views. First use rails to generate the users controller.

$ rails g controller users

In our users controller we are going to define the seven basic actions to maintain the RESTful protocol, so open up app/controllers/users_controller.rb and make it look like the following:

class UsersController < ApplicationController
  before_filter :authorize_user!

  load_and_authorize_resource

  def index
    @users = User.all
  end

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

  def new
    @user = User.new
  end

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

  def create
    @user = User.new(params[:user])

    if @user.save
      redirect_to @user, :flash => { :success => 'User was successfully created.' }
    else
      render :action => 'new'
    end
  end

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

    if @user.update_attributes(params[:user])
      sign_in(@user, :bypass => true) if @user == current_user
      redirect_to @user, :flash => { :success => 'User was successfully updated.' }
    else
      render :action => 'edit'
    end
  end

  def destroy
    @user = User.find(params[:id])
    @user.destroy
    redirect_to users_path, :flash => { :success => 'User was successfully deleted.' }
  end
end

Now we need to create the associated views and a form partial.

$ cd app/views/users && touch _form.html.erb edit.html.erb index.html.erb new.html.erb show.html.erb

Make each of these files look as follows respectively:

In /app/views/users/_form.html.erb,

<%= form_for @user do |f| %>
  <% if @user.errors.any? %>
    <div class="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :username %>
    <div class="controls">
      <%= f.text_field :username %>
    </div>
  </div>

  <div class="field">
    <%= f.label :email %>
    <div class="controls">
      <%= f.email_field :email %>
    </div>
  </div>

  <div class="field">
    <%= f.label :password, "Password" %>
    <div class="controls">
      <%= f.password_field :password %>
    </div>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %>
    <div class="controls">
      <%= f.password_field :password_confirmation %>
    </div>
  </div>

  <div class="field">
    <%= f.label :name %>
    <div class="controls">
      <%= f.text_field :name %>
    </div>
  </div>

  <div class="field">
    <%= f.label :nickname %>
    <div class="controls">
      <%= f.text_field :nickname %>
    </div>
  </div>

  <div class="field">
    <%= f.label :photo %>
    <div class="controls">
      <%= f.file_field :photo %>
    </div>
    <%= f.label :remote_photo_url, "or photo URL" %>
    <%= f.text_field :remote_photo_url %>
  </div>

<% if can? :create, @user %>
  <div class="field">
    <%= f.label :role %>
    <div class="controls">
      <% for role in Role.find(:all) %>
        <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
        <%= role.name %><br/>
      <% end %>
    </div>
  </div>
<% end %>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

In /app/views/users/edit.html.erb,

<h1>Edit User</h1>

<%= render 'form' %>

<%= link_to 'Show User', @user %>
<% if can? :index, @user %>
 | <%= link_to 'All Users', users_path %>
<% end %>

In /app/views/users/index.html.erb,

<h1>Users</h1>

<table>
  <thead>
    <tr>
      <th>Username</th>
      <th>Email</th>
      <th>Name</th>
      <th>Nickname</th>
      <th>Role</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.username %></td>
        <td><%= user.email %></td>
        <td><%= user.name %></td>
        <td><%= user.nickname %></td>
        <td><%= user.roles.map(&:name).join(", ") %></td>
        <td>
          <%= link_to 'Show', user %>
          <%= link_to 'Edit', edit_user_path(user) %>
          <%= link_to 'Delete', user_path(user), :method => 'delete', :confirm => 'Are you sure?' %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

<%= link_to 'New User', new_user_path %>

In /app/views/users/new.html.erb,

<h1>New User</h1>

<%= render 'form' %>

<%= link_to 'All Users', users_path %>

In /app/views/users/show.html.erb,

<h1>User</h1>

<p><strong>Username:</strong> <%= @user.username %></p>

<p><strong>Email:</strong> <%= @user.email %></p>

<p><strong>Name:</strong> <%= @user.name %></p>

<p><strong>Nickname:</strong> <%= @user.nickname %></p>

<p><strong>Photo:<strong><br />
  <%= image_tag @user.photo_url %>
</p>

<p><strong>Role:</strong> <%= @user.roles.map(&:name).join(", ") %></p>

<% if can? :index, @user %>
<%= link_to 'All Users', users_path %> |
<% end %>
<%= link_to 'Edit User', edit_user_path(@user) %>
<% if can? :destroy, @user %>
 | <%= link_to 'Delete User', user_path(@user), :method => 'delete', :confirm => 'Are you sure?' %>
<% end %>

Next we need to set up some routes so that the routes for devise and our users controller don't have any conflicts. So, open up config/routes.rb and make it look as follows:

Userapp::Application.routes.draw do
  resources :users

  devise_for :users, :skip => [:registrations, :sessions]

  as :user do
    get "/login" => "devise/sessions#new", :as => :new_user_session
    post "/login" => "devise/sessions#create", :as => :user_session
    delete "/logout" => "devise/sessions#destroy", :as => :destroy_user_session
  end

  root :to => 'users#index'
end

This routes.rb file first defines the RESTful routes for our users controller then tells devise to generate routes for users but to skip registrations and sessions. We skip the sessions routes because we will have to define them in a custom fashion so that they will not conflict with our RESTful users routes.

In /app/views/common/_session.html.erb,

<%- if user_signed_in? %>
  <p>Welcome, <%= current_user.nickname %>!<br />
  <%= link_to 'Sign out', destroy_user_session_path, :method => :delete %></p>
<%- else %>
  <p>
    <%= link_to 'Sign in', new_user_session_path %>
  </p>
<%- end %>

In /app/controllers/application_controller.rb,

class ApplicationController < ActionController::Base
  protect_from_forgery
  
  def after_sign_in_path_for(resource)
    user_path(current_user) #your path
  end

  protected
  def authorize_user!
    if user_signed_in?
      return
    else
      flash[:notice] = 'You need to sign in first'
      redirect_to new_user_session_path
    end
  end
end

In /app/views/devise/shared/_links.erb,

<%- if controller_name != 'sessions' %>
  <%#= link_to "Sign in", new_session_path(resource_name) %><!--<br />-->
<% end -%>

<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%#= link_to "Sign up", new_registration_path(resource_name) %><!--<br />-->
<% end -%>

Now lets launch the application.

$ rails s

Navigate to http://localhost:3000/users/new in a web browser. Here we can create the first user of our application. Go ahead and create a user with an email address and password of your choice. Now go to http://localhost:3000 and click on the Log In link we made earlier. This brings us to the sign in page generated by devise but uses our custom /login route we specified in config/routes.rb. Authenticate with the account you just created. You should be greeted by the home page of the application with a success flash message and now a Log Out link. Congratulations! You can now create users and authenticate with devise. You can view a list of all the users by going to http://localhost:3000/users and from there you can edit or delete them.

Return to Internship Note (LoanStreet)
Previous Episode: User Authorization with Rolify and Cancan
Next Episode: Testing with RSpec

0 comments:

Post a Comment