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:
- Możemy skorzystać z has_and_belongs_to tworząc tabelę przechodnią bez tworzenia dla niej modelu
- 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:
- Juggernaut Rails Chat – część I (czym jest Juggernaut i jak go zainstalować)
- Juggernaut Rails Chat – część II (design)
- Juggernaut Rails Chat – część III (rejestracja i logowanie)
- 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)