diff --git a/.env.example b/.env.example index 436cbac..9653160 100644 --- a/.env.example +++ b/.env.example @@ -1,14 +1,2 @@ -PUSHER_APP_ID=12345 - -PUSHER_KEY=abc -PUSHER_SECRET=123 - -PUSHER_CLIENT_PORT=80 -PUSHER_CLIENT_PORT_SSL=443 -PUSHER_CLIENT_HOST=ws.pusherapp.com - -PUSHER_API_HOST=api.pusherapp.com -PUSHER_API_PORT=80 - ADMIN_USERNAME=admin ADMIN_PASSWORD=secret diff --git a/Gemfile b/Gemfile index 2534e5a..bd5f940 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rails-assets.org' ruby '2.1.2' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.1.6' +gem 'rails', '4.2.3' # Use SCSS for stylesheets gem 'sass-rails', '~> 4.0.3' # Use Uglifier as compressor for JavaScript assets @@ -17,8 +17,6 @@ gem 'coffee-rails', '~> 4.0.0' gem 'jquery-rails' # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks gem 'turbolinks' -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc @@ -39,7 +37,9 @@ gem 'spring', group: :development gem "autoprefixer-rails" gem 'rails-assets-angular', "1.3.0.rc.4" -gem 'pusher' +gem 'actioncable', github: "rails/actioncable" +gem 'puma' +gem "active_model_serializers" group :production, :staging do gem 'pg' diff --git a/Gemfile.lock b/Gemfile.lock index 9eaced5..a08f393 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,34 +1,58 @@ +GIT + remote: git://github.com/rails/actioncable.git + revision: c7dc339b1dbdb1982d82536d87b6e654e7b7108d + specs: + actioncable (0.1.0) + actionpack (>= 4.2.0) + activesupport (>= 4.2.0) + celluloid (~> 0.16.0) + em-hiredis (~> 0.3.0) + faye-websocket (~> 0.9.2) + redis (~> 3.0) + websocket-driver (= 0.5.4) + GEM remote: https://rubygems.org/ remote: https://rails-assets.org/ specs: - actionmailer (4.1.6) - actionpack (= 4.1.6) - actionview (= 4.1.6) + actionmailer (4.2.3) + actionpack (= 4.2.3) + actionview (= 4.2.3) + activejob (= 4.2.3) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.6) - actionview (= 4.1.6) - activesupport (= 4.1.6) - rack (~> 1.5.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.3) + actionview (= 4.2.3) + activesupport (= 4.2.3) + rack (~> 1.6) rack-test (~> 0.6.2) - actionview (4.1.6) - activesupport (= 4.1.6) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.3) + activesupport (= 4.2.3) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.6) - activesupport (= 4.1.6) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_model_serializers (0.9.3) + activemodel (>= 3.2) + activejob (4.2.3) + activesupport (= 4.2.3) + globalid (>= 0.3.0) + activemodel (4.2.3) + activesupport (= 4.2.3) builder (~> 3.1) - activerecord (4.1.6) - activemodel (= 4.1.6) - activesupport (= 4.1.6) - arel (~> 5.0.0) - activesupport (4.1.6) - i18n (~> 0.6, >= 0.6.9) + activerecord (4.2.3) + activemodel (= 4.2.3) + activesupport (= 4.2.3) + arel (~> 6.0) + activesupport (4.2.3) + i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - arel (5.0.1.20140414130214) + arel (6.0.0) autoprefixer-rails (2.2.0.20140727) execjs better_errors (1.0.1) @@ -37,6 +61,8 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) + celluloid (0.16.0) + timers (~> 4.0.0) coderay (1.1.0) coffee-rails (4.0.1) coffee-script (>= 2.2.0) @@ -51,56 +77,76 @@ GEM dotenv-deployment (0.0.2) dotenv-rails (0.11.1) dotenv (= 0.11.1) + em-hiredis (0.3.0) + eventmachine (~> 1.0) + hiredis (~> 0.5.0) erubis (2.7.0) + eventmachine (1.0.7) execjs (2.2.1) + faye-websocket (0.9.2) + eventmachine (>= 0.12.0) + websocket-driver (>= 0.5.1) + globalid (0.3.5) + activesupport (>= 4.1.0) hike (1.2.3) - httpclient (2.4.0) - i18n (0.6.11) - jbuilder (2.1.3) - activesupport (>= 3.0.0, < 5) - multi_json (~> 1.2) + hiredis (0.5.2) + hitimes (1.2.2) + i18n (0.7.0) jquery-rails (3.1.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - json (1.8.1) - mail (2.6.1) + json (1.8.3) + loofah (2.0.2) + nokogiri (>= 1.5.9) + mail (2.6.3) mime-types (>= 1.16, < 3) - mime-types (2.3) - minitest (5.4.2) - multi_json (1.10.1) + mime-types (2.6.1) + mini_portile (0.6.2) + minitest (5.7.0) + multi_json (1.11.2) mysql2 (0.3.16) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) pg (0.17.0) - pusher (0.14.1) - httpclient (~> 2.3) - multi_json (~> 1.0) - signature (~> 0.1.6) - rack (1.5.2) - rack-test (0.6.2) + puma (2.11.3) + rack (>= 1.1, < 2.0) + rack (1.6.4) + rack-test (0.6.3) rack (>= 1.0) - rails (4.1.6) - actionmailer (= 4.1.6) - actionpack (= 4.1.6) - actionview (= 4.1.6) - activemodel (= 4.1.6) - activerecord (= 4.1.6) - activesupport (= 4.1.6) + rails (4.2.3) + actionmailer (= 4.2.3) + actionpack (= 4.2.3) + actionview (= 4.2.3) + activejob (= 4.2.3) + activemodel (= 4.2.3) + activerecord (= 4.2.3) + activesupport (= 4.2.3) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.6) - sprockets-rails (~> 2.0) + railties (= 4.2.3) + sprockets-rails rails-assets-angular (1.3.0.rc.4) + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.6) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) rails_12factor (0.0.2) rails_serve_static_assets rails_stdout_logging rails_serve_static_assets (0.0.2) rails_stdout_logging (0.0.3) - railties (4.1.6) - actionpack (= 4.1.6) - activesupport (= 4.1.6) + railties (4.2.3) + actionpack (= 4.2.3) + activesupport (= 4.2.3) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.3.2) + rake (10.4.2) rdoc (4.1.2) json (~> 1.4) + redis (3.2.1) sass (3.2.19) sass-rails (4.0.3) railties (>= 4.0.0, < 5.0) @@ -110,20 +156,21 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - signature (0.1.7) spring (1.1.3) sprockets (2.11.0) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.1.4) + sprockets-rails (2.3.2) actionpack (>= 3.0) activesupport (>= 3.0) - sprockets (~> 2.8) + sprockets (>= 2.8, < 4.0) thor (0.19.1) - thread_safe (0.3.4) + thread_safe (0.3.5) tilt (1.4.1) + timers (4.0.1) + hitimes turbolinks (2.4.0) coffee-rails tzinfo (1.2.2) @@ -131,22 +178,26 @@ GEM uglifier (2.5.3) execjs (>= 0.3.0) json (>= 1.8.0) + websocket-driver (0.5.4) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) PLATFORMS ruby DEPENDENCIES + actioncable! + active_model_serializers autoprefixer-rails better_errors binding_of_caller coffee-rails (~> 4.0.0) dotenv-rails - jbuilder (~> 2.0) jquery-rails mysql2 pg - pusher - rails (= 4.1.6) + puma + rails (= 4.2.3) rails-assets-angular (= 1.3.0.rc.4) rails_12factor sass-rails (~> 4.0.3) diff --git a/README.md b/README.md index 52862b5..72f3e04 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,6 @@ $ rake db:migrate $ cp .env.example .env ``` -Modify the `.env` file to match with your [Pusher](http://pusher.com) application credentials or setup [Slanger](https://github.com/stevegraham/slanger) (perhaps with [docker-slanger](https://github.com/adambutler/docker-slanger)) - - ## Contributing Contributions are welcome, please follow [GitHub Flow](https://guides.github.com/introduction/flow/index.html) diff --git a/app/assets/javascripts/angular/controllers/result_controller.js.coffee b/app/assets/javascripts/angular/controllers/result_controller.js.coffee index f17e14d..f3602e0 100644 --- a/app/assets/javascripts/angular/controllers/result_controller.js.coffee +++ b/app/assets/javascripts/angular/controllers/result_controller.js.coffee @@ -1,6 +1,6 @@ angular .module("Poll") - .controller "ResultController", ["$scope", "$interval", "Pusher", ($scope, $interval, Pusher) -> + .controller "ResultController", ["$scope", "$interval", ($scope, $interval) -> $scope.ctx = $('canvas')[0].getContext("2d") @@ -11,38 +11,44 @@ angular animationSteps: 45 } + $scope.render = (data) -> + $scope.$apply -> + if $scope.chartData.length != data.length + colors = color.randomColors(data.length) + $scope.chartData = $.extend true, data, colors + else + $scope.chartData = $.extend true, $scope.chartData, data + + total = 0 + for datum, index in data + total += datum.value + + if $scope.chart? + for datum, index in data + $scope.chart.segments[index].value = datum.value + $scope.chart.update() + else if total > 0 + $scope.chart = new Chart($scope.ctx).Doughnut($scope.chartData, $scope.chartOptions) + $scope.updateChart = -> $.ajax url: "/#{$scope.question}/results.json" success: (data) -> - $scope.$apply -> - if $scope.chartData.length != data.length - colors = color.randomColors(data.length) - $scope.chartData = $.extend true, data, colors - else - $scope.chartData = $.extend true, $scope.chartData, data - - total = 0 - for datum, index in data - total += datum.value - - console.log "Total = #{total}" - - if $scope.chart? - console.log 'a' - for datum, index in data - $scope.chart.segments[index].value = datum.value - $scope.chart.update() - else if total > 0 - console.log 'b' - $scope.chart = new Chart($scope.ctx).Doughnut($scope.chartData, $scope.chartOptions) + $scope.render(data) $scope.setQuestion = (question) -> $scope.question = question $scope.updateChart() - channel = Pusher.subscribe(question) - channel.bind "vote", -> - $scope.updateChart() + + App.votes = App.cable.subscriptions.create "VotesChannel", + connected: -> + setTimeout => + @perform 'follow', question_id: $("[data-question-secret]").data("question-secret") + , 500 + + received: (data) -> + $scope.render(data) + ] diff --git a/app/assets/javascripts/angular/factories/Pusher.coffee.erb b/app/assets/javascripts/angular/factories/Pusher.coffee.erb deleted file mode 100644 index 563de46..0000000 --- a/app/assets/javascripts/angular/factories/Pusher.coffee.erb +++ /dev/null @@ -1,18 +0,0 @@ -angular - .module("Poll") - .factory "Pusher", -> - - config = { - key: document.getElementsByTagName('html')[0].getAttribute('data-pusher-key') - host: document.getElementsByTagName('html')[0].getAttribute('data-pusher-host') - port: document.getElementsByTagName('html')[0].getAttribute('data-pusher-port') - portSSL: document.getElementsByTagName('html')[0].getAttribute('data-pusher-port-ssl') - } - - Pusher.host = config.host - Pusher.ws_port = config.port - Pusher.wss_port = config.portSSL - - console.log config - - new Pusher(config.key) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 50199cb..7ebd712 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -18,10 +18,8 @@ //= require tinycolor //= require angles //= require nanobar -//= require pusher //= require_tree ./angular/modules -//= require_tree ./angular/factories //= require_tree ./angular/directives //= require_tree ./angular/controllers diff --git a/app/assets/javascripts/channels/index.coffee b/app/assets/javascripts/channels/index.coffee new file mode 100644 index 0000000..255891a --- /dev/null +++ b/app/assets/javascripts/channels/index.coffee @@ -0,0 +1,6 @@ +#= require cable +#= require_self +#= require_tree . + +@App = {} +App.cable = Cable.createConsumer 'ws://localhost:28080' diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100755 index 0000000..d672697 --- /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 100755 index 0000000..614e681 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,7 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + def connect + true + end + end +end diff --git a/app/channels/votes_channel.rb b/app/channels/votes_channel.rb new file mode 100644 index 0000000..d6c4c45 --- /dev/null +++ b/app/channels/votes_channel.rb @@ -0,0 +1,11 @@ +class VotesChannel < ApplicationCable::Channel + def follow(data) + stop_all_streams + Rails.logger.debug "question:#{data['question_id']}:vote" + stream_from "question:#{data['question_id']}:vote" + end + + def unfollow + stop_all_streams + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9942dc7..0b64619 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,4 +10,8 @@ class ApplicationController < ActionController::Base def allow_iframe response.headers.except! 'X-Frame-Options' end + + def default_serializer_options + { root: false } + end end diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index f99a709..b8afbea 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -32,6 +32,10 @@ def show def results @options = @question.options + respond_to do |format| + format.html + format.json { render json: @options } + end end def check_secret_availability diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb index 1e854ff..323fec4 100644 --- a/app/controllers/votes_controller.rb +++ b/app/controllers/votes_controller.rb @@ -11,11 +11,12 @@ def update vote.question_id = question.id unless vote.question_id vote.option_id = Option.find(params[:vote][:option_id]).id - Pusher[question_id].trigger("vote", {}) - if vote.save! cookies.permanent["vote_#{question.secret}"] = vote.secret + serialized_options = question.options.map { |option| OptionSerializer.new(option).attributes } + ActionCable.server.broadcast "question:#{question.secret}:vote", serialized_options + respond_to do |format| format.html { redirect_to "/#{question.secret}" } format.json { render json: {}, status: :created } diff --git a/app/serializers/option_serializer.rb b/app/serializers/option_serializer.rb new file mode 100644 index 0000000..08883bd --- /dev/null +++ b/app/serializers/option_serializer.rb @@ -0,0 +1,11 @@ +class OptionSerializer < ActiveModel::Serializer + attributes :value, :label + + def value + object.votes + end + + def label + object.title + end +end diff --git a/app/serializers/question_serializer.rb b/app/serializers/question_serializer.rb new file mode 100644 index 0000000..2d0d09a --- /dev/null +++ b/app/serializers/question_serializer.rb @@ -0,0 +1,3 @@ +class QuestionSerializer < ActiveModel::Serializer + has_many :options +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index dd2ce7f..31ced3f 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,11 +1,5 @@ - + Poll - Voting done simply in real-times <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> @@ -40,7 +34,7 @@