diff --git a/episodes/284/ru.html b/episodes/284/ru.html new file mode 100644 index 0000000..b389b18 --- /dev/null +++ b/episodes/284/ru.html @@ -0,0 +1,315 @@ +

В этом эпизоде мы рассмотрим Active Admin. Этот гем позволяет с легкостью добавить административный интерфейс к вашему приложению на Rails. Он создает симпатичные страницы админки и очень гибкок в настройке. Можете посмотреть на него в действии, взглянув на live demo.

+ +

В этом эпизоде мы добавим Active Admin к уже существующему Rails приложению. Приложение, с которым мы будем работать - простой интернет-магазин, в котором есть товары, у каждого из которых есть цена и принадлежность к категории. Мы используем Active Admin для создания административного интерфейса, чтобы мы смогли управлять товарами.

+ +
+ Наше приложение. +
+ +

Установка Active Admin

+ +

Active Admin оформлен как гем и устанавливается как обычно, добавлением ссылки на него в Gemfile и последующим запуском bundle. Наше приложение работает на Rails 3.1, поэтому мы должны убедиться, что мы также включили гем sass-rails, так как Active Admin от него зависит. В Rails 3.0 это не вызывает проблем и не нужно его включать.

+ +

/Gemfile

+
gem 'activeadmin'
+ +

Когда Bundler завершит работу, нужно запустить генератор для добавления Active Admin к нашему приложению. Этот генератор предоставит нам инструкции по дополнительной настройке, которую нам надо будет сделать после его запуска. Нужно добавить опцию host в конфигурацию Mailer в среде development, убедиться, что у нас есть root URL, а также добавить сообщения notice и alert к файлу вёрстки приложения.

+ +
$ rails g active_admin:install
+      invoke  devise
+    generate    devise:install
+      create    config/initializers/devise.rb
+      create    config/locales/devise.en.yml
+
+==================================================================
+
+Some setup you must do manually if you haven't yet:
+
+  1. Setup default url options for your specific environment. Here is an example of development environment:
+
+       config.action_mailer.default_url_options = { :host => 'localhost:3000' }
+
+     This is a required Rails configuration. In production it must be the actual host of your application
+
+  2. Ensure you have defined root_url to *something* in your config/routes.rb.
+     For example:
+
+       root :to => "home#index"
+
+  3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example:
+
+       <p class="notice"><%= notice %></p>
+       <p class="alert"><%= alert %></p>
+ +

Всё это мы уже сделали для нашего приложения, поэтому мы готовы продолжить.

+ +

Команда также создает несколько миграций, поэтому запустим их.

+ +
$ rake db:migrate
+ +

Использование Active Admin

+ +

Если взглянуть на документацию Active Admin, то мы увидим, что когда мы устанавливаем его, то он создает пользователя с именем admin@example.com и паролем password. Мы можем использовать эти данные для входа. (Вы можете изменить имя и пароль, изменив файл миграции devise_create_admin_users.rb прежде чем их запускать.) Если мы зайдем на http://localhost:3000/admin с запущенным сервером Rails, то увидим форму аутентификации, на которой мы можем выполнить вход с помощью вышеприведенных данных.

+ +
+ Страница входа Active Admin. +
+ +

После выполнения входа, мы переместимся в панель управления Active Admin, хотя пока там мало что можно увидеть.

+ +
+ Панель управления Active Admin. +
+ +

Мы хотим управлять нашими товарами, поэтому добавим ресурс Product к Active Admin, выполнив следующую команду:

+ +
$ rails g active_admin:resource product
+      create  app/admin/products.rb
+ +

Этот генератор создает файл products.rb в директории приложения app/admin. Когда мы обновим административную панель управления, то увидим ссылку “Products”. Если мы нажмем на эту ссылку, то попадём на страницу, на которой есть всё для управления нашими товарами.

+ +
+ Административная страница списка товаров. +
+ +

На этой странице есть возможности для сортировки или фильтрации товаров по любому атрибуту. Active Admin даже обнаружит связи belongs_to с Category и предоставляет нам выпадающее меню, чтобы можно было фильтровать товары по их категориям. Это работает также когда мы создаем новый продукт. Категория показывается в виде выпадающего списка, а остальные атрибуты имеют поля ввода, основанные на их типе данных.

+ +
+ Страница нового товара. +
+ +

