solid_cable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '00408473f35ef85d126584f435899efb2e921a9094c15b5a79bc76a975268adf'
4
+ data.tar.gz: 690b0c1bb50e20e17facb9255bdeb98f38877c02929903c518b826093a7e806a
5
+ SHA512:
6
+ metadata.gz: 702654c332b42fab18bf27b427151617462d6d4ab42f81e29b50ba6adf106eefff3bb149b58da169dbed14221f8e68c6efe04148db385fd9bb9b8840f004f6d6
7
+ data.tar.gz: 2733a331ac2e4ca7b1ced62247ffeda561cb48615c6a9a33f1af6056bbda63f269d9eaa1e840d9dee51d23426406589b7d1bb5800fa6bf37851a59c7fae723e1
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Nick Pezza
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.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # SolidCable
2
+
3
+ Solid Cable is a DB-based queuing backend for Action Cabble, designed with simplicity and performance in mind.
4
+
5
+
6
+ ## Installation
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem "solid_cable"
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ $ bundle
16
+ ```
17
+
18
+ Or install it yourself as:
19
+ ```bash
20
+ $ gem install solid_cable
21
+ ```
22
+
23
+ Now, you need to install the necessary migrations and configure the Action Cable's adapter.
24
+
25
+ ```bash
26
+ $ bin/rails generate solid_cable:install
27
+ ```
28
+
29
+ Update `config/cable.yml` to use the new adapter:
30
+
31
+ ```yaml
32
+ development:
33
+ adapter: solid_cable
34
+
35
+ test:
36
+ adapter: test
37
+
38
+ production:
39
+ adapter: solid_cable
40
+ ```
41
+
42
+ Finally, you need to run the migrations:
43
+
44
+ ```bash
45
+ $ bin/rails db:migrate
46
+ ```
47
+
48
+ ## License
49
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ require "bundler/gem_tasks"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCable
4
+ class Message < SolidCable::Record
5
+ scope :prunable, -> { where(created_at: ..30.minutes.ago) }
6
+ scope :broadcastable, lambda { |channels, last_id|
7
+ where(channel: channels).where(id: (last_id + 1)..).order(:id)
8
+ }
9
+
10
+ def prune
11
+ prunable.delete_all
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCable
4
+ class Record < ActiveRecord::Base
5
+ self.abstract_class = true
6
+
7
+ connects_to(**SolidCable.connects_to) if SolidCable.connects_to
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSolidCableMessage < ActiveRecord::Migration[7.1]
4
+ def change
5
+ create_table :solid_cable_messages, if_not_exists: true do |t|
6
+ t.text :channel
7
+ t.text :payload
8
+
9
+ t.timestamps
10
+
11
+ t.index :created_at
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionCable
4
+ module SubscriptionAdapter
5
+ class SolidCable < Base
6
+ prepend ChannelPrefix
7
+
8
+ def initialize(*)
9
+ super
10
+ @listener = nil
11
+ end
12
+
13
+ def broadcast(channel, payload)
14
+ ::SolidCable::Message.create(channel:, payload:)
15
+ end
16
+
17
+ def subscribe(channel, callback, success_callback = nil)
18
+ listener.add_subscriber(channel, callback, success_callback)
19
+ end
20
+
21
+ def unsubscribe(channel, callback)
22
+ listener.remove_subscriber(channel, callback)
23
+ end
24
+
25
+ delegate :shutdown, to: :listener
26
+
27
+ private
28
+
29
+ def listener
30
+ @listener || @server.mutex.synchronize do
31
+ @listener ||= Listener.new(@server.event_loop)
32
+ end
33
+ end
34
+
35
+ class Listener < SubscriberMap
36
+ def initialize(event_loop)
37
+ super()
38
+
39
+ @event_loop = event_loop
40
+
41
+ @thread = Thread.new do
42
+ Thread.current.abort_on_exception = true
43
+ listen
44
+ end
45
+ end
46
+
47
+ def listen
48
+ while running?
49
+ with_polling_volume { broadcast_messages }
50
+
51
+ sleep 0.5
52
+ end
53
+ end
54
+
55
+ def shutdown
56
+ self.running = false
57
+ ::SolidCable::Message.prune
58
+ Thread.pass while thread.alive?
59
+ end
60
+
61
+ def add_channel(channel, on_success)
62
+ channels.add(channel)
63
+ event_loop.post(&on_success) if on_success
64
+ end
65
+
66
+ def remove_channel(channel)
67
+ channels.delete(channel)
68
+ end
69
+
70
+ def invoke_callback(*)
71
+ event_loop.post { super }
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :event_loop, :thread
77
+ attr_writer :running, :last_id
78
+
79
+ def running?
80
+ if defined?(@running)
81
+ @running
82
+ else
83
+ self.running = true
84
+ end
85
+ end
86
+
87
+ def last_id
88
+ @last_id ||= ::SolidCable::Message.maximum(:id) || 0
89
+ end
90
+
91
+ def channels
92
+ @channels ||= Set.new
93
+ end
94
+
95
+ def broadcast_messages
96
+ ::SolidCable::Message.broadcastable(channels, last_id).
97
+ each do |message|
98
+ broadcast(message.channel, message.payload)
99
+ self.last_id = message.id
100
+ end
101
+ end
102
+
103
+ def with_polling_volume
104
+ if ::SolidCable.silence_polling?
105
+ ActiveRecord::Base.logger.silence { yield }
106
+ else
107
+ yield
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Installs solid_cable as the Action Cable adapter
3
+
4
+ Example:
5
+ bin/rails generate solid_cable:install
6
+
7
+ This will perform the following:
8
+ Installs solid_cable migrations
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SolidCable::InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ class_option :skip_migrations, type: :boolean, default: nil,
7
+ desc: "Skip migrations"
8
+
9
+ def create_migrations
10
+ return if options[:skip_migrations]
11
+
12
+ rails_command "railties:install:migrations FROM=solid_cable", inline: true
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCable
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace SolidCable
6
+
7
+ config.solid_cable = ActiveSupport::OrderedOptions.new
8
+
9
+ initializer "solid_cable.config" do
10
+ config.solid_cable.each do |name, value|
11
+ SolidCable.public_send("#{name}=", value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidCable
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solid_cable/version"
4
+ require "solid_cable/engine"
5
+ require "action_cable/subscription_adapter/solid_cable"
6
+
7
+ module SolidCable
8
+ mattr_accessor :connects_to
9
+
10
+ def self.silence_polling?
11
+ !!Rails.application.config_for("cable")[:silence_polling]
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solid_cable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Pezza
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ description: Database-backed Action Cable backend.
28
+ email:
29
+ - pezza@hey.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - app/models/solid_cable/message.rb
38
+ - app/models/solid_cable/record.rb
39
+ - db/migrate/20240103034713_create_solid_cable_message.rb
40
+ - lib/action_cable/subscription_adapter/solid_cable.rb
41
+ - lib/generators/solid_cable/install/USAGE
42
+ - lib/generators/solid_cable/install/install_generator.rb
43
+ - lib/solid_cable.rb
44
+ - lib/solid_cable/engine.rb
45
+ - lib/solid_cable/version.rb
46
+ homepage: http://github.com/npezza93/solid_cable
47
+ licenses:
48
+ - MIT
49
+ metadata:
50
+ homepage_uri: http://github.com/npezza93/solid_cable
51
+ source_code_uri: http://github.com/npezza93/solid_cable
52
+ rubygems_mfa_required: 'true'
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.5.3
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Database-backed Action Cable backend.
72
+ test_files: []