Live timelines using ActionCable
This commit is contained in:
parent
10ba09f546
commit
6deb9f966e
24 changed files with 99 additions and 53 deletions
|
@ -1,3 +0,0 @@
|
|||
# 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/
|
|
@ -1,3 +0,0 @@
|
|||
# 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/
|
|
@ -1,3 +0,0 @@
|
|||
# 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/
|
|
@ -1,3 +0,0 @@
|
|||
# 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 .
|
||||
|
|
13
app/assets/javascripts/cable.js
Normal file
13
app/assets/javascripts/cable.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// 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);
|
13
app/assets/javascripts/channels/timeline.js
Normal file
13
app/assets/javascripts/channels/timeline.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
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));
|
||||
}
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
# 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/
|
|
@ -1,5 +0,0 @@
|
|||
$ ->
|
||||
$(document).on 'turbolinks:load', ->
|
||||
unless typeof window.MiniProfiler == 'undefined'
|
||||
window.MiniProfiler.init()
|
||||
window.MiniProfiler.pageTransition()
|
|
@ -1,3 +0,0 @@
|
|||
# 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/
|
|
@ -1,3 +0,0 @@
|
|||
# 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/
|
5
app/channels/application_cable/channel.rb
Normal file
5
app/channels/application_cable/channel.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
# 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
|
20
app/channels/application_cable/connection.rb
Normal file
20
app/channels/application_cable/connection.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# 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
|
10
app/channels/timeline_channel.rb
Normal file
10
app/channels/timeline_channel.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# 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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue