Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for lifecycle callbacks. #304

Merged
merged 8 commits into from
Sep 20, 2015
2 changes: 1 addition & 1 deletion app/volt/tasks/query_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def add_listener(collection, query)
# For requests from the client (with @channel), we track the channel
# so we can send the results back. Server side requests don't stay live,
# they simply return to :dirty once the query is issued.
@channel.user_id = Volt.current_user_id
@channel.update_user_id(Volt.current_user_id)

# live_query.add_channel(@channel)
end
Expand Down
6 changes: 6 additions & 0 deletions app/volt/tasks/user_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ def login(login_info)
end
end
end

def logout
# Remove user_id from user's channel
@channel.update_user_id(nil) if @channel
end

end
39 changes: 39 additions & 0 deletions lib/volt/server/socket_connection_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@ def initialize(session, *args)

@@channels ||= []
@@channels << self

# Trigger a client connect event
@@dispatcher.volt_app.trigger!("client_connect")

end

def update_user_id(user_id)
if !@user_id && user_id
# If there is currently no user id associated with this channel
# and we get a new valid user_id, set it then trigger a
# user_connect event
@user_id = user_id
@@dispatcher.volt_app.trigger!("user_connect", @user_id)
elsif @user_id && !user_id
# If there is currently a user id associated with this channel
# and we get a nil user id, trigger a user_disconnect event then
# set the id to nil
@@dispatcher.volt_app.trigger!("user_disconnect", @user_id)
@user_id = user_id
else
# Otherwise, lets just set the id (should never really run)
@user_id = user_id
end
end

def self.dispatcher=(val)
Expand All @@ -25,6 +48,10 @@ def self.dispatcher
@@dispatcher
end

def self.channels
@@channels
end

# Sends a message to all, optionally skipping a users channel
def self.send_message_all(skip_channel = nil, *args)
return unless defined?(@@channels)
Expand Down Expand Up @@ -85,6 +112,18 @@ def closed

begin
@@dispatcher.close_channel(self)

# Check for volt_app (@@dispatcher could be an ErrorDispatcher)
if @@dispatcher.respond_to?(:volt_app)
# Trigger a client disconnect event
@@dispatcher.volt_app.trigger!("client_disconnect")

# Trigger a user disconnect event even if the user hasn't logged out
if @user_id
@@dispatcher.volt_app.trigger!("user_disconnect", @user_id)
end
end

rescue DRb::DRbConnError => e
# ignore drb read of @@dispatcher error if child has closed
end
Expand Down
3 changes: 3 additions & 0 deletions lib/volt/volt/server_setup/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
module Volt
module ServerSetup
module App
# Include Eventable to allow for lifecycle callbacks
include Eventable

# The root url is where the volt app is mounted
attr_reader :root_url
# The app url is where the app folder (and sprockets) is mounted
Expand Down
4 changes: 4 additions & 0 deletions lib/volt/volt/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ def login(username, password)
end

def logout
# Notify the backend so we can remove the user_id from the user's channel
UserTasks.logout

# Remove the cookie so user is no longer logged in
Volt.current_app.cookies.delete(:user_id)
end

Expand Down
1 change: 1 addition & 0 deletions spec/apps/kitchen_sink/app/main/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
client '/require_test', action: 'require_test'
client '/images', action: 'images'
client '/login_from_task', action: 'login_from_task'
client '/callbacks', action: 'callbacks'

# Events
client '/events', component: 'main', controller: 'events', action: 'index'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ def do_login_from_task
LoginTasks.login_first_user
end

def callbacks
end

private

# the main template contains a #template binding that shows another
Expand Down
18 changes: 18 additions & 0 deletions spec/apps/kitchen_sink/app/main/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,22 @@ class User < Volt::User

validate login_field, unique: true, length: 8
validate :email, email: true

