falcon-rails 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +43 -0
- data/context/http-streaming.md +200 -0
- data/context/job-processing.md +78 -0
- data/context/real-time-views.md +259 -0
- data/context/server-sent-events.md +149 -0
- data/context/websockets.md +133 -0
- data/lib/falcon/rails/version.rb +9 -0
- data/lib/falcon/rails.rb +15 -0
- data/license.md +21 -0
- data/readme.md +54 -0
- data/releases.md +3 -0
- data.tar.gz.sig +0 -0
- metadata +177 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 34ee82aedb10194c91447a3ea58db73ba79da8ebbe6621cc4a9148f5517bdeb6
|
4
|
+
data.tar.gz: 14d659677ee4e20bddfc2b1107277b01c883aecf6a843eaca4ff2799bf58976b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e8ad267e81745e93527f25511a56775ff6cf3c8b38ef99553fdd2679de87c8b2d090788f34b48ebca0ed0a20f8a881fa97026466ac62ffdc99399618ed5fc2a
|
7
|
+
data.tar.gz: 150e56abe7e16e42d56421a5b4c860d1da3d5e653615ee2dcb7b20e94fc213e3c1010596959593c4a57b7830fb5c95a6e1d503adb8699a1052dd605356a8465e
|
checksums.yaml.gz.sig
ADDED
Binary file
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
This guide explains how to get started using Falcon to host your Rails application.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
In your existing Rails application:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
bundle add falcon-rails
|
11
|
+
```
|
12
|
+
|
13
|
+
You might also like to remove your existing web server, such as Puma, and replace it with Falcon:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
bundle remove puma
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Falcon is an Async-compatible web server that can be used with Rails. It provides high concurrency and low latency for web applications.
|
22
|
+
|
23
|
+
To use Falcon in development, first install the development TLS certificates:
|
24
|
+
|
25
|
+
```
|
26
|
+
> bundle exec bake localhost:install
|
27
|
+
```
|
28
|
+
|
29
|
+
Then, you can start your Rails application with Falcon:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
> bundle exec falcon serve
|
33
|
+
```
|
34
|
+
|
35
|
+
You can access your application at `https://localhost:9292`.
|
36
|
+
|
37
|
+
## Integrations
|
38
|
+
|
39
|
+
Consult the other integration guides for more information on using Falcon with popular libraries and frameworks.
|
40
|
+
|
41
|
+
### Example
|
42
|
+
|
43
|
+
Many of the integrations are demonstrated in the example application available at: https://github.com/socketry/falcon-rails-examples
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# HTTP Streaming
|
2
|
+
|
3
|
+
This guide explains how to implement HTTP response streaming with Falcon and Rails.
|
4
|
+
|
5
|
+
## What is HTTP Streaming?
|
6
|
+
|
7
|
+
HTTP streaming allows you to send data to the client progressively over a single HTTP connection using chunked transfer encoding. Unlike Server-Sent Events, HTTP streaming gives you complete control over the data format and doesn't require specific protocols.
|
8
|
+
|
9
|
+
**When to use HTTP streaming:**
|
10
|
+
- Progress indicators for long-running tasks.
|
11
|
+
- Live log streaming.
|
12
|
+
- Large file generation (CSV, JSON exports).
|
13
|
+
- Real-time data feeds with custom formats.
|
14
|
+
- Streaming API responses.
|
15
|
+
|
16
|
+
**When NOT to use HTTP streaming:**
|
17
|
+
- When you need persistent connections (use SSE or WebSockets instead).
|
18
|
+
- For simple real-time updates (SSE is easier).
|
19
|
+
|
20
|
+
## Basic Implementation
|
21
|
+
|
22
|
+
### Server-Side: Rails Controller
|
23
|
+
|
24
|
+
Create a controller action that streams data using `Rack::Response`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class StreamingController < ApplicationController
|
28
|
+
def index
|
29
|
+
# Render the page with streaming JavaScript
|
30
|
+
end
|
31
|
+
|
32
|
+
def stream
|
33
|
+
body = proc do |stream|
|
34
|
+
10.downto(1) do |i|
|
35
|
+
stream.write "#{i} bottles of beer on the wall\n"
|
36
|
+
sleep 1
|
37
|
+
stream.write "#{i} bottles of beer\n"
|
38
|
+
sleep 1
|
39
|
+
stream.write "Take one down, pass it around\n"
|
40
|
+
sleep 1
|
41
|
+
stream.write "#{i - 1} bottles of beer on the wall\n"
|
42
|
+
sleep 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
self.response = Rack::Response[200, {"content-type" => "text/plain"}, body]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
**Key Points:**
|
52
|
+
- Use `Rack::Response` with a callable body for streaming.
|
53
|
+
- `content-type` can be `text/plain`, `application/json`, or any format you need.
|
54
|
+
- Each `stream.write` sends data immediately to the client.
|
55
|
+
- No special formatting required (unlike SSE's `data: ` prefix).
|
56
|
+
|
57
|
+
### Client-Side: Fetch API with ReadableStream
|
58
|
+
|
59
|
+
Create an HTML page that consumes the HTTP stream:
|
60
|
+
|
61
|
+
```html
|
62
|
+
<button id="startStream" class="button">🚀 Start Streaming Demo</button>
|
63
|
+
<button id="stopStream" class="button" disabled>⏹️ Stop Stream</button>
|
64
|
+
<div id="streamOutput" class="terminal"></div>
|
65
|
+
|
66
|
+
<script>
|
67
|
+
let streamController = null;
|
68
|
+
let streamReader = null;
|
69
|
+
|
70
|
+
document.getElementById('startStream').addEventListener('click', function() {
|
71
|
+
const output = document.getElementById('streamOutput');
|
72
|
+
const startBtn = document.getElementById('startStream');
|
73
|
+
const stopBtn = document.getElementById('stopStream');
|
74
|
+
|
75
|
+
// Clear previous output
|
76
|
+
output.innerHTML = '<div class="terminal-status">🔄 Starting stream...</div>';
|
77
|
+
|
78
|
+
// Create abort controller for stopping the stream
|
79
|
+
streamController = new AbortController();
|
80
|
+
|
81
|
+
// Start streaming
|
82
|
+
fetch('/streaming/stream', { signal: streamController.signal })
|
83
|
+
.then(response => {
|
84
|
+
if (!response.ok) throw new Error('Network response was not ok');
|
85
|
+
|
86
|
+
streamReader = response.body.getReader();
|
87
|
+
const decoder = new TextDecoder();
|
88
|
+
|
89
|
+
function readStream() {
|
90
|
+
streamReader.read().then(({ done, value }) => {
|
91
|
+
if (done) {
|
92
|
+
output.innerHTML += '<div class="terminal-complete">✅ Stream completed!</div>';
|
93
|
+
startBtn.disabled = false;
|
94
|
+
stopBtn.disabled = true;
|
95
|
+
return;
|
96
|
+
}
|
97
|
+
|
98
|
+
const text = decoder.decode(value, { stream: true });
|
99
|
+
const lines = text.split('\n');
|
100
|
+
|
101
|
+
lines.forEach(line => {
|
102
|
+
if (line.trim()) {
|
103
|
+
const lineDiv = document.createElement('div');
|
104
|
+
lineDiv.className = 'terminal-line';
|
105
|
+
lineDiv.textContent = line;
|
106
|
+
output.appendChild(lineDiv);
|
107
|
+
output.scrollTop = output.scrollHeight;
|
108
|
+
}
|
109
|
+
});
|
110
|
+
|
111
|
+
readStream();
|
112
|
+
});
|
113
|
+
}
|
114
|
+
|
115
|
+
readStream();
|
116
|
+
});
|
117
|
+
});
|
118
|
+
|
119
|
+
document.getElementById('stopStream').addEventListener('click', function() {
|
120
|
+
if (streamController) {
|
121
|
+
streamController.abort();
|
122
|
+
}
|
123
|
+
});
|
124
|
+
</script>
|
125
|
+
```
|
126
|
+
|
127
|
+
**Key Points:**
|
128
|
+
- Use `fetch()` with `AbortController` for cancellation support.
|
129
|
+
- Get a `ReadableStream` reader with `response.body.getReader()`.
|
130
|
+
- Use `TextDecoder` to convert binary data to text.
|
131
|
+
- Handle the stream chunks manually in the `readStream()` function.
|
132
|
+
|
133
|
+
### Routing Configuration
|
134
|
+
|
135
|
+
Add routes to your `config/routes.rb`:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
Rails.application.routes.draw do
|
139
|
+
# Streaming Example:
|
140
|
+
get 'streaming/index' # Page with streaming JavaScript
|
141
|
+
get 'streaming/stream' # HTTP streaming endpoint
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
## Advanced Patterns
|
146
|
+
|
147
|
+
### Streaming NDJSON Data
|
148
|
+
|
149
|
+
Server-side:
|
150
|
+
```ruby
|
151
|
+
def stream
|
152
|
+
body = proc do |stream|
|
153
|
+
User.find_each(batch_size: 100) do |user|
|
154
|
+
# Each line is a complete JSON object
|
155
|
+
stream.write "#{user.to_json}\n"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
self.response = Rack::Response[200, {"content-type" => "application/x-ndjson"}, body]
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
Client-side:
|
164
|
+
```javascript
|
165
|
+
fetch('/streaming/users')
|
166
|
+
.then(response => {
|
167
|
+
const reader = response.body.getReader();
|
168
|
+
const decoder = new TextDecoder();
|
169
|
+
let buffer = '';
|
170
|
+
|
171
|
+
function readStream() {
|
172
|
+
reader.read().then(({ done, value }) => {
|
173
|
+
if (done) {
|
174
|
+
console.log('All users loaded');
|
175
|
+
return;
|
176
|
+
}
|
177
|
+
|
178
|
+
buffer += decoder.decode(value, { stream: true });
|
179
|
+
const lines = buffer.split('\n');
|
180
|
+
buffer = lines.pop(); // Keep incomplete line in buffer
|
181
|
+
|
182
|
+
lines.forEach(line => {
|
183
|
+
if (line.trim()) {
|
184
|
+
try {
|
185
|
+
const user = JSON.parse(line);
|
186
|
+
console.log('User loaded:', user);
|
187
|
+
displayUser(user);
|
188
|
+
} catch (e) {
|
189
|
+
console.error('Invalid JSON:', line);
|
190
|
+
}
|
191
|
+
}
|
192
|
+
});
|
193
|
+
|
194
|
+
readStream();
|
195
|
+
});
|
196
|
+
}
|
197
|
+
|
198
|
+
readStream();
|
199
|
+
});
|
200
|
+
```
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Job Processing
|
2
|
+
|
3
|
+
This guide explains how to implement background job processing with Falcon and Rails using the `async-job` gem.
|
4
|
+
|
5
|
+
## What is Async::Job?
|
6
|
+
|
7
|
+
`Async::Job` is a framework for creating background jobs that run asynchronously without blocking web requests. It integrates seamlessly with Rails' ActiveJob, allowing you to offload long-running tasks to background workers. The Rails integration uses a specific gem to provide this functionality, called `async-job-adapter-active_job`.
|
8
|
+
|
9
|
+
**When to use async jobs:**
|
10
|
+
- Long-running tasks (data processing, file uploads).
|
11
|
+
- Email sending and external API calls.
|
12
|
+
- Scheduled tasks and periodic jobs.
|
13
|
+
- Heavy computations that would slow down web responses.
|
14
|
+
|
15
|
+
**When NOT to use async jobs:**
|
16
|
+
- Simple operations that complete quickly.
|
17
|
+
- Tasks that need immediate user feedback.
|
18
|
+
|
19
|
+
## Basic Implementation
|
20
|
+
|
21
|
+
### Server-Side: Job Class
|
22
|
+
|
23
|
+
Create a job class that inherits from `ApplicationJob`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class MyJob < ApplicationJob
|
27
|
+
# Specify the queue adapter per-job rather than globally:
|
28
|
+
# queue_adapter :async_job
|
29
|
+
|
30
|
+
queue_as "default"
|
31
|
+
|
32
|
+
def perform
|
33
|
+
# ... work ...
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
**Key Points:**
|
39
|
+
- Use `queue_as` to specify which queue this job should use.
|
40
|
+
- The `perform` method contains your background work.
|
41
|
+
|
42
|
+
### Configuration
|
43
|
+
|
44
|
+
Configure async-job queues in `config/initializers/async_job.rb`:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
require 'async/job'
|
48
|
+
require 'async/job/processor/aggregate'
|
49
|
+
require 'async/job/processor/redis'
|
50
|
+
require 'async/job/processor/inline'
|
51
|
+
|
52
|
+
Rails.application.configure do
|
53
|
+
config.async_job.define_queue "default" do
|
54
|
+
# Double-buffers incoming jobs to avoid submission latency:
|
55
|
+
enqueue Async::Job::Processor::Aggregate
|
56
|
+
dequeue Async::Job::Processor::Redis
|
57
|
+
end
|
58
|
+
|
59
|
+
config.async_job.define_queue "local" do
|
60
|
+
dequeue Async::Job::Processor::Inline
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
**Key Points:**
|
66
|
+
- `default` queue uses Redis for persistent job storage.
|
67
|
+
- `local` queue processes jobs inline, but in a background Async task - higher throughput but lower robustness.
|
68
|
+
- Different processors can be used for different queue behaviors.
|
69
|
+
|
70
|
+
Configure the default adapter in `config/application.rb`:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
# ... in the application configuration:
|
74
|
+
config.active_job.queue_adapter = :async_job
|
75
|
+
```
|
76
|
+
|
77
|
+
**Key Points:**
|
78
|
+
- This sets `Async::Job` as the default queue adapter. You can instead specify this per-job for incremental migration.
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# Real-Time Views
|
2
|
+
|
3
|
+
This guide explains how to implement real-time interfaces with `Live::View`.
|
4
|
+
|
5
|
+
## What is `Live::View`?
|
6
|
+
|
7
|
+
`Live::View` enables real-time, interactive web interfaces using WebSocket connections. It allows you to update the DOM in real-time without JavaScript, making it perfect for dynamic content that changes frequently.
|
8
|
+
|
9
|
+
**When to use `Live::View`:**
|
10
|
+
- Real-time dashboards and status displays.
|
11
|
+
- Interactive forms with live validation.
|
12
|
+
- Live updating content (clocks, counters, progress bars).
|
13
|
+
- Simple games and interactive applications.
|
14
|
+
|
15
|
+
**When NOT to use `Live::View`:**
|
16
|
+
- Static content that doesn't change.
|
17
|
+
- Complex client-side interactions requiring heavy JavaScript.
|
18
|
+
|
19
|
+
## Setup
|
20
|
+
|
21
|
+
### Import Maps Configuration
|
22
|
+
|
23
|
+
Use `bin/importmap` to install the required JavaScript packages:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
> bin/importmap pin @socketry/live
|
27
|
+
Pinning "@socketry/live" to vendor/javascript/@socketry/live.js via download from https://ga.jspm.io/npm:@socketry/live@0.14.0/Live.js
|
28
|
+
Pinning "morphdom" to vendor/javascript/morphdom.js via download from https://ga.jspm.io/npm:morphdom@2.7.7/dist/morphdom-esm.js
|
29
|
+
```
|
30
|
+
|
31
|
+
### JavaScript Setup
|
32
|
+
|
33
|
+
Create `app/javascript/live.js` to initialize `Live::View`:
|
34
|
+
|
35
|
+
```javascript
|
36
|
+
import {Live} from "@socketry/live"
|
37
|
+
window.live = Live.start()
|
38
|
+
```
|
39
|
+
|
40
|
+
Pin your local `live.js` file in `config/importmap.rb`:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
pin "live"
|
44
|
+
```
|
45
|
+
|
46
|
+
**Key Points:**
|
47
|
+
- Import maps handle the `@socketry/live` package automatically.
|
48
|
+
- The `live.js` file starts the `Live::View` client connection.
|
49
|
+
- `pin "live"` makes your local `live.js` file importable in view templates.
|
50
|
+
- `window.live` makes the Live connection globally available.
|
51
|
+
|
52
|
+
## Basic Implementation: Live Clock
|
53
|
+
|
54
|
+
### Live::View Class
|
55
|
+
|
56
|
+
Create a `Live::View` class that handles the real-time logic:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
require 'live'
|
60
|
+
|
61
|
+
class ClockTag < Live::View
|
62
|
+
def initialize(...)
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
def bind(page)
|
67
|
+
@task ||= start_clock
|
68
|
+
end
|
69
|
+
|
70
|
+
def close
|
71
|
+
if task = @task
|
72
|
+
@task = nil
|
73
|
+
task.stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def start_clock
|
78
|
+
Async do
|
79
|
+
while true
|
80
|
+
sleep 1
|
81
|
+
self.update!
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def forward_event(name)
|
87
|
+
"event.preventDefault(); live.forwardEvent(#{JSON.dump(@id)}, event, {name: #{name.inspect}})"
|
88
|
+
end
|
89
|
+
|
90
|
+
def render(builder)
|
91
|
+
builder.tag(:div, class: "clock-container") do
|
92
|
+
builder.tag(:h2) {builder.text("Live Clock")}
|
93
|
+
builder.tag(:div, id: "clock", class: "clock-display") do
|
94
|
+
builder.text(Time.now.strftime("%H:%M:%S"))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
**Key Points:**
|
102
|
+
- Inherit from `Live::View` to get real-time capabilities.
|
103
|
+
- Use `Async` blocks for background tasks.
|
104
|
+
- Use `self.update!` to queue a full re-render.
|
105
|
+
- Define `forward_event` method to handle user interactions.
|
106
|
+
- The `render` method defines the initial HTML structure.
|
107
|
+
|
108
|
+
### Controller
|
109
|
+
|
110
|
+
Create a controller to handle the Live::View connection:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
require 'async/websocket/adapters/rails'
|
114
|
+
|
115
|
+
class ClockController < ApplicationController
|
116
|
+
RESOLVER = Live::Resolver.allow(ClockTag)
|
117
|
+
|
118
|
+
def index
|
119
|
+
@tag = ClockTag.new('clock')
|
120
|
+
end
|
121
|
+
|
122
|
+
skip_before_action :verify_authenticity_token, only: :live
|
123
|
+
|
124
|
+
def live
|
125
|
+
self.response = Async::WebSocket::Adapters::Rails.open(request) do |connection|
|
126
|
+
Live::Page.new(RESOLVER).run(connection)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
**Key Points:**
|
133
|
+
- Use `Live::Resolver.allow` to whitelist your `Live::View` sub-classes.
|
134
|
+
- Skip CSRF verification for the WebSocket endpoint.
|
135
|
+
- Use `Async::WebSocket::Adapters::Rails.open` for WebSocket handling.
|
136
|
+
- `Live::Page.new(RESOLVER).run(connection)` handles the `Live::View` protocol.
|
137
|
+
|
138
|
+
### View Template
|
139
|
+
|
140
|
+
Create a view template that renders the Live::View:
|
141
|
+
|
142
|
+
```html
|
143
|
+
<h1>⏰ Live Clock Example</h1>
|
144
|
+
<p>This clock updates every second using Live::View.</p>
|
145
|
+
|
146
|
+
<%= javascript_import_module_tag "live" %>
|
147
|
+
|
148
|
+
<div class="clock-wrapper">
|
149
|
+
<%= raw @tag.to_html %>
|
150
|
+
</div>
|
151
|
+
|
152
|
+
<style>
|
153
|
+
.clock-container {
|
154
|
+
text-align: center;
|
155
|
+
padding: 2rem;
|
156
|
+
border: 2px solid #ddd;
|
157
|
+
border-radius: 8px;
|
158
|
+
margin: 2rem 0;
|
159
|
+
}
|
160
|
+
|
161
|
+
.clock-display {
|
162
|
+
font-size: 3rem;
|
163
|
+
font-family: 'Monaco', 'Consolas', monospace;
|
164
|
+
color: #333;
|
165
|
+
background: #f0f0f0;
|
166
|
+
padding: 1rem;
|
167
|
+
border-radius: 4px;
|
168
|
+
margin-top: 1rem;
|
169
|
+
}
|
170
|
+
</style>
|
171
|
+
```
|
172
|
+
|
173
|
+
**Key Points:**
|
174
|
+
- Use `<%= javascript_import_module_tag "live" %>` to load Live::View on specific pages.
|
175
|
+
- Use `raw @tag.to_html` to render the Live::View component.
|
176
|
+
- The Live::View will automatically connect via WebSocket.
|
177
|
+
- This loads Live::View only on pages that need it, not globally.
|
178
|
+
|
179
|
+
### Routing Configuration
|
180
|
+
|
181
|
+
Add routes to your `config/routes.rb`:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
Rails.application.routes.draw do
|
185
|
+
# Live Clock Example:
|
186
|
+
get "clock/index"
|
187
|
+
connect "clock/live"
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
**Key Points:**
|
192
|
+
- Use the Rails 8 `connect` helper for WebSocket routes.
|
193
|
+
- The `live` action handles the WebSocket connection.
|
194
|
+
|
195
|
+
## Interactive Example: Counter with Buttons
|
196
|
+
|
197
|
+
### Live::View Class with User Interaction
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class CounterTag < Live::View
|
201
|
+
def initialize(count: 0)
|
202
|
+
super
|
203
|
+
|
204
|
+
# @data is persisted on the tag in `data-` attributes.
|
205
|
+
@data["count"] = @data.fetch("count", count).to_i
|
206
|
+
end
|
207
|
+
|
208
|
+
def handle(event)
|
209
|
+
case event[:type]
|
210
|
+
when "click"
|
211
|
+
case event.dig(:detail, :name)
|
212
|
+
when "increment"
|
213
|
+
@data["count"] += 1
|
214
|
+
when "decrement"
|
215
|
+
@data["count"] -= 1
|
216
|
+
end
|
217
|
+
|
218
|
+
update_counter
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def update_counter
|
223
|
+
self.replace("#counter") do |builder|
|
224
|
+
builder.tag(:div, id: "counter", class: "counter-display") do
|
225
|
+
builder.text(@data["count"].to_s)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def forward_event(name)
|
231
|
+
"event.preventDefault(); live.forwardEvent(#{JSON.dump(@id)}, event, {name: #{name.inspect}})"
|
232
|
+
end
|
233
|
+
|
234
|
+
def render(builder)
|
235
|
+
builder.tag(:div, class: "counter-container") do
|
236
|
+
builder.tag(:h2) { builder.text("Live Counter") }
|
237
|
+
|
238
|
+
builder.tag(:div, id: "counter", class: "counter-display") do
|
239
|
+
builder.text(@count.to_s)
|
240
|
+
end
|
241
|
+
|
242
|
+
builder.tag(:div, class: "counter-buttons") do
|
243
|
+
builder.tag(:button, onclick: forward_event("decrement")) do
|
244
|
+
builder.text("-")
|
245
|
+
end
|
246
|
+
builder.tag(:button, onclick: forward_event("increment")) do
|
247
|
+
builder.text("+")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
255
|
+
**Key Points:**
|
256
|
+
- Define `forward_event` (or `forward_click`, etc) methods to generate JavaScript for event handling.
|
257
|
+
- `live.forwardEvent(...)` on the client side invokes `handle(event)` on the server side.
|
258
|
+
- Update the DOM in response to user actions.
|
259
|
+
- Maintain persistent state in `@data`.
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Server-Sent Events
|
2
|
+
|
3
|
+
This guide explains how to implement Server-Sent Events with Falcon and Rails.
|
4
|
+
|
5
|
+
## What are Server-Sent Events?
|
6
|
+
|
7
|
+
Server-Sent Events (SSE) provide a way to push real-time updates from the server to the client over a single HTTP connection. Unlike WebSockets, SSE is unidirectional (server-to-client only) and uses the standard HTTP protocol.
|
8
|
+
|
9
|
+
**When to use SSE:**
|
10
|
+
- Live dashboards and monitoring systems.
|
11
|
+
- Real-time notifications.
|
12
|
+
- Progress indicators for long-running tasks.
|
13
|
+
- Live feeds (news, social media updates).
|
14
|
+
- Database change notifications.
|
15
|
+
|
16
|
+
**When NOT to use SSE:**
|
17
|
+
- When you need bidirectional communication (use WebSockets instead).
|
18
|
+
- When you need binary data transmission.
|
19
|
+
|
20
|
+
## Basic Implementation
|
21
|
+
|
22
|
+
### Server-Side: Rails Controller
|
23
|
+
|
24
|
+
Create a controller action that streams events using `Rack::Response`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class SseController < ApplicationController
|
28
|
+
def index
|
29
|
+
# Render the page with EventSource JavaScript
|
30
|
+
end
|
31
|
+
|
32
|
+
EVENT_STREAM_HEADERS = {
|
33
|
+
'content-type' => 'text/event-stream',
|
34
|
+
'cache-control' => 'no-cache',
|
35
|
+
'connection' => 'keep-alive'
|
36
|
+
}
|
37
|
+
|
38
|
+
def events
|
39
|
+
body = proc do |stream|
|
40
|
+
while true
|
41
|
+
# Send timestamped data:
|
42
|
+
stream.write("data: #{Time.now}\n\n")
|
43
|
+
sleep 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
self.response = Rack::Response[200, EVENT_STREAM_HEADERS.dup, body]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
**Key Points:**
|
53
|
+
- `content-type: text/event-stream` is required for SSE.
|
54
|
+
- Each message must end with `\n\n` (two newlines).
|
55
|
+
- The `data: ` prefix is required for the message content.
|
56
|
+
- Use a callable body for streaming responses.
|
57
|
+
|
58
|
+
### Client-Side: JavaScript EventSource
|
59
|
+
|
60
|
+
Create an HTML page that consumes the SSE stream:
|
61
|
+
|
62
|
+
```html
|
63
|
+
<div id="status" class="status warning">🔄 Connecting to event stream...</div>
|
64
|
+
<div id="history" class="terminal"></div>
|
65
|
+
|
66
|
+
<script>
|
67
|
+
var eventSource = new EventSource("events");
|
68
|
+
var messageCount = 0;
|
69
|
+
|
70
|
+
eventSource.addEventListener("open", function(event) {
|
71
|
+
document.getElementById("status").innerHTML = "✅ Connected to event stream";
|
72
|
+
document.getElementById("status").className = "status success";
|
73
|
+
});
|
74
|
+
|
75
|
+
eventSource.addEventListener("message", function(event) {
|
76
|
+
messageCount++;
|
77
|
+
var container = document.createElement("div");
|
78
|
+
container.className = "terminal-line";
|
79
|
+
container.innerHTML = `<span class="event-count">#${messageCount}</span> <span class="event-data">${event.data}</span>`;
|
80
|
+
|
81
|
+
var history = document.querySelector("#history");
|
82
|
+
history.appendChild(container);
|
83
|
+
history.scrollTop = history.scrollHeight;
|
84
|
+
|
85
|
+
// Keep only last 20 messages to prevent memory issues
|
86
|
+
if (history.children.length > 20) {
|
87
|
+
history.removeChild(history.firstChild);
|
88
|
+
}
|
89
|
+
});
|
90
|
+
|
91
|
+
eventSource.addEventListener("error", function(event) {
|
92
|
+
document.getElementById("status").innerHTML = "❌ Connection error - attempting to reconnect...";
|
93
|
+
document.getElementById("status").className = "status error";
|
94
|
+
});
|
95
|
+
</script>
|
96
|
+
```
|
97
|
+
|
98
|
+
**Key Points:**
|
99
|
+
- `EventSource` automatically handles connection and reconnection.
|
100
|
+
- Listen for `open`, `message`, and `error` events.
|
101
|
+
- `event.data` contains the message content.
|
102
|
+
- Implement UI feedback for connection status.
|
103
|
+
|
104
|
+
### Routing Configuration
|
105
|
+
|
106
|
+
Add routes to your `config/routes.rb`:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
Rails.application.routes.draw do
|
110
|
+
# SSE Example:
|
111
|
+
get 'sse/index' # Page with EventSource JavaScript
|
112
|
+
get 'sse/events' # SSE endpoint
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
## Advanced Patterns
|
117
|
+
|
118
|
+
### Sending Custom Event Types
|
119
|
+
|
120
|
+
By default, `event: message` is assumed for each record sent on the stream. However, it's possible to specify different kinds of events:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
def events
|
124
|
+
body = proc do |stream|
|
125
|
+
# Send different event types
|
126
|
+
stream.write("event: user_joined\n")
|
127
|
+
stream.write("data: #{user.to_json}\n\n")
|
128
|
+
|
129
|
+
stream.write("event: message\n")
|
130
|
+
stream.write("data: #{message.to_json}\n\n")
|
131
|
+
end
|
132
|
+
|
133
|
+
self.response = Rack::Response[200, EVENT_STREAM_HEADERS.dup, body]
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
On the client, you need to register multiple event listeners:
|
138
|
+
|
139
|
+
```javascript
|
140
|
+
eventSource.addEventListener("user_joined", function(event) {
|
141
|
+
var user = JSON.parse(event.data);
|
142
|
+
// Handle user joined event
|
143
|
+
});
|
144
|
+
|
145
|
+
eventSource.addEventListener("message", function(event) {
|
146
|
+
var message = JSON.parse(event.data);
|
147
|
+
// Handle message event
|
148
|
+
});
|
149
|
+
```
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# WebSockets
|
2
|
+
|
3
|
+
This guide explains how to implement WebSocket connections with Falcon and Rails.
|
4
|
+
|
5
|
+
## What are WebSockets?
|
6
|
+
|
7
|
+
WebSockets provide full-duplex communication between client and server over a single persistent connection. Unlike HTTP streaming or SSE, WebSockets allow both client and server to send messages at any time.
|
8
|
+
|
9
|
+
**When to use WebSockets:**
|
10
|
+
- Real-time chat applications.
|
11
|
+
- Interactive games and collaborative tools.
|
12
|
+
- Live dashboards with user interaction.
|
13
|
+
- Real-time notifications with user actions.
|
14
|
+
|
15
|
+
**When NOT to use WebSockets:**
|
16
|
+
- Simple server-to-client updates (use SSE instead).
|
17
|
+
- Request/response patterns (use regular HTTP).
|
18
|
+
|
19
|
+
## Basic Implementation
|
20
|
+
|
21
|
+
### Server-Side: Rails Controller
|
22
|
+
|
23
|
+
Create a controller that handles WebSocket connections:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'async/websocket/adapters/rails'
|
27
|
+
|
28
|
+
class ChatController < ApplicationController
|
29
|
+
def index
|
30
|
+
# Render the page with WebSocket JavaScript
|
31
|
+
end
|
32
|
+
|
33
|
+
skip_before_action :verify_authenticity_token, only: :connect
|
34
|
+
|
35
|
+
def connect
|
36
|
+
self.response = Async::WebSocket::Adapters::Rails.open(request) do |connection|
|
37
|
+
Sync do
|
38
|
+
# Perpetually read incoming messages from client:
|
39
|
+
while message = connection.read
|
40
|
+
# Echo message back to client:
|
41
|
+
response_data = { text: "Echo: #{JSON.parse(message.buffer)['text']}" }
|
42
|
+
connection.send_text(response_data.to_json)
|
43
|
+
connection.flush
|
44
|
+
end
|
45
|
+
rescue Protocol::WebSocket::ClosedError
|
46
|
+
# Connection closed by client.
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
**Key Points:**
|
54
|
+
- Use `Async::WebSocket::Adapters::Rails.open` for WebSocket handling.
|
55
|
+
- Skip CSRF token verification for WebSocket endpoints.
|
56
|
+
- Use `connection.read` to receive messages from clients.
|
57
|
+
- Use `connection.send_text` and `connection.flush` to send messages.
|
58
|
+
|
59
|
+
### Client-Side: WebSocket JavaScript
|
60
|
+
|
61
|
+
Create JavaScript that connects to the WebSocket endpoint:
|
62
|
+
|
63
|
+
```html
|
64
|
+
<section id="response"></section>
|
65
|
+
<section class="input">
|
66
|
+
<input id="chat" disabled="true" placeholder="Type your message and press Enter..." />
|
67
|
+
</section>
|
68
|
+
|
69
|
+
<script>
|
70
|
+
function connectToChatServer(url) {
|
71
|
+
console.log("WebSocket Connecting...", url);
|
72
|
+
var server = new WebSocket(url.href);
|
73
|
+
|
74
|
+
server.onopen = function(event) {
|
75
|
+
console.log("WebSocket Connected:", server);
|
76
|
+
chat.disabled = false;
|
77
|
+
|
78
|
+
chat.onkeypress = function(event) {
|
79
|
+
if (event.keyCode == 13) {
|
80
|
+
server.send(JSON.stringify({text: chat.value}));
|
81
|
+
chat.value = "";
|
82
|
+
}
|
83
|
+
}
|
84
|
+
};
|
85
|
+
|
86
|
+
server.onmessage = function(event) {
|
87
|
+
console.log("WebSocket Message:", event);
|
88
|
+
|
89
|
+
var message = JSON.parse(event.data);
|
90
|
+
|
91
|
+
var pre = document.createElement('pre');
|
92
|
+
pre.innerText = message.text;
|
93
|
+
|
94
|
+
response.appendChild(pre);
|
95
|
+
};
|
96
|
+
|
97
|
+
server.onerror = function(event) {
|
98
|
+
console.log("WebSocket Error:", event);
|
99
|
+
chat.disabled = true;
|
100
|
+
server.close();
|
101
|
+
};
|
102
|
+
|
103
|
+
server.onclose = function(event) {
|
104
|
+
console.log("WebSocket Close:", event);
|
105
|
+
|
106
|
+
setTimeout(function() {
|
107
|
+
connectToChatServer(url);
|
108
|
+
}, 1000);
|
109
|
+
};
|
110
|
+
}
|
111
|
+
|
112
|
+
var url = new URL('/chat/connect', window.location.href);
|
113
|
+
url.protocol = url.protocol.replace('http', 'ws');
|
114
|
+
connectToChatServer(url);
|
115
|
+
</script>
|
116
|
+
```
|
117
|
+
|
118
|
+
**Key Points:**
|
119
|
+
- Convert HTTP URL to WebSocket URL by replacing protocol.
|
120
|
+
- Handle `onopen`, `onmessage`, `onerror`, and `onclose` events.
|
121
|
+
- Use `JSON.stringify` and `JSON.parse` for structured messages.
|
122
|
+
- Implement automatic reconnection on close.
|
123
|
+
|
124
|
+
### Routing Configuration
|
125
|
+
|
126
|
+
Add routes to your `config/routes.rb`:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
Rails.application.routes.draw do
|
130
|
+
get "chat/index"
|
131
|
+
connect "chat/connect"
|
132
|
+
end
|
133
|
+
```
|
data/lib/falcon/rails.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "rails/version"
|
7
|
+
|
8
|
+
require "falcon"
|
9
|
+
|
10
|
+
# Load all the dependencies:
|
11
|
+
require "async/cable"
|
12
|
+
require "async/job/adapter/active_job"
|
13
|
+
require "async/websocket"
|
14
|
+
require "console/adapter/rails"
|
15
|
+
require "live"
|
data/license.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# MIT License
|
2
|
+
|
3
|
+
Copyright, 2025, by Samuel Williams.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/readme.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Async::Rails
|
2
|
+
|
3
|
+
Provides integration support for Async and Rails, allowing you to use Async's concurrency model within a Rails application.
|
4
|
+
|
5
|
+
[](https://github.com/socketry/falcon-rails/actions?workflow=Test)
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Please see the [project documentation](https://socketry.github.io/falcon-rails/) for more details.
|
10
|
+
|
11
|
+
- [Getting Started](https://socketry.github.io/falcon-rails/guides/getting-started/index) - This guide explains how to get started using Falcon to host your Rails application.
|
12
|
+
|
13
|
+
- [Job Processing](https://socketry.github.io/falcon-rails/guides/job-processing/index) - This guide explains how to implement background job processing with Falcon and Rails using the `async-job` gem.
|
14
|
+
|
15
|
+
- [HTTP Streaming](https://socketry.github.io/falcon-rails/guides/http-streaming/index) - This guide explains how to implement HTTP response streaming with Falcon and Rails.
|
16
|
+
|
17
|
+
- [Server-Sent Events](https://socketry.github.io/falcon-rails/guides/server-sent-events/index) - This guide explains how to implement Server-Sent Events with Falcon and Rails.
|
18
|
+
|
19
|
+
- [WebSockets](https://socketry.github.io/falcon-rails/guides/websockets/index) - This guide explains how to implement WebSocket connections with Falcon and Rails.
|
20
|
+
|
21
|
+
- [Real-Time Views](https://socketry.github.io/falcon-rails/guides/real-time-views/index) - This guide explains how to implement real-time interfaces with `Live::View`.
|
22
|
+
|
23
|
+
## Releases
|
24
|
+
|
25
|
+
Please see the [project releases](https://socketry.github.io/falcon-rails/releases/index) for all releases.
|
26
|
+
|
27
|
+
### v0.1.0
|
28
|
+
|
29
|
+
## See Also
|
30
|
+
|
31
|
+
- [Async](https://github.com/socketry/async) - The core library for asynchronous programming in Ruby.
|
32
|
+
- [Async::Cable](https://github.com/socketry/async-cable) - Async-compatible ActionCable implementation.
|
33
|
+
- [Async::Job](https://github.com/socketry/async-job) - Background job processing with ActiveJob adapter.
|
34
|
+
- [Console::Adapter::Rails](https://github.com/socketry/console-adapter-rails) - Rails logging adapter for the console gem.
|
35
|
+
- [Falcon](https://github.com/socketry/falcon) - High-performance web server for Ruby.
|
36
|
+
- [Live](https://github.com/socketry/live) - Real-time server components for interactive web applications.
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
We welcome contributions to this project.
|
41
|
+
|
42
|
+
1. Fork it.
|
43
|
+
2. Create your feature branch (`git checkout -b my-new-feature`).
|
44
|
+
3. Commit your changes (`git commit -am 'Add some feature'`).
|
45
|
+
4. Push to the branch (`git push origin my-new-feature`).
|
46
|
+
5. Create new Pull Request.
|
47
|
+
|
48
|
+
### Developer Certificate of Origin
|
49
|
+
|
50
|
+
In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
|
51
|
+
|
52
|
+
### Community Guidelines
|
53
|
+
|
54
|
+
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
data/releases.md
ADDED
data.tar.gz.sig
ADDED
Binary file
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: falcon-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Samuel Williams
|
8
|
+
bindir: bin
|
9
|
+
cert_chain:
|
10
|
+
- |
|
11
|
+
-----BEGIN CERTIFICATE-----
|
12
|
+
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
|
13
|
+
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
|
14
|
+
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
|
15
|
+
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
|
16
|
+
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
|
17
|
+
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
|
18
|
+
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
|
19
|
+
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
|
20
|
+
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
|
21
|
+
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
|
22
|
+
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
|
23
|
+
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
|
24
|
+
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
|
25
|
+
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
|
26
|
+
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
|
27
|
+
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
|
28
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
|
29
|
+
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
|
30
|
+
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
|
31
|
+
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
|
32
|
+
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
|
33
|
+
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
|
34
|
+
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
|
35
|
+
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
|
36
|
+
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
37
|
+
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
38
|
+
-----END CERTIFICATE-----
|
39
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
40
|
+
dependencies:
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: async-cable
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: async-job-adapter-active_job
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: async-websocket
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: console-adapter-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: falcon
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: live
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rails
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '8.0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '8.0'
|
139
|
+
executables: []
|
140
|
+
extensions: []
|
141
|
+
extra_rdoc_files: []
|
142
|
+
files:
|
143
|
+
- context/getting-started.md
|
144
|
+
- context/http-streaming.md
|
145
|
+
- context/job-processing.md
|
146
|
+
- context/real-time-views.md
|
147
|
+
- context/server-sent-events.md
|
148
|
+
- context/websockets.md
|
149
|
+
- lib/falcon/rails.rb
|
150
|
+
- lib/falcon/rails/version.rb
|
151
|
+
- license.md
|
152
|
+
- readme.md
|
153
|
+
- releases.md
|
154
|
+
homepage: https://github.com/socketry/falcon-rails
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata:
|
158
|
+
documentation_uri: https://socketry.github.io/falcon-rails/
|
159
|
+
source_code_uri: https://github.com/socketry/falcon-rails.git
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '3.2'
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
requirements: []
|
174
|
+
rubygems_version: 3.6.7
|
175
|
+
specification_version: 4
|
176
|
+
summary: Easy Falcon and Rails integration.
|
177
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|