solid_cable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []