Skip to content

Instantly share code, notes, and snippets.

@julianrubisch
Last active May 6, 2022 09:50
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save julianrubisch/9e7cda7036509e22e9ba775118c4dc7f to your computer and use it in GitHub Desktop.
Save julianrubisch/9e7cda7036509e22e9ba775118c4dc7f to your computer and use it in GitHub Desktop.
StimulusReflex Patterns - 1 Forms
# app/models/board.rb
class Board < ApplicationRecord
has_many :embeds
end
<%= form_for([embed.board, embed, comment], data: {controller: "reflex-form", reflex_form_reflex_value: "CommentReflex#submit", resource: "Comment"}) do |form| %>
<!-- ... -->
<% end %>
<%= form_for([embed.board, embed], remote: true, data: {reflex: "submit->EmbedReflex#submit"}) do |form| %>
<!-- ... -->
<% end %>
# app/reflexes/embed_reflex.rb
class EmbedReflex < ApplicationReflex
def submit
@embed = Embed.new(submit_params)
if @embed.save
@embed = @embed.board.embeds.build
end
StreamBoardJob.perform_now(board: @embed.board)
morph :nothing
end
private
def submit_params
params.require(:embed).permit(:board_id, :title, :input)
end
end
<!-- app/views/boards/show.html.erb -->
<div id="embed-form">
<%= render "embeds/form", embed: @embed, board: @board %>
</div>
<%= render @board %>
<!-- app/views/boards/_board.html.erb -->
<ul class="grid grid-cols-1 gap-4 sm:grid-cols-2"
data-controller="board"
data-board-id-value="<%= board.id %>"
id="<%= dom_id(board) %>">
<%= render partial: "embeds/embed", collection: board.embeds, locals: {board: board}, cached: true %>
</ul>
<!-- app/views/embeds/_form.html.erb -->
<%= form_for([embed.board, embed], remote: true) do |form| %>
<!-- errors omitted -->
<%= form.label :input, class: "sr-only" %>
<%= form.text_field :input, class: "lots of tailwind classes", placeholder: "Enter an embeddable link" %>
<div class="hidden">
<%= form.submit %>
</div>
<% end %>
<!-- app/views/embeds/_embed.html.erb partial omitted, is contains basically -->
<%= embed.embed_code %>
# app/models/embed.rb
class Embed < ApplicationRecord
belongs_to :board, touch: true
delegated_type :embeddable, types: %w(OEmbedLink)
delegate :embed_code, to: :embeddable
end
# app/models/concerns/embeddable.rb
module Embeddable
extend ActiveSupport::Concern
included do
has_one :embed, as: :embeddable, touch: true
end
end
# app/models/o_embed_link.rb
class OEmbedLink < ApplicationRecord
include Embeddable
# ... code to fetch oembed code
end
class EmbedsController < ApplicationController
include CableReady::Broadcaster
# ...
def create
@board = Board.friendly.find(params[:board_id])
@embed = @board.embeds.build(embed_params)
if @embed.save
cable_ready[BoardsChannel].morph(
selector: dom_id(@board),
html: ApplicationController.render(@board)
).inner_html(
selector: "#embed-form",
html: ApplicationController.render(partial: "embeds/form", locals: {board: @board, embed: @board.embeds.build}).broadcast_to(@board)
else
render "boards/show"
end
end
end
# app/models/history_entry.rb
class HistoryEntry < ApplicationRecord
belongs_to :embed, touch: true
delegated_type :entryable, types: %w(Comment)
delegate :content, to: :entryable
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :user
has_rich_text :content
end
$ bin/rails g stimulus_reflex Comment submit
$ bin/rails g stimulus_reflex Embed submit
$ bin/rails g job StreamBoard
# app/reflexes/concerns/submittable.rb
module Submittable
def self.with_before_submit(&block)
mod = Module.new
mod.class_eval do
extend ActiveSupport::Concern
included do
before_reflex do
# ... populate instance variable like above
yield instance_variable_get("@#{@resource_name.underscore}"), params, connection if block_given?
end
end
def submit
# ...
end
end
mod
end
end
# app/reflexes/comment_reflex.rb
class CommentReflex < ApplicationReflex
include(Submittable.with_before_submit do |comment, params, connection|
comment.user = connection.current_user
comment.build_history_entry(embed_id: params[:id])
end)
# ...
end
# app/reflexes/embed_reflex.rb
class EmbedReflex < ApplicationReflex
include(Submittable.with_before_submit do |embed, params|
embed.board = Board.friendly.find(params[:id])
end)
# ...
end
# app/reflexes/comment_reflex.rb
class CommentReflex < ApplicationReflex
include Submittable
before_reflex do
@resource.user = current_user
@resource.build_history_entry(embed_id: params[:id])
end
after_reflex do
@comment = Comment.new(user: current_user)
@comment.build_history_entry(embed: @resource.embed)
StreamBoardJob.perform_now(board: @resource.embed.board)
end
private
def submit_params
params.require(:comment).permit(:content)
end
end
# app/reflexes/embed_reflex.rb
class EmbedReflex < ApplicationReflex
include Submittable
before_reflex do
@resource.board = Board.friendly.find(params[:id])
end
after_reflex do
@embed = @resource.board.embeds.build if @resource.valid?
StreamBoardJob.perform_now(board: @resource.board, embed: @embed)
end
# override because we want a nothing morph
def submit
super do
morph :nothing
end
end
private
def submit_params
params.require(:embed).permit(:input)
end
end
# app/reflexes/concerns/submittable.rb
module Submittable
extend ActiveSupport::Concern
included do
before_reflex do
@resource = element.dataset.resource
@resource_class = @resource.safe_constantize
instance_variable_set("@#{@resource.underscore}", @resource_class.new(submit_params))
end
end
def submit
instance_variable_get("@#{@resource.underscore}").save
yield if block_given?
end
end
# app/reflexes/comment_reflex.rb
class CommentReflex < ApplicationReflex
# ...
before_reflex do
@comment.user = current_user
@comment.build_history_entry(embed_id: params[:id])
end
after_reflex do
embed = @comment.embed
if @comment.valid?
@comment = Comment.new(user: current_user)
@comment.build_history_entry(embed: embed)
end
StreamBoardJob.perform_now(board: @comment.embed.board)
end
# ...
end
# app/reflexes/embed_reflex.rb
class EmbedReflex < ApplicationReflex
# ...
before_reflex do
@embed.board = Board.friendly.find(params[:id])
end
after_reflex do
@embed = @embed.board.embeds.build if @embed.valid?
StreamBoardJob.perform_now(board: @embed.board, embed: @embed)
end
# ...
end
# app/jobs/stream_board_job.rb
class StreamBoardJob < ApplicationJob
include CableReady::Broadcaster
queue_as :default
def perform(board:, embed: nil)
embed ||= board.embeds.build
cable_ready[BoardsChannel].inner_html(
selector: "#embed-form",
html: ApplicationController.render(partial: "embeds/form", locals: {board: board, embed: embed})
).morph(
selector: dom_id(board),
html: ApplicationController.render(board)
).broadcast_to(board)
end
end
# app/controllers/board_controller.rb
# ...
def create
@board = Board.friendly.find(params[:board_id])
@embed = @board.embeds.build(embed_params)
if @embed.save
StreamBoardJob.perform_now(board: @board, embed: @embed)
else
render "boards/show"
end
end
# app/reflexes/concerns/submittable.rb
module Submittable
extend ActiveSupport::Concern
included do
before_reflex do
resource_class = element.dataset.resource.safe_constantize
@resource = resource_class.new(submit_params)
end
end
def submit
@resource.save
yield if block_given?
end
end
@michalkorzawski
Copy link

in 01-module-builder-pattern.rb, @resource_name.underscore should it be @resource.underscore ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment