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)