Concerns, Decorators, Presenters, Service Objects, Helpers - Help Me Understand Them

Concerns, Decorators, Presenters, Service Objects, Helpers - Help me Understand Them

Well, as I said in the comment, you'll be better of with simple google searches.

For example, this is a nice article about most of them.

I'll just walk you through the basics.

  1. Concerns are mainly for DRYing up your models and controllers. If you have a very fat controller/model that has lots of functionality in it (violating the SRP), it's much better to break it into several independant concerns and just include them back in. That way you can also share functionality between similar controllers/models. Here's a nice article.

  2. Decorators are used for separating models' business logic with their user appearance. E.g. for storing methods only used in the views and for other displaying. They are also used to extend the logic of an object. Here is a nice thoughbot post.

  3. Presenters are practically the same, but used only for displaying purposes.

  4. Service Objects are mainly used for sophisticated logic that does not necesserally belong in a specific model/controller and/or deals with several models for example.

  5. Helpers are solidly for moving logic out of the view and thus simplifying the view patterns and DRYing up the views. Usually used for simple things ('cause otherwise it's better to use a decorator or a presenter).

Ruby on Rails patterns - decorator vs presenter

A decorator is more of a "let's add some functionality to this entity". A presenter is more of a "let's build a bridge between the model/backend and view". The presenter pattern has several interpretations.

Decorators are generic/general purpose. Presenters have a narrower range of responsibilities/uses. Decorators are used across domains, presenters are almost always related to view-like functionality.

  • Draper site
  • RailsCasts Draper Episode

Decorators vs Helpers in Rails?

Rails' (built-in) way of organizing your code is: Fat models, skinny controllers, and throw the rest in Helpers (or Concerns, which are Helpers but for controllers/models).

Biggest problems with helpers (IMO):

  • they are accessible in any view. Yep, everything defined in every Helpers of your app is available in the views...
  • they are modules: they cannot be instantiated, therefore you call their methods only giving arguments. I prefer the OOP version: @user.full_name.

Decorators:

  • basically, they wrap your model's instance and provide methods for display purpose. A Decorator should not modify the data, just arrange it, pluralize, translate, add commas, display currency with the price, etc. It decorates the object and its data.
  • they are tied to a specific object (not only model instance, but you can also use a Decorator for a Plain-Old-Ruby-Object as well, such as UserRole or Country).

Using the Decorator pattern will reduce the amount of code in the fat models (imposed by Rails' built-in way to do things):

  • Your model, which is supposed to hold the business logic, is not polluted with display logic anymore.
  • Your Helpers are no longer big fat piles of methods available anywhere, but instead only defining really globally-helping methods like link_to_icon(icon_name, *args), hours_from_datetime(datetime, format = '24'), menu_link(name, path, *args), etc.

Should I use a presenter or a decorator?

tl;dr:
Use a Presenter in this scenario

Elaboration:
A Decorator is a structural design pattern that wraps other objects and adds new functionality without the need to extend the class you are decorating.

A Presenter on the other hand should use methods of the object you are presenting to format data in a way you want to show them. Eg. you have a User model:

class User < ActiveRecord:Base
# it has first_name and last_name columns
end

and you want to present the full name without much logic in the views. You can create a UserPresenter class like this:

class UserPresenter
def initialize(user)
@user = user
end

def full_name
"#{@user.last_name} #{@user.first_name}"
end
end

So instead of calling both attributes separately, you just do it with the presenter

user = User.new(first_name: "John", last_name: "Doe")
user_presenter = UserPresenter.new(user)
user_presenter.full_name #=> "Doe John"

What is the Rails Presenters folder for?

presenters is a design pattern commonly reffered to as Model View Presenter(MVP)

This is a derivation of the Model View Controller pattern and is used for creating user interfaces.

It's useful for the Separation of Concerns for making code more DRY.

Here's how Wikipedia describes it

model - interface defining the data to be displayed or otherwise acted upon in the user interface.

presenter - acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view.

view - a passive interface that displays data (the model) and routes user commands (events) to the presenter to act upon that data.

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter

Presenters in Ruby on Rails

Presenters are simple classes that sit between the model and the view and provide a nice, DRY object oriented way of working with complex display logic.

In Rails, the convention is for them to be located in the app/presenters folder

Here is a userful article that explains the pattern and its use in Ruby on Rails.

https://kpumuk.info/ruby-on-rails/simplifying-your-ruby-on-rails-code/

Rails model - change true to yes

You can fetch and override the is_value method on the returned records. This would be ok if you are only fetching the records for display purposes and you know that is_value won't be used in any other context.

module TrueToYes
def is_value
super ? "Yes" : "No" # Assuming is_value on MyModel is a boolean
end
end

# For a collection
decorated_models = MyModel.all.map{|model| model.extend(TrueToYes) }
decorated_models.first.is_value # => 'Yes'

# Single Record
my_model = MyModel.first
my_model.is_value # => true
my_model.extend(TrueToYes)
my_model.is_value # => 'Yes'


Related Topics



Leave a reply



Submit