Tag: chat

Juggernaut Rails Chat – część V (łączenie użytkowników z pokojami)

Witam w przedostatniej (nielicząć części VII która dot. "przybajerzenia" naszego czatu ;) ) części tutoriala, poświęconego budowaniu chatu z pomocą Railsów oraz Juggernaut.

W tej części zajmiemy się połączeniem użytkowników z pokojami, tak aby w ostatniej części tutoriala, można było na bieżąco aktualizować spis użytkowników z danych pokoi.

Aby dokonać takiego połączenia które jest połączeniem wiele-do-wielu (wielu użytkowników może być w wielu pokojach ale i wiele pokoi może zawierać wielu użytkowników) można skorzystać z dwóch sposobów:

  1. Możemy skorzystać z has_and_belongs_to tworząc tabelę przechodnią bez tworzenia dla niej modelu
  2. Skorzystać z has_many :through i stworzyć dla tej relacji model

My skorzystamy z metody drugiej, chociaż na pierwszy rzut oka rozwiązanie pierwsze wydaje się lepsze.

Dlaczego więc nie jest?

Kiedy pisałem ten czat, sam skorzystałem z pierwszej metody, jednak Juggernaut posiada system callbacków przy których praca z has_and_belongs_to robiła się troszkę problematyczna. Kiedy ktoś korzystał z jednego okna czatu naraz wszystko było ok, jednak kiedy powiedzmy z czystej ciekawości, ktoś w drugiej karcie otwierał drugi raz ten sam pokój, a następnie zamykał jedną kartę, relacja has_and_belongs_to była usuwana (ponieważ callback miał usuwać relację aby lista była aktualna). W takiej sytuacji użytkownik był na czacie jednak znikał z listy użytkowników (system uznawał zamknięcie karty za zamknięcie czatu).

Jak to rozwiązałem?

Bardzo prosto. Stworzyłem model ChatRoomsUser którego zadaniem było stworzenie relacji między użytkownikami i pokojami, z jednoczesnym zliczeniem tego w ilu kartach ktoś "siedzi" w jednym pokoju czatu, tak więc kiedy wchodziliśmy pierwszy raz na czat, ilość naszych "istnień" w czacie była równa 1. Kiedy otwieraliśmy drugą zakładkę z tym samym pokojem, ilość wzrastała do 2.

Ten sam mechanizm działa w drugą stronę. Zamykając jedną zakładkę, Juggernaut zwraca nam callback że socket usera o takim a takim ID został zamknięty. Model obniża wtedy ilość o 1. Jeśli ilość była większa niż 1 (np 2) tzn że wciąż mamy aktywne przynajmniej jedno okno z pokojem. Oznacza to że wciąż jesteśmy w tym pokoju. Jednak kiedy zamykamy ostatnie okno, relacja zostaje usunięta i osoba znika z listy użytkowników tego pokoju.

Implementujmy

Implementację zaczniemy od utworzenia modelu którego migracja wygląda tak:

class CreateChatRoomUsers < ActiveRecord::Migration
  def self.up
    create_table :chat_rooms_users do |t|
      t.integer :user_id
      t.integer :chat_room_id
      t.integer :attemps

      t.timestamps
    end
  end

  def self.down
    drop_table :chat_rooms_users
  end
end

Sam model posiada informacje na temat tego do kogo należy. Posiada także dwie metody które odpowiadają za robienie tego co opisałem powyżej. Obie są metodami klasowymi ponieważ nie potrzebujemy tworzyć obiektu aby z nich korzystać.

Metoda connect - odpowiada za utworzenie relacji między userem i pokojem. Sprawdzamy najpierw czy istnieje już taka relacja (co oznacza że ktoś otwiera drugą kartę z tym samym pokojem) i jeśli tak, to podnosimy o 1 wartość pola attemps. Jeśli takiej relacji nie było (otwarcie pokoju po raz 1) to tworzymy taką relację.

Metoda disconnect - działa odwrotnie do poprzedniej. Zamykając pokój obniżamy wartość attemps o 1, chyba że ta była równa 1, wtedy usuwamy relację (ponieważ nie ma nas już w pokoju)

A oto kod:

class ChatRoomsUser < ActiveRecord::Base
  belongs_to :user
  belongs_to :chat_room

  def self.connect(user, room)
    el = self.find_by_user_id_and_chat_room_id(user.id, room.id)
    if el.nil?
      create(:user_id => user.id, :chat_room_id => room.id, :attemps => 1)
    else
      el.attemps += 1
      el.save
    end
  end

  def self.disconnect(user, room)
    el = self.find_by_user_id_and_chat_room_id(user.id, room.id)
    if el.attemps == 1
      el.delete
    else
      el.attemps-=1
      el.save
    end
  end

end

Aby wszystko działało należycie, do modelu ChatRoom musimy dodać opis naszej relacji:


has_many :chat_rooms_users
 has_many :users, :through => :chat_rooms_users

Dzięki temu możemy korzystać z metod w następujący sposób:


room = ChatRoom.find(params[:channels].first.to_i)
 user = User.find(params[:client_id])

 ChatRoomsUser.connect(user, room)

room = ChatRoom.find(params[:channels].first.to_i)
 user = User.find(params[:client_id])
 ChatRoomsUser.disconnect(user, room)

Części tutorialu:

  1. Juggernaut Rails Chat – część I (czym jest Juggernaut i jak go zainstalować)
  2. Juggernaut Rails Chat – część II (design)
  3. Juggernaut Rails Chat – część III (rejestracja i logowanie)
  4. Juggernaut Rails Chat – część IV (zarządzanie pokojami)
  5. Juggernaut Rails Chat – część V (łączenie użytkowników z pokojami)
  6. Juggernaut Rails Chat – część VI (odpalamy Juggernaut i nasz chat)
  7. Juggernaut Rails Chat – część VII (emotikonki i dźwięk)

