0

I'm discovering the presenter (or decorator) pattern thanks to Ryan Bates' tutorial and implementing it in a training project.

I'm wondering if there's any way to use ActiveSupport delegate methods between custom objects ? After refactoring my first model (Product), I'd like to use some ProductPresenter instance methods inside a CartPresenter instance. If not, maybe should I use presenter's concerns ?

I'm currently instantiating presenters inside views and accessing helpers methods by redirecting missing methods to the template, but maybe I need to instantiate presenters inside controllers (in order to have access to both CartPresenter & ProductPresenter) and define a getter for the template (so it doesn't obfuscate the method_missing method) ?


EDIT

Thanks to jvillian answer, :product_presenter now refers to a ProductPresenter instance.

As I may have other situations where I need to delegate presenters methods, I added :delegated_presenter to my BasePresenter

Class BasePresenter
  def initialize(object, template)
    @object = object
    @template = template
  end

  def self.delegated_presenter(name)
    define_method("#{name}_presenter") do
      klass = "#{name.capitalize}Presenter".constantize
      @delegator ||= klass.new(@object.send(name), @template)
    end
  end
end

Now inside my presenter subclasses :

class CartPresenter < BasePresenter
  delegated_presenter :product
  delegate :product_presenter_instance_method, to: :product_presenter
end

I'm thinking about grouping those into one BasePresenter class method that will do all the job.

This is how it's use inside a view:

<% present product do |product_presenter| %>
  <div class="card" style="width: 14rem;">
    <%= product_presenter.display_card_image %>
    <div class="card-body">
      <%= product_presenter.display_link_to_product_name(class: 'card-title text-dark') %>
      <%= product_presenter.display_link_to_product_supplier(class: 'small text-right') %>
      <%= product_presenter.display_truncated_description(class: 'card-text') %>
      <%= render partial: 'product_buttons', locals: { product: product } %>
      <%= product_presenter.display_tags(class: 'badge badge-pill badge-secondary') %>
    </div>
  </div>
<% end %>

present is a helper method that returns a presenter object.

Community
  • 1
  • 1
Sumak
  • 927
  • 7
  • 21
  • Sure, you can use `delegate` in exactly the way you're describing - that's what it is for. So, I'm not sure exactly what your question is. – jvillian Apr 17 '20 at 17:23
  • `delegate :my_instance_method, to: :product_presenter` won't work, `:product_presenter` is not recognized and will fall into the `:method_missing`. Sorry if I'm not clear, what I'm looking for is to use `ProductPresenter` instance methods through a `CartPresenter` object – Sumak Apr 17 '20 at 17:54

1 Answers1

1

This:

delegate :my_instance_method, to: :product_presenter

...doesn't work because :product_presenter is a symbol, not an instance of ProductPresenter. Perhaps try something more like:

class CartPresenter

  delegate :my_instance_method, to: product_presenter 

  def product_presenter
    @product_presenter ||= ProductPresenter.new 
  end

end

...and...

class ProductPresenter

  def my_instance_method
    # do something
  end

end

This statement:

I'm currently instantiating presenters inside views

...is a little concerning to me since you're creating tight coupling between the view and the presenter. It's a longer topic, but if I were generating that view you show in your code it would look something more like:

<% @presenter = local_assigns[:presenter] if local_assigns[:presenter] %>

<div class="card" style="width: 14rem;">
  <%= @presenter.card_content %>
</div>

Then, naturally, whatever presenter you pass in using locals needs to implement card_content. Now, your view knows nothing about presenter or its methods beyond that one method, card_content. You can do whatever you want in card_content and make changes in the future to product_presenter methods without ever having to worry about updating your view. Decoupled!

jvillian
  • 19,953
  • 5
  • 31
  • 44
  • I'd to tweak it a bit to fit my context but it works perfectly. I'll edit my question to share some code. Thanks ! Could you elaborate on your concern ? I'll add the helper method I'm using to access a Presenter object from a view – Sumak Apr 17 '20 at 19:13
  • Thanks for your edit, it really helps distinguishing views and presenters. I was considering presenters as "classes that access data from models and format it for views", but without embedding that much HTML. – Sumak Apr 17 '20 at 22:37
  • Actually, my presenters never know anything about the model layer. That's left to a class called `Manager`. But, again, that's a whole other story. – jvillian Apr 17 '20 at 23:33