Skip to content

Commit 6be99d6

Browse files
richajustin808
authored andcommitted
Actioncable - React (#355)
Actioncable with React * updates comments in real time * added 'Refresh' link to load comments manually
1 parent a451b3b commit 6be99d6

22 files changed

+102
-18
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ gem "coffee-rails"
2727

2828
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
2929
gem "jbuilder"
30+
gem 'redis'
3031

3132
# bundle exec rake doc:rails generates the API under doc/api.
3233
gem "sdoc", group: :doc

Gemfile.lock

+2
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ GEM
206206
foreman
207207
rails (>= 3.2)
208208
rainbow (~> 2.1)
209+
redis (3.3.0)
209210
rspec-core (3.5.4)
210211
rspec-support (~> 3.5.0)
211212
rspec-expectations (3.5.0)
@@ -330,6 +331,7 @@ DEPENDENCIES
330331
rails-html-sanitizer
331332
rainbow
332333
react_on_rails (~> 6.1)
334+
redis
333335
rspec-rails (~> 3)
334336
rspec-retry
335337
rubocop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module ApplicationCable
2+
class Channel < ActionCable::Channel::Base
3+
end
4+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module ApplicationCable
2+
class Connection < ActionCable::Connection::Base
3+
end
4+
end

app/channels/comments_channel.rb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class CommentsChannel < ApplicationCable::Channel
2+
def subscribed
3+
stream_from "comments"
4+
end
5+
end

app/jobs/application_job.rb

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class ApplicationJob < ActiveJob::Base
2+
end

app/jobs/comment_relay_job.rb

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class CommentRelayJob < ApplicationJob
2+
def perform(comment)
3+
ActionCable.server.broadcast "comments", comment unless comment.destroyed?
4+
end
5+
end

app/models/comment.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
class Comment < ActiveRecord::Base
2-
validates_presence_of :author
3-
validates_presence_of :text
2+
validates :author, :text, presence: true
3+
after_commit { CommentRelayJob.perform_later(self) }
44
end

app/views/layouts/application.html.erb

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
<body>
2323

2424
<%= react_component "NavigationBarApp" %>
25-
2625
<div class="container">
2726
<%= yield %>
2827
</div>

client/app/bundles/comments/actions/commentsActionCreators.js

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ export function fetchCommentsFailure(error) {
2727
};
2828
}
2929

30+
export function messageReceived(comment) {
31+
return {
32+
type: actionTypes.MESSAGE_RECEIVED,
33+
comment,
34+
};
35+
}
36+
3037
export function submitCommentSuccess(comment) {
3138
return {
3239
type: actionTypes.SUBMIT_COMMENT_SUCCESS,

client/app/bundles/comments/components/CommentBox/CommentBox.jsx

+34-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import React, { PropTypes } from 'react';
44
import CommentForm from './CommentForm/CommentForm';
55
import CommentList, { CommentPropTypes } from './CommentList/CommentList';
66
import css from './CommentBox.scss';
7+
import Immutable from 'immutable';
8+
import ActionCable from 'actioncable';
79

810
export default class CommentBox extends BaseComponent {
911
static propTypes = {
@@ -19,14 +21,43 @@ export default class CommentBox extends BaseComponent {
1921
}).isRequired,
2022
};
2123

24+
constructor() {
25+
super();
26+
_.bindAll(this, [
27+
'refreshComments',
28+
]);
29+
}
30+
31+
subscribeChannel() {
32+
const { messageReceived } = this.props.actions;
33+
const protocol = window.location.protocol === "https:" ? "wss://" : "ws://"
34+
const cable = ActionCable.createConsumer(protocol+window.location.hostname+":"+window.location.port+"/cable");
35+
cable.subscriptions.create({channel: "CommentsChannel"}, {
36+
connected: () => {
37+
console.log("connected")
38+
},
39+
disconnected: () => {
40+
console.log("disconnected")
41+
},
42+
received: (comment) => {
43+
messageReceived(Immutable.fromJS(comment));
44+
}
45+
});
46+
}
47+
2248
componentDidMount() {
2349
const { fetchComments } = this.props.actions;
2450
fetchComments();
25-
this.intervalId = setInterval(fetchComments, this.props.pollInterval);
51+
this.subscribeChannel();
2652
}
2753

2854
componentWillUnmount() {
29-
clearInterval(this.intervalId);
55+
App.cable.subscriptions.remove({ channel: "CommentsChannel" });
56+
}
57+
58+
refreshComments() {
59+
const { fetchComments } = this.props.actions;
60+
fetchComments();
3061
}
3162

3263
render() {
@@ -43,6 +74,7 @@ export default class CommentBox extends BaseComponent {
4374
<h2>
4475
Comments {data.get('isFetching') && 'Loading...'}
4576
</h2>
77+
<a href="javascript:void(0)" onClick={this.refreshComments}>Refresh</a>
4678
<p>
4779
<b>Text</b> supports Github Flavored Markdown.
4880
Comments older than 24 hours are deleted.<br />

client/app/bundles/comments/constants/commentsConstants.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const FETCH_COMMENTS_FAILURE = 'FETCH_COMMENTS_FAILURE';
33

44
export const SUBMIT_COMMENT_SUCCESS = 'SUBMIT_COMMENT_SUCCESS';
55
export const SUBMIT_COMMENT_FAILURE = 'SUBMIT_COMMENT_FAILURE';
6+
export const MESSAGE_RECEIVED = 'MESSAGE_RECEIVED';
67

78
export const SET_IS_FETCHING = 'SET_IS_FETCHING';
89
export const SET_IS_SAVING = 'SET_IS_SAVING';

client/app/bundles/comments/reducers/commentsReducer.js

+10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ export default function commentsReducer($$state = $$initialState, action = null)
3232
});
3333
}
3434

35+
case actionTypes.MESSAGE_RECEIVED: {
36+
return $$state.withMutations(state => (
37+
state
38+
.updateIn(
39+
['$$comments'],
40+
$$comments => ($$comments.findIndex(com => com.get('id') === comment.get('id')) === -1 ? $$comments.unshift(Immutable.fromJS(comment)) : $$comments),
41+
)
42+
));
43+
}
44+
3545
case actionTypes.SUBMIT_COMMENT_SUCCESS: {
3646
return $$state.withMutations(state => (
3747
state

client/npm-shrinkwrap.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"lint": "eslint --ext .js,.jsx ."
4040
},
4141
"dependencies": {
42+
"actioncable": "^5.0.1",
4243
"autoprefixer": "^6.5.3",
4344
"axios": "^0.15.2",
4445
"babel": "^6.5.2",

client/webpack.client.base.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ module.exports = {
2727
// vendor-bundle.js. Note, if we added some library here, but don't use it in the
2828
// app-bundle.js, then we just wasted a bunch of space.
2929
'axios',
30+
'actioncable',
3031
'classnames',
3132
'immutable',
3233
'lodash',

config/application.rb

+1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ class Application < Rails::Application
1111
# Settings in config/environments/* take precedence over those specified here.
1212
# Application configuration should go into files in config/initializers
1313
# -- all .rb files in that directory are automatically loaded.
14+
config.action_cable.allowed_request_origins = [Rails.application.secrets.action_cable_url]
1415
end
1516
end

config/cable.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.
22
production:
33
adapter: redis
4-
url: redis://localhost:6379/1
4+
url: <%= ENV["REDISCLOUD_URL"] %>
55

66
development:
7-
adapter: async
7+
adapter: redis
8+
url: redis://localhost:6379/1
89

910
test:
1011
adapter: async

config/database.yml

+10-10
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,21 @@
2626
# database: db/production.sqlite3
2727

2828
# Uncomment below for a setup with just postgres and change your Gemfile to reflect this
29-
default: &default
30-
adapter: postgresql
31-
username:
32-
password:
29+
default: &default
30+
adapter: postgresql
31+
username:
32+
password:
3333

34-
development:
35-
<<: *default
36-
database: react_webpack_dev
34+
development:
35+
<<: *default
36+
database: react_webpack_dev
3737

3838
# Warning: The database defined as "test" will be erased and
3939
# re-generated from your development database when you run "rake".
4040
# Do not set this db to the same as development or production.
41-
test:
42-
<<: *default
43-
database: react_webpack_test
41+
test:
42+
<<: *default
43+
database: react_webpack_test
4444

4545
production:
4646
<<: *default

config/environments/production.rb

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040

4141
# Action Cable endpoint configuration
4242
# config.action_cable.url = 'wss://example.com/cable'
43-
# config.action_cable.allowed_request_origins = [ 'http://example.com', /https:\/\/fanyv88.com:443\/http\/example.*/ ]
4443

4544
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
4645
config.force_ssl = true

config/routes.rb

+1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
get "react-router(/*all)", to: "pages#index"
1414

1515
resources :comments
16+
mount ActionCable.server => "/cable"
1617
end

config/secrets.yml

+3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212

1313
development:
1414
secret_key_base: 231bf79489c63f8c8facd7bf27db1c2582a42a7f4302fccdb74ef35bc5dc91fb4e19dbf167f3003bdb4073818dfab4a9916890d193d535a7be458dbef1609800
15+
action_cable_url : http://localhost:3000
1516

1617
test:
1718
secret_key_base: 1ab8adbcf8410aebbce9b6dd6db7b5d090297bd22cf789b91ff44ae02711e8c128453d3e5c97eadf9066efe1a1e0dc1921faf7314d566c114d3ed60ae7ea614c
19+
action_cable_url : http://localhost:3000
1820

1921
# Do not keep production secrets in the repository,
2022
# instead read values from the environment.
2123
production:
2224
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
25+
action_cable_url : <%= ENV["SERVER_PORT"] %>

0 commit comments

Comments
 (0)