diff --git a/Gemfile b/Gemfile index f5a39a4d9..338c45e1d 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem "coffee-rails" # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem "jbuilder" +gem 'redis' # bundle exec rake doc:rails generates the API under doc/api. gem "sdoc", group: :doc diff --git a/Gemfile.lock b/Gemfile.lock index 4839f1b35..233be69f4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,6 +206,7 @@ GEM foreman rails (>= 3.2) rainbow (~> 2.1) + redis (3.3.0) rspec-core (3.5.4) rspec-support (~> 3.5.0) rspec-expectations (3.5.0) @@ -330,6 +331,7 @@ DEPENDENCIES rails-html-sanitizer rainbow react_on_rails (~> 6.1) + redis rspec-rails (~> 3) rspec-retry rubocop diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d67269728 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..0ff5442f4 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/channels/comments_channel.rb b/app/channels/comments_channel.rb new file mode 100644 index 000000000..cf1a1c535 --- /dev/null +++ b/app/channels/comments_channel.rb @@ -0,0 +1,5 @@ +class CommentsChannel < ApplicationCable::Channel + def subscribed + stream_from "comments" + end +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/jobs/comment_relay_job.rb b/app/jobs/comment_relay_job.rb new file mode 100644 index 000000000..bce0317e5 --- /dev/null +++ b/app/jobs/comment_relay_job.rb @@ -0,0 +1,5 @@ +class CommentRelayJob < ApplicationJob + def perform(comment) + ActionCable.server.broadcast "comments", comment unless comment.destroyed? + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index c86b86c6b..90faf1e93 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,4 +1,4 @@ class Comment < ActiveRecord::Base - validates_presence_of :author - validates_presence_of :text + validates :author, :text, presence: true + after_commit { CommentRelayJob.perform_later(self) } end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 841075b77..ef1b805db 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -22,7 +22,6 @@
<%= react_component "NavigationBarApp" %> -
Text supports Github Flavored Markdown.
Comments older than 24 hours are deleted.
diff --git a/client/app/bundles/comments/constants/commentsConstants.js b/client/app/bundles/comments/constants/commentsConstants.js
index db3c55a87..03749319a 100644
--- a/client/app/bundles/comments/constants/commentsConstants.js
+++ b/client/app/bundles/comments/constants/commentsConstants.js
@@ -3,6 +3,7 @@ export const FETCH_COMMENTS_FAILURE = 'FETCH_COMMENTS_FAILURE';
export const SUBMIT_COMMENT_SUCCESS = 'SUBMIT_COMMENT_SUCCESS';
export const SUBMIT_COMMENT_FAILURE = 'SUBMIT_COMMENT_FAILURE';
+export const MESSAGE_RECEIVED = 'MESSAGE_RECEIVED';
export const SET_IS_FETCHING = 'SET_IS_FETCHING';
export const SET_IS_SAVING = 'SET_IS_SAVING';
diff --git a/client/app/bundles/comments/reducers/commentsReducer.js b/client/app/bundles/comments/reducers/commentsReducer.js
index 91d4a9a06..91cbc5a62 100644
--- a/client/app/bundles/comments/reducers/commentsReducer.js
+++ b/client/app/bundles/comments/reducers/commentsReducer.js
@@ -32,6 +32,16 @@ export default function commentsReducer($$state = $$initialState, action = null)
});
}
+ case actionTypes.MESSAGE_RECEIVED: {
+ return $$state.withMutations(state => (
+ state
+ .updateIn(
+ ['$$comments'],
+ $$comments => ($$comments.findIndex(com => com.get('id') === comment.get('id')) === -1 ? $$comments.unshift(Immutable.fromJS(comment)) : $$comments),
+ )
+ ));
+ }
+
case actionTypes.SUBMIT_COMMENT_SUCCESS: {
return $$state.withMutations(state => (
state
diff --git a/client/npm-shrinkwrap.json b/client/npm-shrinkwrap.json
index 58cce0a98..9db0f9cbd 100644
--- a/client/npm-shrinkwrap.json
+++ b/client/npm-shrinkwrap.json
@@ -44,6 +44,11 @@
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
"dev": true
},
+ "actioncable": {
+ "version": "5.0.1",
+ "from": "actioncable@latest",
+ "resolved": "https://registry.npmjs.org/actioncable/-/actioncable-5.0.1.tgz"
+ },
"ajv": {
"version": "4.9.0",
"from": "ajv@>=4.7.0 <5.0.0",
diff --git a/client/package.json b/client/package.json
index d197a1160..253b253c0 100644
--- a/client/package.json
+++ b/client/package.json
@@ -39,6 +39,7 @@
"lint": "eslint --ext .js,.jsx ."
},
"dependencies": {
+ "actioncable": "^5.0.1",
"autoprefixer": "^6.5.3",
"axios": "^0.15.2",
"babel": "^6.5.2",
diff --git a/client/webpack.client.base.config.js b/client/webpack.client.base.config.js
index 1a41918c3..6d29af922 100644
--- a/client/webpack.client.base.config.js
+++ b/client/webpack.client.base.config.js
@@ -27,6 +27,7 @@ module.exports = {
// vendor-bundle.js. Note, if we added some library here, but don't use it in the
// app-bundle.js, then we just wasted a bunch of space.
'axios',
+ 'actioncable',
'classnames',
'immutable',
'lodash',
diff --git a/config/application.rb b/config/application.rb
index 57d8053f1..c0d8ea141 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -11,5 +11,6 @@ class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
+ config.action_cable.allowed_request_origins = [Rails.application.secrets.action_cable_url]
end
end
diff --git a/config/cable.yml b/config/cable.yml
index aa4e83274..c29230ed7 100644
--- a/config/cable.yml
+++ b/config/cable.yml
@@ -1,10 +1,11 @@
# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.
production:
adapter: redis
- url: redis://localhost:6379/1
+ url: <%= ENV["REDISCLOUD_URL"] %>
development:
- adapter: async
+ adapter: redis
+ url: redis://localhost:6379/1
test:
adapter: async
diff --git a/config/database.yml b/config/database.yml
index 0662a56ac..c96d90929 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -26,21 +26,21 @@
# database: db/production.sqlite3
# Uncomment below for a setup with just postgres and change your Gemfile to reflect this
- default: &default
- adapter: postgresql
- username:
- password:
+default: &default
+ adapter: postgresql
+ username:
+ password:
- development:
- <<: *default
- database: react_webpack_dev
+development:
+ <<: *default
+ database: react_webpack_dev
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
- test:
- <<: *default
- database: react_webpack_test
+test:
+ <<: *default
+ database: react_webpack_test
production:
<<: *default
diff --git a/config/environments/production.rb b/config/environments/production.rb
index c146c7dc3..3be9a24fb 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -40,7 +40,6 @@
# Action Cable endpoint configuration
# config.action_cable.url = 'wss://example.com/cable'
- # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
diff --git a/config/routes.rb b/config/routes.rb
index 1ceda4475..83bc46408 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -13,4 +13,5 @@
get "react-router(/*all)", to: "pages#index"
resources :comments
+ mount ActionCable.server => "/cable"
end
diff --git a/config/secrets.yml b/config/secrets.yml
index bad5014a8..3a014e1f2 100644
--- a/config/secrets.yml
+++ b/config/secrets.yml
@@ -12,11 +12,14 @@
development:
secret_key_base: 231bf79489c63f8c8facd7bf27db1c2582a42a7f4302fccdb74ef35bc5dc91fb4e19dbf167f3003bdb4073818dfab4a9916890d193d535a7be458dbef1609800
+ action_cable_url : http://localhost:3000
test:
secret_key_base: 1ab8adbcf8410aebbce9b6dd6db7b5d090297bd22cf789b91ff44ae02711e8c128453d3e5c97eadf9066efe1a1e0dc1921faf7314d566c114d3ed60ae7ea614c
+ action_cable_url : http://localhost:3000
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+ action_cable_url : <%= ENV["SERVER_PORT"] %>