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

[WIP] Actioncable for messages #34

Open
wants to merge 7 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ gem 'rack-cors', require: 'rack/cors'
gem 'devise_token_auth'
gem 'active_model_serializers'
gem 'haversine'
gem 'redis'
gem 'faker'

group :development, :test do
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ GEM
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.2.1)
responders (3.0.0)
actionpack (>= 5.0)
railties (>= 5.0)
Expand Down Expand Up @@ -243,6 +244,7 @@ DEPENDENCIES
puma (~> 4.1)
rack-cors
rails (~> 6.0.3, >= 6.0.3.1)
redis
rspec-rails
shoulda-matchers
spring
Expand Down
17 changes: 17 additions & 0 deletions app/channels/application_cable/connection.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
# frozen_string_literal: true

module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user

def connect
self.current_user = find_verified_user
end

private

def find_verified_user
if verified_user = User.find_by(email: request.params[:uid])
verified_user
else
reject_unauthorized_connection
end
end
end
end
23 changes: 23 additions & 0 deletions app/channels/offer_conversation_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

class OfferConversationChannel < ApplicationCable::Channel
def subscribed
stream_from offer_identifier
end

def unsubscribed
# Any cleanup needed when channel is unsubscribed
end

private

def offer_identifier
if params[:room][:offer_id]
channel = "offer_conversation_#{params[:room][:offer_id]}"
else
connection.transmit identifier: params, message: 'No params specified.'
reject && return
end
channel
end
end
1 change: 1 addition & 0 deletions app/controllers/api/messages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def create
message = Offer.find(params[:offer_id]).conversation
.messages.create(content: params[:content], sender: current_user)
if message.persisted?
# ActionCable.server.broadcast("offer_conversation_#{params[:offer_id]}", message: message.content )
render status: :created
else
render_error_message(message.errors)
Expand Down
11 changes: 11 additions & 0 deletions app/models/message.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
# frozen_string_literal: true

class Message < ApplicationRecord
belongs_to :conversation
belongs_to :sender, class_name: 'User'
validates_presence_of :content
before_create :validate_user_is_authorized
after_save :broadcast

private

def validate_user_is_authorized
is_valid_user = sender == conversation.offer.helper || sender == conversation.offer.request.requester
raise StandardError, 'You are not authorized to do this!' unless is_valid_user
end

def broadcast
data = { content: content, sender_id: sender.id }
ActionCable.server.broadcast(
"offer_conversation_#{conversation.offer.id}",
message: data
)
end
end
15 changes: 6 additions & 9 deletions config/cable.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
development:
adapter: async

test:
adapter: test

production:
redis: &redis
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: reQuest_api_production
url: redis://localhost:6379/1
production: *redis
development: *redis
test:
adapter: test
6 changes: 4 additions & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# frozen_string_literal: true

Rails.application.routes.draw do
mount ActionCable.server => '/cable'
mount_devise_token_auth_for 'User', at: 'api/auth'
namespace :api do
resources :messages, only: [:create]
resources :offers, only: %i[create show update]
resources :offers, only: %i[create show update] do
resources :messages, only: [:create]
end
resources :karma_points, only: [:index], constraints: { format: 'json' }
resources :requests, only: %i[index], constraints: { format: 'json' }
namespace :my_request do
Expand Down
13 changes: 13 additions & 0 deletions spec/channels/connection_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

RSpec.describe ApplicationCable::Connection, type: :channel do
let(:user) { create(:user) }
it 'successfully connects' do
connect "/cable?uid=#{user.email}"
expect(connection.current_user).to eq user
end

it 'rejects connection' do
expect { connect '/cable' }.to have_rejected_connection
end
end
36 changes: 36 additions & 0 deletions spec/channels/offer_conversation_channel_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe OfferConversationChannel, type: :channel do
let(:user) { create(:user) }
describe 'subscription' do
before { stub_connection current_user: user }
describe 'valid parameters' do
before do
subscribe(room: { offer_id: 234 })
end

it {
expect(subscription).to be_confirmed
}

it {
expect(subscription).to have_stream_from("offer_conversation_234")
}
end

describe 'without valid params' do

before do
subscribe(room: {})
end

it { expect(subscription).to be_rejected }

it {
expect(transmissions.last).to eq('No params specified.')
}
end
end
end
9 changes: 9 additions & 0 deletions spec/models/message_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,13 @@
expect(create(:message)).to be_valid
end
end

describe 'broadcast' do
it 'should broadcast after creation' do
message = create(:message)
expect {
message.save
}.to have_broadcasted_to("offer_conversation_#{Message.last.conversation.offer.id}")
end
end
end
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'spec_helper'

require 'rspec/rails'

ActiveRecord::Migration.maintain_test_schema!
Expand Down
49 changes: 28 additions & 21 deletions spec/requests/api/conversions/users_can_send_messages_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
RSpec.describe 'POST /message users can post messages' do
# frozen_string_literal: true

RSpec.describe 'POST /offers/:offer_id/message users can post messages' do
let(:requester) { create(:user, email: '[email protected]') }
let(:req_creds) { requester.create_new_auth_token }
let(:req_headers) { { HTTP_ACCEPT: 'application/json' }.merge!(req_creds) }
Expand All @@ -10,15 +12,15 @@
let(:third_user) { create(:user) }
let(:third_user_credentials) { third_user.create_new_auth_token }
let(:third_user_headers) { { HTTP_ACCEPT: 'application/json' }.merge!(third_user_credentials) }

let(:request) { create(:request, requester: requester) }
let(:offer) { create(:offer, request: request, helper: helper) }

let!(:message) { create(:message, conversation: offer.conversation, sender: helper) }

describe 'successfully as the requester' do
before do
post '/api/messages', headers: req_headers, params: { offer_id: offer.id, content: "message content" }
post "/api/offers/#{offer.id}/messages", headers: req_headers, params: { content: 'message content' }
end

it 'gives a success status' do
Expand All @@ -27,13 +29,19 @@

it 'creates a message based on the params' do
offer.reload
expect(offer.conversation.messages.last['content']).to eq "message content"
expect(offer.conversation.messages.last['content']).to eq 'message content'
end

it 'dispatches a websocket message' do
expect(
ActionCable.server.worker_pool.executor.worker_task_completed
).to eq 1
end
end

describe 'successfully as the helper' do
before do
post '/api/messages', headers: helper_headers, params: { offer_id: offer.id, content: "message content" }
post "/api/offers/#{offer.id}/messages", headers: helper_headers, params: { content: 'message content' }
end

it 'gives a success status' do
Expand All @@ -42,14 +50,14 @@

it 'creates a message based on the params' do
offer.reload
expect(offer.conversation.messages.last['content']).to eq "message content"
expect(offer.conversation.messages.last['content']).to eq 'message content'
end
end

describe 'unsuccessfully' do
describe 'without content' do
before do
post '/api/messages', headers: helper_headers, params: { offer_id: offer.id }
post "/api/offers/#{offer.id}/messages", headers: helper_headers
end

it 'gives an error status' do
Expand All @@ -61,23 +69,23 @@
end
end

describe 'without offer_id' do
before do
post '/api/messages', headers: helper_headers, params: { content: 'message content' }
end
# describe 'without offer_id' do
# before do
# post '/api/messages', headers: helper_headers, params: { content: 'message content' }
# end

it 'gives an error status' do
expect(response).to have_http_status 422
end
# it 'gives an error status' do
# expect(response).to have_http_status 422
# end

it 'gives an error message' do
expect(response_json['message']).to eq "Couldn't find Offer without an ID"
end
end
# it 'gives an error message' do
# expect(response_json['message']).to eq "Couldn't find Offer without an ID"
# end
# end

describe 'unauthorized' do
before do
post '/api/messages', headers: third_user_headers, params: { offer_id: offer.id, content: 'message content' }
post "/api/offers/#{offer.id}/messages", headers: third_user_headers, params: { content: 'message content' }
end

it 'gives an error status' do
Expand All @@ -91,10 +99,9 @@

describe 'unauthenticated' do
before do
post '/api/messages', params: { offer_id: offer.id, content: 'message content' }
post "/api/offers/#{offer.id}/messages", params: { content: 'message content' }
end


it 'gives an error status' do
expect(response).to have_http_status 401
end
Expand Down