Skip to content
Snippets Groups Projects
Commit 6deb9f96 authored by Eugen Rochko's avatar Eugen Rochko
Browse files

Live timelines using ActionCable

parent 10ba09f5
No related branches found
No related tags found
No related merge requests found
Showing
with 91 additions and 46 deletions
......@@ -35,7 +35,6 @@ gem 'onebox'
gem 'simple_form'
gem 'will_paginate'
gem 'rack-attack'
gem 'turbolinks'
gem 'sidekiq'
gem 'sinatra', require: nil, github: 'sinatra'
......@@ -66,5 +65,5 @@ group :production do
end
group :development, :production do
gem 'rack-mini-profiler', require: false
gem 'rack-mini-profiler'
end
......@@ -321,9 +321,6 @@ GEM
thread_safe (0.3.5)
tilt (2.0.5)
tool (0.2.3)
turbolinks (5.0.1)
turbolinks-source (~> 5)
turbolinks-source (5.0.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (3.0.1)
......@@ -394,7 +391,6 @@ DEPENDENCIES
simplecov
sinatra!
therubyracer
turbolinks
uglifier (>= 1.3.0)
webmock
will_paginate
......
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
......@@ -12,5 +12,4 @@
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the rails generate channel command.
//
//= require action_cable
//= require_self
//= require_tree ./channels
(function() {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
}).call(this);
App.timeline = App.cable.subscriptions.create("TimelineChannel", {
connected: function() {
console.log('Connected');
},
disconnected: function() {
console.log('Disconnected');
},
received: function(data) {
console.log(JSON.parse(data.message));
}
});
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
$ ->
$(document).on 'turbolinks:load', ->
unless typeof window.MiniProfiler == 'undefined'
window.MiniProfiler.init()
window.MiniProfiler.pageTransition()
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading.
class TimelineChannel < ApplicationCable::Channel
def subscribed
stream_from "timeline:#{current_user.id}"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
......@@ -5,7 +5,7 @@ class ApplicationController < ActionController::Base
# Profiling
before_action do
if current_user && current_user.admin?
if (current_user && current_user.admin?) || Rails.env == 'development'
Rack::MiniProfiler.authorize_request
end
end
......
......@@ -10,13 +10,13 @@ class FanOutOnWriteService < BaseService
private
def deliver_to_self(status)
push(:home, status.account.id, status)
push(:home, status.account, status)
end
def deliver_to_followers(status)
status.account.followers.each do |follower|
next if !follower.local? || FeedManager.filter_status?(status, follower)
push(:home, follower.id, status)
push(:home, follower, status)
end
end
......@@ -24,23 +24,38 @@ class FanOutOnWriteService < BaseService
status.mentions.each do |mention|
mentioned_account = mention.account
next unless mentioned_account.local?
push(:mentions, mentioned_account.id, status)
push(:mentions, mentioned_account, status)
end
end
def push(type, receiver_id, status)
redis.zadd(FeedManager.key(type, receiver_id), status.id, status.id)
trim(type, receiver_id)
def push(type, receiver, status)
redis.zadd(FeedManager.key(type, receiver.id), status.id, status.id)
trim(type, receiver)
ActionCable.server.broadcast("timeline:#{receiver.id}", message: inline_render(receiver, status))
end
def trim(type, receiver_id)
return unless redis.zcard(FeedManager.key(type, receiver_id)) > FeedManager::MAX_ITEMS
def trim(type, receiver)
return unless redis.zcard(FeedManager.key(type, receiver.id)) > FeedManager::MAX_ITEMS
last = redis.zrevrange(FeedManager.key(type, receiver_id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1)
redis.zremrangebyscore(FeedManager.key(type, receiver_id), '-inf', "(#{last.last}")
last = redis.zrevrange(FeedManager.key(type, receiver.id), FeedManager::MAX_ITEMS - 1, FeedManager::MAX_ITEMS - 1)
redis.zremrangebyscore(FeedManager.key(type, receiver.id), '-inf', "(#{last.last}")
end
def redis
$redis
end
def inline_render(receiver, status)
rabl_scope = Class.new(BaseService) do
def initialize(account)
@account = account
end
def current_user
@account.user
end
end
Rabl::Renderer.new('api/statuses/show', status, view_path: 'app/views', format: :json, scope: rabl_scope.new(receiver)).render
end
end
......@@ -8,7 +8,7 @@ class PrecomputeFeedService < BaseService
Status.send("as_#{type}_timeline", account).order('created_at desc').limit(FeedManager::MAX_ITEMS).each do |status|
next if type == :home && FeedManager.filter_status?(status, account)
redis.zadd(FeedManager.key(type, receiver_id), status.id, status.id)
redis.zadd(FeedManager.key(type, account.id), status.id, status.id)
instant_return << status unless instant_return.size > limit
end
......
development:
adapter: async
adapter: redis
url: redis://localhost:6379/1
test:
adapter: async
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment