├── .gitignore
├── Gemfile
├── Gemfile.lock
├── MIT-LICENSE
├── README.md
├── app
└── views
│ └── realtime
│ ├── _realtime_loader.html.erb
│ ├── _realtime_message_console_logger.html.erb
│ ├── _realtime_message_handler.html.erb
│ ├── async_realtime_support.html.erb
│ ├── realtime_message_console_logger.html.erb
│ ├── realtime_message_handler.html.erb
│ └── realtime_support.html.erb
├── lib
├── realtime.rb
└── realtime
│ ├── railtie.rb
│ ├── realtime_controller.rb
│ ├── redis_wrapper.rb
│ ├── version.rb
│ ├── view_helpers.rb
│ └── zmq_wrapper.rb
└── realtime.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | realtime (0.0.1)
5 | rails (~> 4.1.1)
6 | redis
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actionmailer (4.1.2)
12 | actionpack (= 4.1.2)
13 | actionview (= 4.1.2)
14 | mail (~> 2.5.4)
15 | actionpack (4.1.2)
16 | actionview (= 4.1.2)
17 | activesupport (= 4.1.2)
18 | rack (~> 1.5.2)
19 | rack-test (~> 0.6.2)
20 | actionview (4.1.2)
21 | activesupport (= 4.1.2)
22 | builder (~> 3.1)
23 | erubis (~> 2.7.0)
24 | activemodel (4.1.2)
25 | activesupport (= 4.1.2)
26 | builder (~> 3.1)
27 | activerecord (4.1.2)
28 | activemodel (= 4.1.2)
29 | activesupport (= 4.1.2)
30 | arel (~> 5.0.0)
31 | activesupport (4.1.2)
32 | i18n (~> 0.6, >= 0.6.9)
33 | json (~> 1.7, >= 1.7.7)
34 | minitest (~> 5.1)
35 | thread_safe (~> 0.1)
36 | tzinfo (~> 1.1)
37 | arel (5.0.1.20140414130214)
38 | builder (3.2.2)
39 | erubis (2.7.0)
40 | hike (1.2.3)
41 | i18n (0.6.9)
42 | json (1.8.1)
43 | mail (2.5.4)
44 | mime-types (~> 1.16)
45 | treetop (~> 1.4.8)
46 | mime-types (1.25.1)
47 | minitest (5.3.5)
48 | multi_json (1.10.1)
49 | polyglot (0.3.5)
50 | rack (1.5.2)
51 | rack-test (0.6.2)
52 | rack (>= 1.0)
53 | rails (4.1.2)
54 | actionmailer (= 4.1.2)
55 | actionpack (= 4.1.2)
56 | actionview (= 4.1.2)
57 | activemodel (= 4.1.2)
58 | activerecord (= 4.1.2)
59 | activesupport (= 4.1.2)
60 | bundler (>= 1.3.0, < 2.0)
61 | railties (= 4.1.2)
62 | sprockets-rails (~> 2.0)
63 | railties (4.1.2)
64 | actionpack (= 4.1.2)
65 | activesupport (= 4.1.2)
66 | rake (>= 0.8.7)
67 | thor (>= 0.18.1, < 2.0)
68 | rake (10.3.2)
69 | redis (3.1.0)
70 | sprockets (2.12.1)
71 | hike (~> 1.2)
72 | multi_json (~> 1.0)
73 | rack (~> 1.0)
74 | tilt (~> 1.1, != 1.3.0)
75 | sprockets-rails (2.1.3)
76 | actionpack (>= 3.0)
77 | activesupport (>= 3.0)
78 | sprockets (~> 2.8)
79 | thor (0.19.1)
80 | thread_safe (0.3.4)
81 | tilt (1.4.1)
82 | treetop (1.4.15)
83 | polyglot
84 | polyglot (>= 0.3.1)
85 | tzinfo (1.2.1)
86 | thread_safe (~> 0.1)
87 |
88 | PLATFORMS
89 | ruby
90 |
91 | DEPENDENCIES
92 | realtime!
93 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Mike Atlas
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Realtime Support for Rails
2 | ====
3 |
4 | Note: This gem is a concept piece originally conceived in 2013 and published as a generic open source project in early 2014.
5 |
6 | As of mid-2015, support for performant, native and scalable websockets are available in Rails. See ~~[ActiveCable](https://github.com/rails/actioncable)~~ [ActionCable](https://github.com/rails/rails/tree/master/actioncable), which landed in Rails 5 and will probably be officially released early/mid 2016.
7 |
8 | As such, with [`ActionCable`'s design](http://weblog.rubyonrails.org/2016/2/2/Rails-5-0-beta2/), you don't even need a separate pub/sub server (redis) and Node.js running anymore to achieve similar lightweight realtime bi-directional communication with a large number of connected clients to your Rails application.
9 |
10 | Progress marches forward! I presume, if you are stuck and can't upgrade to Rails 5 anytime soon, perhaps this project is lightweight compared to some of the heavier alternatives like Faye or PubNub.
11 |
12 | Full documentation is available at:
13 |
14 | http://mikeatlas.github.io/realtime-rails/
15 |
--------------------------------------------------------------------------------
/app/views/realtime/_realtime_loader.html.erb:
--------------------------------------------------------------------------------
1 | if (window.realtime.socketIo) {
2 |
3 | window.realtime.enabled = true;
4 |
5 | window.realtime.socketIo.on('connect', function() {
6 | // Give a nice round-trip ACK to our realtime server that we connected.
7 | window.realtime.socketIo.emit('realtime_user_id_connected', { userId: window.realtime.userId });
8 | });
9 |
10 | // Queue up all incoming realtime messages.
11 | window.realtime.socketIo.on('realtime_msg', function(message) {
12 | window.realtime.messageQueue.push(message);
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/app/views/realtime/_realtime_message_console_logger.html.erb:
--------------------------------------------------------------------------------
1 | if (window.realtime.enabled && window.realtime.eventBus){
2 | // handle events in the queue with eventing
3 | var realtimeMessageEventConsoleLogger = function(message) {
4 | console.log(message);
5 | };
6 | window.realtime.eventBus.on('realtimeMessage', realtimeMessageEventConsoleLogger);
7 |
8 | } else if (window.realtime.enabled) {
9 | // handle events in the queue without eventing
10 | messageQueueConsoleLogger = function() {
11 | message = window.realtime.messageQueue.shift();
12 | if (message) {
13 | console.log(message);
14 | }
15 | };
16 | setInterval(messageQueueConsoleLogger, 100);
17 |
18 | } else {
19 | console.log('Error: Realtime was not enabled.')
20 | }
21 |
--------------------------------------------------------------------------------
/app/views/realtime/_realtime_message_handler.html.erb:
--------------------------------------------------------------------------------
1 | if (window.realtime.enabled){
2 |
3 | window.realtime.eventBus = _.extend({}, Backbone.Events);
4 |
5 | messageQueueRunner = function() {
6 | message = window.realtime.messageQueue.shift();
7 | if (message) {
8 | window.realtime.eventBus.trigger('realtimeMessage', message);
9 | }
10 | };
11 |
12 | setInterval(messageQueueRunner, 100);
13 | }
14 |
--------------------------------------------------------------------------------
/app/views/realtime/async_realtime_support.html.erb:
--------------------------------------------------------------------------------
1 |
72 |
--------------------------------------------------------------------------------
/app/views/realtime/realtime_message_console_logger.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/views/realtime/realtime_message_handler.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/views/realtime/realtime_support.html.erb:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
22 |
23 | <% if message_handler %>
24 |
25 |
26 |
27 |
28 |
31 | <% end %>
32 |
33 | <% if message_console_logger %>
34 |
37 | <% end %>
38 |
--------------------------------------------------------------------------------
/lib/realtime.rb:
--------------------------------------------------------------------------------
1 |
2 | require "realtime/realtime_controller.rb"
3 | require "realtime/redis_wrapper.rb"
4 | require "realtime/zmq_wrapper.rb"
5 | require 'realtime/view_helpers.rb'
6 | require 'realtime/railtie.rb'
7 |
--------------------------------------------------------------------------------
/lib/realtime/railtie.rb:
--------------------------------------------------------------------------------
1 | module Realtime
2 |
3 | class Railtie < Rails::Railtie
4 |
5 | initializer "realtime.action_controller" do
6 | ActiveSupport.on_load(:action_controller) do
7 | ActionController::Base.send :include, Realtime::Controller
8 | end
9 | end
10 |
11 | initializer "realtime.view_helpers" do
12 | ActionView::Base.send :include, ViewHelpers
13 |
14 | # kinda blah way to force rails to load our helper views path
15 | curr_path = File.expand_path(File.dirname(__FILE__))
16 | view_path = "#{curr_path}/../../app/views"
17 | ActionController::Base.append_view_path(view_path)
18 | end
19 |
20 | end
21 |
22 | end
--------------------------------------------------------------------------------
/lib/realtime/realtime_controller.rb:
--------------------------------------------------------------------------------
1 | module Realtime
2 | module Controller
3 | extend ActiveSupport::Concern
4 |
5 | module ClassMethods
6 | def realtime_controller(options = {})
7 | queue = options.delete(:queue)
8 | before_action :do_realtime_token, options
9 | before_action :do_realtime_user_id, options
10 | before_action :do_realtime_server_url, options
11 | if queue.nil? || queue == :redis
12 | after_action :store_realtime_session_redis, options
13 | elsif queue == :zmq
14 | after_action :store_realtime_session_zmq, options
15 | end
16 | end
17 | end
18 |
19 | def do_realtime_token
20 | @realtime_token = Digest::MD5.hexdigest("#{session[:session_id]}:#{realtime_user_id}")
21 | return @realtime_token
22 | end
23 |
24 | def do_realtime_user_id
25 | @realtime_user_id = realtime_user_id
26 | return @realtime_user_id
27 | end
28 |
29 | def do_realtime_server_url
30 | @realtime_server_url = realtime_server_url
31 | return @realtime_server_url
32 | end
33 |
34 | # create shared session tokens for redis/socketio realtime server running on node.js
35 | def store_realtime_session_zmq
36 | # store session data or any authentication data you want here, generate to JSON data
37 | session_data = {
38 | "user_id" => realtime_user_id,
39 | }
40 |
41 | # todo: merge additional session data passed in
42 | stored_session_data = JSON.generate(session_data)
43 |
44 | ZmqWrapper.store_session(
45 | realtime_user_id,
46 | @realtime_token,
47 | stored_session_data,
48 | 86400
49 | )
50 | end
51 |
52 | # create shared session tokens for redis/socketio realtime server running on node.js
53 | def store_realtime_session_redis
54 | # store session data or any authentication data you want here, generate to JSON data
55 | session_data = {
56 | "user_id" => realtime_user_id,
57 | }
58 |
59 | # todo: merge additional session data passed in
60 |
61 | stored_session_data = JSON.generate(session_data)
62 |
63 | RedisWrapper.redis.hset(
64 | "rtSession-" + realtime_user_id.to_s,
65 | @realtime_token,
66 | stored_session_data,
67 | )
68 |
69 | # expire this realtime session after one day.
70 | RedisWrapper.redis.expire("rtSession-" + realtime_user_id.to_s, 86400)
71 | end
72 |
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/lib/realtime/redis_wrapper.rb:
--------------------------------------------------------------------------------
1 | class RedisWrapper
2 |
3 | def self.publish(message)
4 | redis.publish 'realtime_msg', message.to_json
5 | end
6 |
7 | def redis
8 | return $redis # global set in redis.rb initializer
9 | end
10 |
11 | def self.redis
12 | return $redis # global set in redis.rb initializer
13 | end
14 |
15 | end
--------------------------------------------------------------------------------
/lib/realtime/version.rb:
--------------------------------------------------------------------------------
1 | module Realtime
2 | VERSION = "0.1.0"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/realtime/view_helpers.rb:
--------------------------------------------------------------------------------
1 | module Realtime
2 | module ViewHelpers
3 |
4 | def realtime_support(args = {})
5 | async = args[:async] ? 'async_' : ''
6 | return render(template: "realtime/#{async}realtime_support",
7 | layout: nil,
8 | locals:
9 | { realtime_token: @realtime_token,
10 | realtime_domain: @realtime_domain,
11 | realtime_server_url: @realtime_server_url,
12 | realtime_user_id: @realtime_user_id,
13 | message_handler: args[:message_handler],
14 | message_console_logger: args[:message_console_logger] }).to_s
15 | end
16 |
17 | def realtime_message_handler
18 | ActiveSupport::Deprecation.warn("'realtime_message_handler' is deprecated, "\
19 | "please refer to the documentation for details.")
20 | return render(template: "realtime/realtime_message_handler",
21 | layout: nil,
22 | locals: {}).to_s
23 | end
24 |
25 | def realtime_message_console_logger
26 | ActiveSupport::Deprecation.warn("'realtime_message_console_logger' is deprecated, "\
27 | "please refer to the documentation for details.")
28 | return render(template: "realtime/realtime_message_console_logger",
29 | layout: nil,
30 | locals: {}).to_s
31 | end
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/lib/realtime/zmq_wrapper.rb:
--------------------------------------------------------------------------------
1 | class ZmqWrapper
2 |
3 | if defined?(ZMQ) && defined?(EmZeromq)
4 | require 'em-zeromq'
5 |
6 | def self.publish(the_message)
7 | EM.run {
8 | zmq = EM::ZeroMQ::Context.new(1)
9 | pusher = zmq.socket(ZMQ::PUSH)
10 | pusher.connect($zmq_server)
11 | message = "realtime_msg:" + the_message.to_json
12 | puts "Pushing realtime message: " + message
13 | pusher.send_msg(message)
14 | EM.stop
15 | }
16 | end
17 |
18 | def self.store_session(user_id, session_id, session_data, expiration)
19 | EM.run {
20 | zmq = EM::ZeroMQ::Context.new(1)
21 | pusher = zmq.socket(ZMQ::PUSH)
22 | pusher.connect($zmq_server)
23 | the_message = {user_id: user_id,
24 | session_id: session_id,
25 | session_data: session_data,
26 | expiration: expiration}
27 | message = "rtSession:" + the_message.to_json
28 | puts "Pushing session message: " + message
29 | pusher.send_msg(message)
30 | EM.stop
31 | }
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/realtime.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "realtime/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "realtime"
9 | s.version = Realtime::VERSION
10 | s.authors = ["Mike Atlas", "Ahmad Abdel-Yaman (@ayaman)", "Nick Prokesch (@prokizzle)"]
11 | s.email = ["mike.atlas@gmail.com"]
12 | s.homepage = "http://mikeatlas.github.io/realtime-rails/"
13 | s.summary = "Realtime support for Rails applications."
14 | s.description = "Provides a simple Realtime framework for Rails applications."
15 | s.license = "MIT"
16 |
17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
18 | s.test_files = Dir["test/**/*"]
19 |
20 | s.add_dependency "rails", ">= 4.0"
21 |
22 | #s.add_dependency "redis"
23 | #s.add_dependency "zmq"
24 | #s.add_dependency "em-zmq"
25 |
26 | end
27 |
--------------------------------------------------------------------------------