Juggernaut Rails Chat – część IV (zarządzanie pokojami)

W części tej utworzymy system zarządzania pokojami.

Model pokoju

Model zawierać będzie takie pola jak:

  • Nazwa - tekst informujący o czym jest dany pokój (np Ruby on Rails)
  • ID - standardowo ;)
  • Skrót - przerobiona nazwa, same małe litery oraz zamiast spacji znaki podkreslenia (ruby_on_rails) - do URLi - skrót będzie generowany automatycznie z nazwy
  • Created_at, updated_at - standardowo :)

Z racji tego że przykłady jak tworzyć modele i kontrolery podałem w poprzedniej części tutoriala, tutaj ominę ten element i będę podawał tylko kod źródłowy bez kodu generatorów:

Najpierw kod migracji:

class CreateChatRooms < ActiveRecord::Migration
  def self.up
    create_table :chat_rooms do |t|
      t.string :name
      t.string :link

      t.timestamps
    end
  end

  def self.down
    drop_table :chat_rooms
  end
end

Kod modelu:

class ChatRoom < ActiveRecord::Base
  ROOM_MAX_LENGTH    = 14
  ROOM_MIN_LENGTH    = 5

  validates_uniqueness_of :name, :message => 'którą wybrałeś już istnieje'
  validates_length_of :name, :within =>  ROOM_MIN_LENGTH..ROOM_MAX_LENGTH,
    :too_long => "jest za długa",
    :too_short => "jest za krótka"
  validates_format_of :name, :with => /^[A-Z0-9_ ]*$/i,
    :message => 'jest nieprawidłowa'

  HUMANIZED_ATTRIBUTES = {
    :name => 'Nazwa'
  }

  def self.human_attribute_name(attr)
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end

  def save
    self.link = self.name.gsub(' ', '_')
    super
  end

end

Kontroler admin

Kontroler ten będzie RESTowy ponieważ idealnie się do tego nadaje ;)

Warto tutaj wspomnieć, że wbrew zasadom nie nazwę kontrolera ChatRooms tylko admin, ponieważ jest to jedyne zadanie admina w naszym czacie. Jednak gdybyście chcieli rozbudować ten czat to trzymajcie się prawidłowego nazewnictwa ;)

W routes.rb:


map.resources :chat_rooms, :controller => :admin

Nasz kontroler:

class AdminController < ApplicationController
  before_filter :authorize
  before_filter :only_admin

  layout "main"

  def index
    @chat_rooms = ChatRoom.find(:all)
  end

  def new
  end

  def create
    if request.post?
      @chat_room = ChatRoom.new(params[:chat_room])
      if @chat_room.save
        flash[:message] = 'Pokój został utworzony'
        redirect_to :action => :index
      else
        render :action => :new
      end
    else
      redirect_to :action => :index
    end
  end

  def edit
    @chat_room = ChatRoom.find(params[:id])
  end

  def update
    if request.put?
      @chat_room = ChatRoom.find(params[:id])
      @chat_room.update_attributes(params[:chat_room])
      if @chat_room.save
        flash[:message] = 'Pokój został uaktualniony'
        redirect_to :action => :index
      else
        render :action => :edit
      end
    else
      redirect_to :action => :index
    end
  end

  def destroy
    chat_room = ChatRoom.find(params[:id])
    chat_room.delete
    flash[:message] = 'Pokój został usunięty'
    redirect_to :action => :index
  end

end

Oraz odpowiednie widoki do niego:
index.rb:

<table id="room_list">
  <tr>
    <th>Nazwa</th>
    <th>Skrót</th>
    <th>Edytuj</th>
    <th>Usuń</th>
  </tr>
<% @chat_rooms.each do  |room| %>
  <tr>
    <td><%= room.name %></td>
    <td><%= room.link %></td>
    <td><%= link_to "Edytuj",edit_chat_room_path(room) %></td>
    <td><%= link_to "Usuń", chat_room_path(room), :confirm => "Jesteś pewien?", :method => :delete %></td>
  </tr>

<% end %>
</table>
<br/>
<%= link_to "Dodaj nowy pokój", new_chat_room_path %>

new.rb:

<h2>Tworzenie pokoju</h2>

<%= error_messages_for :chat_room %>

<% form_for :chat_room, :url => chat_rooms_path do |f| %>
  Nazwa: <%= f.text_field :name %>
  <%= submit_tag "Zapisz" %>
<% end %>

Oraz edit.rb:

<h2>Edycja pokoju</h2>


<%= error_messages_for :chat_room %>

<% form_for @chat_room, :method => :put do |f| %>
  Nazwa: <%= f.text_field :name %>
  <%= submit_tag "Zapisz" %>
<% end %>

I to by było na tyle na IV część toturiala. W tej części utworzyliśmy model pokoju i jego obsługę dla administratora. Od teraz możemy tworzyć i edytować pokoje w naszym czacie. Jak połączyć to z Juggernautem dowiemy się z części V i (może) VI jeśli część V okaże się za długa ;)

Części tutorialu:

  1. Juggernaut Rails Chat – część I (czym jest Juggernaut i jak go zainstalować)
  2. Juggernaut Rails Chat – część II (design)
  3. Juggernaut Rails Chat – część III (rejestracja i logowanie)
  4. Juggernaut Rails Chat – część IV (zarządzanie pokojami)
  5. Juggernaut Rails Chat – część V (łączenie użytkowników z pokojami)
  6. Juggernaut Rails Chat – część VI (odpalamy Juggernaut i nasz chat)
  7. Juggernaut Rails Chat – część VII (emotikonki i dźwięk)

admin

Copyright © 2024 Closer to Code

Theme by Anders NorenUp ↑