Достаточно просто изменить эту функциональность. Мы начнем с изменения страницы index товаров и уменьшения количества отображаемых колонок. Чтобы это сделать, мы изменим файл /app/admin/products.rb, который был сгенерирован ранее. Мы изменим страницу index, переписав метод index. Этот метод принимает блок, в котором мы определяем колонки, которые хотим видеть на странице с помощью вызова column.

+ +

/app/admin/products.rb

+
ActiveAdmin.register Product do
+  index do
+    column :name
+    column :category
+    column :released_at
+    column :price
+  end
+end
+ +

Когда мы перезагружаем страницу товаров, мы видим, что она показывает колонки, которые мы хотели.

+ +
+ Страница товаров теперь показывает наши выбранные колонки. +
+ +

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

+ +

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

+ +

/app/admin/products.rb

+
ActiveAdmin.register Product do
+  index do
+    column :name
+    column :category
+    column "Release Date", :released_at
+    column :price
+  end
+end
+ +

Если мы хотим изменить значения в колонке, мы можем это сделать, передавая блок в column. Поле цены на данный момент не отображает символ валюты, но можно сделать так, чтобы отображало. Метод column может принимать блок и когда мы добавляем его, то текущая модель, в данном случае Product, передается в него. Что бы блок ни вернул, оно будет отображено в этой колонке. Здесь у нас есть доступ к методам хелпера, так чо мы можем использовать number_to_currency для правильного отображения цены.

+ +

/app/admin/products.rb

+
ActiveAdmin.register Product do
+  index do
+    column :name
+    column :category
+    column "Release Date", :released_at
+    column :price do |product|
+      number_to_currency product.price, :unit => "&pound;"
+    end
+  end
+end
+ +

Если мы сейчас перезагрузим страницу, то увидим измененный заголовок “Release Date” и цену каждого товара, отображаемую в виде валютного значения.

+ +
+ Колонка цены теперь показывает знак валюты. +
+ +

Изменение значения, возвращаемого полем с ценой означает, что теперь это поле не сортируемо. Также у нас больше нет ссылок изменения и удаления для каждого элемента. Сейчас мы это исправим. Когда бы мы ни использовали блок для настройки значения, мы должны также использовать опцию :sortable чтобы указать Active Admin, каким образом сортировать это поле. Сделаем это и вызовем default_actions чтобы вернуть обратно ссылки редактирования и удаления.

+ +

/app/admin/products.rb

+
ActiveAdmin.register Product do
+  index do
+    column :name
+    column :category
+    column "Release Date", :released_at
+    column :price, :sortable => :price do |product|
+      number_to_currency product.price, :unit => "&pound;"
+    end
+    default_actions
+  end
+end
+ +

Поле цены теперь показывает символ валюты, но будет лучше, если значения также будут выровнены по правому краю. Мы можем это сделать с помощью CSS, но чтобы сделать это, нам для начала потребуется способ ссылаться на колонку. Active Admin предоставляет возможность генерировать HTML, который очень похож на Markaby. Все что нам нужно сделать - вызвать метод с именем тега, который мы хотим сгенерировать. Мы можем передать туда параметр :class чтобы придать тегу что-то, на что мы можем ссылаться в CSS.

+ +

/app/admin/products.rb

+
ActiveAdmin.register Product do
+  index do
+    column :name
+    column :category
+    column "Release Date", :released_at
+    column :price, :sortable => :price do |product|
+      div :class => "price" do
+        number_to_currency product.price, :unit => "&pound;"
+      end
+    end
+    default_actions
+  end
+end
+ +

Теперь мы можем применить стиль к этой колонке, изменяя файл active_admin.css.scss.

+ +

/app/assets/stylesheets/active_admin.css.scss

+
// Active Admin CSS Styles
+@import "active_admin/mixins";
+@import "active_admin/base";
+
+// To customize the Active Admin interfaces, add your
+// styles here:
+.price {
+  text-align :right;
+}
+ +

Колонка цены теперь выравнивается правильно.

+ +
+ Колонка цены теперь выровнена по правому краю. +
+ +

Scopes

+ +

Scopes (области действия) это еще одна важная функция Active Admin. Они работают как заранее заданные фильтры, нужно сделать два шага, чтобы создать scope. Во-первых, мы добавим вызов scope в файле конфигурации Active Admin для наших товаров, передавая имя scope.

+ +

/app/admin/products.rb

+
ActiveAdmin.register Product do
+  scope :unreleased
+  index do
+    column :name
+    column :category
+    column "Release Date", :released_at
+    column :price, :sortable => :price do |product|
+      div :class => "price" do
+        number_to_currency product.price, :unit => "&pound;"
+      end
+    end
+    default_actions
+  end
+end
+ +

А во-вторых, мы должны вписать scope в модель Product.

+ +

/app/models/product.rb

+
class Product < ActiveRecord::Base
+  belongs_to :category
+  scope :unreleased, where(:released_at => nil)
+end
+ +

Если мы перезагрузим админ-страницу товаров теперь, то увидим scope в списке. Когда мы нажимаем на него, то показывается список товаров отфильтрованных по данному scope.

+ +
+ Товары отфильтрованы по unreleased scope. +
+ +

Настройка панели управления

+ +

Далее рассмотрим вопрос настройки панели управления. Она пуста по умолчанию, поэтому мы изменим ее для отображения списка недавних товаров. Сделаем это, изменив файл /app/admin/dashboards.rb. В этом файле представлена некоторая полезная документация, объясняющая, каким образом работают различные настройки.

+ +

Чтобы добавить раздел в панель, воспользуемся методом section. Нам нужно отобразить список недавних товаров в таблице и мы можем это сделать используя команду table_for. В его блоке мы указываем колонки, которые хотим отобразить с помощью column точно так же, как сделали это при настройке админ-страницы index товаров. Также мы добавим ссылку на страницу, которая показывает все товары.

+ +

/app/admin/dashboards.rb

+
ActiveAdmin::Dashboards.build do
+  section "Recent Products" do
+    table_for Product.order("released_at desc").limit(5) do
+      column :name
+      column :released_at
+    end
+    strong { link_to "View All Products", admin_products_path }
+  end
+end
+ +

Теперь, когда мы перезагржаем страницу панели управления, то увидим пять последних выпущенных товаров наряду со ссылкой.

+ +
+ Самые последние товары отображаются в панели управления. +
+ +

Страница будет более полезна, если каждый товар в списке будет ссылаться на админ-страницу этого товара. Мы можем это сделать, снова передав блок в метод column, точно так же, как мы это сделали с колонкой цены.

+ +

/app/admin/dashboards.rb

+
ActiveAdmin::Dashboards.build do
+  section "Recent Products" do
+    table_for Product.order("released_at desc").limit(5) do
+      column :name do |product|
+        link_to product.title, admin_product_path(product)
+      end
+      column :released_at
+    end
+    strong { link_to "View All Products", admin_products_path }
+  end
+end
+ +

Есть более короткий путь для определения пути в link_to. Вместо использования admin_product_path(product) мы можем передать массив с первым элементом - символом и вторым элементом - товаром, например так:

+ +
link_to product.title, [:admin, product]
+ +

Если мы перезагрузим панель управления, то увидим, что заголовок каждого товара показан в виде ссылки. Когда мы нажимаем на одну из ссылок, то попадаем на админ-страницу данного товара.

+ +

Исправление файлов стилей

+ +

Есть проблема, которую нужно учитывать при использовании Active Admin с Rails 3.1 и мы можем ее увидеть, возвращаясь на основной сайт.

+ +
+ Домашняя страница имеет неправильные стили. +
+ +

Страница выглядит не так, как выглядела раньше из-за стилей Active Admin, включеных на всех страницах. Rails 3.1 включает все файлы стилей по умолчанию из-за строчки require_tree . в файле манифеста application.css. Это не то, что мы хотели получить, но в любом случае будет правильно удалить эту строку, чтобы получить больше контроля над стилями нашего приложения. У нас есть единственный другой файл стилей в основном приложении, поэтому мы заменим require_tree . на require products.

+ +

/app/assets/stylesheets/application.css

+
/*
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
+ *= require_self
+ *= require products
+*/
+
+/* Остальная часть файла опущена */
+ +

Еще лучшим решением будет переключиться на команду SASS import. Можно переключить основной CSS файл приложения на SASS добавлением расширения .scss к его имени. Затем мы можем убрать манифест наверху файла (часть файла вы можете увидеть в отрывке кода выше) и добавить команду import внизу.

+ +

/app/assets/stylesheets/application.css.scss

+
/* Стили опущены */
+
+@import "products";
+ +

Теперь, когда мы перезагружаем страницу, только правильные файлы стилей подключены и страница выглядит корректно.

+ +

Глобальная конфигурация

+ +

У Active Admin есть файл конфигурации в директории /config/initializers и мы посвятим оставшуюся часть этого эпизода рассмотрению этого файла. Файл включает большое количество опций конфигурации, большая часть из которых закомментирована. Одна из работающих строк относится к заголовку административной части сайта и мы изменим ее.

+ +

/config/initializers/active_admin.rb

+
ActiveAdmin.setup do |config|
+
+  # == Site Title
+  #
+  # Set the title that is displayed on the main layout
+  # for each of the active admin pages.
+  #
+  config.site_title = "Eifion's Store"
+
+  # Остальные опции конфигурации опущены.
+end
+ +

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

+ +
+ Показан новый заголовок. +
+ +

На этом закончим данный эпизод. Есть еще много того, что мы не рассмотрели на счет Active Admin, поэтому уделите внимание документации, чтобы узнать, что еще можно сделать. Можно настроить внешний вид и функциональность страниц Active Admin для удовлетворения наших потребностей и эти возможности делают Active Admin мощным решением для добавления административной функциональности к вашему приложению на Rails.

\ No newline at end of file diff --git a/episodes/286/ru.html b/episodes/286/ru.html new file mode 100644 index 0000000..59b8cbb --- /dev/null +++ b/episodes/286/ru.html @@ -0,0 +1,383 @@ +

В этом эпизоде мы рассмотрим Draper, gem, который позволяет добавлять декораторы к вьюхам Rails-приложения, очень похоже на паттерн presenter. Если у вас много сложной логики отображения в ваших шаблонах и хелперах, то Draper может помочь очистить этот код с использованием более объектно-ориентированного подхода. В этом эпизоде мы покажем, как это работает.

+ +

Приложение, с которым будем работать показано ниже. В нем есть страница профиля пользователя, которая показывает различные части информации о данном пользователе включая аватар, полное имя, имя пользователя, короткую биографию с использованием разметки и ссылки на вебсайт и ленту Twitter. Если пользователь задал вебсайт, то аватар и полное имя будут ссылаться на этот сайт.

+ +
+ Страница профиля для пользователя, который ввел все свои данные. +
+ +

Страница кажется достаточно простой, но мы также должны учитывать пользователей, которые не ввели так много данных как “MrMystery”.

+ +
+ Страница профиля для пользователя, который ввел лишь часть данных. +
+ +

Этот пользователь ввел только имя пользователя, поэтому выводим его вместо его полного имени, показываем аватар по умолчанию и некоторую замену для текста в остальных полях. Это делает шаблон для этой страницы более сложным, с необходимостью множества выражений if для обработки пользователей с разным количеством информации. Мы могли бы сделать этот шаблон значительно чище, если бы могли переместить часть этой логики куда-нибудь еще.

+ +

/app/views/users/show.html.erb

+
<div id="profile">
+  <%= link_to_if @user.url.present?, ↵
+  image_tag("avatars/#{avatar_name(@user)}", class: "avatar"), ↵
+  @user.url %>
+  <h1><%= link_to_if @user.url.present?, ↵
+    (@user.full_name.present? ? @user.full_name : ↵
+    @user.username), @user.url %></h1>
+  <dl>
+    <dt>Username:</dt>
+    <dd><%= @user.username %></dd>
+    <dt>Member Since:</dt>
+    <dd><%= @user.member_since %></dd>
+    <dt>Website:</dt>
+    <dd>
+    <% if @user.url.present? %>
+      <%= link_to @user.url, @user.url %>
+    <% else %>
+      <span class="none">None given</span>
+    <% end %>
+    </dd>
+    <dt>Twitter:</dt>
+    <dd>
+    <% if @user.twitter_name.present? %>
+      <%= link_to @user.twitter_name, ↵
+  "http://twitter.com/#{@user.twitter_name}" %>
+    <% else %>
+      <span class="none">None given</span>
+    <% end %>
+    </dd>
+    <dt>Bio:</dt>
+    <dd>
+    <% if @user.bio.present? %>
+      <%=raw Redcarpet.new(@user.bio, :hard_wrap, :filter_html, ↵
+        :autolink).to_html %>
+    <% else %>
+      <span class="none">None given</span>
+    <% end %>
+    </dd>
+  </dl>
+</div>
+ +

Так как эта логика относится к вьюхам, мы не можем выделить ее в модель. Одним из решений было бы использования методов хелперов. Мы уже используем один, обозначаемый image_tag в этом шаблоне для отображения аватара. Давайте взглянем на него.

+ +

/app/helpers/users_helper.rb

+
module UsersHelper
+  def avatar_name(user)
+    if user.avatar_image_name.present?
+      user.avatar_image_name
+    else
+      "default.png"
+    end
+  end
+end
+ +

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

+ +

Установка Draper

+ +

Этот сценарий - отличная возможность для использования presenter или декоратора. Так как Draper относится к ним, давайте добавим его в наше приложение. Gem Draper устанавливается как обычно, добавлением его в Gemfile с последующим запуском bundle.

+ +

/Gemfile

+
source 'http://rubygems.org'
+
+gem 'rails', '3.1.0'
+gem 'sqlite3'
+
+
+# Gems used only for assets and not required
+# in production environments by default.
+group :assets do
+  gem 'sass-rails', "  ~> 3.1.0"
+  gem 'coffee-rails', "~> 3.1.0"
+  gem 'uglifier'
+end
+
+gem 'jquery-rails'
+gem 'redcarpet'
+
+gem 'draper'
+ +

Когда Draper установлен, мы создадим декоратор для нашей модели User, запустив генератор draper:decorator.

+ +
$ rails g draper:decorator user
+      create  app/decorators
+      create  app/decorators/application_decorator.rb
+      create  app/decorators/user_decorator.rb
+ +

Раз это наш первый декоратор, то также будет сгенерирован application_decorator. Любые декораторы, которые мы генерируем, наследуются от ApplicationDecorator, поэтому мы можем разместить там любую функциональность, которую хотим разделить между декораторами.

+ +

Класс UserDecorator достаточно простой, состоящий преимущественно из комментариев, которые объясняют, как собственно он работает. Давайте сразу начнем чистить наши шаблоны.

+ +

Приведение в порядок страницы профиля

+ +

Для использования Draper на нашей странице профиля для начала нужно сделать изменения в экшене show контроллера UsersController. Этот экшен сейчас извлекает пользователя как обычно.

+ +

/app/controllers/users_controller.rb

+
class UsersController < ApplicationController
+  def index
+    @users = User.all
+  end
+
+  def show
+    @user = User.find(params[:id])
+  end
+end
+ +

Мы должны обернуть этого пользователя в наш декоратор, для чего мы заменяем User.find на UserDecorator.find.

+ +

/app/controllers/users_controller.rb

+
def show
+  @user = UserDecorator.find(params[:id])
+end
+ +

Этот код теперь вернет объект UserDecorator, который оборачивает объект User и по умолчанию делегирует все методы ему (об этом – позже). Экшен будет работать точно так же, как и до этого, даже когда мы работаем с UserDecorator вместо User. Теперь мы можем начать чистить наши вьюхи и начнем с кода, который отображает аватар пользователя.

+ +

/app/views/users/show.html.erb

+
<%= link_to_if @user.url.present?, image_tag( ↵
+  "avatars/#{avatar_name(@user)}", class: "avatar"), @user.url %>
+ +

Мы заменим это во вьюхе следующим кодом:

+ +

/app/views/users/show.html.erb

+
<%= @user.avatar %>
+ +

Этот код обращается к методу avatar в UserDecorator, который мы сейчас напишем. Есть несколько вещей, которые стоит учитывать во время написания этого метода. Всякий раз, когда вызывается метод хелпера из декоратора, такой как наш link_to_if, мы должны вызывать его через метод h (что означает “helpers”). Когда мы хотим сослаться на модель, мы вызываем вместо нее метод model, в данном случае вместо @user.

+ +

Код, который мы скопировали из вьюхи в avatar, вызывает метод хелпера avatar_name. Так как мы вызываем avatar_name из нашего декоратора, мы переместим его туда из класса UsersHelper. Теперь, имея метод в том же классе, нам не нужно передавать ему объект User, и мы можем заменить все обращения к пользователю на model.

+ +

/app/decorators/user_decorator.rb

+
class UserDecorator < ApplicationDecorator
+  decorates :user
+
+  def avatar
+    h.link_to_if model.url.present?, h.image_tag("avatars/#{avatar_name}", class: "avatar"), model.url
+  end
+
+  private
+  def avatar_name
+    if model.avatar_image_name.present?
+      model.avatar_image_name
+    else
+      "default.png"
+    end
+  end
+end
+ +

Далее мы приведем в порядок код, который отображает имя пользователя. Мы заменим этот код во вьюхе:

+ +

/app/views/users/show.html.erb

+
<h1><%= link_to_if @user.url.present?, (@user.full_name.present? ? @user.full_name : @user.username), @user.url %></h1>
+ +

на это:

+ +

/app/views/users/show.html.erb

+
<h1><%= @user.linked_name %></h1>
+ +

Нам потребуется написать метод linked_name в UserDecorator. Есть сходства между кодом, который мы взяли из шаблона и методом avatar, который мы написали ранее. Они оба отображают ссылку, содержимое которой зависит от наличия url пользователя. Так как мы используем класс, то достаточно просто устранить это дублирование.

+ +

Чтобы обеспечить создание ссылки, мы создадим новый private метод site_link, который принимает содержимое как параметр. Мы можем использовать этот метод потом как в avatar, так и в linked_name методах, чтобы привести их в порядок. Как раньше мы заменили любые обращения к @user в linked_name на model. Сделав всё это, наш декоратор теперь выглядит так:

+ +

app/decorators/user_decorator.rb

+
class UserDecorator < ApplicationDecorator
+  decorates :user
+
+  def avatar
+    site_link h.image_tag("avatars/#{avatar_name}", ↵
+      class: "avatar")
+  end
+
+  def linked_name
+    site_link(model.full_name.present? ? model.full_name : ↵
+      model.username)
+  end
+
+  private
+  def site_link(content)
+    h.link_to_if model.url.present?, content, model.url
+  end
+
+  def avatar_name
+    if model.avatar_image_name.present?
+      model.avatar_image_name
+    else
+      "default.png"
+    end
+  end
+end
+ +

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

+ +

Наш шаблон уже выглядит намного чище, но есть еще много чего мы можем сделать. Далее мы рефакторим большой кусок кода вьюхи - код, который выводит ссылку на вебсайт пользователя.

+ +

/app/views/user/show.html.erb

+
<dt>Website:</dt>
+<dd>
+  <% if @user.url.present? %>
+    <%= link_to @user.url, @user.url %>
+  <% else %>
+    <span class="none">None given</span>
+  <% end %>
+</dd>
+ +

Мы заменим на это:

+ +

/app/views/user/show.html.erb

+
<dt>Website:</dt>
+<dd><%= @user.website %></dd>
+ +

Так же как и до этого, мы создадим метод в классе декоратора. Мы можем видеть из кода, который мы убрали из вьюхи, что если у пользователя нет url, то выводится некоторый HTML. Можно просто возвратить это как строку, но мы не хотим размещать голый HTML в строке Ruby. Другим решением могло бы быть переместить код в partial и вывести его, но так как нам нужно только лишь вывести единственный элемент HTML, больше смысла будет использовать метод хелпера content_tag.

+ +

/app/decorators/user_decorator.rb

+
def website
+  if model.url.present?
+    h.link_to model.url, model.url
+  else
+    h.content_tag :span, "None given", class: "none"
+  end
+end
+ +

Можно сделать аналогично для двух частей шаблона, которые выводят информацию Twitter и биографию пользователя. Здесь не будут показаны детали, но в результате изменений код нашей вьюхи выглядит значительно чище.

+ +

/app/views/users/show.html.erb

+
<div id="profile">
+  <%= @user.avatar %>
+  <h1><%= @user.linked_name %></h1>
+  <dl>
+    <dt>Username:</dt>
+    <dd><%= @user.username %></dd>
+    <dt>Member Since:</dt>
+    <dd><%= @user.member_since %></dd>
+    <dt>Website:</dt>
+    <dd><%= @user.website %></dd>
+    <dt>Twitter:</dt>
+    <dd><%= @user.twitter %></dd>
+    <dt>Bio:</dt>
+    <dd><%= @user.bio %></dd>
+  </dl>
+</div>
+ +

Новые методы в декораторе – twitter и bio выглядят следующим образом:

+ +

/app/decorators/user_decorator.rb

+
def website
+  if model.url.present?
+    h.link_to model.url, model.url
+  else
+    h.content_tag :span, "None given", class: "none"
+  end
+end
+
+def twitter
+  if model.twitter_name.present?
+    h.link_to model.twitter_name, ↵
+      "http://twitter.com/#{model.twitter_name}"
+  else
+    h.content_tag :span, "None given", class: "none"
+  end
+end
+
+def bio
+  if model.bio.present?
+    Redcarpet.new(model.bio, :hard_wrap, :filter_html, ↵
+ 		:autolink).to_html.html_safe
+  else
+    h.content_tag :span, "None given", class: "none"
+  end
+end
+ +

Два новых метода очень похожи друг на друга и на метод website, написанный ранее. Есть небольшое дублирование между тремя методами, особенно в каждом выражении else, поэтому было бы правильно выделить эту часть в отдельный метод.

+ +

Можно для этого использовать блок. Мы выделим код из else в отдельный метод, назовем его handle_none. Передадим значение, наличие которого мы хотим проверить в этот метод, а так же блок. Если значение существует, код в блоке будет выполнен, а иначе будет выведен тег span. Тогда мы можем использовать этот handle_none, чтобы очистить методы website, twitter и bio.

+ +

/app/decorators/user_decorator.rb

+
def website
+  handle_none model.url do
+    h.link_to model.url, model.url
+  end
+end
+
+def twitter
+  handle_none model.twitter_name do
+    h.link_to model.twitter_name, ↵
+      "http://twitter.com/#{model.twitter_name}"
+  end
+end
+
+def bio
+  handle_none model.bio do
+    Redcarpet.new(model.bio, :hard_wrap, :filter_html, ↵
+      :autolink).to_html.html_safe
+  end
+end
+
+private
+def handle_none(value)
+  if value.present?
+    yield
+  else
+    h.content_tag :span, "None given", class: "none"
+  end
+end
+ +

Еще одно изменение, которое мы можем сделать - выделить вывод разметки в ApplicationDecorator так, чтобы можно было бы вызывать его из любого другого декоратора, который мы могли бы сделать. Мы можем создать новый метод markdown, который будет выводить любой текст, который мы передадим в него.

+ +

/app/decorators/application_decorator.rb

+
class ApplicationDecorator < Draper::Base
+  def markdown(text)
+    Redcarpet.new(text, :hard_wrap, :filter_html, ↵
+      :autolink).to_html.html_safe
+  end
+end
+ +

Теперь в UserDecorator мы можем изменить метод bio так, чтобы он вызывал markdown.

+ +

/app/decorators/user_decorator.rb

+
def bio
+  handle_none model.bio do
+    markdown(model.bio)
+  end
+end
+ +

Изменение модели

+ +

Теперь у нас есть декоратор, поэтому будет правильно просмотреть модель на предмет кода, относящегося ко вьюхам, который мы можем переместить в соответствующий декоратор. Например, в нашей модели User есть метод member_since, который форматирует времяcreated_at пользователя. Этот код может считаться относящимся ко вьюхам, так как все что он делает - возвращает форматированную строку, поэтому мы переместим его в декоратор.

+ +

/app/models/user.rb

+
class User < ActiveRecord::Base
+  def member_since
+    created_at.strftime("%B %e, %Y")
+  end
+end
+ +

Все что нам нужно сделать - переместить метод в декоратор и приписать model перед created_at.

+ +

/app/decorators/user_decorator.rb

+
def member_since
+  model.created_at.strftime("%B %e, %Y")
+end
+ +

Ограничение доступа к модели с помощью метода allows

+ +

Пока мы изменяем UserDecorator, есть еще одна возможность Draper, которую мы продемонстрируем: метод allows. В его нынешнем виде UserDecorator делегирует все свои методы объекту User, но мы можем выбирать, какие методы передаются модели User, используя метод allows и передавая ему имя методов, которые мы хотим делегировать.

+ +

/app/decorators/user_decorator.rb

+
class UserDecorator < ApplicationDecorator
+  decorates :user
+  allows :username
+
+  # Other methods omitted
+end
+ +

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

+ +

Теперь мы закончили с рефакторингом с помощью декоратора и попробуем загрузить страницу профиля пользователя снова, чтобы убедиться, что до сих пор всё выглядит так же.

+ +
+ Страница профиля пользователя выглядит так же после наших изменений. +
+ +

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

+ +
+ Страница профиля для MrMystery также не изменилась. +
+ +

Используя декоратор, наш шаблон show был уменьшен с 1050 байтов в 34 строк до 382 байтов в 16 строк, уменьшение в размере - практически на две трети. Так всё выглядит значительно чище и мы упростили себе задачу редактирования, если бы понадобилось изменять вёрстку страницы.

\ No newline at end of file