В части №1 мы увидели, что нулевые значения, на наш взгляд, могут накапливаться по многим причинам.

Сегодня мы собираемся использовать рубиновый драгоценный камень Draper, чтобы реализовать шаблон проектирования Decorator в нашем приложении Ruby 2.4.0 и Rails 5.1.4.

Использование этого шаблона упростит разработку интерфейса пользователя; 5 строк в 3 файлах рефакторинга.

Декоратор наследует методы от объекта, который он обертывает, добавляя дополнительные методы, которые мы реализуем, не влияя на базовый объект. То, что мы добавим сегодня, касается логики представления.

Какие?

Другими словами, декораторы обертывают или, как мне нравится думать, владеют рубиновым объектом:

# Two ways to call
2.4.0 :010 > user = User.first.decorate
=> #<UserDecorator:0x007fd10659bb98 @object=#<User id: 1, name: "Loi V Tran", email: "[email protected]", ...)
2.4.0 :012 > user = UserDecorator.new(User.first)
=> #<UserDecorator:0x007fd103366128 @object=#<User id: 1, name: "Loi V Tran", email: "[email protected]", ...)

и заботятся, среди прочего, о представлении нашего приложения конечным пользователям. Посмотрим как.

Сначала добавьте гем Draper в наш Gemfile.

# Gemfile
gem 'draper'

Потом:

bundle install

Когда это будет завершено, запустите:

rails g decorator User

Это создаст для нас новый файл по адресу

# app/decorators/user.rb
class UserDecorator < Draper::Decorator
  delegate_all
end

Теперь мы можем провести рефакторинг и исправить ошибку внешнего интерфейса с помощью 5 строк кода в 3 файлах.

Первые два - это наши изменения в контроллере и представлении соответственно:

  1. Контроллер:
# app/controllers/users_controller.rb
def index
  @users = User.all
end

становится:

def index
  @users = User.all.decorate
end

2. Просмотр:

# app/views/index.html.erb
<% @users.each do |other_user| %>
... 
# Displaying location requires 2 method calls
  <ul>
    <li>Position: <%= other_user.position %></li>
    <li>School: <%= other_user.school %></li>
    <li>Location: <%= other_user.city + ', ' + other_user.state %></li>
  </ul>
...
<% end %>

становится:

# app/views/index.html.erb
<% @users.each do |other_user| %>
...
# Displaying location requires 1 method call
<ul>
  <li>Position: <%= other_user.position %></li>
  <li>School: <%= other_user.school %></li>
  <li>Location: <%= other_user.location %></li>
</ul>
...
<% end %>

Наконец, нам нужно реализовать метод location, который мы только что вызвали в представлении.

# app/decorators/user.rb
class UserDecorator < Draper::Decorator
  delegate_all
  def location
    return object.city + ', ' + object.state if object.city && object.state
    return object.city if object.city
    'Unlisted'
  end
end

Теперь, когда мы находимся в нашем представлении, мы больше не вызываем методы объекта User, а вызываем объект декоратора. Давайте докажем это, остановив сервер с помощью pry.

оригинал:

# Remember
@users = [User, User, User, ...]

other_user.class == Пользователь

новый:

@users = [UserDecorator, UserDecorator, UserDecorator, ...]

other_user.class == UserDecorator.

после этого, когда мы запустим other_user.location

<ul>
  <li>Position: <%= other_user.position %></li>
  <li>School: <%= other_user.school %></li>
  <li>Location: <%= other_user.location %></li>
</ul>

Метод location, который мы определяем в UserDecorator, называется:

# app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator
  delegate_all
  
  def location
    return object.city + ', ' + object.state if object.city && object.state
    return object.city if object.city
    'Unlisted'
  end
end

В результате мы исправили ошибку, обнаруженную в интерфейсе.

Использование шаблона проектирования декоратора в Ruby может облегчить нам жизнь несколькими способами. Вот несколько.

1. Мы не реализуем сложную в обслуживании условную логику во внешнем интерфейсе, чтобы обойти нулевые значения.

2. Мы не добавляем логику в нашу модель ActiveRecord User, потому что это поведение не касается нашей модели, а скорее представления нашего объекта конечному пользователю.

3. Мы избегаем вспомогательных методов, вместо этого передаем логику декоратору, используя шаблон проектирования, который можно снова использовать позже. Это также помогает нам не загрязнять глобальное пространство имен, как это сделал бы вспомогательный метод.

Спасибо за прочтение!

Следите за новостями.

Поставьте отметку Нравится или прокомментируйте, чтобы получать уведомления о части 3, где мы пишем тесты с использованием гема RSpec 3.7, чтобы иметь уверенность, что наш метод ведет себя так, как мы ожидаем.