From 646dac4a6b50bd613a324ff777a6a3dbda196ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bue=20Gr=C3=B8nlund?= Date: Mon, 2 Mar 2020 14:18:55 +0100 Subject: [PATCH 1/7] Update README.md with instructions for namespaced decorators --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index a7b65fba..ca7051b6 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,17 @@ class ArticleDecorator < Draper::Decorator end ``` + +To decorate a model in a namespace e.g. `Admin::Catalogue` place the decorator under the +directory `app/decorators/admin` in the same way you would with views and models. + +```ruby +# app/decorators/admin/catalogue_decorator.rb +class Admin::CatalogueDecorator < Draper::Decorator +# ... +end +``` + ### Generators To create an `ApplicationDecorator` that all generated decorators inherit from, run... From 2af3aefd5c0a07a3005cac6123b88ce2f82c98a5 Mon Sep 17 00:00:00 2001 From: mishina Date: Tue, 1 Mar 2022 23:10:06 +0900 Subject: [PATCH 2/7] Use https for GitHub link --- draper.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draper.gemspec b/draper.gemspec index 5c73dd15..19a49162 100644 --- a/draper.gemspec +++ b/draper.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.version = Draper::VERSION s.authors = ["Jeff Casimir", "Steve Klabnik"] s.email = ["jeff@casimircreative.com", "steve@steveklabnik.com"] - s.homepage = "http://github.com/drapergem/draper" + s.homepage = "https://github.com/drapergem/draper" s.summary = "View Models for Rails" s.description = "Draper adds an object-oriented layer of presentation logic to your Rails apps." s.license = "MIT" From 83e286da3d354ad554b9e802a86c9dddadaf2f30 Mon Sep 17 00:00:00 2001 From: itoshusu Date: Fri, 24 Feb 2023 23:12:42 +0900 Subject: [PATCH 3/7] Update README with config to disable generation of decorator files --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 97fef7f0..83f5cc59 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,15 @@ rails generate decorator Article ...to create the `ArticleDecorator`. +If you don't want Rails to generate decorator files when generating a new controller, +you can add the following configuration to your `config/application.rb` file: + +```ruby +config.generators do |g| + g.decorator false +end +``` + ### Accessing Helpers Normal Rails helpers are still useful for lots of tasks. Both Rails' provided From 9daf3ac19f2cd2be2b447b33b151157a4b772fb0 Mon Sep 17 00:00:00 2001 From: Carlos Atkinson Date: Wed, 18 May 2022 12:36:55 -0300 Subject: [PATCH 4/7] Fix CollectionDecorator#respond_to? for non AR collections --- lib/draper/query_methods.rb | 2 +- spec/draper/query_methods_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/draper/query_methods.rb b/lib/draper/query_methods.rb index abeeda81..1f9c6019 100644 --- a/lib/draper/query_methods.rb +++ b/lib/draper/query_methods.rb @@ -10,7 +10,7 @@ module QueryMethods end def respond_to_missing?(method, include_private = false) - strategy.allowed?(method) || super + object.respond_to?(method) && strategy.allowed?(method) || super end private diff --git a/spec/draper/query_methods_spec.rb b/spec/draper/query_methods_spec.rb index 7849654d..4bafffdb 100644 --- a/spec/draper/query_methods_spec.rb +++ b/spec/draper/query_methods_spec.rb @@ -53,14 +53,24 @@ module Draper context 'when strategy allows collection to call the method' do before do allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(true) + allow(collection).to receive(:respond_to?).with(:some_query_method).and_return(true) end it { is_expected.to eq(true) } + + context 'and collection does not implement the method' do + before do + allow(collection).to receive(:respond_to?).with(:some_query_method).and_return(false) + end + + it { is_expected.to eq(false) } + end end context 'when strategy does not allow collection to call the method' do before do allow(fake_strategy).to receive(:allowed?).with(:some_query_method).and_return(false) + allow(collection).to receive(:respond_to?).with(:some_query_method).and_return(true) end it { is_expected.to eq(false) } From a93223ee38a311f31c9ad7be95d904b4ab45f896 Mon Sep 17 00:00:00 2001 From: Tim Diggins Date: Tue, 1 Aug 2023 16:47:20 +0100 Subject: [PATCH 5/7] fix issue with using draper outside of controller/view context fixes #926 however the current PR also drops support for rails < 6.0 open to discussion around this if there's any engagement on this PR. --- lib/draper/view_context/build_strategy.rb | 10 +--------- .../view_context/build_strategy_spec.rb | 19 +------------------ 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/lib/draper/view_context/build_strategy.rb b/lib/draper/view_context/build_strategy.rb index 9832a05d..1b6ff47e 100644 --- a/lib/draper/view_context/build_strategy.rb +++ b/lib/draper/view_context/build_strategy.rb @@ -38,17 +38,9 @@ def call def controller Draper::ViewContext.controller ||= Draper.default_controller.new Draper::ViewContext.controller.tap do |controller| - controller.request ||= new_test_request controller if defined?(ActionController::TestRequest) + controller.request ||= ActionDispatch::TestRequest.create end end - - def new_test_request(controller) - is_above_rails_5_1 ? ActionController::TestRequest.create(controller) : ActionController::TestRequest.create - end - - def is_above_rails_5_1 - ActionController::TestRequest.method(:create).parameters.first == [:req, :controller_class] - end end end end diff --git a/spec/draper/view_context/build_strategy_spec.rb b/spec/draper/view_context/build_strategy_spec.rb index 54774926..18acdf61 100644 --- a/spec/draper/view_context/build_strategy_spec.rb +++ b/spec/draper/view_context/build_strategy_spec.rb @@ -37,7 +37,7 @@ module Draper expect(controller.request).to be_nil strategy.call - expect(controller.request).to be_an ActionController::TestRequest + expect(controller.request).to be_an ActionDispatch::TestRequest expect(controller.params).to be_empty # sanity checks @@ -45,23 +45,6 @@ module Draper expect(controller.view_context.params).to be controller.params end - it "compatible with rails 5.1 change on ActionController::TestRequest.create method" do - ActionController::TestRequest.class_eval do - if ActionController::TestRequest.method(:create).parameters.first == [] - def create controller_class - create - end - end - end - controller = Class.new(ActionController::Base).new - allow(ViewContext).to receive_messages controller: controller - strategy = ViewContext::BuildStrategy::Full.new - - expect(controller.request).to be_nil - strategy.call - expect(controller.request).to be_an ActionController::TestRequest - end - it "adds methods to the view context from the constructor block" do allow(ViewContext).to receive(:controller).and_return(fake_controller) strategy = ViewContext::BuildStrategy::Full.new do From 4d06805fb2859dea33214a7281e0e6e4272a6963 Mon Sep 17 00:00:00 2001 From: Alexander Senko Date: Tue, 6 Feb 2024 20:37:46 +0700 Subject: [PATCH 6/7] Improved integration with Global ID Implementing `Decorator.find` improves compatibility with Global ID and allows one to use decorated objects in jobs seamlessly. Resolves drapergem/draper#663. Improves drapergem/draper#817. --- README.md | 5 ++--- lib/draper/compatibility/global_id.rb | 14 +++++++++----- spec/dummy/spec/decorators/post_decorator_spec.rb | 9 +++++++++ spec/dummy/spec/jobs/publish_post_job_spec.rb | 4 +++- spec/dummy/spec/models/post_spec.rb | 7 ------- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8d400106..7fa2e016 100644 --- a/README.md +++ b/README.md @@ -672,9 +672,8 @@ you can include this module manually. [Active Job](http://edgeguides.rubyonrails.org/active_job_basics.html) allows you to pass ActiveRecord objects to background tasks directly and performs the necessary serialization and deserialization. In order to do this, arguments to a background job must implement [Global ID](https://github.com/rails/globalid). -Decorated objects implement Global ID by delegating to the object they are decorating. This means -you can pass decorated objects to background jobs, however, the object won't be decorated when it is -deserialized. +Decorators implement Global ID. +This means you can pass decorated objects to background jobs, and get them just as decorated when deserialized. ## Contributors diff --git a/lib/draper/compatibility/global_id.rb b/lib/draper/compatibility/global_id.rb index 271be65f..fb8e8da9 100644 --- a/lib/draper/compatibility/global_id.rb +++ b/lib/draper/compatibility/global_id.rb @@ -5,17 +5,21 @@ module Compatibility # and deserialization. In order to do this, arguments to a background job must implement # [Global ID](https://github.com/rails/globalid). # - # This compatibility patch implements Global ID for decorated objects by delegating to the object - # that is decorated. This means you can pass decorated objects to background jobs, but - # the object won't be decorated when it is deserialized. This patch is meant as an intermediate - # fix until we can find a way to deserialize the decorated object correctly. + # This compatibility patch implements Global ID for decorated objects by defining `.find(id)` + # class method that uses the original one and decorates the result. + # This means you can pass decorated objects to background jobs and they will be decorated when + # deserialized. module GlobalID extend ActiveSupport::Concern included do include ::GlobalID::Identification + end - delegate :to_global_id, :to_signed_global_id, to: :object + class_methods do + def find(*args) + object_class.find(*args).decorate + end end end end diff --git a/spec/dummy/spec/decorators/post_decorator_spec.rb b/spec/dummy/spec/decorators/post_decorator_spec.rb index e1655b9a..7260e68b 100644 --- a/spec/dummy/spec/decorators/post_decorator_spec.rb +++ b/spec/dummy/spec/decorators/post_decorator_spec.rb @@ -61,4 +61,13 @@ it "uses a test view context from BaseController" do expect(Draper::ViewContext.current.controller).to be_an BaseController end + + describe 'Global ID' do + it { expect(GlobalID::Locator.locate decorator.to_gid).to eq decorator } + it { expect(GlobalID::Locator.locate decorator.to_gid).to be_decorated } + it { expect(GlobalID::Locator.locate object.to_gid).not_to be_decorated } + it { expect(GlobalID::Locator.locate_signed decorator.to_sgid).to eq decorator } + it { expect(GlobalID::Locator.locate_signed decorator.to_sgid).to be_decorated } + it { expect(GlobalID::Locator.locate_signed object.to_sgid).not_to be_decorated } + end end diff --git a/spec/dummy/spec/jobs/publish_post_job_spec.rb b/spec/dummy/spec/jobs/publish_post_job_spec.rb index e0ffbb15..c809f42a 100644 --- a/spec/dummy/spec/jobs/publish_post_job_spec.rb +++ b/spec/dummy/spec/jobs/publish_post_job_spec.rb @@ -4,6 +4,8 @@ subject(:job) { described_class.perform_later(post) } it 'queues the job' do - expect { job }.to have_enqueued_job(described_class).with(post.object) + expect { job }.to have_enqueued_job(described_class).with { |post| + expect(post).to be_decorated + } end end diff --git a/spec/dummy/spec/models/post_spec.rb b/spec/dummy/spec/models/post_spec.rb index 22a5fb61..1ddf8b56 100644 --- a/spec/dummy/spec/models/post_spec.rb +++ b/spec/dummy/spec/models/post_spec.rb @@ -5,11 +5,4 @@ it_behaves_like 'a decoratable model' it { should be_a ApplicationRecord } - - describe '#to_global_id' do - let(:post) { Post.create } - subject { post.to_global_id } - - it { is_expected.to eq post.decorate.to_global_id } - end end From a1439fff830ce6206ef3599b728ebd8485f6a190 Mon Sep 17 00:00:00 2001 From: Alexander Senko Date: Wed, 7 Feb 2024 02:41:58 +0700 Subject: [PATCH 7/7] 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 | 7 +++++-- 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, 60 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..3f72e74b 100644 --- a/lib/draper/decoratable.rb +++ b/lib/draper/decoratable.rb @@ -1,4 +1,5 @@ require 'draper/decoratable/equality' +require 'draper/compatibility/broadcastable' module Draper # Provides shortcuts to decorate objects directly, so you can do @@ -11,6 +12,10 @@ module Decoratable extend ActiveSupport::Concern include Draper::Decoratable::Equality + included do + prepend Draper::Compatibility::Broadcastable if defined? Turbo::Broadcastable + end + # Decorates the object using the inferred {#decorator_class}. # @param [Hash] options # see {Decorator#initialize} @@ -87,8 +92,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