Skip to content

Instantly share code, notes, and snippets.

@julianrubisch
Created May 17, 2021 07:07
Show Gist options
  • Save julianrubisch/03932e2c20d099b7b4b09120ad4e1fb8 to your computer and use it in GitHub Desktop.
Save julianrubisch/03932e2c20d099b7b4b09120ad4e1fb8 to your computer and use it in GitHub Desktop.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
identified_by :session_id
def connect
self.current_user = env["warden"].user
self.session_id = request.session.id
reject_unauthorized_connection unless self.current_user || self.session_id
end
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
include CableReady::StreamIdentifier
helper_method :signed_stream_identifier
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
helper_method :verifier
def verifier
@verifier ||= ActiveSupport::MessageVerifier.new(ENV.fetch("REFLEX_VERIFIER_SECRET") { "secret" }, digest: "SHA256", serializer: JSON)
end
end
<!-- app/views/boards/index.html.erb -->
<%= text_field_tag "Search",
filter_for("Board").query,
placeholder: "Search for a board",
data: {reflex: "debounced:input->Filter#filter",
resource: verifier.generate("Board"),
param: verifier.generate("query")}
%>
<!-- app/views/boards/index.html.erb -->
<%= text_field_tag "Search",
filter_for("Board").query,
placeholder: "Search for a board",
data: {reflex: "debounced:input->Filter#filter",
resource: "Board",
param: "secret",
value: true}
%>
<!-- app/views/boards/index.html.erb -->
<%= text_field_tag "Search",
filter_for("Board").query,
placeholder: "Search for a board",
data: {reflex: "debounced:input->Filter#filter",
resource: "Board",
param: "query"}
%>
<ul data-controller="board"
data-board-identifier-value="<%= signed_stream_identifier("board:#{user.id}") %>"
id="<%= dom_id(board) %>">
<!-- ... -->
</ul>
import { Controller } from "stimulus";
import CableReady from "cable_ready";
export default class extends Controller {
static values = { identifier: String };
connect() {
this.channel = this.application.consumer.subscriptions.create(
{
channel: "BoardsChannel",
identifier: this.identifierValue
},
{
received(data) {
if (data.cableReady) CableReady.perform(data.operations);
}
}
);
}
disconnect() {
this.channel.unsubscribe();
}
}
. # app/reflexes/filter_refex.rb
class FilterReflex < ApplicationReflex
include Filterable
def filter
resource, param = element.dataset.to_h.fetch_values(:resource, :param)
value = element.dataset.value || element.value
set_filter_for!(controller.verifier.verify(resource), controller.verifier.verify(param), value)
end
end
class BoardReflex < ApplicationReflex
def dangerous_operation
board = element.signed[:secure]
# => #<Board:0x00007f87472fd7c8 id: 35, ... >
end
end
<input type="text" name="Search" id="Search" value="" placeholder="Search for a board"
data-reflex="debounced:input->Filter#filter"
data-resource="IkJvYXJkIg==--4341e17945b9982ce2944bb813dd830560f9b0e18f65a3c6dc8252b928ee61bd"
data-param="InF1ZXJ5Ig==--d5a28119a23783ad763525e2d0bd1f51c9442979826f2cc23a9812bdacd5c666"
data-controller="stimulus-reflex"
data-action="debounced:input->stimulus-reflex#__perform">
# sign and output
board_sgid = Board.find(1).to_sgid.to_s
# => "BAh7CEkiCGdpZAY6BkVUSSIdZ2lkOi8vbW9zb3VuZGljL0JvYXJkLzM1BjsAVEkiDHB1cnBvc2UGOwBUSSIMZGVmYXVsdAY7AFRJIg9leHBpcmVzX2F0BjsAVEkiHTIwMjEtMDYtMTJUMDc6MzM6NTMuNTg5WgY7AFQ=--c5f0297aab3398ce86141569aa82a3d058661b54"
# make it expire
board_sgid = Board.find(1).to_sgid(expires_in: 2.hours).to_s
# => "BAh7CEkiCGdpZAY6BkVUSSItZ2lkOi8vbW9zb3VuZGljL0JvYXJkLzM1P2V4cGlyZXNfaW49NzIwMAY7AFRJIgxwdXJwb3NlBjsAVEkiDGRlZmF1bHQGOwBUSSIPZXhwaXJlc19hdAY7AFRJIh0yMDIxLTA1LTEyVDA5OjM0OjI4Ljc0M1oGOwBU--4bba497b69246d674d6c398b6b4b78fcd16bd522"
# locate
GlobalID::Locator.locate_signed board_sgid
# => #<Board:0x00007f87472fd7c8 id: 35, ... >
class StreamBoardJob < ApplicationJob
include CableReady::Broadcaster
include CableReady::StreamIdentifier
queue_as :default
def perform(board:, user:)
cable_ready[signed_stream_identifier("board:#{user.id}")].morph(
selector: dom_id(board),
html: ApplicationController.render(...)
).broadcast
end
end
<!-- app/embeds/_form.html.erb -->
<%= form_for([embed.board, embed], remote: true, data: {reflex: "submit->Embed#submit", reflex_root: "##{dom_id(embed.board)}"}) do |form| %>
<!-- ... -->
<% end %>
<!-- app/comments/_form.html.erb -->
<%= form_for([embed.board, embed, comment], data: {controller: "reflex-form", reflex_form_reflex_value: "Comment#submit"}) do |form| %>
<!-- ... -->
<% end %>
# app/reflexes/concerns/submittable.rb
module Submittable
extend ActiveSupport::Concern
include StimulusReflex::ConcernEnhancer
included do
prepend_before_reflex do
instance_variable_set("@#{resource_name.underscore}", resource_class.new(submit_params))
end
end
private
RESOURCE_ALLOWLIST = {
"boards#show" => "Embed",
"embeds#show" => "Comment"
}.freeze
def resource_name
RESOURCE_ALLOWLIST["#{request.parameters["controller"]}##{request.parameters["action"]}"]
end
def resource_class
resource_name.safe_constantize
end
end
# app/reflexes/concerns/submittable.rb
module Submittable
extend ActiveSupport::Concern
include StimulusReflex::ConcernEnhancer
included do
prepend_before_reflex do
@resource_name = element.dataset.resource
@resource_class = @resource_name.safe_constantize
instance_variable_set("@#{@resource_name.underscore}", @resource_class.new(submit_params))
end
end
end
<!-- app/embeds/_form.html.erb -->
<%= form_for([embed.board, embed], remote: true, data: {reflex: "submit->Embed#submit", resource: "Embed", reflex_root: "##{dom_id(embed.board)}"}) do |form| %>
<!-- ... -->
<% end %>
<!-- app/comments/_form.html.erb -->
<%= form_for([embed.board, embed, comment], data: {controller: "reflex-form", reflex_form_reflex_value: "Comment#submit", resource: "Comment"}) do |form| %>
<!-- ... -->
<% end %>
<input type="text" name="Search" id="Search" value="" placeholder="Search for a board"
data-reflex="debounced:input->Filter#filter"
data-resource="Board"
data-param="query"
data-controller="stimulus-reflex"
data-action="debounced:input->stimulus-reflex#__perform">
class BoardsChannel < ApplicationCable::Channel
include CableReady::StreamIdentifier
def subscribed
locator = verified_stream_identifier(params[:identifier])
locator.present? ? stream_from(locator) : reject
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment