From 3b4e98d3e3eaed2585d94a903bf1175732f65572 Mon Sep 17 00:00:00 2001 From: Alexander Senko Date: Wed, 7 Feb 2024 02:41:58 +0700 Subject: [PATCH] Integration with `Turbo::Broadcastable` Overriding defaults for Turbo broadcast jobs allows one to get decorated objects in model partials by default. Resolves drapergem/draper#910. Requires drapergem/draper#928. --- Gemfile | 5 +++++ lib/draper/compatibility/broadcastable.rb | 24 +++++++++++++++++++++++ lib/draper/decoratable.rb | 9 +++++++-- spec/dummy/app/models/post.rb | 4 ++++ spec/dummy/config/application.rb | 1 + spec/dummy/config/cable.yml | 8 ++++++++ spec/dummy/spec/models/post_spec.rb | 13 ++++++++++++ 7 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 lib/draper/compatibility/broadcastable.rb create mode 100644 spec/dummy/config/cable.yml diff --git a/Gemfile b/Gemfile index 1ec9bada..f9458e44 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,11 @@ platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" end +if RUBY_VERSION >= "2.6.0" + gem "turbo-rails" + gem "redis", "~> 4.0" +end + if RUBY_VERSION >= "2.5.0" gem "rails", "~> 6.0" gem 'webrick' diff --git a/lib/draper/compatibility/broadcastable.rb b/lib/draper/compatibility/broadcastable.rb new file mode 100644 index 00000000..59f33f12 --- /dev/null +++ b/lib/draper/compatibility/broadcastable.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Draper + module Compatibility + # It would look consistent to use decorated objects inside templates broadcasted with + # Turbo::Broadcastable. + # + # This compatibility patch fixes the issue by overriding the original defaults to decorate the + # object, that's passed to the partial in a local variable. + module Broadcastable + private + + def broadcast_rendering_with_defaults(options) + return super unless decorator_class? + + # Add the decorated current instance into the locals (see original method for details). + options[:locals] = + (options[:locals] || {}).reverse_merge!(model_name.element.to_sym => decorate) + + super + end + end + end +end diff --git a/lib/draper/decoratable.rb b/lib/draper/decoratable.rb index 5a3aee39..64f840bf 100644 --- a/lib/draper/decoratable.rb +++ b/lib/draper/decoratable.rb @@ -11,6 +11,13 @@ module Decoratable extend ActiveSupport::Concern include Draper::Decoratable::Equality + included do + if defined? Turbo::Broadcastable + require_relative 'compatibility/broadcastable' + prepend Compatibility::Broadcastable + end + end + # Decorates the object using the inferred {#decorator_class}. # @param [Hash] options # see {Decorator#initialize} @@ -87,8 +94,6 @@ def decorator_class(called_on = self) def ===(other) super || (other.is_a?(Draper::Decorator) && super(other.object)) end - end - end end diff --git a/spec/dummy/app/models/post.rb b/spec/dummy/app/models/post.rb index 59b1f954..6352fdbe 100644 --- a/spec/dummy/app/models/post.rb +++ b/spec/dummy/app/models/post.rb @@ -1,3 +1,7 @@ +require 'turbo/broadcastable' if defined? Turbo::Broadcastable # HACK: looks weird, but works + class Post < ApplicationRecord # attr_accessible :title, :body + + broadcasts if defined? Turbo::Broadcastable end diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index f0e1feaf..159f806c 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -9,6 +9,7 @@ def attempt_require(file) require 'draper' attempt_require 'mongoid' attempt_require 'devise' +attempt_require 'turbo-rails' require 'active_model_serializers' module Dummy diff --git a/spec/dummy/config/cable.yml b/spec/dummy/config/cable.yml new file mode 100644 index 00000000..cfb308a0 --- /dev/null +++ b/spec/dummy/config/cable.yml @@ -0,0 +1,8 @@ +# production: +# url: redis://redis.example.com:6379 + +local: &local + url: redis://localhost:6379 + +development: *local +test: *local diff --git a/spec/dummy/spec/models/post_spec.rb b/spec/dummy/spec/models/post_spec.rb index 1ddf8b56..8bc05a0f 100644 --- a/spec/dummy/spec/models/post_spec.rb +++ b/spec/dummy/spec/models/post_spec.rb @@ -5,4 +5,17 @@ it_behaves_like 'a decoratable model' it { should be_a ApplicationRecord } + + describe 'broadcasts' do + let(:modification) { described_class.create! } + + it 'passes a decorated object for rendering' do + expect do + modification + end.to have_enqueued_job(Turbo::Streams::ActionBroadcastJob).with { |stream, action:, target:, **rendering| + expect(rendering[:locals]).to include :post + expect(rendering[:locals][:post]).to be_decorated + } + end + end if defined? Turbo::Broadcastable end