Chris Geihsler
@seejee
Joe Stanco, Packt Publishing
http://i.stack.imgur.com/BTm1H.png
The safest way to respond to a thrown error is to shut down the process.
function TeacherRoster() { this.teachers = {}; }
TeacherRoster.prototype.add = function(teacher, callback) {
this.teachers[teacher.id] = teacher;
callback(null, teacher);
};
TeacherRoster.prototype.find = function(id, callback) {
var t = this.teachers[id];
callback(null, t);
};
TeacherRoster.prototype.canAcceptMoreStudents = function(teacherId, callback) {
this.find(teacherId, function(err, teacher) {
var canAccept = teacher.students.length < 5;
callback(null, canAccept);
});
};
TeacherRoster.prototype.claimStudent = function(teacherId, studentId, callback) {
this.find(teacherId, function(err, teacher) {
teacher.students.push(studentId);
callback(null, teacher);
});
};
TeacherRoster.prototype.stats = function(callback) {
var stats = { total: _.keys(@teachers).length };
callback(null, stats);
};
defmodule ElixirChat.TeacherRoster do
def new do
HashDict.new
end
def add(roster, teacher) do
teacher = Map.merge(teacher, %{student_ids: []})
Dict.put(roster, teacher.id, teacher)
end
def find(roster, teacher_id) do
roster[teacher_id]
end
def can_accept_more_students?(teacher) do
length(teacher.student_ids) < 5
end
def claim_student(roster, teacher_id, student_id) do
Dict.update!(roster, teacher_id, fn(t) ->
%{t | student_ids: t.student_ids ++ [student_id]}
end)
end
def stats(roster) do
%{
total: length(Dict.values(roster)),
}
end
end
iex(1)> alias ElixirChat.TeacherRoster, as: TeacherRoster
nil
iex(2)> roster = TeacherRoster.new
#HashDict<[]>
iex(3)> TeacherRoster.add(roster, %{id: 1})
#HashDict<[{1, %{id: 1, student_ids: []}}]>
iex(4)> roster
#HashDict<[]>
iex(5)> roster = TeacherRoster.add(roster, %{id: 1})
#HashDict<[{1, %{id: 1, student_ids: []}}]>
iex(6)> roster
#HashDict<[{1, %{id: 1, student_ids: []}}]>
iex(7)> roster = TeacherRoster.add(roster, %{id: 2})
#HashDict<[{2, %{id: 2, student_ids: []}}, {1, %{id: 1, student_ids: []}}]>
iex(8)> TeacherRoster.stats(roster)
%{total: 2}
iex(9)> TeacherRoster.new
|> TeacherRoster.add(%{id: 10})
|> TeacherRoster.add(%{id: 20})
|> TeacherRoster.add(%{id: 30})
|> TeacherRoster.stats
%{total: 3}
defmodule ElixirChat.TeacherRoster do
def new do
end
def add(roster, teacher) do
end
def find(roster, teacher_id) do
end
def can_accept_more_students?(teacher) do
end
def claim_student(roster, teacher_id, student_id) do
end
def stats(roster) do
end
end
function ChatLifetime(teachers, students, chatLog) {
this.teachers = teachers;
this.students = students;
this.chatLog = chatLog;
}
ChatLifetime.prototype.createChatForNextStudent = function(teacherId, callback) {
var _this = this;
_this.teachers.canAcceptMoreStudents(teacherId, function(err, canAccept) {
if(canAccept) {
_this.students.next(function(err, student) {
if(student) {
_this.students.assignTo(student.id, teacherId, function(err) {
_this.teachers.claimStudent(teacherId, student.id, function(err) {
_this.chatLog.new(teacherId, student.id, function(err, chat) {
callback(null, chat);
});
});
});
}
});
}
});
};
defmodule ElixirChat.ChatLifetimeServer do
use ExActor.GenServer, export: :chat_lifetime_server
alias ElixirChat.ChatLogServer, as: Chats
alias ElixirChat.TeacherRosterServer, as: Teachers
alias ElixirChat.StudentRosterServer, as: Students
defcall create_chat_for_next_student(teacher_id), state: _ do
chat = nil
if Teachers.can_accept_more_students?(teacher_id) do
student_id = Students.next_student(teacher_id)
if student_id do
:ok = Students.assign_student_to_teacher(student_id, teacher_id)
:ok = Teachers.claim_student(teacher_id, student_id)
chat = Chats.new(teacher_id, student_id)
end
end
reply(chat)
end
end
function PresenceChannel(faye) {
// continued
this.chatLifetime = new ChatLifetime(this.teachers, this.students, this.chatLog)
this.chatChannel = new ChatChannel(this.faye, this.chatLog, this.chatLifetime)
}
PresenceChannel.prototype.attach = function() {
// continued
this.faye.subscribe('/presence/claim_student', this.onClaimStudent.bind(this));
}
PresenceChannel.prototype.onClaimStudent = function(payload) {
var _this = this;
_this.chatLifetime.createChatForNextStudent(payload.teacherId, function(err, chat) {
_this.chatChannel.attach(chat.id);
_this.publishNewChat(chat);
});
};
PresenceChannel.prototype.publishNewChat = function(chat) {
var teacherChannel = "/presence/new_chat/teacher/" + chat.teacherId;
var studentChannel = "/presence/new_chat/student/" + chat.studentId;
this.faye.publish(teacherChannel, chat.teacherChannels);
this.faye.publish(studentChannel, chat.studentChannels);
};
defmodule ElixirChat.PresenceChannel do
use Phoenix.Channel
alias ElixirChat.ChatLifetimeServer, as: Chats
alias ElixirChat.TeacherRosterServer, as: Teachers
alias ElixirChat.StudentRosterServer, as: Students
def join(socket, topic, %{"userId" => id, "role" => "teacher"}) do
Teachers.add(%{id: id})
socket = assign(socket, :id, id)
{:ok, socket}
end
def join(socket, topic, %{"userId" => id, "role" => "student"}) do
Students.add(%{id: id})
socket = assign(socket, :id, id)
broadcast_status
{:ok, socket}
end
def broadcast_status do
data = %{
teachers: Teachers.stats,
students: Students.stats
}
broadcast "presence", "teachers", "user:status", data
end
end
defmodule ElixirChat.PresenceChannel do
def leave(socket, _message) do
Students.remove(socket.assigns[:id])
broadcast_status
socket
end
def event(socket, "claim:student", %{"teacherId" => teacher_id}) do
chat = Chats.create_chat_for_next_student(teacher_id)
if chat do
reply socket, "new:chat:#{chat.teacher_id}", chat
broadcast "presence", "student:#{chat.student_id}", "new:chat", chat
end
socket
end
end
Text
Chris Geihsler
@seejee