Welcome to the third part of Juggernaut Rails Chat Tutorial
Today we will start Ruby code development.
First of all, we need to create user model. He should be able to login and chat. Also we need administrator role. Administrator should be able to create and destroy chat rooms.
Let's use account attribute to determine whether user is an std user or an administrator.
User model
User model is remarkably simple. It contains login, hashed password, salt and last login timestamp.
script/generate model User
Migration file:
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :login
t.string :hashed_password
t.string :salt
# Account type - user/admin
t.string :account
t.timestamps
end
end
def self.down
drop_table :users
end
end
Let's "fill" user:
require 'digest/sha2'
class User < ActiveRecord::Base
LOGIN_MAX_LENGTH = 14
LOGIN_MIN_LENGTH = 5
PASSWORD_MAX_LENGTH = 16
PASSWORD_MIN_LENGTH = 8
attr_accessor :password_confirmation
attr_accessor :password
attr_accessor :current_password
validates_uniqueness_of :login
validates_length_of :login, :within => LOGIN_MIN_LENGTH..LOGIN_MAX_LENGTH
validates_format_of :login, :with => /^[A-ZąćęłńóśżźĄĆĘŁŃÓŚŹŻ0-9_]*$/i
validates_format_of :password, :with => /^[A-ZąćęłńóśżźĄĆĘŁŃÓŚŹŻ0-9_]*$/i
validates_length_of :password, :within => PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH
validates_inclusion_of :account, :in => ["admin", "user"]
validates_confirmation_of :password
# Find user and try to authenticate him
def self.authenticate(login, password)
user = self.find_by_login(login)
if user
expected_password = encrypted_password(password, user.salt)
if user.hashed_password != expected_password
user = nil
end
end
user
end
def password
@password
end
# Generate hashed password
def password=(pwd)
@password = pwd
return if pwd.blank?
create_new_salt
self.hashed_password = User.encrypted_password(self.password, self.salt)
end
# Log in user
def login!(session)
session[:user_id] = self.id
end
# Log out user
def self.logout!(session)
session[:user_id] = nil
end
def clear_password!
self.password = nil
self.password_confirmation = nil
end
def account_type
return :user if self.account == 'user'
return :admin if self.account == 'admin'
end
protected
def create_new_salt
self.salt = self.object_id.to_s + rand.to_s
end
def self.encrypted_password(password, salt)
return if password.nil? || salt.nil?
string_to_hash = password + "GB609ru30j)(*&Th" + salt
Digest::SHA2.hexdigest(string_to_hash)
end
end
User exists. Now let's init db session store
Database sessions
rake db:sessions:create
(in /home/maciek/Ruby/RailsChat)
exists db/migrate
create db/migrate/20100522201155_create_sessions.rb
Uncomment in config/environment.rb :
config.action_controller.session_store = :active_record_store
Add into controllers/application.rb:
protect_from_forgery :secret => 'a8310eac6747c66f90127ef3370c5ad7'
DB migrating:
rake db:migrate
Ok, now it's time to create some views but first lets add one administrator via db migration:
Admin
script/generate migration AddAdmin
Migration:
class AddAdmin < ActiveRecord::Migration
def self.up
User.delete_all
User.create(
:login => 'admin',
:password => 'mysuperpassword',
:account => 'admin')
end
def self.down
User.delete_all
end
end
Registration
First of all, it is required to create registration controller:
script/generate controller Register index
Main layout
Layout 1
In layouts/ create main.erb file containing:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en" lang="en" >
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="language" content="pl"/>
<meta name="description" content="Racer - Rails Chatter" />
<%= stylesheet_link_tag 'main' %>
<title>Racer - Rails Chatter</title>
</head>
<body>
<div id="contener">
<div id="logo"></div>
<div id="menu">
<% !@logged %>
<%= link_to "Login", :controller => :main %>
<%= link_to "Register", :controller => :register %>
<% else %>
<%= link_to "Logout", :controller => :main, :action => :logout %>
<% end %>
</div>
<div id="content">
<% if flash[:error] %>
<%= "<div id=\"error\">#{flash[:error]}</div>" %>
<% end %>
<% if flash[:message] %>
<%= "<div id=\"message\">#{flash[:message]}</div>" %>
<% end %>
<%= yield %>
</div>
</div>
</body>
</html>
And also main.css:
body {
background: #f8f8f8;
}
#contener {
margin: 0 auto; width: 900px;
margin-top: 20px;
}
#logo {
background: url('/images/racer_logo.png') no-repeat;
width: 430px; height: 220px; float: left;
border: 1px solid black;
}
#content {
width: 100%; float: left; border: 1px solid black;
margin-top: 50px; background: #ffffff;
padding: 20px;
-moz-border-radius: 24px;
-webkit-border-radius: 24px;
}
#error {
border: 1px solid red; padding: 5px; margin: 10px 10px 10px 0;
width: 430px;
}
#message {
border: 1px solid green; padding: 5px; margin: 10px 10px 10px 0;
width: 430px;
}
.register_input {
width: 150px; float: left;
}
.errorExplanation {
margin: 0 auto; border: 1px solid #DA0211;
padding: 5px 5px 0 15px; background: #FFBABA; color: #DA0211;
margin-bottom: 10px;
}
.errorExplanation h2 {
font-size: 13px; margin-top: 8px;
}
.errorExplanation li {
font-size: 13px;
padding-left: 16px;list-style: circle inside;
padding-bottom: 4px;
}
.errorExplanation p {
padding: 10px 0 0 0; font-size: 13px;
}
.errorExplanation ul {
padding: 2px 0 2px 0;
}
.fieldWithErrors input, .fieldWithErrors select {
border: 1px solid red;
}
#menu {
float: right; width: 400px; font-size: 12px;
margin-top: 200px; text-align: right;
}
#menu a {
text-decoration: none; margin-left: 40px;
}
#room_list td {
border-right: 1px solid black; padding: 4px 20px;
}
Let's get back to our view in views/register/index.erb:
<h3>Registration</h3>
<% form_for :user do |f| %>
<%= f.error_messages %>
<span class="register_input">Login:</span> <%= f.text_field :login %><br/>
<span class="register_input">Password:</span> <%= f.password_field :password %><br/>
<span class="register_input">Password confirmation:</span> <%= f.password_field :password_confirmation %><br/><br/>
<%= f.submit "Register!" %>
<% end %>
And finally register_controller.rb:
class RegisterController < ApplicationController
layout "main"
def index
if request.post?
@user = User.new(params[:user])
@user.account = 'user'
if @user.save
flash[:message] = "User registered successfully!"
redirect_to :controller => :main
else
@user.clear_password!
end
end
end
end
Registration is ready. Now login panel.
Login panel
Controller:
script/generate controller Main index login logout
config/routes.rb:
map.root :controller => :main
Controller content:
class MainController < ApplicationController
layout "main"
def index
if !session[:user_id].nil?
@logged = true
flash[:message] = "You are logged in as: #{User.find(session[:user_id]).login.capitalize}"
end
end
def login
if request.post?
@user = User.authenticate(params[:user][:login], params[:user][:password])
if @user
@user.login!(session)
session[:color] = "rgb(#{rand(255).to_s},#{rand(255).to_s},#{rand(255).to_s})"
redirect_to :controller => @user.account_type == :user ? :chat : :admin
else
flash[:error] = "Invalid login or password"
redirect_to :action => :index
end
else
redirect_to :action => :index
end
end
def logout
User.logout!(session)
flash[:message] = "Wylogowano poprawnie"
redirect_to :action => :index
end
end
Login view:
<% if @logged %>
<%= button_to "Go chat", :controller => "chat" %>
<% else %>
<% form_for :user, :url => { :action => "login" } do |f| %>
<%= f.error_messages %>
Login: <%= f.text_field :login %><br/>
Hasło: <%= f.password_field :password %><br/><br/>
<%= f.submit "Go chat" %>
<% end %>
<% end %>
To protect controllers and actions, we use before_filter:
def authorize
unless (@user = User.find_by_id(session[:user_id]))
User.logout!(session)
flash[:error] = ('Please log in')
redirect_to :controller => :main
end
end
Now let's protect resources dedicated only for administrators:
def only_admin
redirect_to :controller => :main unless @user.account_type == :admin
end
That's all for now
Tutorial parts:
- Juggernaut Rails Chat – part I (what is and how to install Juggernaut)
- Juggernaut Rails Chat – part II (design)
- Juggernaut Rails Chat – part III (registration and authentication)
- Juggernaut Rails Chat – część IV (zarządzanie pokojami)
- Juggernaut Rails Chat – część V (łączenie użytkowników z pokojami)
- Juggernaut Rails Chat – część VI (odpalamy Juggernaut i nasz chat)
- Juggernaut Rails Chat – część VII (emotikonki i dźwięk)