unless RUBY_PLATFORM == "opal"
Volt.current_app.on("user_connect") do |user_id|
begin
Volt.current_app.store.users.where(id: user_id).first.sync._event_triggered = "user_connect"
rescue
#we rescue as this callback will also get called from the SocketConnectionHandler specs (and will fail)
end
end

Volt.current_app.on("user_disconnect") do |user_id|
begin
Volt.current_app.store.users.where(id: user_id).first.sync._event_triggered = "user_disconnect"
rescue
#we rescue as this callback will also get called from the SocketConnectionHandler specs (and will fail)
end
end
end
end
7 changes: 7 additions & 0 deletions spec/apps/kitchen_sink/app/main/views/main/callbacks.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<:Title>
Lifecycle Callbacks

<:Body>
<h1>Lifecycle Callbacks</h1>

{{ store.users.first._event_triggered }}
31 changes: 31 additions & 0 deletions spec/integration/callbacks_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require 'spec_helper'

describe 'lifecycle callbacks', type: :feature, sauce: true do

context 'with a user' do
before do
# Add the user
store._users! << { email: 'test@test.com', password: 'awes0mesEcRet', name: 'Test Account 9550' }
end

it 'should trigger a user_connect event when a user logs in and a user_disconnect event when a user logs out' do
visit '/'

click_link 'Login'

fields = all(:css, 'form .form-control')
fields[0].set('test@test.com')
fields[1].set('awes0mesEcRet')
click_button 'Login'

visit '/callbacks'

expect(page).to have_content('user_connect')

click_link 'Test Account 9550'
click_link 'Logout'

expect(page).to have_content('user_disconnect')
end
end
end
94 changes: 94 additions & 0 deletions spec/server/socket_connection_handler_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
require 'spec_helper'

if RUBY_PLATFORM != 'opal'
require 'volt/server/socket_connection_handler'
describe Volt::SocketConnectionHandler do
let(:fake_dispatcher) { double("Dispatcher", volt_app: Volt.current_app)}

let(:fake_session) {double("Faye::WebSocket")}

before do
Volt::SocketConnectionHandler.dispatcher = fake_dispatcher
end

let(:connection_handler) { Volt::SocketConnectionHandler.new(fake_session) }

subject!{ connection_handler }

describe '#creation' do

context 'with valid session' do

it 'should append itself to @@channels' do
expect(Volt::SocketConnectionHandler.channels).to include(subject)
end

it 'should trigger a client_connect event' do
val = 0

Volt.current_app.on("client_connect") do
val = 1
end

# TODO: change the way this is handled, we shouldn't have to instantiate a new SocketConnectionHandler just for this test
expect{Volt::SocketConnectionHandler.new(fake_session)}.to change{val}.by(1)
end
end
end

describe '#update' do
context 'with nil user_id' do
it 'should trigger a user_connect event when given a valid user_id' do
id = 0
Volt.current_app.on("user_connect") do |user_id|
id = user_id
end

expect{subject.update_user_id(123)}.to change{id}.by(123)
end
end

context 'with valid user_id' do
it 'should trigger a user_disconnect event when given a nil user_id' do
id = 0
Volt.current_app.on("user_disconnect") do |user_id|
id = user_id
end

subject.user_id = 123

expect{subject.update_user_id(nil)}.to change{id}.by(123)
end
end
end

describe '#close' do
it 'should trigger a client_disconnect event' do
allow(Volt::SocketConnectionHandler.dispatcher).to receive(:close_channel).and_return true

val = 0

Volt.current_app.on("client_disconnect") do
val = 1
end

expect{subject.closed}.to change{val}.by(1)
end
context 'with valid user_id' do
it 'should trigger a user_disconnect event' do
allow(Volt::SocketConnectionHandler.dispatcher).to receive(:close_channel).and_return true

subject.user_id = 123

id = 0

Volt.current_app.on("user_disconnect") do |user_id|
id = user_id
end

expect{subject.closed}.to change{id}.by(123)
end
end
end
end
end