Repository: rails/solid_cable
Branch: main
Commit: 92818a0fb781
Files: 168
Total size: 154.5 KB
Directory structure:
gitextract_sjfrw0w9/
├── .github/
│ └── workflows/
│ ├── lint.yml
│ └── test.yml
├── .gitignore
├── .rubocop.yml
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app/
│ ├── jobs/
│ │ └── solid_cable/
│ │ └── trim_job.rb
│ └── models/
│ └── solid_cable/
│ ├── message.rb
│ └── record.rb
├── bench/
│ ├── .dockerignore
│ ├── .ruby-version
│ ├── Dockerfile
│ ├── Gemfile
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ ├── builds/
│ │ │ │ └── .keep
│ │ │ ├── images/
│ │ │ │ └── .keep
│ │ │ └── stylesheets/
│ │ │ └── application.tailwind.css
│ │ ├── channels/
│ │ │ ├── application_cable/
│ │ │ │ ├── channel.rb
│ │ │ │ └── connection.rb
│ │ │ └── broadcast_channel.rb
│ │ ├── controllers/
│ │ │ ├── application_controller.rb
│ │ │ ├── concerns/
│ │ │ │ └── .keep
│ │ │ ├── rooms_controller.rb
│ │ │ └── ws_debugger_controller.rb
│ │ ├── javascript/
│ │ │ ├── application.js
│ │ │ ├── channels/
│ │ │ │ ├── broadcast_channel.js
│ │ │ │ ├── consumer.js
│ │ │ │ └── index.js
│ │ │ └── controllers/
│ │ │ ├── application.js
│ │ │ └── index.js
│ │ ├── jobs/
│ │ │ └── application_job.rb
│ │ ├── mailers/
│ │ │ └── application_mailer.rb
│ │ ├── models/
│ │ │ ├── application_record.rb
│ │ │ ├── concerns/
│ │ │ │ └── .keep
│ │ │ └── room.rb
│ │ └── views/
│ │ ├── layouts/
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ │ ├── rooms/
│ │ │ ├── _form.html.erb
│ │ │ ├── _room.html.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.html.erb
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ │ └── ws_debugger/
│ │ └── show.html.erb
│ ├── bin/
│ │ ├── bundle
│ │ ├── docker-entrypoint
│ │ ├── importmap
│ │ ├── kamal
│ │ ├── rails
│ │ ├── rake
│ │ ├── rubocop
│ │ └── setup
│ ├── config/
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── credentials.yml.enc
│ │ ├── database.yml
│ │ ├── deploy.yml
│ │ ├── environment.rb
│ │ ├── environments/
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── importmap.rb
│ │ ├── init.sql
│ │ ├── initializers/
│ │ │ ├── assets.rb
│ │ │ ├── content_security_policy.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── inflections.rb
│ │ │ └── permissions_policy.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── puma.rb
│ │ ├── routes.rb
│ │ ├── storage.yml
│ │ └── tailwind.config.js
│ ├── config.ru
│ ├── db/
│ │ ├── migrate/
│ │ │ ├── 20240529231225_create_rooms.rb
│ │ │ ├── 20240530031126_create_solid_cable_message.solid_cable.rb
│ │ │ ├── 20240607184931_index_channels.solid_cable.rb
│ │ │ ├── 20240609023040_create_active_error_faults.active_error.rb
│ │ │ ├── 20240609023041_create_active_error_instances.active_error.rb
│ │ │ └── 20240912235943_create_compact_channel.rb
│ │ ├── schema.rb
│ │ └── seeds.rb
│ ├── loadtest.js
│ ├── log/
│ │ └── .keep
│ ├── public/
│ │ ├── 404.html
│ │ ├── 406-unsupported-browser.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ └── robots.txt
│ ├── test/
│ │ ├── channels/
│ │ │ ├── application_cable/
│ │ │ │ └── connection_test.rb
│ │ │ └── broadcast_channel_test.rb
│ │ ├── controllers/
│ │ │ └── .keep
│ │ ├── fixtures/
│ │ │ ├── files/
│ │ │ │ └── .keep
│ │ │ └── rooms.yml
│ │ ├── models/
│ │ │ ├── .keep
│ │ │ └── room_test.rb
│ │ └── test_helper.rb
│ └── vendor/
│ ├── .keep
│ └── javascript/
│ └── .keep
├── bin/
│ ├── rails
│ ├── release
│ └── test
├── lib/
│ ├── action_cable/
│ │ └── subscription_adapter/
│ │ └── solid_cable.rb
│ ├── generators/
│ │ └── solid_cable/
│ │ ├── install/
│ │ │ ├── USAGE
│ │ │ ├── install_generator.rb
│ │ │ └── templates/
│ │ │ ├── config/
│ │ │ │ └── cable.yml
│ │ │ └── db/
│ │ │ └── cable_schema.rb
│ │ └── update/
│ │ ├── USAGE
│ │ ├── templates/
│ │ │ └── db/
│ │ │ └── migrate/
│ │ │ └── create_compact_channel.rb
│ │ └── update_generator.rb
│ ├── solid_cable/
│ │ ├── engine.rb
│ │ └── version.rb
│ ├── solid_cable.rb
│ └── tasks/
│ └── solid_cable_tasks.rake
├── solid_cable.gemspec
└── test/
├── config_stubs.rb
├── dummy/
│ ├── Rakefile
│ ├── app/
│ │ ├── controllers/
│ │ │ ├── application_controller.rb
│ │ │ └── concerns/
│ │ │ └── .keep
│ │ ├── helpers/
│ │ │ └── application_helper.rb
│ │ ├── jobs/
│ │ │ └── application_job.rb
│ │ ├── models/
│ │ │ ├── application_record.rb
│ │ │ └── concerns/
│ │ │ └── .keep
│ │ └── views/
│ │ ├── layouts/
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ │ └── pwa/
│ │ ├── manifest.json.erb
│ │ └── service-worker.js
│ ├── bin/
│ │ ├── ci
│ │ ├── dev
│ │ ├── rails
│ │ ├── rake
│ │ └── setup
│ ├── config/
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── ci.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments/
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers/
│ │ │ ├── assets.rb
│ │ │ ├── content_security_policy.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ └── inflections.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── puma.rb
│ │ ├── routes.rb
│ │ └── storage.yml
│ ├── config.ru
│ ├── db/
│ │ └── schema.rb
│ ├── log/
│ │ └── .keep
│ └── public/
│ ├── 400.html
│ ├── 404.html
│ ├── 406-unsupported-browser.html
│ ├── 422.html
│ └── 500.html
├── jobs/
│ └── trim_job_test.rb
├── lib/
│ ├── action_cable/
│ │ └── subscription_adapter/
│ │ └── solid_cable_test.rb
│ └── generators/
│ └── solid_cable/
│ ├── install/
│ │ └── install_generator_test.rb
│ └── update/
│ └── update_generator_test.rb
├── solid_cable_test.rb
└── test_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/lint.yml
================================================
name: lint
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
name: Linting
steps:
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '4.0'
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rubocop --config=./.rubocop.yml --parallel
================================================
FILE: .github/workflows/test.yml
================================================
name: tests
on:
push:
branches:
- main
pull_request:
jobs:
build:
name: Tests
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
database: [mysql, postgres, sqlite]
ruby-version:
- 3.2
- 3.3
- 3.4
- 4.0
services:
mysql:
image: mysql:8.0.31
env:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
ports:
- 33060:3306
options: --health-cmd "mysql -h localhost -e \"select now()\"" --health-interval 1s --health-timeout 5s --health-retries 30
postgres:
image: postgres:15.1
env:
POSTGRES_HOST_AUTH_METHOD: "trust"
ports:
- 55432:5432
env:
TARGET_DB: ${{ matrix.database }}
steps:
- uses: actions/checkout@v6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Install dependencies
run: bundle install
- name: Setup db
run: |
bin/rails db:setup
- name: Run tests
run: bin/test
================================================
FILE: .gitignore
================================================
/.bundle/
/doc/
/log/*.log
/pkg/
/tmp/
/test/dummy/db/*.sqlite3
/test/dummy/db/*.sqlite3-*
/test/dummy/log/*.log
/test/dummy/storage/
/test/dummy/tmp/
Gemfile.lock
/bench/db/*.sqlite3
/bench/db/*.sqlite3-*
/bench/log/*.log
/bench/storage/
/bench/tmp/
/bench/app/assets/builds/*
!/bench/app/assets/builds/.keep
/bench/config/master.key
/bench/storage/*
!/bench/storage/.keep
/bench/tmp/storage/*
!/bench/tmp/storage/
!/bench/tmp/storage/.keep
/bench/public/assets
/bench/.env*
!/bench/.env*.erb
/bench/k6
test/dummy/solid_cable_test
================================================
FILE: .rubocop.yml
================================================
inherit_gem:
rubocop-rails-omakase: rubocop.yml
AllCops:
NewCops: enable
SuggestExtensions: false
Exclude:
- vendor/**/*
- Gemfile.lock
- db/*_schema.rb
- db/schema.rb
- tmp/**/*
- bin/**/*
- test/dummy/**/*
- bench/**/*
- test/lib/action_cable/subscription_adapter/solid_cable_test.rb
- lib/generators/**/*_schema.rb
TargetRubyVersion: 3.3
================================================
FILE: Gemfile
================================================
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Specify your gem's dependencies in solid_cable.gemspec.
gemspec
gem "puma"
gem "pg"
gem "sqlite3"
gem "trilogy"
gem "rubocop-rails-omakase"
================================================
FILE: MIT-LICENSE
================================================
Copyright Nick Pezza
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Solid Cable
Solid Cable is a database-backed Action Cable adapter that keeps messages in a table and continuously polls for updates. This makes it possible to drop the common dependency on Redis, if it isn't needed for any other purpose. Despite polling, the performance of Solid Cable is comparable to Redis in most situations. And in all circumstances, it makes it easier to deploy Rails when Redis is no longer a required dependency for Action Cable functionality.
> [!NOTE]
> Solid Cable is tested to work with MySQL, SQLite, and PostgreSQL.
>
> Action Cable already has a [dedicated PostgreSQL adapter](https://guides.rubyonrails.org/action_cable_overview.html#postgresql-adapter),
> which utilizes the builtin `NOTIFY` command for better performance. However, that
> adapter has an 8kb limit on its payload. Solid Cable is a great alternative if you find yourself
> broadcasting large payloads, or prefer not to use the `NOTIFY` command.
## Installation
Solid Cable is configured by default in new Rails 8 applications. But if you're running an earlier version, you can add it manually following these steps:
1. `bundle add solid_cable`
2. `bin/rails solid_cable:install`
This will configure Solid Cable as the production cable adapter by overwritting `config/cable.yml` and create `db/cable_schema.rb`.
You will then have to add the configuration for the cable database in `config/database.yml`. If you're using SQLite, it'll look like this:
```yaml
production:
primary:
<<: *default
database: storage/production.sqlite3
cable:
<<: *default
database: storage/production_cable.sqlite3
migrations_paths: db/cable_migrate
```
...or if you're using MySQL/PostgreSQL/Trilogy:
```yaml
production:
primary: &primary_production
<<: *default
database: app_production
username: app
password: <%= ENV["APP_DATABASE_PASSWORD"] %>
cable:
<<: *primary_production
database: app_production_cable
migrations_paths: db/cable_migrate
```
> [!NOTE]
> Calling `bin/rails solid_cable:install` will automatically setup `config/cable.yml`, so no additional configuration is needed there (although you must make sure that you use the `cable` name in `database.yml` for this to match!). But if you want to use Solid Cable in a different environment (like staging or even development), you'll have to manually add that `connects_to` block to the respective environment in the `config/cable.yml` file. And, as always, make sure that the name you're using for the database in `config/cable.yml` matches the name you define in `config/database.yml`.
Then run `db:prepare` in production to ensure the database is created and the schema is loaded.
### Single database configuration
Running Solid Cable in a separate database is recommended, but it's also possible to use a single database for both the app and Action Cable.
1. Copy the contents of `db/cable_schema.rb` into a normal migration and delete `db/cable_schema.rb`
2. Remove `connects_to` from `config/cable.yml`
3. `bin/rails db:migrate`
You won't have multiple databases, so `database.yml` doesn't need to have primary and cable database.
## Configuration
All configuration is managed via the `config/cable.yml` file. By default, it'll be configured like this:
```yaml
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 1.day
```
The options are:
- `connects_to` - set the Active Record database configuration for the Solid Cable models. All options available in Active Record can be used here.
- `polling_interval` - sets the frequency of the polling interval. (Defaults to
0.1.seconds)
- `message_retention` - sets the retention time for messages kept in the database. Used as the cut-off when trimming is performed. (Defaults to 1.day)
- `autotrim` - sets wether you want Solid Cable to handle autotrimming messages. (Defaults to true)
- `silence_polling` - whether to silence Active Record logs emitted when polling (Defaults to true)
- `use_skip_locked` - whether to use `FOR UPDATE SKIP LOCKED` when performing trimming. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential. (Defaults to true)
- `trim_batch_size` - the batch size to use when deleting old records (default: `100`)
- `reconnect_attempts` - Supports a number of connection attempts or an array of
durations to wait between attempts. (Defaults to 1 retry attempt)
## Trimming
Messages are autotrimmed based upon the `message_retention` setting to determine how long messages are to be kept around. If no `message_retention` is given or parsing fails, it defaults to `1.day`. Messages are trimmed when a messsage is broadcast.
Autotrimming can negatively impact performance slightly depending on your workload because it is potentially doing a delete on broadcast. If
you would prefer, you can disable autotrimming by setting `autotrim: false` and you can manually enqueue the job later, `SolidCable::TrimJob.perform_later`, or run it on a recurring interval out of band.
## Upgrading
If you have already installed Solid Cable < 3 and are upgrading to version 3,
run `solid_cable:update` to install a new migration.
## Benchmarks
Inside the `bench` directory there is a minimal Rails app that is used to benchmark.
You are welcome to update the config/deploy.yml file to point to your own server
if you want to deploy the app to your own server and run benchmarks.
To benchmark we use [k6](https://k6.io). Most of the setup was gotten from this
[article](https://evilmartians.com/chronicles/real-time-stress-anycable-k6-websockets-and-yabeda).
1. Install k6
1. Install xk6-cable by running `xk6 build --with
github.com/anycable/xk6-cable`. This will output a custom k6 binary.
1. Run the load test with `./k6 run loadtest.js`
- This script takes a variety of ENV variables:
- WS_URL: The url to send websocket connections
- MAX: The number of virtual users to hit the server with
- TIME: The duration of the load test
- MESSAGES_NUM: The number of messages each VU will send to the server
#### Results
Our loadtest is run on a Hetzner CCX13, with a MESSAGES_NUM of 5, and a TIME of 90.
##### SQLite
With a polling interval of 0.1 seconds and autotrimming enabled.
100 VUs
```
rtt..................: avg=135.82ms min=50ms med=138ms max=357ms p(90)=174ms p(95)=195ms
ws_connecting........: avg=205.81ms min=149.35ms med=199.01ms max=509.48ms p(90)=254.04ms p(95)=261.77ms
```
250 VUs
```
rtt..................: avg=146.24ms min=50ms med=144ms max=435ms p(90)=209ms p(95)=234.04ms
ws_connecting........: avg=222.15ms min=146.47ms med=208.57ms max=1.3s p(90)=263.6ms p(95)=284.18ms
```
500 VUs
```
rtt..................: avg=271.79ms min=48ms med=205ms max=1.15s p(90)=558ms p(95)=660ms
ws_connecting........: avg=248.81ms min=145.89ms med=221.89ms max=1.38s p(90)=290.41ms p(95)=322.2ms
```
750 VUs
```
rtt..................: avg=548.27ms min=51ms med=438ms max=5.19s p(90)=1.18s p(95)=1.29s
ws_connecting........: avg=266.37ms min=144.06ms med=224.93ms max=2.33s p(90)=298ms p(95)=342.87ms
```
With trimming disabled
250 VUs
```
rtt..................: avg=139.47ms min=48ms med=142ms max=807ms p(90)=189ms p(95)=214ms
ws_connecting........: avg=212.58ms min=146.19ms med=196.25ms max=1.25s p(90)=255.74ms p(95)=272.44ms
```
With a polling interval of 0.01 seconds it becomes comparable to Redis
250 VUs
```
rtt..................: avg=84.22ms min=43ms med=69ms max=416ms p(90)=137ms p(95)=150ms
ws_connecting........: avg=219.37ms min=144.71ms med=200.77ms max=2.17s p(90)=265.23ms p(95)=290.83ms
```
##### Redis
This instance was hosted on the same machine.
100 VUs
```
rtt..................: avg=68.95ms min=41ms med=56ms max=6.23s p(90)=114ms p(95)=129ms
ws_connecting........: avg=211.09ms min=153.23ms med=195.69ms max=1.44s p(90)=258.1ms p(95)=272.23ms
```
250 VUs
```
rtt..................: avg=69.32ms min=40ms med=56ms max=645ms p(90)=119ms p(95)=135ms
ws_connecting........: avg=212.95ms min=142.92ms med=196.31ms max=1.25s p(90)=260.25ms p(95)=273.49ms
```
500 VUs
```
rtt..................: avg=87.5ms min=40ms med=67ms max=839ms p(90)=149ms p(95)=176ms
ws_connecting........: avg=242.62ms min=142.03ms med=213.76ms max=2.34s p(90)=291.25ms p(95)=324.04ms
```
750 VUs
```
rtt..................: avg=162.54ms min=39ms med=123ms max=2.26s p(90)=343.1ms p(95)=438ms
ws_connecting........: avg=353.08ms min=143ms med=264.15ms max=2.73s p(90)=541.36ms p(95)=1.15s
```
##### MySQL
With a polling interval of 0.1 seconds and autotrimming enabled. This instance
was also hosted on the same machine.
100 VUs
```
rtt..................: avg=136.02ms min=51ms med=137ms max=877ms p(90)=168.1ms p(95)=198ms
ws_connecting........: avg=207.76ms min=151.93ms med=196.74ms max=1.21s p(90)=249.91ms p(95)=260.37ms
```
250 VUs
```
rtt..................: avg=159.33ms min=51ms med=149ms max=559ms p(90)=236ms p(95)=263ms
ws_connecting........: avg=232.38ms min=151.6ms med=218.09ms max=1.38s p(90)=287.99ms p(95)=324.6ms
```
500 VUs
```
rtt..................: avg=441.07ms min=51ms med=312ms max=2.29s p(90)=931ms p(95)=1.07s
ws_connecting........: avg=256.73ms min=152.23ms med=231.02ms max=2.31s p(90)=305.69ms p(95)=340.83ms
```
750 VUs
```
rtt..................: avg=822.08ms min=51ms med=732ms max=5.05s p(90)=1.76s p(95)=1.97s
ws_connecting........: avg=278.08ms min=146.66ms med=236.35ms max=2.37s p(90)=318.17ms p(95)=374.98ms
```
##### PostgreSQL with Solid Cable
With a polling interval of 0.1 seconds and autotrimming enabled. This instance
was also hosted on the same machine.
100 VUs
```
rtt..................: avg=137.45ms min=48ms med=139ms max=439ms p(90)=179.1ms p(95)=204ms
ws_connecting........: avg=207.13ms min=150.29ms med=197.76ms max=443.67ms p(90)=254.44ms p(95)=263.29ms
```
250 VUs
```
rtt..................: avg=151.63ms min=49ms med=146ms max=538ms p(90)=222ms p(95)=248.04ms
ws_connecting........: avg=245.89ms min=147.18ms med=205.57ms max=30s p(90)=265.08ms p(95)=281.15ms
```
500 VUs
```
rtt..................: avg=362.79ms min=50ms med=249ms max=1.21s p(90)=757ms p(95)=844ms
ws_connecting........: avg=257.02ms min=146.13ms med=227.65ms max=2.39s p(90)=303.22ms p(95)=344.39ms
```
##### PostgreSQL with dedicated adapter
100 VUs
```
rtt..................: avg=69.76ms min=41ms med=57ms max=622ms p(90)=116ms p(95)=133ms
ws_connecting........: avg=210.97ms min=149.68ms med=196.06ms max=1.27s p(90)=259.67ms p(95)=273.17ms
```
250 VUs
```
rtt..................: avg=73.43ms min=40ms med=58ms max=698ms p(90)=126ms p(95)=141ms
ws_connecting........: avg=210.83ms min=143.01ms med=195.22ms max=1.27s p(90)=259.27ms p(95)=272.6ms
```
## License
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
================================================
FILE: Rakefile
================================================
require "bundler/setup"
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
load "rails/tasks/engine.rake"
require "bundler/gem_tasks"
================================================
FILE: app/jobs/solid_cable/trim_job.rb
================================================
# frozen_string_literal: true
module SolidCable
class TrimJob < ActiveJob::Base
def perform
return unless trim?
::SolidCable::Message.transaction do
ids = ::SolidCable::Message.trimmable.non_blocking_lock.
limit(trim_batch_size).pluck(:id)
::SolidCable::Message.where(id: ids).delete_all
end
end
private
def trim_batch_size
::SolidCable.trim_batch_size
end
def trim?
expires_per_write = (1 / trim_batch_size.to_f) * ::SolidCable.trim_chance
!::SolidCable.autotrim? ||
rand < (expires_per_write - expires_per_write.floor)
end
end
end
================================================
FILE: app/models/solid_cable/message.rb
================================================
# frozen_string_literal: true
module SolidCable
class Message < SolidCable::Record
scope :trimmable, lambda {
where(created_at: ...::SolidCable.message_retention.ago)
}
scope :broadcastable, lambda { |channels, last_id|
where(channel_hash: channel_hashes_for(channels)).
where(id: (last_id.to_i + 1)..).order(:id)
}
class << self
def broadcast(channel, payload)
insert({ created_at: Time.current, channel:, payload:,
channel_hash: channel_hash_for(channel) })
end
def channel_hashes_for(channels)
channels.map { |channel| channel_hash_for(channel) }
end
# Need to unpack this as a signed integer since Postgresql and SQLite
# don't support unsigned integers
def channel_hash_for(channel)
Digest::SHA256.digest(channel.to_s).unpack1("q>")
end
end
end
end
================================================
FILE: app/models/solid_cable/record.rb
================================================
# frozen_string_literal: true
module SolidCable
class Record < ActiveRecord::Base
self.abstract_class = true
connects_to(**SolidCable.connects_to) if SolidCable.connects_to.present?
def self.non_blocking_lock
if SolidCable.use_skip_locked
lock(Arel.sql("FOR UPDATE SKIP LOCKED"))
else
lock
end
end
end
end
================================================
FILE: bench/.dockerignore
================================================
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
# Ignore git directory.
/.git/
# Ignore bundler config.
/.bundle
# Ignore all default key files.
/config/master.key
/config/credentials/*.key
# Ignore all environment files.
/.env*
!/.env.example
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
tmp/*
log/*
tmp/cache/assets/*
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
/tmp/cache/*
tmp/storage/*
tmp/cache/*
/coverage/*
coverage/*
!/tmp/storage/.keep
# Ignore assets.
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets
/vendor/bundle
test/*
/test/*
tags
/tags
k6
================================================
FILE: bench/.ruby-version
================================================
3.3.5
================================================
FILE: bench/Dockerfile
================================================
# syntax = docker/dockerfile:1
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t my-app .
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.3.5
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here
WORKDIR /rails
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_WITHOUT="development:test:linters:deploy" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git pkg-config libpq-dev && \
apt-get clean && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
# Copy application code
COPY . .
RUN bundle exec bootsnap precompile app/ lib/ && \
SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile && \
rm -rf vendor/ruby/3.3.0/cache
# Final stage for app image
FROM base
# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
mkdir /data && \
chown -R rails:rails db log storage tmp /data
USER 1000:1000
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
================================================
FILE: bench/Gemfile
================================================
source "https://rubygems.org"
gem "rails", github: "rails/rails", branch: "main"
gem "activeerror"
gem "propshaft"
gem "solid_cable", github: "rails/solid_cable", branch: "main"
gem "sqlite3"
gem "trilogy"
gem "pg"
gem "puma"
gem "importmap-rails"
gem "redis"
gem "bootsnap", require: false
gem "kamal", require: false
gem "tailwindcss-rails"
================================================
FILE: bench/Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application"
Rails.application.load_tasks
================================================
FILE: bench/app/assets/builds/.keep
================================================
================================================
FILE: bench/app/assets/images/.keep
================================================
================================================
FILE: bench/app/assets/stylesheets/application.tailwind.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
/*
@layer components {
.btn-primary {
@apply py-2 px-4 bg-blue-200;
}
}
*/
================================================
FILE: bench/app/channels/application_cable/channel.rb
================================================
# :markup: markdown
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end
================================================
FILE: bench/app/channels/application_cable/connection.rb
================================================
module ApplicationCable
class Connection < ActionCable::Connection::Base
rescue_from Exception do |error|
Rails.error.report(e, handled: false,
source: "application.action_cable")
end
identified_by :id
def connect
self.id = SecureRandom.uuid
end
end
end
================================================
FILE: bench/app/channels/broadcast_channel.rb
================================================
class BroadcastChannel < ApplicationCable::Channel
def subscribed
Rails.logger.info "a client subscribed: #{id}"
stream_from "broadcast:#{id}"
end
def unsubscribed
Rails.logger.info "unsubscribed: #{id}"
stop_all_streams
end
def ping(data)
broadcast_to id, { message: "pong #{data.with_indifferent_access[:message]}" }
end
end
================================================
FILE: bench/app/controllers/application_controller.rb
================================================
class ApplicationController < ActionController::Base
end
================================================
FILE: bench/app/controllers/concerns/.keep
================================================
================================================
FILE: bench/app/controllers/rooms_controller.rb
================================================
class RoomsController < ApplicationController
before_action :set_room, only: %i[ show edit update destroy ]
# GET /rooms
def index
@rooms = Room.all
end
# GET /rooms/1
def show
end
# GET /rooms/new
def new
@room = Room.new
end
# GET /rooms/1/edit
def edit
end
# POST /rooms
def create
@room = Room.new(room_params)
if @room.save
redirect_to @room, notice: "Room was successfully created."
else
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /rooms/1
def update
if @room.update(room_params)
redirect_to @room, notice: "Room was successfully updated.", status: :see_other
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /rooms/1
def destroy
@room.destroy!
redirect_to rooms_url, notice: "Room was successfully destroyed.", status: :see_other
end
private
# Use callbacks to share common setup or constraints between actions.
def set_room
@room = Room.find(params[:id])
end
# Only allow a list of trusted parameters through.
def room_params
params.require(:room).permit(:name)
end
end
================================================
FILE: bench/app/controllers/ws_debugger_controller.rb
================================================
class WsDebuggerController < ApplicationController
end
================================================
FILE: bench/app/javascript/application.js
================================================
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "channels"
================================================
FILE: bench/app/javascript/channels/broadcast_channel.js
================================================
import consumer from "channels/consumer"
consumer.subscriptions.create("BroadcastChannel", {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
// Called when there's incoming data on the websocket for this channel
}
});
================================================
FILE: bench/app/javascript/channels/consumer.js
================================================
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
================================================
FILE: bench/app/javascript/channels/index.js
================================================
// Import all the channels to be used by Action Cable
import "channels/broadcast_channel"
================================================
FILE: bench/app/javascript/controllers/application.js
================================================
import { Application } from "@hotwired/stimulus"
const application = Application.start()
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
export { application }
================================================
FILE: bench/app/javascript/controllers/index.js
================================================
// Import and register all your controllers from the importmap under controllers/*
import { application } from "controllers/application"
// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
// lazyLoadControllersFrom("controllers", application)
================================================
FILE: bench/app/jobs/application_job.rb
================================================
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end
================================================
FILE: bench/app/mailers/application_mailer.rb
================================================
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout "mailer"
end
================================================
FILE: bench/app/models/application_record.rb
================================================
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end
================================================
FILE: bench/app/models/concerns/.keep
================================================
================================================
FILE: bench/app/models/room.rb
================================================
class Room < ApplicationRecord
end
================================================
FILE: bench/app/views/layouts/application.html.erb
================================================
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "Cablebench" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
<link rel="icon" href="/icon.png" type="image/png">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag :all, "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<main class="container mx-auto mt-28 px-5 flex">
<%= yield %>
</main>
</body>
</html>
================================================
FILE: bench/app/views/layouts/mailer.html.erb
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<%= yield %>
</body>
</html>
================================================
FILE: bench/app/views/layouts/mailer.text.erb
================================================
<%= yield %>
================================================
FILE: bench/app/views/rooms/_form.html.erb
================================================
<%= form_with(model: room, class: "contents") do |form| %>
<% if room.errors.any? %>
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
<h2><%= pluralize(room.errors.count, "error") %> prohibited this room from being saved:</h2>
<ul>
<% room.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="my-5">
<%= form.label :name %>
<%= form.text_field :name, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
</div>
<div class="inline">
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
</div>
<% end %>
================================================
FILE: bench/app/views/rooms/_room.html.erb
================================================
<div id="<%= dom_id room %>">
<p class="my-5">
<strong class="block font-medium mb-1">Name:</strong>
<%= room.name %>
</p>
</div>
================================================
FILE: bench/app/views/rooms/edit.html.erb
================================================
<div class="mx-auto md:w-2/3 w-full">
<h1 class="font-bold text-4xl">Editing room</h1>
<%= render "form", room: @room %>
<%= link_to "Show this room", @room, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
<%= link_to "Back to rooms", rooms_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>
================================================
FILE: bench/app/views/rooms/index.html.erb
================================================
<div class="w-full">
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<% content_for :title, "Rooms" %>
<div class="flex justify-between items-center">
<h1 class="font-bold text-4xl">Rooms</h1>
<%= link_to "New room", new_room_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
</div>
<div id="rooms" class="min-w-full">
<% @rooms.each do |room| %>
<%= render room %>
<p>
<%= link_to "Show this room", room, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</p>
<% end %>
</div>
</div>
================================================
FILE: bench/app/views/rooms/new.html.erb
================================================
<div class="mx-auto md:w-2/3 w-full">
<h1 class="font-bold text-4xl">New room</h1>
<%= render "form", room: @room %>
<%= link_to "Back to rooms", rooms_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>
================================================
FILE: bench/app/views/rooms/show.html.erb
================================================
<div class="mx-auto md:w-2/3 w-full flex">
<div class="mx-auto">
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>
<%= render @room %>
<%= link_to "Edit this room", edit_room_path(@room), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
<%= link_to "Back to rooms", rooms_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
<div class="inline-block ml-2">
<%= button_to "Destroy this room", @room, method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
</div>
</div>
</div>
================================================
FILE: bench/app/views/ws_debugger/show.html.erb
================================================
<p>hello world</p>
================================================
FILE: bench/bin/bundle
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "rubygems"
m = Module.new do
module_function
def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__)
end
def env_var_version
ENV["BUNDLER_VERSION"]
end
def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = $1
update_index = i
end
bundler_version
end
def gemfile
gemfile = ENV["BUNDLE_GEMFILE"]
return gemfile if gemfile && !gemfile.empty?
File.expand_path("../Gemfile", __dir__)
end
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
end
def lockfile_version
return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1)
end
def bundler_requirement
@bundler_requirement ||=
env_var_version ||
cli_arg_version ||
bundler_requirement_for(lockfile_version)
end
def bundler_requirement_for(version)
return "#{Gem::Requirement.default}.a" unless version
bundler_gem_version = Gem::Version.new(version)
bundler_gem_version.approximate_recommendation
end
def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile
activate_bundler
end
def activate_bundler
gem_error = activation_error_handling do
gem "bundler", bundler_requirement
end
return if gem_error.nil?
require_error = activation_error_handling do
require "bundler/version"
end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42
end
def activation_error_handling
yield
nil
rescue StandardError, LoadError => e
e
end
end
m.load_bundler!
if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end
================================================
FILE: bench/bin/docker-entrypoint
================================================
#!/bin/bash -e
# Enable jemalloc for reduced memory usage and latency.
if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
fi
# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
./bin/rails db:prepare
fi
exec "${@}"
================================================
FILE: bench/bin/importmap
================================================
#!/usr/bin/env ruby
require_relative "../config/application"
require "importmap/commands"
================================================
FILE: bench/bin/kamal
================================================
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'kamal' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("kamal", "kamal")
================================================
FILE: bench/bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
================================================
FILE: bench/bin/rake
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run
================================================
FILE: bench/bin/rubocop
================================================
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"
# explicit rubocop config increases performance slightly while avoiding config confusion.
ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
load Gem.bin_path("rubocop", "rubocop")
================================================
FILE: bench/bin/setup
================================================
#!/usr/bin/env ruby
require "fileutils"
APP_ROOT = File.expand_path("..", __dir__)
APP_NAME = "cablebench"
def system!(*args)
system(*args, exception: true)
end
FileUtils.chdir APP_ROOT do
# This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
system! "bin/rails db:prepare"
puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server =="
system! "bin/rails restart"
# puts "\n== Configuring puma-dev =="
# system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}"
# system "curl -Is https://#{APP_NAME}.test/up | head -n 1"
end
================================================
FILE: bench/config/application.rb
================================================
require_relative "boot"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Cablebench
class Application < Rails::Application
config.action_cable.mount_path = '/cable'
config.action_cable.disable_request_forgery_protection = true
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 8.0
# Please, add to the `ignore` list any other `lib` subdirectories that do
# not contain `.rb` files, or that should not be reloaded or eager loaded.
# Common ones are `templates`, `generators`, or `middleware`, for example.
config.autoload_lib(ignore: %w[assets tasks])
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
end
end
================================================
FILE: bench/config/boot.rb
================================================
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" # Set up gems listed in the Gemfile.
require "bootsnap/setup" # Speed up boot time by caching expensive operations.
================================================
FILE: bench/config/cable.yml
================================================
development:
# adapter: redis
# url: redis://localhost:6379/1
adapter: solid_cable
message_retention: 15.minutes
polling_interval: 0.1.seconds
test:
adapter: test
production:
# adapter: redis
# url: <%= ENV.fetch("REDIS_URL") { "redis://#{ENV["HOST"]}:6379/1" } %>
# channel_prefix: cablebench_production
# adapter: postgresql
# database: cablebench
# user: cablebench
# password: <%= ENV["POSTGRES_PASSWORD"] %>
# host: <%= ENV["HOST"] %>
adapter: solid_cable
message_retention: 1.day
polling_interval: 0.1.seconds
================================================
FILE: bench/config/credentials.yml.enc
================================================
7lxKveoOh/YSo6BSUdrNHtg29iVtfI9AinJgpSiiQkSWpoVPHluT1PNVQpleQhs3LqM0WP4aMBX12xzUP843RI2LYQmtUmbjkJtfkMs/dCijfslkwfrwSI8ApQINOTwVG3fbYdFKe5N8C2Qhqbch+vMDktSL/eTXw7x1dSaUUjRF1zfEzi+xtn8D4nbDSv3m5tbUAzG2D3NU2bYIBaMtSNlnu0orY6RFPi8HRnGArsoMd41cBxUUJP6a23jatCGTl7BRrwRncBpoMKPbimiqE2YpE1XD+A25qcjA/1kxneSVvRl+0S0lUqVbUO9L84tsj9prt8YWKTeDWP9MWu9wGvyVoUb9eDMf15k3soBrKvG2++oZ6t/2S7pIg9gGEMBcRZwq9nHRnaS5SEhUFBmb4yr40/qWdpKGFKGAA/9al3sD3ChAlYXc5zQ81V4+o3hCxapvWbUr--2KQ9UYDapBokyl5i--Me6kHSK8rQVQyCeZM2w1lw==
================================================
FILE: bench/config/database.yml
================================================
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: storage/development.sqlite3
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: storage/test.sqlite3
# Store production database in the storage/ directory, which by default
# is mounted as a persistent Docker volume in config/deploy.yml.
production:
# <<: *default
# adapter: sqlite3
# pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
# timeout: 5000
# database: /data/production.sqlite3
# adapter: trilogy
# pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 50 } %>
# timeout: 5000
# database: solid_cable
# user: root
# password: <%= ENV["MYSQL_ROOT_PASSWORD"] %>
# host: <%= ENV["HOST"] %>
# ssl: true
adapter: postgresql
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 50 } %>
timeout: 5000
database: cablebench
user: cablebench
password: <%= ENV["POSTGRES_PASSWORD"] %>
host: <%= ENV["HOST"] %>
================================================
FILE: bench/config/deploy.yml
================================================
service: solidcable
image: npezza/solid_cable
asset_path: /rails/public/assets
servers:
web:
- <%= ENV["HOST"] %>
registry:
username: npezza
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
- MYSQL_ROOT_PASSWORD
- HOST
- POSTGRES_PASSWORD
volumes:
- "solidcable:/data"
builder:
context: "."
local:
arch: amd64
accessories:
mysql:
image: mysql:8.3
host: "<%= ENV['HOST'] %>"
port: 3306
env:
clear:
MYSQL_ROOT_HOST: '%'
secret:
- MYSQL_ROOT_PASSWORD
files:
- config/init.sql:/docker-entrypoint-initdb.d/setup.sql
directories:
- data:/var/lib/mysql
redis:
image: redis:latest
host: "<%= ENV['HOST'] %>"
port: 6379
cmd: "redis-server"
volumes:
- /var/lib/redis:/data
postgres:
image: postgres:16
host: "<%= ENV['HOST'] %>"
port: 5432
env:
clear:
POSTGRES_USER: "cablebench"
POSTGRES_DB: "cablebench"
secret:
- POSTGRES_PASSWORD
files:
- config/init.sql:/docker-entrypoint-initdb.d/setup.sql
directories:
- data:/var/lib/postgresql/data
================================================
FILE: bench/config/environment.rb
================================================
# Load the Rails application.
require_relative "application"
# Initialize the Rails application.
Rails.application.initialize!
================================================
FILE: bench/config/environments/development.rb
================================================
require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded any time
# it changes. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.enable_reloading = true
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports.
config.consider_all_requests_local = true
# Enable server timing.
config.server_timing = true
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
config.cache_store = :memory_store
config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" }
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Highlight code that enqueued background job in logs.
config.active_job.verbose_enqueue_logs = true
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
config.action_view.annotate_rendered_view_with_filenames = true
# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
# config.generators.apply_rubocop_autocorrect_after_generate!
end
================================================
FILE: bench/config/environments/production.rb
================================================
require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.enable_reloading = false
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment
# key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from `public/`, relying on NGINX/Apache to do so instead.
# config.public_file_server.enabled = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com"
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
# config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
# Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = "wss://example.com/cable"
# config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
config.action_cable.worker_pool_size = 10
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
# Skip http-to-https redirect for the default health check endpoint.
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
# Log to STDOUT by default
config.logger = ActiveSupport::Logger.new(STDOUT)
.tap { |logger| logger.formatter = ::Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
# Prepend all log lines with the following tags.
config.log_tags = [ :request_id ]
# "info" includes generic and useful information about system operation, but avoids logging too much
# information to avoid inadvertent exposure of personally identifiable information (PII). If you
# want to log everything, set the level to "debug".
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "debug")
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Use a real queuing backend for Active Job (and separate queues per environment).
# config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "cablebench_production"
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Enable DNS rebinding protection and other `Host` header attacks.
# config.hosts = [
# "example.com", # Allow requests from example.com
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
# ]
# Skip DNS rebinding protection for the default health check endpoint.
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end
================================================
FILE: bench/config/environments/test.rb
================================================
require "active_support/core_ext/integer/time"
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# While tests run files are not watched, reloading is not necessary.
config.enable_reloading = false
# Eager loading loads your entire application. When running a single test locally,
# this is usually not necessary, and can slow down your test suite. However, it's
# recommended that you enable it in continuous integration systems to ensure eager
# loading is working properly before deploying your code.
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" }
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Render exception templates for rescuable exceptions and raise for other exceptions.
config.action_dispatch.show_exceptions = :rescuable
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Unlike controllers, the mailer instance doesn't have any context about the
# incoming request so you'll need to provide the :host parameter yourself.
config.action_mailer.default_url_options = { host: "www.example.com" }
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
end
================================================
FILE: bench/config/importmap.rb
================================================
# Pin npm packages by running ./bin/importmap
pin "application"
pin "@rails/actioncable", to: "actioncable.esm.js"
pin_all_from "app/javascript/channels", under: "channels"
================================================
FILE: bench/config/init.sql
================================================
CREATE DATABASE cablebench;
================================================
FILE: bench/config/initializers/assets.rb
================================================
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
================================================
FILE: bench/config/initializers/content_security_policy.rb
================================================
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy.
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header
# Rails.application.configure do
# config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
#
# # Generate session nonces for permitted importmap, inline scripts, and inline styles.
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
# config.content_security_policy_nonce_directives = %w(script-src style-src)
#
# # Report violations without enforcing the policy.
# # config.content_security_policy_report_only = true
# end
================================================
FILE: bench/config/initializers/filter_parameter_logging.rb
================================================
# Be sure to restart your server when you modify this file.
# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
# Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
]
================================================
FILE: bench/config/initializers/inflections.rb
================================================
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, "\\1en"
# inflect.singular /^(ox)en/i, "\\1"
# inflect.irregular "person", "people"
# inflect.uncountable %w( fish sheep )
# end
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym "RESTful"
# end
================================================
FILE: bench/config/initializers/permissions_policy.rb
================================================
# Be sure to restart your server when you modify this file.
# Define an application-wide HTTP permissions policy. For further
# information see: https://developers.google.com/web/updates/2018/06/feature-policy
# Rails.application.config.permissions_policy do |policy|
# policy.camera :none
# policy.gyroscope :none
# policy.microphone :none
# policy.usb :none
# policy.fullscreen :self
# policy.payment :self, "https://secure.example.com"
# end
================================================
FILE: bench/config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization and
# are automatically loaded by Rails. If you want to use locales other than
# English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
# <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more about the API, please read the Rails Internationalization guide
# at https://guides.rubyonrails.org/i18n.html.
#
# Be aware that YAML interprets the following case-insensitive strings as
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
# must be quoted to be interpreted as strings. For example:
#
# en:
# "yes": yup
# enabled: "ON"
en:
hello: "Hello world"
================================================
FILE: bench/config/puma.rb
================================================
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
# Specifies the `environment` that Puma will run in.
rails_env = ENV.fetch("RAILS_ENV", "development")
environment rails_env
case rails_env
when "production"
# If you are running more than 1 thread per process, the workers count
# should be equal to the number of processors (CPU cores) in production.
#
# Automatically detect the number of available processors in production.
require "concurrent-ruby"
workers_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.available_processor_count })
workers workers_count if workers_count > 1
worker_timeout 240
preload_app!
when "development"
# Specifies a very generous `worker_timeout` so that the worker
# isn't killed by Puma when suspended by a debugger.
worker_timeout 3600
end
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
# Only use a pidfile when requested
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
================================================
FILE: bench/config/routes.rb
================================================
Rails.application.routes.draw do
mount ActiveError::Engine => "/errors"
mount ActionCable.server => "/cable"
get "/ws" => "ws_debugger#show"
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
resources :rooms
root "rooms#index"
end
================================================
FILE: bench/config/storage.yml
================================================
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
# service: S3
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
# region: us-east-1
# bucket: your_own_bucket-<%= Rails.env %>
# Remember not to checkin your GCS keyfile to a repository
# google:
# service: GCS
# project: your_project
# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
# bucket: your_own_bucket-<%= Rails.env %>
# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
# service: AzureStorage
# storage_account_name: your_account_name
# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
# container: your_container_name-<%= Rails.env %>
# mirror:
# service: Mirror
# primary: local
# mirrors: [ amazon, google, microsoft ]
================================================
FILE: bench/config/tailwind.config.js
================================================
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
content: [
'./public/*.html',
'./app/helpers/**/*.rb',
'./app/javascript/**/*.js',
'./app/views/**/*.{erb,haml,html,slim}'
],
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
require('@tailwindcss/container-queries'),
]
}
================================================
FILE: bench/config.ru
================================================
# This file is used by Rack-based servers to start the application.
require_relative "config/environment"
run Rails.application
Rails.application.load_server
================================================
FILE: bench/db/migrate/20240529231225_create_rooms.rb
================================================
class CreateRooms < ActiveRecord::Migration[8.0]
def change
create_table :rooms do |t|
t.string :name
t.timestamps
end
end
end
================================================
FILE: bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb
================================================
# frozen_string_literal: true
# This migration comes from solid_cable (originally 20240103034713)
class CreateSolidCableMessage < ActiveRecord::Migration[7.1]
def change
create_table :solid_cable_messages, if_not_exists: true do |t|
t.text :channel
t.text :payload
t.timestamps
t.index :created_at
end
end
end
================================================
FILE: bench/db/migrate/20240607184931_index_channels.solid_cable.rb
================================================
# This migration comes from solid_cable (originally 20240607184711)
class IndexChannels < ActiveRecord::Migration[7.1]
def change
add_index :solid_cable_messages, :channel, length: 500
end
end
================================================
FILE: bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb
================================================
# frozen_string_literal: true
# This migration comes from active_error (originally 20200727220359)
class CreateActiveErrorFaults < ActiveRecord::Migration[7.1]
def change # rubocop:disable Metrics/AbcSize
create_table :active_error_faults do |t|
t.belongs_to :cause
t.binary :backtrace, limit: 512.megabytes
t.binary :backtrace_locations, limit: 512.megabytes
t.string :klass
t.text :message
t.string :controller
t.string :action
t.integer :instances_count
t.text :blamed_files, limit: 512.megabytes
t.text :options
t.timestamps
end
end
end
================================================
FILE: bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb
================================================
# frozen_string_literal: true
# This migration comes from active_error (originally 20200727225318)
class CreateActiveErrorInstances < ActiveRecord::Migration[7.1]
def change
create_table :active_error_instances do |t|
t.belongs_to :fault
t.text :url
t.binary :headers, limit: 512.megabytes
t.binary :parameters, limit: 512.megabytes
t.timestamps
end
end
end
================================================
FILE: bench/db/migrate/20240912235943_create_compact_channel.rb
================================================
# frozen_string_literal: true
class CreateCompactChannel < ActiveRecord::Migration[7.2]
def up
change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false
add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true
add_index :solid_cable_messages, :channel_hash, if_not_exists: true
change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false
SolidCable::Message.reset_column_information
SolidCable::Message.find_each do |msg|
msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))
end
end
def down
change_column :solid_cable_messages, :channel, :text
remove_column :solid_cable_messages, :channel_hash, if_exists: true
change_column :solid_cable_messages, :payload, :text
end
end
================================================
FILE: bench/db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2024_09_12_235943) do
create_table "active_error_faults", force: :cascade do |t|
t.integer "cause_id"
t.binary "backtrace", limit: 536870912
t.binary "backtrace_locations", limit: 536870912
t.string "klass"
t.text "message"
t.string "controller"
t.string "action"
t.integer "instances_count"
t.text "blamed_files", limit: 536870912
t.text "options"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["cause_id"], name: "index_active_error_faults_on_cause_id"
end
create_table "active_error_instances", force: :cascade do |t|
t.integer "fault_id"
t.text "url"
t.binary "headers", limit: 536870912
t.binary "parameters", limit: 536870912
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["fault_id"], name: "index_active_error_instances_on_fault_id"
end
create_table "rooms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "solid_cable_messages", force: :cascade do |t|
t.binary "channel", limit: 1024, null: false
t.binary "payload", limit: 536870912, null: false
t.datetime "created_at", null: false
t.integer "channel_hash", limit: 8, null: false
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
end
end
================================================
FILE: bench/db/seeds.rb
================================================
# This file should ensure the existence of records required to run the application in every environment (production,
# development, test). The code here should be idempotent so that it can be executed at any point in every environment.
# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
#
# Example:
#
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
# MovieGenre.find_or_create_by!(name: genre_name)
# end
================================================
FILE: bench/loadtest.js
================================================
import { check, sleep, fail } from "k6";
import cable from "k6/x/cable";
import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.1.0/index.js";
import { Trend } from "k6/metrics";
let rttTrend = new Trend("rtt", true);
const WS_URL = __ENV.WS_URL || "wss://solid-cable.dev/cable";
const WS_COOKIE = __ENV.WS_COOKIE; // we need a valid cookie to authorize request
const MAX = parseInt(__ENV.MAX || "20");
// Total test duration
const TIME = parseInt(__ENV.TIME || "90");
const MESSAGES_NUM = parseInt(__ENV.NUM || "5");
export let options = {
thresholds: {
checks: ["rate>0.9"],
},
scenarios: {
ping: {
// We use ramping executor to slowly increase the number of users during a test
executor: "ramping-vus",
startVUs: (MAX / 10 || 1) | 0,
stages: [
{ duration: `${TIME / 3}s`, target: (MAX / 4) | 0 },
{ duration: `${(7 * TIME) / 12}s`, target: MAX },
{ duration: `${TIME / 12}s`, target: 0 },
],
},
},
};
export default function () {
const client = cable.connect(WS_URL, { cookies: WS_COOKIE, receiveTimeoutMS: 60000 });
if (!check(client, { "successful connection": (obj) => obj })) {
fail("connection failed");
}
const channel = client.subscribe("BroadcastChannel", {});
if (!check(channel, { "successful subscription": (obj) => obj })) {
fail("failed to subscribe");
}
for (let i = 0; i < MESSAGES_NUM; i++) {
let startMessage = Date.now();
channel.perform("ping", { message: `Hello ${i}` });
let message = channel.receive();
if (!check(message, { "received res": (obj) => obj.message === `pong Hello ${i}` })) {
fail("expected message hasn't been received");
}
let endMessage = Date.now();
rttTrend.add(endMessage - startMessage);
sleep(randomIntBetween(5, 10) / 10);
}
// Terminate the WS connection
client.disconnect();
}
================================================
FILE: bench/log/.keep
================================================
================================================
FILE: bench/public/404.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
.rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
.rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
.rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
.rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
.rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
<body class="rails-default-error-page">
<!-- This file lives in public/404.html -->
<div class="dialog">
<div>
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
<p>If you are the application owner check the logs for more information.</p>
</div>
</body>
</html>
================================================
FILE: bench/public/406-unsupported-browser.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Your browser is not supported (406)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
.rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
.rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
.rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
.rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
.rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
<body class="rails-default-error-page">
<!-- This file lives in public/406-unsupported-browser.html -->
<div class="dialog">
<div>
<h1>Your browser is not supported.</h1>
<p>Please upgrade your browser to continue.</p>
</div>
</div>
</body>
</html>
================================================
FILE: bench/public/422.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>The change you wanted was rejected (422)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
.rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
.rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
.rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
.rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
.rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
<body class="rails-default-error-page">
<!-- This file lives in public/422.html -->
<div class="dialog">
<div>
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
<p>If you are the application owner check the logs for more information.</p>
</div>
</body>
</html>
================================================
FILE: bench/public/500.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
.rails-default-error-page {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
.rails-default-error-page div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
.rails-default-error-page div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
.rails-default-error-page h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}
.rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style>
</head>
<body class="rails-default-error-page">
<!-- This file lives in public/500.html -->
<div class="dialog">
<div>
<h1>We're sorry, but something went wrong.</h1>
</div>
<p>If you are the application owner check the logs for more information.</p>
</div>
</body>
</html>
================================================
FILE: bench/public/robots.txt
================================================
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
================================================
FILE: bench/test/channels/application_cable/connection_test.rb
================================================
require "test_helper"
module ApplicationCable
class ConnectionTest < ActionCable::Connection::TestCase
# test "connects with cookies" do
# cookies.signed[:user_id] = 42
#
# connect
#
# assert_equal connection.user_id, "42"
# end
end
end
================================================
FILE: bench/test/channels/broadcast_channel_test.rb
================================================
require "test_helper"
class BroadcastChannelTest < ActionCable::Channel::TestCase
# test "subscribes" do
# subscribe
# assert subscription.confirmed?
# end
end
================================================
FILE: bench/test/controllers/.keep
================================================
================================================
FILE: bench/test/fixtures/files/.keep
================================================
================================================
FILE: bench/test/fixtures/rooms.yml
================================================
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
two:
name: MyString
================================================
FILE: bench/test/models/.keep
================================================
================================================
FILE: bench/test/models/room_test.rb
================================================
require "test_helper"
class RoomTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
================================================
FILE: bench/test/test_helper.rb
================================================
ENV["RAILS_ENV"] ||= "test"
require_relative "../config/environment"
require "rails/test_help"
module ActiveSupport
class TestCase
# Run tests in parallel with specified workers
parallelize(workers: :number_of_processors)
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
end
================================================
FILE: bench/vendor/.keep
================================================
================================================
FILE: bench/vendor/javascript/.keep
================================================
================================================
FILE: bin/rails
================================================
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails gems
# installed from the root of your application.
ENGINE_ROOT = File.expand_path("..", __dir__)
ENGINE_PATH = File.expand_path("../lib/solid_cable/engine", __dir__)
APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)
# Set up gems listed in the Gemfile.
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
require "rails/all"
require "rails/engine/commands"
================================================
FILE: bin/release
================================================
#!/usr/bin/env bash
VERSION=$1
if [ -z "$VERSION" ]
then
echo "Usage: bin/release <version>"
exit 1
fi
printf "# frozen_string_literal: true\n\nmodule SolidCable\n VERSION = \"$VERSION\"\nend\n" > ./lib/solid_cable/version.rb
bundle
git add lib/solid_cable/version.rb
git commit -m "Bump version for $VERSION"
rake release
================================================
FILE: bin/test
================================================
#!/usr/bin/env ruby
$: << File.expand_path("../test", __dir__)
require "bundler/setup"
require "rails/plugin/test"
================================================
FILE: lib/action_cable/subscription_adapter/solid_cable.rb
================================================
# frozen_string_literal: true
require "action_cable/subscription_adapter/base"
require "action_cable/subscription_adapter/channel_prefix"
require "action_cable/subscription_adapter/subscriber_map"
require "concurrent/atomic/semaphore"
module ActionCable
module SubscriptionAdapter
class SolidCable < ::ActionCable::SubscriptionAdapter::Base
prepend ::ActionCable::SubscriptionAdapter::ChannelPrefix
def initialize(*)
super
@listener = nil
end
def broadcast(channel, payload)
::SolidCable::Message.broadcast(channel, payload)
::SolidCable::TrimJob.perform_now if ::SolidCable.autotrim?
end
def subscribe(channel, callback, success_callback = nil)
listener.add_subscriber(channel, callback, success_callback)
end
def unsubscribe(channel, callback)
listener.remove_subscriber(channel, callback)
end
delegate :shutdown, to: :listener
private
def listener
@listener || @server.mutex.synchronize do
@listener ||= Listener.new(@server.event_loop)
end
end
class Listener < ::ActionCable::SubscriptionAdapter::SubscriberMap
CONNECTION_ERRORS = [ ActiveRecord::ConnectionFailed ]
Stop = Class.new(Exception)
def initialize(event_loop)
super()
@event_loop = event_loop
# Critical section begins with 0 permits. It can be understood as
# being "normally held" by the listener thread. It is released
# for specific sections of code, rather than acquired.
@critical = Concurrent::Semaphore.new(0)
@reconnect_attempt = 0
@last_id = last_message_id
@thread = Thread.new do
Thread.current.name = "solid_cable_listener"
Thread.current.report_on_exception = true
begin
listen
rescue *CONNECTION_ERRORS
retry if retry_connecting?
end
end
end
def listen
loop do
begin
instance = interruptible { Rails.application.executor.run! }
with_polling_volume { broadcast_messages }
ensure
instance.complete! if instance
end
interruptible { sleep ::SolidCable.polling_interval }
end
rescue Stop
ensure
@critical.release
end
def interruptible
@critical.release
yield
ensure
@critical.acquire
end
def shutdown
@critical.acquire
# We have the critical permit, and so the listen thread must be
# safe to interrupt.
thread.raise(Stop)
@critical.release
thread.join
end
def add_channel(channel, on_success)
channels[channel] = last_message_id
event_loop.post(&on_success) if on_success
end
def remove_channel(channel)
channels.delete(channel)
end
def invoke_callback(*)
event_loop.post { super }
end
private
attr_reader :event_loop, :thread
attr_accessor :last_id, :reconnect_attempt
def last_message_id
::SolidCable::Message.maximum(:id) || 0
end
def channels
@channels ||= Concurrent::Map.new
end
def broadcast_messages
current_channels = channels.dup
::SolidCable::Message.
broadcastable(current_channels.keys, last_id).
each do |message|
should_broadcast_message = false
channels.compute_if_present(message.channel) do |channel_last_id|
break if channel_last_id >= message.id
should_broadcast_message = true
message.id
end
broadcast(message.channel, message.payload) if should_broadcast_message
self.last_id = message.id
end
end
def with_polling_volume
if ::SolidCable.silence_polling? && ActiveRecord::Base.logger
ActiveRecord::Base.logger.silence { yield }
else
yield
end
end
def reconnect_attempts
@reconnect_attempts ||= ::SolidCable.reconnect_attempts
end
def retry_connecting?
self.reconnect_attempt += 1
return false if reconnect_attempt > reconnect_attempts.size
sleep_t = reconnect_attempts[reconnect_attempt - 1]
sleep(sleep_t) if sleep_t > 0
true
end
end
end
end
end
================================================
FILE: lib/generators/solid_cable/install/USAGE
================================================
Description:
Installs solid_cable as the Action Cable adapter
Example:
bin/rails generate solid_cable:install
This will perform the following:
Installs solid_cable migrations
================================================
FILE: lib/generators/solid_cable/install/install_generator.rb
================================================
# frozen_string_literal: true
class SolidCable::InstallGenerator < Rails::Generators::Base
source_root File.expand_path("templates", __dir__)
def copy_files
template "db/cable_schema.rb"
template "config/cable.yml", force: true
end
end
================================================
FILE: lib/generators/solid_cable/install/templates/config/cable.yml
================================================
# Async adapter only works within the same process, so for manually triggering cable updates from a console,
# and seeing results in the browser, you must do so from the web console (running inside the dev process),
# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view
# to make the web console appear.
development:
adapter: async
test:
adapter: test
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 1.day
================================================
FILE: lib/generators/solid_cable/install/templates/db/cable_schema.rb
================================================
ActiveRecord::Schema[7.1].define(version: 1) do
create_table "solid_cable_messages", force: :cascade do |t|
t.binary "channel", limit: 1024, null: false
t.binary "payload", limit: 536870912, null: false
t.datetime "created_at", null: false
t.integer "channel_hash", limit: 8, null: false
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
end
end
================================================
FILE: lib/generators/solid_cable/update/USAGE
================================================
Description:
Updates Solid Cable migrations
Example:
bin/rails generate solid_cable:update
This will perform the following:
Installs new Solid Cable migrations
================================================
FILE: lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb
================================================
# frozen_string_literal: true
class CreateCompactChannel < ActiveRecord::Migration[7.2]
def up
change_column :solid_cable_messages, :channel, :binary, limit: 1024, null: false
add_column :solid_cable_messages, :channel_hash, :integer, limit: 8, if_not_exists: true
add_index :solid_cable_messages, :channel_hash, if_not_exists: true
change_column :solid_cable_messages, :payload, :binary, limit: 536_870_912, null: false
SolidCable::Message.find_each do |msg|
msg.update(channel_hash: SolidCable::Message.channel_hash_for(msg.channel))
end
end
def down
change_column :solid_cable_messages, :channel, :text
remove_column :solid_cable_messages, :channel_hash, if_exists: true
change_column :solid_cable_messages, :payload, :text
end
end
================================================
FILE: lib/generators/solid_cable/update/update_generator.rb
================================================
# frozen_string_literal: true
require "rails/generators"
require "rails/generators/active_record"
class SolidCable::UpdateGenerator < Rails::Generators::Base
include ActiveRecord::Generators::Migration
source_root File.expand_path("templates", __dir__)
def copy_files
migration_template "db/migrate/create_compact_channel.rb",
"db/cable_migrate/create_compact_channel.rb"
end
end
================================================
FILE: lib/solid_cable/engine.rb
================================================
# frozen_string_literal: true
module SolidCable
class Engine < ::Rails::Engine
isolate_namespace SolidCable
end
end
================================================
FILE: lib/solid_cable/version.rb
================================================
# frozen_string_literal: true
module SolidCable
VERSION = "3.0.12"
end
================================================
FILE: lib/solid_cable.rb
================================================
# frozen_string_literal: true
require "solid_cable/version"
require "solid_cable/engine"
require "action_cable/subscription_adapter/solid_cable"
module SolidCable
class << self
def connects_to
cable_config.connects_to.to_h.deep_transform_values(&:to_sym)
end
def silence_polling?
cable_config.silence_polling != false
end
def polling_interval
parse_duration(cable_config.polling_interval, default: 0.1.seconds)
end
def message_retention
parse_duration(cable_config.message_retention, default: 1.day)
end
def autotrim?
cable_config.autotrim != false
end
def trim_batch_size
if (size = cable_config.trim_batch_size.to_i) < 2
100
else
size
end
end
def use_skip_locked
cable_config.use_skip_locked != false
end
# For every write that we do, we attempt to delete trim_chance times as
# many records. This ensures there is downward pressure on the cache size
# while there is valid data to delete. Read this as 'every time the trim job
# runs theres a trim_multiplier chance this trims'. Adjust number to make it
# more or less likely to trim. Only works like this if trim_batch_size is
# 100
def trim_chance
2
end
def reconnect_attempts
attempts = cable_config.fetch(:reconnect_attempts, 1)
attempts = Array.new(attempts, 0) if attempts.is_a?(Integer)
attempts
end
private
def cable_config
Rails.application.config_for("cable")
end
def parse_duration(duration, default:)
if duration.present?
*amount, units = duration.to_s.split(".")
amount.join(".").to_f.public_send(units)
else
default
end
end
end
end
================================================
FILE: lib/tasks/solid_cable_tasks.rake
================================================
# frozen_string_literal: true
desc "Copy over the schema and set cable adapter for Solid Cable"
namespace :solid_cable do
task :install do
Rails::Command.invoke :generate, [ "solid_cable:install" ]
end
task :update do
Rails::Command.invoke :generate, [ "solid_cable:update" ]
end
end
================================================
FILE: solid_cable.gemspec
================================================
# frozen_string_literal: true
require_relative "lib/solid_cable/version"
Gem::Specification.new do |spec|
spec.name = "solid_cable"
spec.version = SolidCable::VERSION
spec.authors = [ "Nick Pezza" ]
spec.email = [ "pezza@hey.com" ]
spec.homepage = "https://github.com/rails/solid_cable"
spec.summary = "Database-backed Action Cable backend."
spec.description = "Database-backed Action Cable backend."
spec.license = "MIT"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["rubygems_mfa_required"] = "true"
spec.files = Dir.chdir(File.expand_path(__dir__)) do
Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
end
rails_version = ">= 7.2"
spec.required_ruby_version = ">= 3.2.0"
spec.add_dependency "activerecord", rails_version
spec.add_dependency "activejob", rails_version
spec.add_dependency "actioncable", rails_version
spec.add_dependency "railties", rails_version
spec.add_development_dependency "minitest", "~> 5.0"
end
================================================
FILE: test/config_stubs.rb
================================================
# frozen_string_literal: true
module ConfigStubs
extend ActiveSupport::Concern
class ConfigStub
def initialize(**)
@config = ActiveSupport::OrderedOptions.new.
update({ adapter: :test }.merge(**))
end
def config_for(_file)
@config
end
def executor
@executor ||= ExectorStub.new
end
class ExectorStub
def run!
end
end
end
def with_cable_config(**)
Rails.stub(:application, ConfigStub.new(**)) { yield }
end
end
================================================
FILE: test/dummy/Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application"
Rails.application.load_tasks
================================================
FILE: test/dummy/app/controllers/application_controller.rb
================================================
class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
end
================================================
FILE: test/dummy/app/controllers/concerns/.keep
================================================
================================================
FILE: test/dummy/app/helpers/application_helper.rb
================================================
module ApplicationHelper
end
================================================
FILE: test/dummy/app/jobs/application_job.rb
================================================
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end
================================================
FILE: test/dummy/app/models/application_record.rb
================================================
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end
================================================
FILE: test/dummy/app/models/concerns/.keep
================================================
================================================
FILE: test/dummy/app/views/layouts/application.html.erb
================================================
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "Dummy" %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Dummy">
<meta name="mobile-web-app-capable" content="yes">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
<link rel="icon" href="/icon.png" type="image/png">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= stylesheet_link_tag :app %>
</head>
<body>
<%= yield %>
</body>
</html>
================================================
FILE: test/dummy/app/views/layouts/mailer.html.erb
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
/* Email styles need to be inline */
</style>
</head>
<body>
<%= yield %>
</body>
</html>
================================================
FILE: test/dummy/app/views/layouts/mailer.text.erb
================================================
<%= yield %>
================================================
FILE: test/dummy/app/views/pwa/manifest.json.erb
================================================
{
"name": "Dummy",
"icons": [
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"scope": "/",
"description": "Dummy.",
"theme_color": "red",
"background_color": "red"
}
================================================
FILE: test/dummy/app/views/pwa/service-worker.js
================================================
// Add a service worker for processing Web Push notifications:
//
// self.addEventListener("push", async (event) => {
// const { title, options } = await event.data.json()
// event.waitUntil(self.registration.showNotification(title, options))
// })
//
// self.addEventListener("notificationclick", function(event) {
// event.notification.close()
// event.waitUntil(
// clients.matchAll({ type: "window" }).then((clientList) => {
// for (let i = 0; i < clientList.length; i++) {
// let client = clientList[i]
// let clientPath = (new URL(client.url)).pathname
//
// if (clientPath == event.notification.data.path && "focus" in client) {
// return client.focus()
// }
// }
//
// if (clients.openWindow) {
// return clients.openWindow(event.notification.data.path)
// }
// })
// )
// })
================================================
FILE: test/dummy/bin/ci
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "active_support/continuous_integration"
CI = ActiveSupport::ContinuousIntegration
require_relative "../config/ci.rb"
================================================
FILE: test/dummy/bin/dev
================================================
#!/usr/bin/env ruby
exec "./bin/rails", "server", *ARGV
================================================
FILE: test/dummy/bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
================================================
FILE: test/dummy/bin/rake
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run
================================================
FILE: test/dummy/bin/setup
================================================
#!/usr/bin/env ruby
require "fileutils"
APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
system(*args, exception: true)
end
FileUtils.chdir APP_ROOT do
# This script is a way to set up or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
puts "== Installing dependencies =="
system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
system! "bin/rails db:prepare"
system! "bin/rails db:reset" if ARGV.include?("--reset")
puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear"
unless ARGV.include?("--skip-server")
puts "\n== Starting development server =="
STDOUT.flush # flush the output before exec(2) so that it displays
exec "bin/dev"
end
end
================================================
FILE: test/dummy/config/application.rb
================================================
require_relative "boot"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Dummy
class Application < Rails::Application
config.load_defaults Rails::VERSION::STRING.to_f
# For compatibility with applications that use this config
config.action_controller.include_all_helpers = false
# Please, add to the `ignore` list any other `lib` subdirectories that do
# not contain `.rb` files, or that should not be reloaded or eager loaded.
# Common ones are `templates`, `generators`, or `middleware`, for example.
config.autoload_lib(ignore: %w[assets tasks])
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
end
end
================================================
FILE: test/dummy/config/boot.rb
================================================
# Set up gems listed in the Gemfile.
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
================================================
FILE: test/dummy/config/cable.yml
================================================
development:
adapter: async
test:
adapter: test
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: dummy_production
================================================
FILE: test/dummy/config/ci.rb
================================================
# Run using bin/ci
CI.run do
step "Setup", "bin/setup --skip-server"
step "Tests: Rails", "bin/rails test"
step "Tests: System", "bin/rails test:system"
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
# Optional: set a green GitHub commit status to unblock PR merge.
# Requires the `gh` CLI and `gh extension install basecamp/gh-signoff`.
# if success?
# step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
# else
# failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
# end
end
================================================
FILE: test/dummy/config/database.yml
================================================
<% def database_name_from(name); ["mysql", "postgres"].exclude?(ENV["TARGET_DB"]) ? "db/#{name}.sqlite3" : name; end %>
<% if ENV["TARGET_DB"] == "mysql" %>
default: &default
adapter: trilogy
username: root
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
host: "127.0.0.1"
port: 33060
<% elsif ENV["TARGET_DB"] == "postgres" %>
default: &default
adapter: postgresql
encoding: unicode
username: postgres
pool: 5
host: "127.0.0.1"
port: 55432
gssencmode: disable # https://github.com/ged/ruby-pg/issues/311
<% else %>
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 50 } %>
timeout: 100
<% end %>
development:
<<: *default
database: <%= database_name_from("solid_cable_development") %>
test:
<<: *default
pool: 20
database: <%= database_name_from("solid_cable_test") %>
================================================
FILE: test/dummy/config/environment.rb
================================================
# Load the Rails application.
require_relative "application"
# Initialize the Rails application.
Rails.application.initialize!
================================================
FILE: test/dummy/config/environments/development.rb
================================================
require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Make code changes take effect immediately without server restart.
config.enable_reloading = true
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports.
config.consider_all_requests_local = true
# Enable server timing.
config.server_timing = true
# Enable/disable Action Controller caching. By default Action Controller caching is disabled.
# Run rails dev:cache to toggle Action Controller caching.
if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true
config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" }
else
config.action_controller.perform_caching = false
end
# Change to :null_store to avoid any caching.
config.cache_store = :memory_store
# Store uploaded files on the local file system (see config/storage.yml for options).
# config.active_storage.service = :local
# Don't care if the mailer can't send.
# config.action_mailer.raise_delivery_errors = false
# Make template changes take effect immediately.
# config.action_mailer.perform_caching = false
# Set localhost to be used by links generated in mailer templates.
# config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true
# Append comments with runtime information tags to SQL queries in logs.
config.active_record.query_log_tags_enabled = true
# Highlight code that enqueued background job in logs.
config.active_job.verbose_enqueue_logs = true
# Highlight code that triggered redirect in logs.
config.action_dispatch.verbose_redirect_logs = true
# Suppress logger output for asset requests.
# config.assets.quiet = true
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
config.action_view.annotate_rendered_view_with_filenames = true
# Uncomment if you wish to allow Action Cable access from any origin.
# config.action_cable.disable_request_forgery_protection = true
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
end
================================================
FILE: test/dummy/config/environments/production.rb
================================================
require "active_support/core_ext/integer/time"
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.enable_reloading = false
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
config.eager_load = true
# Full error reports are disabled.
config.consider_all_requests_local = false
# Turn on fragment caching in view templates.
config.action_controller.perform_caching = true
# Cache assets for far-future expiry since they are all digest stamped.
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com"
# Store uploaded files on the local file system (see config/storage.yml for options).
# config.active_storage.service = :local
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
# Skip http-to-https redirect for the default health check endpoint.
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
# Log to STDOUT with the current request id as a default log tag.
config.log_tags = [ :request_id ]
config.logger = ActiveSupport::TaggedLogging.logger(STDOUT)
# Change to "debug" to log everything (including potentially personally-identifiable information!).
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
# Prevent health checks from clogging up the logs.
config.silence_healthcheck_path = "/up"
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Replace the default in-process memory cache store with a durable alternative.
# config.cache_store = :mem_cache_store
# Replace the default in-process and non-durable queuing backend for Active Job.
# config.active_job.queue_adapter = :resque
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Set host to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: "example.com" }
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
# config.action_mailer.smtp_settings = {
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
# password: Rails.application.credentials.dig(:smtp, :password),
# address: "smtp.example.com",
# port: 587,
# authentication: :plain
# }
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Only use :id for inspections in production.
config.active_record.attributes_for_inspect = [ :id ]
# Enable DNS rebinding protection and other `Host` header attacks.
# config.hosts = [
# "example.com", # Allow requests from example.com
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
# ]
#
# Skip DNS rebinding protection for the default health check endpoint.
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end
================================================
FILE: test/dummy/config/environments/test.rb
================================================
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# While tests run files are not watched, reloading is not necessary.
config.enable_reloading = false
# Eager loading loads your entire application. When running a single test locally,
# this is usually not necessary, and can slow down your test suite. However, it's
# recommended that you enable it in continuous integration systems to ensure eager
# loading is working properly before deploying your code.
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with cache-control for performance.
config.public_file_server.headers = { "cache-control" => "public, max-age=3600" }
# Show full error reports.
config.consider_all_requests_local = true
config.cache_store = :null_store
# Render exception templates for rescuable exceptions and raise for other exceptions.
config.action_dispatch.show_exceptions = :rescuable
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory.
# config.active_storage.service = :test
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
# config.action_mailer.delivery_method = :test
# Set host to be used by links generated in mailer templates.
# config.action_mailer.default_url_options = { host: "example.com" }
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
end
================================================
FILE: test/dummy/config/initializers/assets.rb
================================================
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
# Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
================================================
FILE: test/dummy/config/initializers/content_security_policy.rb
================================================
# Be sure to restart your server when you modify this file.
# Define an application-wide content security policy.
# See the Securing Rails Applications Guide for more information:
# https://guides.rubyonrails.org/security.html#content-security-policy-header
# Rails.application.configure do
# config.content_security_policy do |policy|
# policy.default_src :self, :https
# policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data
# policy.object_src :none
# policy.script_src :self, :https
# policy.style_src :self, :https
# # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint"
# end
#
# # Generate session nonces for permitted importmap, inline scripts, and inline styles.
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
# config.content_security_policy_nonce_directives = %w(script-src style-src)
#
# # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`
# # if the corresponding directives are specified in `content_security_policy_nonce_directives`.
# # config.content_security_policy_nonce_auto = true
#
# # Report violations without enforcing the policy.
# # config.content_security_policy_report_only = true
# end
================================================
FILE: test/dummy/config/initializers/filter_parameter_logging.rb
================================================
# Be sure to restart your server when you modify this file.
# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file.
# Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc
]
================================================
FILE: test/dummy/config/initializers/inflections.rb
================================================
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, "\\1en"
# inflect.singular /^(ox)en/i, "\\1"
# inflect.irregular "person", "people"
# inflect.uncountable %w( fish sheep )
# end
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym "RESTful"
# end
================================================
FILE: test/dummy/config/locales/en.yml
================================================
# Files in the config/locales directory are used for internationalization and
# are automatically loaded by Rails. If you want to use locales other than
# English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
# <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more about the API, please read the Rails Internationalization guide
# at https://guides.rubyonrails.org/i18n.html.
#
# Be aware that YAML interprets the following case-insensitive strings as
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
# must be quoted to be interpreted as strings. For example:
#
# en:
# "yes": yup
# enabled: "ON"
en:
hello: "Hello world"
================================================
FILE: test/dummy/config/puma.rb
================================================
# This configuration file will be evaluated by Puma. The top-level methods that
# are invoked here are part of Puma's configuration DSL. For more information
# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html.
#
# Puma starts a configurable number of processes (workers) and each process
# serves each request in a thread from an internal thread pool.
#
# You can control the number of workers using ENV["WEB_CONCURRENCY"]. You
# should only set this value when you want to run 2 or more workers. The
# default is already 1. You can set it to `auto` to automatically start a worker
# for each available processor.
#
# The ideal number of threads per worker depends both on how much time the
# application spends waiting for IO operations and on how much you wish to
# prioritize throughput over latency.
#
# As a rule of thumb, increasing the number of threads will increase how much
# traffic a given process can handle (throughput), but due to CRuby's
# Global VM Lock (GVL) it has diminishing returns and will degrade the
# response time (latency) of the application.
#
# The default is set to 3 threads as it's deemed a decent compromise between
# throughput and latency for the average Rails application.
#
# Any libraries that use a connection pool or another resource pool should
# be configured to provide at least as many connections as the number of
# threads. This includes Active Record's `pool` parameter in `database.yml`.
threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
# Specify the PID file. Defaults to tmp/pids/server.pid in development.
# In other environments, only set the PID file if requested.
pidfile ENV["PIDFILE"] if ENV["PIDFILE"]
================================================
FILE: test/dummy/config/routes.rb
================================================
# frozen_string_literal: true
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Defines the root path route ("/")
# root "posts#index"
end
================================================
FILE: test/dummy/config/storage.yml
================================================
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
# service: S3
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
# region: us-east-1
# bucket: your_own_bucket-<%= Rails.env %>
# Remember not to checkin your GCS keyfile to a repository
# google:
# service: GCS
# project: your_project
# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
# bucket: your_own_bucket-<%= Rails.env %>
# mirror:
# service: Mirror
# primary: local
# mirrors: [ amazon, google, microsoft ]
================================================
FILE: test/dummy/config.ru
================================================
# This file is used by Rack-based servers to start the application.
require_relative "config/environment"
run Rails.application
Rails.application.load_server
================================================
FILE: test/dummy/db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2024_09_12_130854) do
create_table "solid_cable_messages", force: :cascade do |t|
t.binary "channel", limit: 1024, null: false
t.binary "payload", limit: 536870912, null: false
t.datetime "created_at", null: false
t.integer "channel_hash", limit: 8, null: false
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
end
end
================================================
FILE: test/dummy/log/.keep
================================================
================================================
FILE: test/dummy/public/400.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>The server cannot process the request due to a client error (400 Bad Request)</title>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width">
<meta name="robots" content="noindex, nofollow">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
font-size: 16px;
}
body {
background: #FFF;
color: #261B23;
display: grid;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: clamp(1rem, 2.5vw, 2rem);
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: 400;
letter-spacing: -0.0025em;
line-height: 1.4;
min-height: 100dvh;
place-items: center;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
}
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a {
color: inherit;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.0925em;
}
b, strong {
font-weight: 700;
}
i, em {
font-style: italic;
}
main {
display: grid;
gap: 1em;
padding: 2em;
place-items: center;
text-align: center;
}
main header {
width: min(100%, 12em);
}
main header svg {
height: auto;
max-width: 100%;
width: 100%;
}
main article {
width: min(100%, 30em);
}
main article p {
font-size: 75%;
}
main article br {
display: none;
@media(min-width: 48em) {
display: inline;
}
}
</style>
</head>
<body>
<!-- This file lives in public/400.html -->
<main>
<header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m123.606 85.4445c3.212 1.0523 5.538 4.2089 5.538 8.0301 0 6.1472-4.209 9.5254-11.298 9.5254h-15.617v-34.0033h14.565c7.089 0 11.353 3.1566 11.353 9.2484 0 3.6551-2.049 6.3134-4.541 7.1994zm-12.904-2.9905h5.095c2.603 0 3.988-.9968 3.988-3.1013 0-2.1044-1.385-3.0459-3.988-3.0459h-5.095zm0 6.6456v6.5902h5.981c2.492 0 3.877-1.3291 3.877-3.2674 0-2.049-1.385-3.3228-3.877-3.3228zm43.786 13.9004h-8.362v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.083-2.769-9.083-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.483 1.3845v-1.606c0-1.606-.942-2.9905-3.046-2.9905-1.606 0-2.548.7199-2.935 1.8275h-8.197c.72-4.8181 4.985-8.6393 11.409-8.6393 7.088 0 11.131 3.7659 11.131 10.2453zm-8.362-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm27.996 6.9779v-1.994c-1.163 1.329-3.599 2.548-6.147 2.548-7.199 0-11.131-5.8151-11.131-13.0145s3.932-13.0143 11.131-13.0143c2.548 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.664-1.3291-2.159-2.326-3.821-2.326-2.99 0-4.763 2.4368-4.763 5.6488s1.773 5.5934 4.763 5.5934c1.717 0 3.157-.9415 3.821-2.326zm35.471-2.049h-3.101v11.2421h-8.806v-34.0033h15.285c7.31 0 12.35 4.1535 12.35 11.5744 0 5.1503-2.603 8.6947-6.757 10.2453l7.975 12.1836h-9.858zm-3.101-15.2849v8.1962h5.538c3.156 0 4.596-1.606 4.596-4.0981s-1.44-4.0981-4.596-4.0981zm36.957 17.8323h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm30.98 27.5234v-10.799c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.9259-11.132-13.0145 0-7.144 3.932-13.0143 11.132-13.0143 2.547 0 4.984 1.2184 6.147 2.5475v-1.9937h8.695v33.726zm0-17.9981v-6.5902c-.665-1.3291-2.105-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.661 0 3.156-.9415 3.821-2.326zm36.789-15.7279v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.996 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm19.084 16.2263h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.963 5.095 11.963 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm13.428 11.0206h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.762-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm27.538-.8861v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.993-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.871 0-9.193-2.769-9.193-9.0819z" id="error-description"/></svg>
</header>
<article>
<p><strong>The server cannot process the request due to a client error.</strong> Please check the request and try again. If you're the application owner check the logs for more information.</p>
</article>
</main>
</body>
</html>
================================================
FILE: test/dummy/public/404.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>The page you were looking for doesn't exist (404 Not found)</title>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width">
<meta name="robots" content="noindex, nofollow">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
font-size: 16px;
}
body {
background: #FFF;
color: #261B23;
display: grid;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: clamp(1rem, 2.5vw, 2rem);
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: 400;
letter-spacing: -0.0025em;
line-height: 1.4;
min-height: 100dvh;
place-items: center;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
}
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a {
color: inherit;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.0925em;
}
b, strong {
font-weight: 700;
}
i, em {
font-style: italic;
}
main {
display: grid;
gap: 1em;
padding: 2em;
place-items: center;
text-align: center;
}
main header {
width: min(100%, 12em);
}
main header svg {
height: auto;
max-width: 100%;
width: 100%;
}
main article {
width: min(100%, 30em);
}
main article p {
font-size: 75%;
}
main article br {
display: none;
@media(min-width: 48em) {
display: inline;
}
}
</style>
</head>
<body>
<!-- This file lives in public/404.html -->
<main>
<header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm165.328-35.41581-45.689 100.02991h26.224v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.184v-31.901l50.285-103.27391z" id="error-id"/><path d="m157.758 68.9967v34.0033h-7.199l-14.233-19.8814v19.8814h-8.584v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm13.184 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm37.027 8.5839h-8.806v-34.0033h23.924v7.6978h-15.118v6.7564h13.9v7.5316h-13.9zm41.876-12.4605c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm35.337-12.4605v24.921h-8.695v-2.16c-1.329 1.551-3.821 2.714-6.646 2.714-5.482 0-8.75-3.5999-8.75-9.1379v-16.3371h8.64v14.288c0 2.1045.997 3.5997 3.212 3.5997 1.606 0 3.101-1.0522 3.544-2.769v-15.1187zm4.076 24.921v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.156.9969-3.6 2.7136v15.0634zm44.113 0v-1.994c-1.163 1.329-3.6 2.548-6.147 2.548-7.2 0-11.132-5.8151-11.132-13.0145s3.932-13.0143 11.132-13.0143c2.547 0 4.984 1.2184 6.147 2.5475v-13.0697h8.695v35.997zm0-9.1931v-6.5902c-.665-1.3291-2.16-2.326-3.821-2.326-2.991 0-4.763 2.4368-4.763 5.6488s1.772 5.5934 4.763 5.5934c1.717 0 3.156-.9415 3.821-2.326z" id="error-description"/></svg>
</header>
<article>
<p><strong>The page you were looking for doesn't exist.</strong> You may have mistyped the address or the page may have moved. If you're the application owner check the logs for more information.</p>
</article>
</main>
</body>
</html>
================================================
FILE: test/dummy/public/406-unsupported-browser.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>Your browser is not supported (406 Not Acceptable)</title>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width">
<meta name="robots" content="noindex, nofollow">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
font-size: 16px;
}
body {
background: #FFF;
color: #261B23;
display: grid;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: clamp(1rem, 2.5vw, 2rem);
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: 400;
letter-spacing: -0.0025em;
line-height: 1.4;
min-height: 100dvh;
place-items: center;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
}
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a {
color: inherit;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.0925em;
}
b, strong {
font-weight: 700;
}
i, em {
font-style: italic;
}
main {
display: grid;
gap: 1em;
padding: 2em;
place-items: center;
text-align: center;
}
main header {
width: min(100%, 12em);
}
main header svg {
height: auto;
max-width: 100%;
width: 100%;
}
main article {
width: min(100%, 30em);
}
main article p {
font-size: 75%;
}
main article br {
display: none;
@media(min-width: 48em) {
display: inline;
}
}
</style>
</head>
<body>
<!-- This file lives in public/406-unsupported-browser.html -->
<main>
<header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm115.583 168.69891c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm202.906 9.7326h-41.093c-2.433-7.2994-7.84-12.4361-17.302-12.4361-16.221 0-25.413 17.5728-25.954 34.8752v1.3517c5.137-7.0291 16.221-12.4361 30.82-12.4361 33.524 0 54.881 24.0612 54.881 53.7998 0 33.253-23.791 58.396-61.64 58.396-21.628 0-39.741-10.003-50.825-27.576-9.733-14.599-13.788-32.442-13.788-54.3406 0-51.9072 24.331-89.485807 66.236-89.485807 32.712 0 53.258 18.654107 58.665 47.851907zm-82.727 66.2355c0 13.247 9.463 22.439 22.71 22.439 12.977 0 22.439-9.192 22.439-22.439 0-13.517-9.462-22.7091-22.439-22.7091-13.247 0-22.71 9.1921-22.71 22.7091z" id="error-id"/><path d="m100.761 68.9967v34.0033h-7.1991l-14.2326-19.8814v19.8814h-8.5839v-34.0033h8.307l13.125 18.7184v-18.7184zm28.454 21.5428c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm13.185 3.8766v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.205v6.7564h-5.205v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.02-25.4194h9.083l12.958 34.0033h-9.027l-2.436-6.5902h-12.35l-2.381 6.5902h-8.806zm4.431 10.5222-3.489 9.5807h6.978zm17.44 11.0206c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm25.676 0c0-7.6978 5.095-13.0143 12.572-13.0143 6.701 0 10.854 3.9874 11.574 9.8023h-8.418c-.221-1.4953-1.384-2.6029-3.156-2.6029-2.437 0-3.988 2.2706-3.988 5.8149s1.551 5.7595 3.988 5.7595c1.772 0 2.935-1.0522 3.156-2.5475h8.418c-.72 5.7596-4.873 9.8025-11.574 9.8025-7.477 0-12.572-5.3167-12.572-13.0145zm42.013 3.7658h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm23.4 16.7244v10.799h-8.694v-33.726h8.694v1.9937c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8703 11.131 13.0143 0 7.0886-3.932 13.0145-11.131 13.0145-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.16 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.717 0-3.157.9969-3.822 2.326zm21.892 7.1994v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm39.458 8.5839h-8.363v-1.274c-.83.831-3.322 1.717-5.981 1.717-4.928 0-9.082-2.769-9.082-8.0301 0-4.818 4.154-7.9193 9.581-7.9193 2.049 0 4.486.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.045-2.9905-1.606 0-2.548.7199-2.936 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.553-1.0522-2.049-1.7167-3.655-1.7167-1.716 0-3.433.7199-3.433 2.3813 0 1.7168 1.717 2.4367 3.433 2.4367 1.606 0 3.102-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.694v-35.997h8.694v13.0697c1.163-1.3291 3.6-2.5475 6.148-2.5475 7.199 0 11.131 5.8149 11.131 13.0143s-3.932 13.0145-11.131 13.0145c-2.548 0-4.985-1.219-6.148-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.822 2.326 2.99 0 4.762-2.3814 4.762-5.5934s-1.772-5.6488-4.762-5.6488c-1.662 0-3.157.9969-3.822 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997z" id="error-description"/></svg>
</header>
<article>
<p><strong>Your browser is not supported.</strong><br> Please upgrade your browser to continue.</p>
</article>
</main>
</body>
</html>
================================================
FILE: test/dummy/public/422.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>The change you wanted was rejected (422 Unprocessable Entity)</title>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width">
<meta name="robots" content="noindex, nofollow">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
font-size: 16px;
}
body {
background: #FFF;
color: #261B23;
display: grid;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: clamp(1rem, 2.5vw, 2rem);
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: 400;
letter-spacing: -0.0025em;
line-height: 1.4;
min-height: 100dvh;
place-items: center;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
}
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a {
color: inherit;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.0925em;
}
b, strong {
font-weight: 700;
}
i, em {
font-style: italic;
}
main {
display: grid;
gap: 1em;
padding: 2em;
place-items: center;
text-align: center;
}
main header {
width: min(100%, 12em);
}
main header svg {
height: auto;
max-width: 100%;
width: 100%;
}
main article {
width: min(100%, 30em);
}
main article p {
font-size: 75%;
}
main article br {
display: none;
@media(min-width: 48em) {
display: inline;
}
}
</style>
</head>
<body>
<!-- This file lives in public/422.html -->
<main>
<header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m124.48 3.00509-45.6889 100.02991h26.2239v-28.1168h38.119v28.1168h21.628v35.145h-21.628v30.82h-37.308v-30.82h-72.1833v-31.901l50.2851-103.27391zm130.453 51.63681c0-8.9215-6.218-15.4099-15.681-15.4099-10.273 0-15.95 7.5698-16.491 16.4913h-44.608c3.244-30.8199 25.683-55.421707 61.099-55.421707 36.498 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.869 36.7676-31.901 52.7186l-27.305 27.035h59.747v37.308h-120.306v-27.846l57.044-56.7736c11.084-11.8954 18.925-20.0059 18.925-29.7385zm140.455 0c0-8.9215-6.218-15.4099-15.68-15.4099-10.274 0-15.951 7.5698-16.492 16.4913h-44.608c3.245-30.8199 25.684-55.421707 61.1-55.421707 36.497 0 59.477 20.816907 59.477 51.636807 0 21.3577-14.87 36.7676-31.902 52.7186l-27.305 27.035h59.747v37.308h-120.305v-27.846l57.043-56.7736c11.085-11.8954 18.925-20.0059 18.925-29.7385z" id="error-id"/><path d="m19.3936 103.554c-8.9715 0-14.84183-5.0952-14.84183-14.4544v-20.1029h8.86083v19.3276c0 4.8181 2.2706 7.3102 5.981 7.3102 3.6551 0 5.9257-2.4921 5.9257-7.3102v-19.3276h8.8608v20.1583c0 9.3038-5.8149 14.399-14.7865 14.399zm18.734-.554v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm36.8584-1.994v10.799h-8.6946v-33.726h8.6946v1.9937c1.163-1.3291 3.5997-2.5475 6.1472-2.5475 7.1994 0 11.1314 5.8703 11.1314 13.0143 0 7.0886-3.932 13.0145-11.1314 13.0145-2.5475 0-4.9842-1.219-6.1472-2.548zm0-13.7893v6.5902c.6646 1.3845 2.1599 2.326 3.8213 2.326 2.9905 0 4.7626-2.3814 4.7626-5.5934s-1.7721-5.6488-4.7626-5.6488c-1.7168 0-3.1567.9969-3.8213 2.326zm36.789-9.2485v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.6949v-24.921h8.6949v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.528 0c0-3.4336-1.496-5.8703-4.209-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.209-2.3813 4.209-5.8149zm10.352 0c0-7.6978 5.095-13.0143 12.571-13.0143 6.701 0 10.855 3.9874 11.574 9.8023h-8.417c-.222-1.4953-1.385-2.6029-3.157-2.6029-2.437 0-3.987 2.2706-3.987 5.8149s1.55 5.7595 3.987 5.7595c1.772 0 2.935-1.0522 3.157-2.5475h8.417c-.719 5.7596-4.873 9.8025-11.574 9.8025-7.476 0-12.571-5.3167-12.571-13.0145zm42.013 3.7658h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.544-3.5997zm13.428 11.0206h8.473c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.938-1.5506l-4.874-.9969c-4.153-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.74-8.2518zm24.269 0h8.474c.387 1.3845 1.606 2.1598 3.156 2.1598 1.44 0 2.548-.5538 2.548-1.7168 0-.9414-.72-1.2737-1.939-1.5506l-4.873-.9969c-4.154-.886-6.867-2.8797-6.867-7.2547 0-5.3165 4.763-8.4178 10.633-8.4178 6.812 0 10.522 3.1567 11.297 8.0855h-8.03c-.277-1.0522-1.052-1.9937-3.046-1.9937-1.273 0-2.326.5538-2.326 1.6614 0 .7753.554 1.163 1.717 1.3845l4.929 1.163c4.541 1.0522 6.978 3.4335 6.978 7.4763 0 5.3168-4.818 8.2518-10.91 8.2518-6.369 0-10.965-2.88-11.741-8.2518zm47.918 7.6978h-8.363v-1.274c-.831.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.434.7199-3.434 2.3813 0 1.7168 1.717 2.4367 3.434 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742 4.9839v1.994h-8.695v-35.997h8.695v13.0697c1.163-1.3291 3.6-2.5475 6.147-2.5475 7.2 0 11.132 5.8149 11.132 13.0143s-3.932 13.0145-11.132 13.0145c-2.547 0-4.984-1.219-6.147-2.548zm0-13.7893v6.5902c.665 1.3845 2.105 2.326 3.821 2.326 2.991 0 4.763-2.3814 4.763-5.5934s-1.772-5.6488-4.763-5.6488c-1.661 0-3.156.9969-3.821 2.326zm28.759-20.2137v35.997h-8.695v-35.997zm19.172 27.3023h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.643 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.515-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.32 4.5965 1.772 0 3.156-.7753 3.655-2.4921zm-3.822-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm25.461-15.2849h24.311v7.6424h-15.561v5.3165h14.232v7.4763h-14.232v5.8703h15.561v7.6978h-24.311zm27.942 34.0033v-24.921h8.694v2.1598c1.385-1.5506 3.822-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.639v-14.2327c0-2.049-1.053-3.5443-3.268-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm29.991-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.942 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm26.161-16.3371v24.921h-8.694v-24.921zm.61-6.7564c0 2.8244-2.271 4.652-4.929 4.652s-4.929-1.8276-4.929-4.652c0-2.8797 2.271-4.7073 4.929-4.7073s4.929 1.8276 4.929 4.7073zm5.382 23.0935v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.584v6.8671h5.206v6.7564h-5.206v8.307c0 1.9383.941 2.769 2.658 2.769.941 0 1.994-.2216 2.769-.5538v7.3654c-.997.443-2.88.775-4.818.775-5.87 0-9.193-2.769-9.193-9.0819zm29.22 17.3889h-8.584l3.655-9.414-9.303-24.312h9.026l4.763 14.1773 4.652-14.1773h8.639z" id="error-description"/></svg>
</header>
<article>
<p><strong>The change you wanted was rejected.</strong> Maybe you tried to change something you didn't have access to. If you're the application owner check the logs for more information.</p>
</article>
</main>
</body>
</html>
================================================
FILE: test/dummy/public/500.html
================================================
<!doctype html>
<html lang="en">
<head>
<title>We're sorry, but something went wrong (500 Internal Server Error)</title>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, width=device-width">
<meta name="robots" content="noindex, nofollow">
<style>
*, *::before, *::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
font-size: 16px;
}
body {
background: #FFF;
color: #261B23;
display: grid;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Aptos, Roboto, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: clamp(1rem, 2.5vw, 2rem);
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: 400;
letter-spacing: -0.0025em;
line-height: 1.4;
min-height: 100dvh;
place-items: center;
text-rendering: optimizeLegibility;
-webkit-text-size-adjust: 100%;
}
#error-description {
fill: #d30001;
}
#error-id {
fill: #f0eff0;
}
@media (prefers-color-scheme: dark) {
body {
background: #101010;
color: #e0e0e0;
}
#error-description {
fill: #FF6161;
}
#error-id {
fill: #2c2c2c;
}
}
a {
color: inherit;
font-weight: 700;
text-decoration: underline;
text-underline-offset: 0.0925em;
}
b, strong {
font-weight: 700;
}
i, em {
font-style: italic;
}
main {
display: grid;
gap: 1em;
padding: 2em;
place-items: center;
text-align: center;
}
main header {
width: min(100%, 12em);
}
main header svg {
height: auto;
max-width: 100%;
width: 100%;
}
main article {
width: min(100%, 30em);
}
main article p {
font-size: 75%;
}
main article br {
display: none;
@media(min-width: 48em) {
display: inline;
}
}
</style>
</head>
<body>
<!-- This file lives in public/500.html -->
<main>
<header>
<svg height="172" viewBox="0 0 480 172" width="480" xmlns="http://www.w3.org/2000/svg"><path d="m101.23 93.8427c-8.1103 0-15.4098 3.7849-19.7354 8.3813h-36.2269v-99.21891h103.8143v37.03791h-68.3984v24.8722c5.1366-2.7035 15.1396-5.9477 24.6014-5.9477 35.146 0 56.233 22.7094 56.233 55.4215 0 34.605-23.791 57.315-60.558 57.315-37.8492 0-61.64-22.169-63.8028-55.963h42.9857c1.0814 10.814 9.1919 19.195 21.6281 19.195 11.355 0 19.465-8.381 19.465-20.547 0-11.625-7.299-20.5463-20.006-20.5463zm138.833 77.8613c-40.822 0-64.884-35.146-64.884-85.7015 0-50.5554 24.062-85.700907 64.884-85.700907 40.823 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.061 85.7015-64.884 85.7015zm0-133.2831c-17.572 0-22.709 21.8984-22.709 47.5816 0 25.6835 5.137 47.5815 22.709 47.5815 17.303 0 22.71-21.898 22.71-47.5815 0-25.6832-5.407-47.5816-22.71-47.5816zm140.456 133.2831c-40.823 0-64.884-35.146-64.884-85.7015 0-50.5554 24.061-85.700907 64.884-85.700907 40.822 0 64.884 35.145507 64.884 85.700907 0 50.5555-24.062 85.7015-64.884 85.7015zm0-133.2831c-17.573 0-22.71 21.8984-22.71 47.5816 0 25.6835 5.137 47.5815 22.71 47.5815 17.302 0 22.709-21.898 22.709-47.5815 0-25.6832-5.407-47.5816-22.709-47.5816z" id="error-id"/><path d="m23.1377 68.9967v34.0033h-8.9162v-34.0033zm4.3157 34.0033v-24.921h8.6947v2.1598c1.3845-1.5506 3.8212-2.7136 6.701-2.7136 5.538 0 8.8054 3.5997 8.8054 9.1377v16.3371h-8.6393v-14.2327c0-2.049-1.0522-3.5443-3.2674-3.5443-1.7168 0-3.1567.9969-3.5997 2.7136v15.0634zm29.9913-8.5839v-9.5807h-3.655v-6.7564h3.655v-6.8671h8.5839v6.8671h5.2058v6.7564h-5.2058v8.307c0 1.9383.9415 2.769 2.6583 2.769.9414 0 1.9937-.2216 2.769-.5538v7.3654c-.9969.443-2.8798.775-4.8181.775-5.8703 0-9.1931-2.769-9.1931-9.0819zm32.3666-.1108h8.0301c-.8861 5.7597-5.2057 9.2487-11.6852 9.2487-7.6424 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.3165-13.0143 12.5159-13.0143 7.6424 0 11.9621 5.095 11.9621 12.5159v2.1598h-16.1156c.2769 2.9905 1.8275 4.5965 4.3196 4.5965 1.7722 0 3.1567-.7753 3.6551-2.4921zm-3.8212-10.0237c-2.0491 0-3.4336 1.2737-3.9874 3.5997h7.5317c-.1107-2.0491-1.3845-3.5997-3.5443-3.5997zm31.4299-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.599-.7753-2.382 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.381.443zm2.949 25.0318v-24.921h8.694v2.1598c1.385-1.5506 3.821-2.7136 6.701-2.7136 5.538 0 8.806 3.5997 8.806 9.1377v16.3371h-8.64v-14.2327c0-2.049-1.052-3.5443-3.267-3.5443-1.717 0-3.157.9969-3.6 2.7136v15.0634zm50.371 0h-8.363v-1.274c-.83.831-3.323 1.717-5.981 1.717-4.929 0-9.082-2.769-9.082-8.0301 0-4.818 4.153-7.9193 9.581-7.9193 2.049 0 4.485.6646 5.482 1.3845v-1.606c0-1.606-.941-2.9905-3.046-2.9905-1.606 0-2.547.7199-2.935 1.8275h-8.196c.72-4.8181 4.984-8.6393 11.408-8.6393 7.089 0 11.132 3.7659 11.132 10.2453zm-8.363-6.9779v-1.4399c-.554-1.0522-2.049-1.7167-3.655-1.7167-1.717 0-3.433.7199-3.433 2.3813 0 1.7168 1.716 2.4367 3.433 2.4367 1.606 0 3.101-.6645 3.655-1.6614zm20.742-29.0191v35.997h-8.694v-35.997zm13.036 25.9178h9.248c.72 2.326 2.714 3.489 5.483 3.489 2.713 0 4.596-1.163 4.596-3.2674 0-1.6061-1.052-2.326-3.212-2.8244l-6.534-1.3845c-4.985-1.1076-8.751-3.7105-8.751-9.47 0-6.6456 5.538-11.0206 13.07-11.0206 8.307 0 13.014 4.5411 13.956 10.4114h-8.695c-.72-1.8829-2.27-3.3228-5.205-3.3228-2.548 0-4.265 1.1076-4.265 2.9905 0 1.4953 1.052 2.326 2.825 2.7137l6.645 1.5506c5.815 1.3845 9.027 4.5412 9.027 9.8023 0 6.9778-5.87 10.9654-13.291 10.9654-8.141 0-13.679-3.9322-14.897-10.6332zm46.509 1.3845h8.031c-.887 5.7597-5.206 9.2487-11.686 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.317-13.0143 12.516-13.0143 7.643 0 11.962 5.095 11.962 12.5159v2.1598h-16.115c.277 2.9905 1.827 4.5965 4.319 4.5965 1.773 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.433 1.2737-3.987 3.5997h7.532c-.111-2.0491-1.385-3.5997-3.545-3.5997zm31.431-6.3134v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.988 1.0522-4.431 2.8244v14.6203h-8.694v-24.921h8.694v2.2152c1.219-1.6614 3.157-2.769 5.649-2.769 1.108 0 1.994.2215 2.382.443zm18.288 25.0318h-7.809l-9.47-24.921h8.861l4.763 14.288 4.652-14.288h8.528zm25.614-8.6947h8.03c-.886 5.7597-5.206 9.2487-11.685 9.2487-7.642 0-12.682-5.2613-12.682-13.0145 0-7.6978 5.316-13.0143 12.516-13.0143 7.642 0 11.962 5.095 11.962 12.5159v2.1598h-16.116c.277 2.9905 1.828 4.5965 4.32 4.5965 1.772 0 3.157-.7753 3.655-2.4921zm-3.821-10.0237c-2.049 0-3.434 1.2737-3.988 3.5997h7.532c-.111-2.0491-1.384-3.5997-3.544-3.5997zm31.43-6.3134v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443zm13.703-8.9715h24.312v7.6424h-15.562v5.3165h14.232v7.4763h-14.232v5.8703h15.562v7.6978h-24.312zm44.667 8.9715v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm19.673 0v8.3624c-1.053-.5538-2.216-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.156-2.769 5.648-2.769 1.108 0 1.994.2215 2.382.443zm26.769 12.5713c0 7.6978-5.15 13.0145-12.737 13.0145-7.532 0-12.738-5.3167-12.738-13.0145s5.206-13.0143 12.738-13.0143c7.587 0 12.737 5.3165 12.737 13.0143zm-8.529 0c0-3.4336-1.495-5.8703-4.208-5.8703-2.659 0-4.154 2.4367-4.154 5.8703s1.495 5.8149 4.154 5.8149c2.713 0 4.208-2.3813 4.208-5.8149zm28.082-12.5713v8.3624c-1.052-.5538-2.215-.7753-3.6-.7753-2.381 0-3.987 1.0522-4.43 2.8244v14.6203h-8.695v-24.921h8.695v2.2152c1.218-1.6614 3.157-2.769 5.649-2.769 1.107 0 1.993.2215 2.381.443z" id="error-description"/></svg>
</header>
<article>
<p><strong>We're sorry, but something went wrong.</strong><br> If you're the application owner check the logs for more information.</p>
</article>
</main>
</body>
</html>
================================================
FILE: test/jobs/trim_job_test.rb
================================================
# frozen_string_literal: true
require "test_helper"
require "config_stubs"
class TrimJobTest < ActiveJob::TestCase
include ConfigStubs
test "trims a limited number of messages" do
SolidCable.stub(:trim_chance, 99.999) do
with_cable_config trim_batch_size: 2, message_rention: "1.second" do
4.times do
SolidCable::Message.broadcast("foo", "bar")
SolidCable::Message.update_all(created_at: 2.days.ago)
end
assert_difference -> { SolidCable::Message.count }, -2 do
SolidCable::TrimJob.perform_now
end
end
end
end
test "trims when out of band with autotrim disabled" do
SolidCable.stub(:trim_chance, 0) do
with_cable_config autotrim: false, trim_batch_size: 2, message_rention: "1.second" do
4.times do
SolidCable::Message.broadcast("foo", "bar")
SolidCable::Message.update_all(created_at: 2.days.ago)
end
assert_difference -> { SolidCable::Message.count }, -2 do
SolidCable::TrimJob.perform_now
end
end
end
end
end
================================================
FILE: test/lib/action_cable/subscription_adapter/solid_cable_test.rb
================================================
# frozen_string_literal: true
require "test_helper"
require "concurrent"
require "active_support/core_ext/hash/indifferent_access"
require "pathname"
require "config_stubs"
class ActionCable::SubscriptionAdapter::SolidCableTest < ActionCable::TestCase
include ConfigStubs
WAIT_WHEN_EXPECTING_EVENT = 1
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2
setup do
server = ActionCable::Server::Base.new
server.config.cable = cable_config.with_indifferent_access
server.config.logger = Logger.new(StringIO.new).tap do |l|
l.level = Logger::UNKNOWN
end
adapter_klass = server.config.pubsub_adapter
@rx_adapter = adapter_klass.new(server)
@tx_adapter = adapter_klass.new(server)
@tx_adapter.shutdown
@tx_adapter = @rx_adapter
end
teardown do
[@rx_adapter, @tx_adapter].uniq.compact.each(&:shutdown)
end
test "subscribe_and_unsubscribe" do
subscribe_as_queue("channel") do |queue|
end
end
test "basic_broadcast" do
subscribe_as_queue("channel") do |queue|
@tx_adapter.broadcast("channel", "hello world")
assert_equal "hello world", next_message_in_queue(queue)
end
end
test "broadcast_after_unsubscribe" do
keep_queue = nil
subscribe_as_queue("channel") do |queue|
keep_queue = queue
@tx_adapter.broadcast("channel", "hello world")
assert_equal "hello world", next_message_in_queue(queue)
end
@tx_adapter.broadcast("channel", "hello void")
sleep WAIT_WHEN_NOT_EXPECTING_EVENT
assert_empty keep_queue
end
test "trims_after_unsubscribe" do
SolidCable.stub(:trim_chance, 99.999999) do
with_cable_config message_retention: "2.seconds", trim_batch_size: 2 do
subscribe_as_queue("channel") do |queue|
4.times do
@tx_adapter.broadcast("channel", "hello world")
sleep 3
end
queue.clear
end
assert_equal 1, SolidCable::Message.where(channel: "channel").count
end
end
end
test "multiple_broadcast" do
subscribe_as_queue("channel") do |queue|
@tx_adapter.broadcast("channel", "bananas")
@tx_adapter.broadcast("channel", "apples")
received = []
2.times { received << next_message_in_queue(queue) }
assert_equal %w(apples bananas), received.sort
end
end
test "identical_subscriptions" do
subscribe_as_queue("channel") do |queue|
subscribe_as_queue("channel") do |queue_2|
@tx_adapter.broadcast("channel", "hello")
assert_equal "hello", next_message_in_queue(queue_2)
end
assert_equal "hello", next_message_in_queue(queue)
end
end
test "simultaneous_subscriptions" do
subscribe_as_queue("channel") do |queue|
subscribe_as_queue("other channel") do |queue_2|
@tx_adapter.broadcast("channel", "apples")
@tx_adapter.broadcast("other channel", "oranges")
assert_equal "apples", next_message_in_queue(queue)
assert_equal "oranges", next_message_in_queue(queue_2)
end
end
end
test "channel_filtered_broadcast" do
subscribe_as_queue("channel") do |queue|
@tx_adapter.broadcast("other channel", "one")
@tx_adapter.broadcast("channel", "two")
assert_equal "two", next_message_in_queue(queue)
end
end
test "long_identifiers" do
channel_1 = "#{'a' * 100}1"
channel_2 = "#{'a' * 100}2"
subscribe_as_queue(channel_1) do |queue|
subscribe_as_queue(channel_2) do |queue_2|
@tx_adapter.broadcast(channel_1, "apples")
@tx_adapter.broadcast(channel_2, "oranges")
assert_equal "apples", next_message_in_queue(queue)
assert_equal "oranges", next_message_in_queue(queue_2)
end
end
end
test "does not raise error when polling with no Active Record logger" do
with_active_record_logger(nil) do
assert_nothing_raised do
subscribe_as_queue("channel") do |queue|
@tx_adapter.broadcast("channel", "hello world")
assert_equal "hello world", next_message_in_queue(queue)
end
end
end
end
test "does not send old messages" do
@tx_adapter.broadcast("channel", "channel1")
@tx_adapter.broadcast("channel", "channel2")
subscribe_as_queue("channel") do |queue|
assert_empty queue
@tx_adapter.broadcast("channel", "channel3")
@tx_adapter.broadcast("channel", "channel4")
@tx_adapter.broadcast("other", "other1")
@tx_adapter.broadcast("other", "other2")
subscribe_as_queue("other") do |other_queue|
assert_empty other_queue
end
assert_equal "channel3", next_message_in_queue(queue)
assert_equal "channel4", next_message_in_queue(queue)
end
@tx_adapter.broadcast("channel", "channel5")
@tx_adapter.broadcast("channel", "channel6")
subscribe_as_queue("channel") do |queue|
assert_empty queue
end
end
test "retries after a connection failure and keeps listening" do
with_cable_config reconnect_attempts: [0] do
raised = false
original = SolidCable::Message.method(:broadcastable)
SolidCable::Message.stub(:broadcastable, lambda { |channels, last_id|
if raised
original.call(channels, last_id)
else
raised = true
raise ActiveRecord::ConnectionFailed, "boom"
end
}) do
subscribe_as_queue("reconnect-channel") do |queue|
@tx_adapter.broadcast("reconnect-channel", "hello")
assert_equal "hello", next_message_in_queue(queue)
end
end
assert raised
end
end
private
def cable_config
{ adapter: "solid_cable", message_retention: "1.second",
polling_interval: "0.01.seconds" }
end
def subscribe_as_queue(channel, adapter = @rx_adapter)
queue = Queue.new
callback = ->(data) { queue << data }
subscribed = Concurrent::Event.new
adapter.subscribe(channel, callback, proc { subscribed.set })
subscribed.wait(WAIT_WHEN_EXPECTING_EVENT)
assert_predicate subscribed, :set?
yield queue
sleep WAIT_WHEN_NOT_EXPECTING_EVENT
assert_empty queue
ensure
adapter.unsubscribe(channel, callback) if subscribed.set?
end
def with_active_record_logger(logger)
old_logger = ActiveRecord::Base.logger
ActiveRecord::Base.logger = logger
yield
ensure
ActiveRecord::Base.logger = old_logger
end
def next_message_in_queue(queue)
Timeout.timeout(5, nil, "Failed to get next item in queue") { queue.pop }
end
end
================================================
FILE: test/lib/generators/solid_cable/install/install_generator_test.rb
================================================
require "test_helper"
require_relative "../../../../../lib/generators/solid_cable/install/install_generator"
class SolidCable::InstallGeneratorTest < Rails::Generators::TestCase
tests SolidCable::InstallGenerator
destination File.expand_path("../../../../../tmp", __dir__)
setup :prepare_destination
setup :run_generator
test "cable_schema exists" do
assert_file "db/cable_schema.rb"
end
test "cable.yml exists" do
assert_file "config/cable.yml"
end
end
================================================
FILE: test/lib/generators/solid_cable/update/update_generator_test.rb
================================================
require "test_helper"
require_relative "../../../../../lib/generators/solid_cable/update/update_generator"
class SolidCable::UpdateGeneratorTest < Rails::Generators::TestCase
tests SolidCable::UpdateGenerator
destination File.expand_path("../../../../../tmp", __dir__)
setup :prepare_destination
setup :run_generator
test "cable_schema exists" do
assert_migration "db/cable_migrate/create_compact_channel.rb"
end
end
================================================
FILE: test/solid_cable_test.rb
================================================
# frozen_string_literal: true
require "test_helper"
require "config_stubs"
class SolidCableTest < ActiveSupport::TestCase
include ConfigStubs
test "it has a version number" do
assert SolidCable::VERSION
end
test "autotrimming when nothing is set" do
assert_not Rails.application.config_for("cable").key?(:autotrim)
assert SolidCable.autotrim?
end
test "autotrimming when set to false" do
with_cable_config autotrim: false do
assert_not SolidCable.autotrim?
end
end
test "autotrimming when set to true" do
with_cable_config autotrim: true do
assert SolidCable.autotrim?
end
end
test "default trim_batch_size is 100" do
assert_equal 100, SolidCable.trim_batch_size
end
test "trim_batch_size when set badly" do
with_cable_config trim_batch_size: "weird" do
assert_equal 100, SolidCable.trim_batch_size
end
with_cable_config trim_batch_size: "0" do
assert_equal 100, SolidCable.trim_batch_size
end
end
test "trim_batch_size when set" do
with_cable_config trim_batch_size: 42 do
assert_equal 42, SolidCable.trim_batch_size
end
end
test "reconnect_attempts defaults to a single zero" do
assert_equal [ 0 ], SolidCable.reconnect_attempts
end
test "reconnect_attempts accepts an integer" do
with_cable_config reconnect_attempts: 3 do
assert_equal [ 0, 0, 0 ], SolidCable.reconnect_attempts
end
end
test "reconnect_attempts accepts an array" do
with_cable_config reconnect_attempts: [ 0, 1, 2 ] do
assert_equal [ 0, 1, 2 ], SolidCable.reconnect_attempts
end
end
end
================================================
FILE: test/test_helper.rb
================================================
# frozen_string_literal: true
# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
require_relative "../test/dummy/config/environment"
ActiveRecord::Migrator.migrations_paths = [ File.expand_path(
"../test/dummy/db/migrate", __dir__
) ]
require "rails/test_help"
require "minitest/autorun"
# Load fixtures from the engine
if ActiveSupport::TestCase.respond_to?(:fixture_paths=)
ActiveSupport::TestCase.fixture_paths =
[ File.expand_path("fixtures", __dir__) ]
ActionDispatch::IntegrationTest.fixture_paths =
ActiveSupport::TestCase.fixture_paths
ActiveSupport::TestCase.file_fixture_path =
"#{File.expand_path('fixtures', __dir__)}/files"
ActiveSupport::TestCase.fixtures :all
end
gitextract_sjfrw0w9/
├── .github/
│ └── workflows/
│ ├── lint.yml
│ └── test.yml
├── .gitignore
├── .rubocop.yml
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app/
│ ├── jobs/
│ │ └── solid_cable/
│ │ └── trim_job.rb
│ └── models/
│ └── solid_cable/
│ ├── message.rb
│ └── record.rb
├── bench/
│ ├── .dockerignore
│ ├── .ruby-version
│ ├── Dockerfile
│ ├── Gemfile
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ ├── builds/
│ │ │ │ └── .keep
│ │ │ ├── images/
│ │ │ │ └── .keep
│ │ │ └── stylesheets/
│ │ │ └── application.tailwind.css
│ │ ├── channels/
│ │ │ ├── application_cable/
│ │ │ │ ├── channel.rb
│ │ │ │ └── connection.rb
│ │ │ └── broadcast_channel.rb
│ │ ├── controllers/
│ │ │ ├── application_controller.rb
│ │ │ ├── concerns/
│ │ │ │ └── .keep
│ │ │ ├── rooms_controller.rb
│ │ │ └── ws_debugger_controller.rb
│ │ ├── javascript/
│ │ │ ├── application.js
│ │ │ ├── channels/
│ │ │ │ ├── broadcast_channel.js
│ │ │ │ ├── consumer.js
│ │ │ │ └── index.js
│ │ │ └── controllers/
│ │ │ ├── application.js
│ │ │ └── index.js
│ │ ├── jobs/
│ │ │ └── application_job.rb
│ │ ├── mailers/
│ │ │ └── application_mailer.rb
│ │ ├── models/
│ │ │ ├── application_record.rb
│ │ │ ├── concerns/
│ │ │ │ └── .keep
│ │ │ └── room.rb
│ │ └── views/
│ │ ├── layouts/
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ │ ├── rooms/
│ │ │ ├── _form.html.erb
│ │ │ ├── _room.html.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.html.erb
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ │ └── ws_debugger/
│ │ └── show.html.erb
│ ├── bin/
│ │ ├── bundle
│ │ ├── docker-entrypoint
│ │ ├── importmap
│ │ ├── kamal
│ │ ├── rails
│ │ ├── rake
│ │ ├── rubocop
│ │ └── setup
│ ├── config/
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── credentials.yml.enc
│ │ ├── database.yml
│ │ ├── deploy.yml
│ │ ├── environment.rb
│ │ ├── environments/
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── importmap.rb
│ │ ├── init.sql
│ │ ├── initializers/
│ │ │ ├── assets.rb
│ │ │ ├── content_security_policy.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── inflections.rb
│ │ │ └── permissions_policy.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── puma.rb
│ │ ├── routes.rb
│ │ ├── storage.yml
│ │ └── tailwind.config.js
│ ├── config.ru
│ ├── db/
│ │ ├── migrate/
│ │ │ ├── 20240529231225_create_rooms.rb
│ │ │ ├── 20240530031126_create_solid_cable_message.solid_cable.rb
│ │ │ ├── 20240607184931_index_channels.solid_cable.rb
│ │ │ ├── 20240609023040_create_active_error_faults.active_error.rb
│ │ │ ├── 20240609023041_create_active_error_instances.active_error.rb
│ │ │ └── 20240912235943_create_compact_channel.rb
│ │ ├── schema.rb
│ │ └── seeds.rb
│ ├── loadtest.js
│ ├── log/
│ │ └── .keep
│ ├── public/
│ │ ├── 404.html
│ │ ├── 406-unsupported-browser.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ └── robots.txt
│ ├── test/
│ │ ├── channels/
│ │ │ ├── application_cable/
│ │ │ │ └── connection_test.rb
│ │ │ └── broadcast_channel_test.rb
│ │ ├── controllers/
│ │ │ └── .keep
│ │ ├── fixtures/
│ │ │ ├── files/
│ │ │ │ └── .keep
│ │ │ └── rooms.yml
│ │ ├── models/
│ │ │ ├── .keep
│ │ │ └── room_test.rb
│ │ └── test_helper.rb
│ └── vendor/
│ ├── .keep
│ └── javascript/
│ └── .keep
├── bin/
│ ├── rails
│ ├── release
│ └── test
├── lib/
│ ├── action_cable/
│ │ └── subscription_adapter/
│ │ └── solid_cable.rb
│ ├── generators/
│ │ └── solid_cable/
│ │ ├── install/
│ │ │ ├── USAGE
│ │ │ ├── install_generator.rb
│ │ │ └── templates/
│ │ │ ├── config/
│ │ │ │ └── cable.yml
│ │ │ └── db/
│ │ │ └── cable_schema.rb
│ │ └── update/
│ │ ├── USAGE
│ │ ├── templates/
│ │ │ └── db/
│ │ │ └── migrate/
│ │ │ └── create_compact_channel.rb
│ │ └── update_generator.rb
│ ├── solid_cable/
│ │ ├── engine.rb
│ │ └── version.rb
│ ├── solid_cable.rb
│ └── tasks/
│ └── solid_cable_tasks.rake
├── solid_cable.gemspec
└── test/
├── config_stubs.rb
├── dummy/
│ ├── Rakefile
│ ├── app/
│ │ ├── controllers/
│ │ │ ├── application_controller.rb
│ │ │ └── concerns/
│ │ │ └── .keep
│ │ ├── helpers/
│ │ │ └── application_helper.rb
│ │ ├── jobs/
│ │ │ └── application_job.rb
│ │ ├── models/
│ │ │ ├── application_record.rb
│ │ │ └── concerns/
│ │ │ └── .keep
│ │ └── views/
│ │ ├── layouts/
│ │ │ ├── application.html.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── mailer.text.erb
│ │ └── pwa/
│ │ ├── manifest.json.erb
│ │ └── service-worker.js
│ ├── bin/
│ │ ├── ci
│ │ ├── dev
│ │ ├── rails
│ │ ├── rake
│ │ └── setup
│ ├── config/
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── ci.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments/
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers/
│ │ │ ├── assets.rb
│ │ │ ├── content_security_policy.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ └── inflections.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── puma.rb
│ │ ├── routes.rb
│ │ └── storage.yml
│ ├── config.ru
│ ├── db/
│ │ └── schema.rb
│ ├── log/
│ │ └── .keep
│ └── public/
│ ├── 400.html
│ ├── 404.html
│ ├── 406-unsupported-browser.html
│ ├── 422.html
│ └── 500.html
├── jobs/
│ └── trim_job_test.rb
├── lib/
│ ├── action_cable/
│ │ └── subscription_adapter/
│ │ └── solid_cable_test.rb
│ └── generators/
│ └── solid_cable/
│ ├── install/
│ │ └── install_generator_test.rb
│ └── update/
│ └── update_generator_test.rb
├── solid_cable_test.rb
└── test_helper.rb
SYMBOL INDEX (134 symbols across 44 files)
FILE: app/jobs/solid_cable/trim_job.rb
type SolidCable (line 3) | module SolidCable
class TrimJob (line 4) | class TrimJob < ActiveJob::Base
method perform (line 5) | def perform
method trim_batch_size (line 16) | def trim_batch_size
method trim? (line 20) | def trim?
FILE: app/models/solid_cable/message.rb
type SolidCable (line 3) | module SolidCable
class Message (line 4) | class Message < SolidCable::Record
method broadcast (line 14) | def broadcast(channel, payload)
method channel_hashes_for (line 19) | def channel_hashes_for(channels)
method channel_hash_for (line 25) | def channel_hash_for(channel)
FILE: app/models/solid_cable/record.rb
type SolidCable (line 3) | module SolidCable
class Record (line 4) | class Record < ActiveRecord::Base
method non_blocking_lock (line 9) | def self.non_blocking_lock
FILE: bench/app/channels/application_cable/channel.rb
type ApplicationCable (line 3) | module ApplicationCable
class Channel (line 4) | class Channel < ActionCable::Channel::Base
FILE: bench/app/channels/application_cable/connection.rb
type ApplicationCable (line 1) | module ApplicationCable
class Connection (line 2) | class Connection < ActionCable::Connection::Base
method connect (line 10) | def connect
FILE: bench/app/channels/broadcast_channel.rb
class BroadcastChannel (line 1) | class BroadcastChannel < ApplicationCable::Channel
method subscribed (line 2) | def subscribed
method unsubscribed (line 7) | def unsubscribed
method ping (line 12) | def ping(data)
FILE: bench/app/controllers/application_controller.rb
class ApplicationController (line 1) | class ApplicationController < ActionController::Base
FILE: bench/app/controllers/rooms_controller.rb
class RoomsController (line 1) | class RoomsController < ApplicationController
method index (line 5) | def index
method show (line 10) | def show
method new (line 14) | def new
method edit (line 19) | def edit
method create (line 23) | def create
method update (line 34) | def update
method destroy (line 43) | def destroy
method set_room (line 50) | def set_room
method room_params (line 55) | def room_params
FILE: bench/app/controllers/ws_debugger_controller.rb
class WsDebuggerController (line 1) | class WsDebuggerController < ApplicationController
FILE: bench/app/javascript/channels/broadcast_channel.js
method connected (line 4) | connected() {
method disconnected (line 8) | disconnected() {
method received (line 12) | received(data) {
FILE: bench/app/jobs/application_job.rb
class ApplicationJob (line 1) | class ApplicationJob < ActiveJob::Base
FILE: bench/app/mailers/application_mailer.rb
class ApplicationMailer (line 1) | class ApplicationMailer < ActionMailer::Base
FILE: bench/app/models/application_record.rb
class ApplicationRecord (line 1) | class ApplicationRecord < ActiveRecord::Base
FILE: bench/app/models/room.rb
class Room (line 1) | class Room < ApplicationRecord
FILE: bench/config/application.rb
type Cablebench (line 9) | module Cablebench
class Application (line 10) | class Application < Rails::Application
FILE: bench/db/migrate/20240529231225_create_rooms.rb
class CreateRooms (line 1) | class CreateRooms < ActiveRecord::Migration[8.0]
method change (line 2) | def change
FILE: bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb
class CreateSolidCableMessage (line 4) | class CreateSolidCableMessage < ActiveRecord::Migration[7.1]
method change (line 5) | def change
FILE: bench/db/migrate/20240607184931_index_channels.solid_cable.rb
class IndexChannels (line 2) | class IndexChannels < ActiveRecord::Migration[7.1]
method change (line 3) | def change
FILE: bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb
class CreateActiveErrorFaults (line 4) | class CreateActiveErrorFaults < ActiveRecord::Migration[7.1]
method change (line 5) | def change # rubocop:disable Metrics/AbcSize
FILE: bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb
class CreateActiveErrorInstances (line 4) | class CreateActiveErrorInstances < ActiveRecord::Migration[7.1]
method change (line 5) | def change
FILE: bench/db/migrate/20240912235943_create_compact_channel.rb
class CreateCompactChannel (line 3) | class CreateCompactChannel < ActiveRecord::Migration[7.2]
method up (line 4) | def up
method down (line 16) | def down
FILE: bench/loadtest.js
constant WS_URL (line 8) | const WS_URL = __ENV.WS_URL || "wss://solid-cable.dev/cable";
constant WS_COOKIE (line 9) | const WS_COOKIE = __ENV.WS_COOKIE;
constant MAX (line 10) | const MAX = parseInt(__ENV.MAX || "20");
constant TIME (line 12) | const TIME = parseInt(__ENV.TIME || "90");
constant MESSAGES_NUM (line 13) | const MESSAGES_NUM = parseInt(__ENV.NUM || "5");
FILE: bench/test/channels/application_cable/connection_test.rb
type ApplicationCable (line 3) | module ApplicationCable
class ConnectionTest (line 4) | class ConnectionTest < ActionCable::Connection::TestCase
FILE: bench/test/channels/broadcast_channel_test.rb
class BroadcastChannelTest (line 3) | class BroadcastChannelTest < ActionCable::Channel::TestCase
FILE: bench/test/models/room_test.rb
class RoomTest (line 3) | class RoomTest < ActiveSupport::TestCase
FILE: bench/test/test_helper.rb
type ActiveSupport (line 5) | module ActiveSupport
class TestCase (line 6) | class TestCase
FILE: lib/action_cable/subscription_adapter/solid_cable.rb
type ActionCable (line 8) | module ActionCable
type SubscriptionAdapter (line 9) | module SubscriptionAdapter
class SolidCable (line 10) | class SolidCable < ::ActionCable::SubscriptionAdapter::Base
method initialize (line 13) | def initialize(*)
method broadcast (line 18) | def broadcast(channel, payload)
method subscribe (line 24) | def subscribe(channel, callback, success_callback = nil)
method unsubscribe (line 28) | def unsubscribe(channel, callback)
method listener (line 35) | def listener
class Listener (line 41) | class Listener < ::ActionCable::SubscriptionAdapter::SubscriberMap
method initialize (line 45) | def initialize(event_loop)
method listen (line 70) | def listen
method interruptible (line 86) | def interruptible
method shutdown (line 93) | def shutdown
method add_channel (line 102) | def add_channel(channel, on_success)
method remove_channel (line 107) | def remove_channel(channel)
method invoke_callback (line 111) | def invoke_callback(*)
method last_message_id (line 119) | def last_message_id
method channels (line 123) | def channels
method broadcast_messages (line 127) | def broadcast_messages
method with_polling_volume (line 146) | def with_polling_volume
method reconnect_attempts (line 154) | def reconnect_attempts
method retry_connecting? (line 158) | def retry_connecting?
FILE: lib/generators/solid_cable/install/install_generator.rb
class SolidCable::InstallGenerator (line 3) | class SolidCable::InstallGenerator < Rails::Generators::Base
method copy_files (line 6) | def copy_files
FILE: lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb
class CreateCompactChannel (line 3) | class CreateCompactChannel < ActiveRecord::Migration[7.2]
method up (line 4) | def up
method down (line 15) | def down
FILE: lib/generators/solid_cable/update/update_generator.rb
class SolidCable::UpdateGenerator (line 6) | class SolidCable::UpdateGenerator < Rails::Generators::Base
method copy_files (line 11) | def copy_files
FILE: lib/solid_cable.rb
type SolidCable (line 7) | module SolidCable
function connects_to (line 9) | def connects_to
function silence_polling? (line 13) | def silence_polling?
function polling_interval (line 17) | def polling_interval
function message_retention (line 21) | def message_retention
function autotrim? (line 25) | def autotrim?
function trim_batch_size (line 29) | def trim_batch_size
function use_skip_locked (line 37) | def use_skip_locked
function trim_chance (line 47) | def trim_chance
function reconnect_attempts (line 51) | def reconnect_attempts
function cable_config (line 58) | def cable_config
function parse_duration (line 62) | def parse_duration(duration, default:)
FILE: lib/solid_cable/engine.rb
type SolidCable (line 3) | module SolidCable
class Engine (line 4) | class Engine < ::Rails::Engine
FILE: lib/solid_cable/version.rb
type SolidCable (line 3) | module SolidCable
FILE: test/config_stubs.rb
type ConfigStubs (line 3) | module ConfigStubs
class ConfigStub (line 6) | class ConfigStub
method initialize (line 7) | def initialize(**)
method config_for (line 12) | def config_for(_file)
method executor (line 16) | def executor
class ExectorStub (line 20) | class ExectorStub
method run! (line 21) | def run!
function with_cable_config (line 26) | def with_cable_config(**)
FILE: test/dummy/app/controllers/application_controller.rb
class ApplicationController (line 1) | class ApplicationController < ActionController::Base
FILE: test/dummy/app/helpers/application_helper.rb
type ApplicationHelper (line 1) | module ApplicationHelper
FILE: test/dummy/app/jobs/application_job.rb
class ApplicationJob (line 1) | class ApplicationJob < ActiveJob::Base
FILE: test/dummy/app/models/application_record.rb
class ApplicationRecord (line 1) | class ApplicationRecord < ActiveRecord::Base
FILE: test/dummy/config/application.rb
type Dummy (line 9) | module Dummy
class Application (line 10) | class Application < Rails::Application
FILE: test/jobs/trim_job_test.rb
class TrimJobTest (line 6) | class TrimJobTest < ActiveJob::TestCase
FILE: test/lib/action_cable/subscription_adapter/solid_cable_test.rb
class ActionCable::SubscriptionAdapter::SolidCableTest (line 10) | class ActionCable::SubscriptionAdapter::SolidCableTest < ActionCable::Te...
method cable_config (line 203) | def cable_config
method subscribe_as_queue (line 208) | def subscribe_as_queue(channel, adapter = @rx_adapter)
method with_active_record_logger (line 225) | def with_active_record_logger(logger)
method next_message_in_queue (line 233) | def next_message_in_queue(queue)
FILE: test/lib/generators/solid_cable/install/install_generator_test.rb
class SolidCable::InstallGeneratorTest (line 4) | class SolidCable::InstallGeneratorTest < Rails::Generators::TestCase
FILE: test/lib/generators/solid_cable/update/update_generator_test.rb
class SolidCable::UpdateGeneratorTest (line 4) | class SolidCable::UpdateGeneratorTest < Rails::Generators::TestCase
FILE: test/solid_cable_test.rb
class SolidCableTest (line 6) | class SolidCableTest < ActiveSupport::TestCase
Condensed preview — 168 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (175K chars).
[
{
"path": ".github/workflows/lint.yml",
"chars": 413,
"preview": "name: lint\n\non:\n push:\n branches:\n - main\n pull_request:\n\njobs:\n build:\n runs-on: ubuntu-latest\n name: "
},
{
"path": ".github/workflows/test.yml",
"chars": 1125,
"preview": "name: tests\n\non:\n push:\n branches:\n - main\n pull_request:\n\njobs:\n build:\n name: Tests\n runs-on: ubuntu-"
},
{
"path": ".gitignore",
"chars": 532,
"preview": "/.bundle/\n/doc/\n/log/*.log\n/pkg/\n/tmp/\n/test/dummy/db/*.sqlite3\n/test/dummy/db/*.sqlite3-*\n/test/dummy/log/*.log\n/test/d"
},
{
"path": ".rubocop.yml",
"chars": 392,
"preview": "inherit_gem:\n rubocop-rails-omakase: rubocop.yml\n\nAllCops:\n NewCops: enable\n SuggestExtensions: false\n Exclude:\n "
},
{
"path": "Gemfile",
"chars": 271,
"preview": "# frozen_string_literal: true\n\nsource \"https://rubygems.org\"\ngit_source(:github) { |repo| \"https://github.com/#{repo}.gi"
},
{
"path": "MIT-LICENSE",
"chars": 1045,
"preview": "Copyright Nick Pezza\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and "
},
{
"path": "README.md",
"chars": 11257,
"preview": "# Solid Cable\n\nSolid Cable is a database-backed Action Cable adapter that keeps messages in a table and continuously pol"
},
{
"path": "Rakefile",
"chars": 149,
"preview": "require \"bundler/setup\"\n\nAPP_RAKEFILE = File.expand_path(\"test/dummy/Rakefile\", __dir__)\nload \"rails/tasks/engine.rake\"\n"
},
{
"path": "app/jobs/solid_cable/trim_job.rb",
"chars": 660,
"preview": "# frozen_string_literal: true\n\nmodule SolidCable\n class TrimJob < ActiveJob::Base\n def perform\n return unless t"
},
{
"path": "app/models/solid_cable/message.rb",
"chars": 888,
"preview": "# frozen_string_literal: true\n\nmodule SolidCable\n class Message < SolidCable::Record\n scope :trimmable, lambda {\n "
},
{
"path": "app/models/solid_cable/record.rb",
"chars": 363,
"preview": "# frozen_string_literal: true\n\nmodule SolidCable\n class Record < ActiveRecord::Base\n self.abstract_class = true\n\n "
},
{
"path": "bench/.dockerignore",
"chars": 836,
"preview": "# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.\n\n# Ignore git d"
},
{
"path": "bench/.ruby-version",
"chars": 6,
"preview": "3.3.5\n"
},
{
"path": "bench/Dockerfile",
"chars": 2378,
"preview": "# syntax = docker/dockerfile:1\n\n# This Dockerfile is designed for production, not development. Use with Kamal or build'n"
},
{
"path": "bench/Gemfile",
"chars": 344,
"preview": "source \"https://rubygems.org\"\n\ngem \"rails\", github: \"rails/rails\", branch: \"main\"\ngem \"activeerror\"\ngem \"propshaft\"\ngem "
},
{
"path": "bench/Rakefile",
"chars": 227,
"preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
},
{
"path": "bench/app/assets/builds/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/app/assets/images/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/app/assets/stylesheets/application.tailwind.css",
"chars": 145,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n/*\n\n@layer components {\n .btn-primary {\n @apply py-2 px-"
},
{
"path": "bench/app/channels/application_cable/channel.rb",
"chars": 100,
"preview": "# :markup: markdown\n\nmodule ApplicationCable\n class Channel < ActionCable::Channel::Base\n end\nend\n"
},
{
"path": "bench/app/channels/application_cable/connection.rb",
"chars": 314,
"preview": "module ApplicationCable\n class Connection < ActionCable::Connection::Base\n rescue_from Exception do |error|\n Ra"
},
{
"path": "bench/app/channels/broadcast_channel.rb",
"chars": 361,
"preview": "class BroadcastChannel < ApplicationCable::Channel\n def subscribed\n Rails.logger.info \"a client subscribed: #{id}\"\n "
},
{
"path": "bench/app/controllers/application_controller.rb",
"chars": 57,
"preview": "class ApplicationController < ActionController::Base\nend\n"
},
{
"path": "bench/app/controllers/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/app/controllers/rooms_controller.rb",
"chars": 1175,
"preview": "class RoomsController < ApplicationController\n before_action :set_room, only: %i[ show edit update destroy ]\n\n # GET /"
},
{
"path": "bench/app/controllers/ws_debugger_controller.rb",
"chars": 55,
"preview": "class WsDebuggerController < ApplicationController\nend\n"
},
{
"path": "bench/app/javascript/application.js",
"chars": 123,
"preview": "// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\nimport \"channel"
},
{
"path": "bench/app/javascript/channels/broadcast_channel.js",
"chars": 380,
"preview": "import consumer from \"channels/consumer\"\n\nconsumer.subscriptions.create(\"BroadcastChannel\", {\n connected() {\n // Cal"
},
{
"path": "bench/app/javascript/channels/consumer.js",
"chars": 270,
"preview": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocke"
},
{
"path": "bench/app/javascript/channels/index.js",
"chars": 90,
"preview": "// Import all the channels to be used by Action Cable\nimport \"channels/broadcast_channel\"\n"
},
{
"path": "bench/app/javascript/controllers/application.js",
"chars": 218,
"preview": "import { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus develop"
},
{
"path": "bench/app/javascript/controllers/index.js",
"chars": 584,
"preview": "// Import and register all your controllers from the importmap under controllers/*\n\nimport { application } from \"control"
},
{
"path": "bench/app/jobs/application_job.rb",
"chars": 269,
"preview": "class ApplicationJob < ActiveJob::Base\n # Automatically retry jobs that encountered a deadlock\n # retry_on ActiveRecor"
},
{
"path": "bench/app/mailers/application_mailer.rb",
"chars": 102,
"preview": "class ApplicationMailer < ActionMailer::Base\n default from: \"from@example.com\"\n layout \"mailer\"\nend\n"
},
{
"path": "bench/app/models/application_record.rb",
"chars": 74,
"preview": "class ApplicationRecord < ActiveRecord::Base\n primary_abstract_class\nend\n"
},
{
"path": "bench/app/models/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/app/models/room.rb",
"chars": 35,
"preview": "class Room < ApplicationRecord\nend\n"
},
{
"path": "bench/app/views/layouts/application.html.erb",
"chars": 781,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title><%= content_for(:title) || \"Cablebench\" %></title>\n <meta name=\"viewport\" "
},
{
"path": "bench/app/views/layouts/mailer.html.erb",
"chars": 227,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <style>\n "
},
{
"path": "bench/app/views/layouts/mailer.text.erb",
"chars": 13,
"preview": "<%= yield %>\n"
},
{
"path": "bench/app/views/rooms/_form.html.erb",
"chars": 783,
"preview": "<%= form_with(model: room, class: \"contents\") do |form| %>\n <% if room.errors.any? %>\n <div id=\"error_explanation\" c"
},
{
"path": "bench/app/views/rooms/_room.html.erb",
"chars": 143,
"preview": "<div id=\"<%= dom_id room %>\">\n <p class=\"my-5\">\n <strong class=\"block font-medium mb-1\">Name:</strong>\n <%= room."
},
{
"path": "bench/app/views/rooms/edit.html.erb",
"chars": 366,
"preview": "<div class=\"mx-auto md:w-2/3 w-full\">\n <h1 class=\"font-bold text-4xl\">Editing room</h1>\n\n <%= render \"form\", room: @ro"
},
{
"path": "bench/app/views/rooms/index.html.erb",
"chars": 709,
"preview": "<div class=\"w-full\">\n <% if notice.present? %>\n <p class=\"py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium roun"
},
{
"path": "bench/app/views/rooms/new.html.erb",
"chars": 248,
"preview": "<div class=\"mx-auto md:w-2/3 w-full\">\n <h1 class=\"font-bold text-4xl\">New room</h1>\n\n <%= render \"form\", room: @room %"
},
{
"path": "bench/app/views/rooms/show.html.erb",
"chars": 703,
"preview": "<div class=\"mx-auto md:w-2/3 w-full flex\">\n <div class=\"mx-auto\">\n <% if notice.present? %>\n <p class=\"py-2 px-"
},
{
"path": "bench/app/views/ws_debugger/show.html.erb",
"chars": 19,
"preview": "<p>hello world</p>\n"
},
{
"path": "bench/bin/bundle",
"chars": 2801,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'bundle' "
},
{
"path": "bench/bin/docker-entrypoint",
"chars": 381,
"preview": "#!/bin/bash -e\n\n# Enable jemalloc for reduced memory usage and latency.\nif [ -z \"${LD_PRELOAD+x}\" ] && [ -f /usr/lib/*/l"
},
{
"path": "bench/bin/importmap",
"chars": 91,
"preview": "#!/usr/bin/env ruby\n\nrequire_relative \"../config/application\"\nrequire \"importmap/commands\"\n"
},
{
"path": "bench/bin/kamal",
"chars": 745,
"preview": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n\n#\n# This file was generated by Bundler.\n#\n# The application 'kamal' i"
},
{
"path": "bench/bin/rails",
"chars": 141,
"preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
},
{
"path": "bench/bin/rake",
"chars": 90,
"preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
},
{
"path": "bench/bin/rubocop",
"chars": 266,
"preview": "#!/usr/bin/env ruby\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\n# explicit rubocop config increases performance slightly"
},
{
"path": "bench/bin/setup",
"chars": 1135,
"preview": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\nAPP_NAME = \"cablebench\"\n\ndef system!"
},
{
"path": "bench/config/application.rb",
"chars": 1107,
"preview": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited "
},
{
"path": "bench/config/boot.rb",
"chars": 207,
"preview": "ENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\n\nrequire \"bundler/setup\" # Set up gems listed in the G"
},
{
"path": "bench/config/cable.yml",
"chars": 554,
"preview": "development:\n # adapter: redis\n # url: redis://localhost:6379/1\n adapter: solid_cable\n message_retention: 15.minutes"
},
{
"path": "bench/config/credentials.yml.enc",
"chars": 500,
"preview": "7lxKveoOh/YSo6BSUdrNHtg29iVtfI9AinJgpSiiQkSWpoVPHluT1PNVQpleQhs3LqM0WP4aMBX12xzUP843RI2LYQmtUmbjkJtfkMs/dCijfslkwfrwSI8A"
},
{
"path": "bench/config/database.yml",
"chars": 1152,
"preview": "default: &default\n adapter: sqlite3\n pool: <%= ENV.fetch(\"RAILS_MAX_THREADS\") { 5 } %>\n timeout: 5000\n\ndevelopment:\n "
},
{
"path": "bench/config/deploy.yml",
"chars": 1164,
"preview": "service: solidcable\nimage: npezza/solid_cable\nasset_path: /rails/public/assets\nservers:\n web:\n - <%= ENV[\"HOST\"] %>\n"
},
{
"path": "bench/config/environment.rb",
"chars": 128,
"preview": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.init"
},
{
"path": "bench/config/environments/development.rb",
"chars": 2578,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "bench/config/environments/production.rb",
"chars": 4312,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "bench/config/environments/test.rb",
"chars": 2592,
"preview": "require \"active_support/core_ext/integer/time\"\n\n# The test environment is used exclusively to run your application's\n# t"
},
{
"path": "bench/config/importmap.rb",
"chars": 174,
"preview": "# Pin npm packages by running ./bin/importmap\n\npin \"application\"\npin \"@rails/actioncable\", to: \"actioncable.esm.js\"\npin_"
},
{
"path": "bench/config/init.sql",
"chars": 28,
"preview": "CREATE DATABASE cablebench;\n"
},
{
"path": "bench/config/initializers/assets.rb",
"chars": 296,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire"
},
{
"path": "bench/config/initializers/content_security_policy.rb",
"chars": 1071,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See"
},
{
"path": "bench/config/initializers/filter_parameter_logging.rb",
"chars": 456,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw "
},
{
"path": "bench/config/initializers/inflections.rb",
"chars": 649,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Infl"
},
{
"path": "bench/config/initializers/permissions_policy.rb",
"chars": 480,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide HTTP permissions policy. For f"
},
{
"path": "bench/config/locales/en.yml",
"chars": 908,
"preview": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If yo"
},
{
"path": "bench/config/puma.rb",
"chars": 2322,
"preview": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's co"
},
{
"path": "bench/config/routes.rb",
"chars": 534,
"preview": "Rails.application.routes.draw do\n mount ActiveError::Engine => \"/errors\"\n mount ActionCable.server => \"/cable\"\n\n get "
},
{
"path": "bench/config/storage.yml",
"chars": 1152,
"preview": "test:\n service: Disk\n root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n service: Disk\n root: <%= Rails.root.join("
},
{
"path": "bench/config/tailwind.config.js",
"chars": 492,
"preview": "const defaultTheme = require('tailwindcss/defaultTheme')\n\nmodule.exports = {\n content: [\n './public/*.html',\n './"
},
{
"path": "bench/config.ru",
"chars": 160,
"preview": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.ap"
},
{
"path": "bench/db/migrate/20240529231225_create_rooms.rb",
"chars": 152,
"preview": "class CreateRooms < ActiveRecord::Migration[8.0]\n def change\n create_table :rooms do |t|\n t.string :name\n\n "
},
{
"path": "bench/db/migrate/20240530031126_create_solid_cable_message.solid_cable.rb",
"chars": 349,
"preview": "# frozen_string_literal: true\n\n# This migration comes from solid_cable (originally 20240103034713)\nclass CreateSolidCabl"
},
{
"path": "bench/db/migrate/20240607184931_index_channels.solid_cable.rb",
"chars": 201,
"preview": "# This migration comes from solid_cable (originally 20240607184711)\nclass IndexChannels < ActiveRecord::Migration[7.1]\n "
},
{
"path": "bench/db/migrate/20240609023040_create_active_error_faults.active_error.rb",
"chars": 621,
"preview": "# frozen_string_literal: true\n\n# This migration comes from active_error (originally 20200727220359)\nclass CreateActiveEr"
},
{
"path": "bench/db/migrate/20240609023041_create_active_error_instances.active_error.rb",
"chars": 402,
"preview": "# frozen_string_literal: true\n\n# This migration comes from active_error (originally 20200727225318)\nclass CreateActiveEr"
},
{
"path": "bench/db/migrate/20240912235943_create_compact_channel.rb",
"chars": 837,
"preview": "# frozen_string_literal: true\n\nclass CreateCompactChannel < ActiveRecord::Migration[7.2]\n def up\n change_column :sol"
},
{
"path": "bench/db/schema.rb",
"chars": 2312,
"preview": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the mig"
},
{
"path": "bench/db/seeds.rb",
"chars": 494,
"preview": "# This file should ensure the existence of records required to run the application in every environment (production,\n# d"
},
{
"path": "bench/loadtest.js",
"chars": 1890,
"preview": "import { check, sleep, fail } from \"k6\";\nimport cable from \"k6/x/cable\";\nimport { randomIntBetween } from \"https://jslib"
},
{
"path": "bench/log/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/public/404.html",
"chars": 1722,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>The page you were looking for doesn't exist (404)</title>\n <meta name=\"viewport\""
},
{
"path": "bench/public/406-unsupported-browser.html",
"chars": 1612,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>Your browser is not supported (406)</title>\n <meta name=\"viewport\" content=\"widt"
},
{
"path": "bench/public/422.html",
"chars": 1705,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>The change you wanted was rejected (422)</title>\n <meta name=\"viewport\" content="
},
{
"path": "bench/public/500.html",
"chars": 1635,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>We're sorry, but something went wrong (500)</title>\n <meta name=\"viewport\" conte"
},
{
"path": "bench/public/robots.txt",
"chars": 99,
"preview": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n"
},
{
"path": "bench/test/channels/application_cable/connection_test.rb",
"chars": 276,
"preview": "require \"test_helper\"\n\nmodule ApplicationCable\n class ConnectionTest < ActionCable::Connection::TestCase\n # test \"co"
},
{
"path": "bench/test/channels/broadcast_channel_test.rb",
"chars": 173,
"preview": "require \"test_helper\"\n\nclass BroadcastChannelTest < ActionCable::Channel::TestCase\n # test \"subscribes\" do\n # subscr"
},
{
"path": "bench/test/controllers/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/test/fixtures/files/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/test/fixtures/rooms.yml",
"chars": 136,
"preview": "# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html\n\none:\n name: MyString\n\ntwo:\n "
},
{
"path": "bench/test/models/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/test/models/room_test.rb",
"chars": 118,
"preview": "require \"test_helper\"\n\nclass RoomTest < ActiveSupport::TestCase\n # test \"the truth\" do\n # assert true\n # end\nend\n"
},
{
"path": "bench/test/test_helper.rb",
"chars": 410,
"preview": "ENV[\"RAILS_ENV\"] ||= \"test\"\nrequire_relative \"../config/environment\"\nrequire \"rails/test_help\"\n\nmodule ActiveSupport\n c"
},
{
"path": "bench/vendor/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bench/vendor/javascript/.keep",
"chars": 0,
"preview": ""
},
{
"path": "bin/rails",
"chars": 553,
"preview": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails gems\n# installed from the r"
},
{
"path": "bin/release",
"chars": 331,
"preview": "#!/usr/bin/env bash\n\nVERSION=$1\n\nif [ -z \"$VERSION\" ]\nthen\n echo \"Usage: bin/release <version>\"\n exit 1\nfi\n\nprintf \"# "
},
{
"path": "bin/test",
"chars": 116,
"preview": "#!/usr/bin/env ruby\n$: << File.expand_path(\"../test\", __dir__)\n\nrequire \"bundler/setup\"\nrequire \"rails/plugin/test\"\n"
},
{
"path": "lib/action_cable/subscription_adapter/solid_cable.rb",
"chars": 4969,
"preview": "# frozen_string_literal: true\n\nrequire \"action_cable/subscription_adapter/base\"\nrequire \"action_cable/subscription_adapt"
},
{
"path": "lib/generators/solid_cable/install/USAGE",
"chars": 197,
"preview": "Description:\n Installs solid_cable as the Action Cable adapter\n\nExample:\n bin/rails generate solid_cable:install\n\n"
},
{
"path": "lib/generators/solid_cable/install/install_generator.rb",
"chars": 252,
"preview": "# frozen_string_literal: true\n\nclass SolidCable::InstallGenerator < Rails::Generators::Base\n source_root File.expand_pa"
},
{
"path": "lib/generators/solid_cable/install/templates/config/cable.yml",
"chars": 549,
"preview": "# Async adapter only works within the same process, so for manually triggering cable updates from a console,\n# and seein"
},
{
"path": "lib/generators/solid_cable/install/templates/db/cable_schema.rb",
"chars": 545,
"preview": "ActiveRecord::Schema[7.1].define(version: 1) do\n create_table \"solid_cable_messages\", force: :cascade do |t|\n t.bina"
},
{
"path": "lib/generators/solid_cable/update/USAGE",
"chars": 182,
"preview": "Description:\n Updates Solid Cable migrations\n\nExample:\n bin/rails generate solid_cable:update\n\n This will perfo"
},
{
"path": "lib/generators/solid_cable/update/templates/db/migrate/create_compact_channel.rb",
"chars": 788,
"preview": "# frozen_string_literal: true\n\nclass CreateCompactChannel < ActiveRecord::Migration[7.2]\n def up\n change_column :sol"
},
{
"path": "lib/generators/solid_cable/update/update_generator.rb",
"chars": 419,
"preview": "# frozen_string_literal: true\n\nrequire \"rails/generators\"\nrequire \"rails/generators/active_record\"\n\nclass SolidCable::Up"
},
{
"path": "lib/solid_cable/engine.rb",
"chars": 125,
"preview": "# frozen_string_literal: true\n\nmodule SolidCable\n class Engine < ::Rails::Engine\n isolate_namespace SolidCable\n end"
},
{
"path": "lib/solid_cable/version.rb",
"chars": 74,
"preview": "# frozen_string_literal: true\n\nmodule SolidCable\n VERSION = \"3.0.12\"\nend\n"
},
{
"path": "lib/solid_cable.rb",
"chars": 1795,
"preview": "# frozen_string_literal: true\n\nrequire \"solid_cable/version\"\nrequire \"solid_cable/engine\"\nrequire \"action_cable/subscrip"
},
{
"path": "lib/tasks/solid_cable_tasks.rake",
"chars": 302,
"preview": "# frozen_string_literal: true\n\ndesc \"Copy over the schema and set cable adapter for Solid Cable\"\nnamespace :solid_cable "
},
{
"path": "solid_cable.gemspec",
"chars": 1090,
"preview": "# frozen_string_literal: true\n\nrequire_relative \"lib/solid_cable/version\"\n\nGem::Specification.new do |spec|\n spec.name "
},
{
"path": "test/config_stubs.rb",
"chars": 508,
"preview": "# frozen_string_literal: true\n\nmodule ConfigStubs\n extend ActiveSupport::Concern\n\n class ConfigStub\n def initialize"
},
{
"path": "test/dummy/Rakefile",
"chars": 227,
"preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
},
{
"path": "test/dummy/app/controllers/application_controller.rb",
"chars": 204,
"preview": "class ApplicationController < ActionController::Base\n # Only allow modern browsers supporting webp images, web push, ba"
},
{
"path": "test/dummy/app/controllers/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/app/helpers/application_helper.rb",
"chars": 29,
"preview": "module ApplicationHelper\nend\n"
},
{
"path": "test/dummy/app/jobs/application_job.rb",
"chars": 269,
"preview": "class ApplicationJob < ActiveJob::Base\n # Automatically retry jobs that encountered a deadlock\n # retry_on ActiveRecor"
},
{
"path": "test/dummy/app/models/application_record.rb",
"chars": 74,
"preview": "class ApplicationRecord < ActiveRecord::Base\n primary_abstract_class\nend\n"
},
{
"path": "test/dummy/app/models/concerns/.keep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/app/views/layouts/application.html.erb",
"chars": 906,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <title><%= content_for(:title) || \"Dummy\" %></title>\n <meta name=\"viewport\" conte"
},
{
"path": "test/dummy/app/views/layouts/mailer.html.erb",
"chars": 227,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <style>\n "
},
{
"path": "test/dummy/app/views/layouts/mailer.text.erb",
"chars": 13,
"preview": "<%= yield %>\n"
},
{
"path": "test/dummy/app/views/pwa/manifest.json.erb",
"chars": 393,
"preview": "{\n \"name\": \"Dummy\",\n \"icons\": [\n {\n \"src\": \"/icon.png\",\n \"type\": \"image/png\",\n \"sizes\": \"512x512\"\n "
},
{
"path": "test/dummy/app/views/pwa/service-worker.js",
"chars": 876,
"preview": "// Add a service worker for processing Web Push notifications:\n//\n// self.addEventListener(\"push\", async (event) => {\n//"
},
{
"path": "test/dummy/bin/ci",
"chars": 180,
"preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"active_support/continuous_integration\"\n\nCI = ActiveSuppor"
},
{
"path": "test/dummy/bin/dev",
"chars": 56,
"preview": "#!/usr/bin/env ruby\nexec \"./bin/rails\", \"server\", *ARGV\n"
},
{
"path": "test/dummy/bin/rails",
"chars": 141,
"preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
},
{
"path": "test/dummy/bin/rake",
"chars": 90,
"preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
},
{
"path": "test/dummy/bin/setup",
"chars": 1069,
"preview": "#!/usr/bin/env ruby\nrequire \"fileutils\"\n\nAPP_ROOT = File.expand_path(\"..\", __dir__)\n\ndef system!(*args)\n system(*args, "
},
{
"path": "test/dummy/config/application.rb",
"chars": 1054,
"preview": "require_relative \"boot\"\n\nrequire \"rails/all\"\n\n# Require the gems listed in Gemfile, including any gems\n# you've limited "
},
{
"path": "test/dummy/config/boot.rb",
"chars": 233,
"preview": "# Set up gems listed in the Gemfile.\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../../../Gemfile\", __dir__)\n\nrequire \"b"
},
{
"path": "test/dummy/config/cable.yml",
"chars": 186,
"preview": "development:\n adapter: async\n\ntest:\n adapter: test\n\nproduction:\n adapter: redis\n url: <%= ENV.fetch(\"REDIS_URL\") { \""
},
{
"path": "test/dummy/config/ci.rb",
"chars": 585,
"preview": "# Run using bin/ci\n\nCI.run do\n step \"Setup\", \"bin/setup --skip-server\"\n\n\n step \"Tests: Rails\", \"bin/rails test\"\n step"
},
{
"path": "test/dummy/config/database.yml",
"chars": 843,
"preview": "<% def database_name_from(name); [\"mysql\", \"postgres\"].exclude?(ENV[\"TARGET_DB\"]) ? \"db/#{name}.sqlite3\" : name; end %>\n"
},
{
"path": "test/dummy/config/environment.rb",
"chars": 128,
"preview": "# Load the Rails application.\nrequire_relative \"application\"\n\n# Initialize the Rails application.\nRails.application.init"
},
{
"path": "test/dummy/config/environments/development.rb",
"chars": 2770,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "test/dummy/config/environments/production.rb",
"chars": 3668,
"preview": "require \"active_support/core_ext/integer/time\"\n\nRails.application.configure do\n # Settings specified here will take pre"
},
{
"path": "test/dummy/config/environments/test.rb",
"chars": 2325,
"preview": "# The test environment is used exclusively to run your application's\n# test suite. You never need to work with it otherw"
},
{
"path": "test/dummy/config/initializers/assets.rb",
"chars": 298,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Version of your assets, change this if you want to expire"
},
{
"path": "test/dummy/config/initializers/content_security_policy.rb",
"chars": 1332,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Define an application-wide content security policy.\n# See"
},
{
"path": "test/dummy/config/initializers/filter_parameter_logging.rb",
"chars": 468,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Configure parameters to be partially matched (e.g. passw "
},
{
"path": "test/dummy/config/initializers/inflections.rb",
"chars": 649,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format. Infl"
},
{
"path": "test/dummy/config/locales/en.yml",
"chars": 908,
"preview": "# Files in the config/locales directory are used for internationalization and\n# are automatically loaded by Rails. If yo"
},
{
"path": "test/dummy/config/puma.rb",
"chars": 1922,
"preview": "# This configuration file will be evaluated by Puma. The top-level methods that\n# are invoked here are part of Puma's co"
},
{
"path": "test/dummy/config/routes.rb",
"chars": 471,
"preview": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n # Define your application routes per the DSL in https:"
},
{
"path": "test/dummy/config/storage.yml",
"chars": 811,
"preview": "test:\n service: Disk\n root: <%= Rails.root.join(\"tmp/storage\") %>\n\nlocal:\n service: Disk\n root: <%= Rails.root.join("
},
{
"path": "test/dummy/config.ru",
"chars": 160,
"preview": "# This file is used by Rack-based servers to start the application.\n\nrequire_relative \"config/environment\"\n\nrun Rails.ap"
},
{
"path": "test/dummy/db/schema.rb",
"chars": 1271,
"preview": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the mig"
},
{
"path": "test/dummy/log/.keep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/public/400.html",
"chars": 7054,
"preview": "<!doctype html>\n\n<html lang=\"en\">\n\n <head>\n\n <title>The server cannot process the request due to a client error (400"
},
{
"path": "test/dummy/public/404.html",
"chars": 5201,
"preview": "<!doctype html>\n\n<html lang=\"en\">\n\n <head>\n\n <title>The page you were looking for doesn't exist (404 Not found)</tit"
},
{
"path": "test/dummy/public/406-unsupported-browser.html",
"chars": 7283,
"preview": "<!doctype html>\n\n<html lang=\"en\">\n\n <head>\n\n <title>Your browser is not supported (406 Not Acceptable)</title>\n\n "
},
{
"path": "test/dummy/public/422.html",
"chars": 8747,
"preview": "<!doctype html>\n\n<html lang=\"en\">\n\n <head>\n\n <title>The change you wanted was rejected (422 Unprocessable Entity)</t"
},
{
"path": "test/dummy/public/500.html",
"chars": 8283,
"preview": "<!doctype html>\n\n<html lang=\"en\">\n\n <head>\n\n <title>We're sorry, but something went wrong (500 Internal Server Error"
},
{
"path": "test/jobs/trim_job_test.rb",
"chars": 1092,
"preview": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"config_stubs\"\n\nclass TrimJobTest < ActiveJob::TestCase\n i"
},
{
"path": "test/lib/action_cable/subscription_adapter/solid_cable_test.rb",
"chars": 6581,
"preview": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"concurrent\"\n\nrequire \"active_support/core_ext/hash/indiffe"
},
{
"path": "test/lib/generators/solid_cable/install/install_generator_test.rb",
"chars": 482,
"preview": "require \"test_helper\"\nrequire_relative \"../../../../../lib/generators/solid_cable/install/install_generator\"\n\nclass Soli"
},
{
"path": "test/lib/generators/solid_cable/update/update_generator_test.rb",
"chars": 436,
"preview": "require \"test_helper\"\nrequire_relative \"../../../../../lib/generators/solid_cable/update/update_generator\"\n\nclass SolidC"
},
{
"path": "test/solid_cable_test.rb",
"chars": 1630,
"preview": "# frozen_string_literal: true\n\nrequire \"test_helper\"\nrequire \"config_stubs\"\n\nclass SolidCableTest < ActiveSupport::TestC"
},
{
"path": "test/test_helper.rb",
"chars": 707,
"preview": "# frozen_string_literal: true\n\n# Configure Rails Environment\nENV[\"RAILS_ENV\"] = \"test\"\n\nrequire_relative \"../test/dummy/"
}
]
About this extraction
This page contains the full source code of the rails/solid_cable GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 168 files (154.5 KB), approximately 55.6k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.