2503ICT: User management 1


Under construction!

Goals

Anonymous, authenticated, administrative users...

Only authenticated users can offer items for sale, remove their items from sale, place bids, view list of (other) users, edit their details, etc.

Only administrative users can remove any user, remove any item, ...

User model

Common fields: (id), username, real name, email address, password, ...

rails generate model User name:string email:string # add password later
rake db:migrate

rails console
# create some users

Constraints on users:

Add validations to app/models/user.rb.

class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, uniqueness: true
end

Modify .../user.rb to save email address in lower case.

class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  ...
end

How to store passwords? In plain text? No.

gem install bcrypt-ruby

rails generate migration add_password_digest_to_users password_digest:string
rake db:migrate

Add more validations to user model.

class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
                    uniqueness: { case_sensitive: false }
end

But this doesn't ensure email uniqueness because of race conditions!

rails generate migration add_index_to users_email

Edit db/migrate/[timestamp]_add_index_to_users_email.rb:

class AddIndexToUsersEmail < ActiveRecord::Migration
  def change
    add_index :users, :email, unique: true
  end
end

Now need to add has_secure_password and validation on password length to .../user.rb.

See the .../user.rb in micropost_app or Listing 6.29 in Hartl's Tutorial for the final definition.

Sign up/in/out

Sign up

Add "resources :users" to routes.rb

Define show new, create, index, show actions in app/controllers/users_controller.rb.

Specify required and permitted parameters for create (and later for update) actions.

Define new, index and show views in app/views/users and define form for creating new users.

Sign in/out

Need "sessions" that preserve information across successive HTTP requests.

rails generate controller Sessions --no-test-framework

Addd sessions routes to config/routes.rb.

SampleApp::Application.routes.draw do
  resources :users
  resources :sessions, only: [:new, :create, :destroy]
  root  'static_pages#home'
  match '/signup',  to: 'users#new',            via: 'get'
  match '/signin',  to: 'sessions#new',         via: 'get'
  match '/signout', to: 'sessions#destroy',     via: 'delete'
  ...
end

Define a signin view in app/views/users.

Define the create action in .../sessions_controllers.rb.

To implement persistent sessions, we could use the special hash session variable. To store the user's id in a cookie:

session[:remember_token] = user.id

To retrieve the current user:

User.find(session[:remember_token])

Aside: A cookie is a small, limited-duration text file on your computer with a name and value that is associated with a particular host and browser. To some extent their use is being replaced by HTML5 "local storage", which has fewer restrictions.

It is preferable to use a remember token for each user as this survives browser closes.

rails generate migration add_remember_token_to_users

Define the migration:

class AddRememberTokenToUsers < ActiveRecord::Migration
  def change
    add_column :users, :remember_token, :string
    add_index  :users, :remember_token
  end
end

Run the migration:

rake db:migrate

Update the user model as follows:

class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  before_create :create_remember_token
  .
  .
  .
  def User.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def User.encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end

  private

    def create_remember_token
      self.remember_token = User.encrypt(User.new_remember_token)
    end
end

When we sign in, we have to remember the newly signed-in user.

To be continued...