Rails/디자인패턴(Design Pattern)/Decorator/Draper Gem을 이용
Rails 포스팅에 앞선 여담이지만 웹 프레임워크는 참 무시무시하다. 솔직히 oop개념과 이것을 기초로 한 디자인 패턴 등을 잘 몰라도(아직 몰라서 공부를 해야함....) 그럭저럭 기능 개발을 하는데 당장 무리가 없는 것 같다고 느낀다.. 이게 바로 무시무시한 점이다. 참 편리하면서도 이거만 얕게 쓰면 숲을 못보는 개발자가 되겠구나 싶다. 그러므로 항상 지식에 대한 경계를 늦추지 않아야 할 것이다.. 각설하고, 그런의미에서 레일즈에서 간략히 디자인 패턴을 적용해보겠다.(이마저도 Gem의 도움을 받아서지만 말이다..)
Decorator 패턴 적용
먼저, Decorator 패턴은..
객체간 결합으로 하나의 객체가 갖고 있는 기능을 확장해주는 패턴을 말한다.
보통은 기본 객체가 있으면, 여기에 추가 기능을 정의한 Decorator 객체를 결합하여 기능을 동적으로 추가해서 쓴다.
아래 캡쳐와 같이 우리가 결국 사용할 클래스를 Component Class라 하자. (제일 상위에 있는 클래스)
Concrete Component Class는 기본빵으로 사용하는 기능을 정의하여 항상 들여다 쓸 것이고, ( 대개는 Component overriding 하여 사용하는듯)
Decorator Class 는 우리가 동적으로 (즉, 넣었다 뺄 수 있는 유연한..) Component에 추가할 Class가 될 것이다.
여기까지가 Decorator Pattern에 대한 간략한 설명이고,
Rails에서 Decorator Pattern 은 주로 MVC 패턴에서 M 과 V 간의 의존성을 느슨하게 만들며, 동시에 C를 가볍게 하는 경우에 사용한다.
다시 말하면, Model 에서 나온 오브젝트 데이터를 View에서 보여주기 위해서는 가공이 필요한 경우가 많은데, 이를 컨트롤러나 View 단에서가 아닌, Decorator라는 새로운 클래스에서 가공을 해줘 기능을 분리시켜주는거다.
예를들면, Model 있는 정보를 View로 쏴주는 게시판 기능이 있다고 하자. 그럼, 게시판 정보를 담고 있는 모델에서 알맞게 만들어낸 게시판 정보 Object들을 View로 쏴주면서 게시판 정보를 View에서 비로소 볼 수 있는 것이다. 이때 Model 에 있는 데이터를 View로 보여주기 전에 약간의 가공이 필요한 경우가 있다. 가령, 생성 시간 정보를 현지 시간에 맞게 바꿔서 보여주거나, Boolean 등으로 출력되는 값을 true / false가 아닌 클라이언트가 이해하기 쉬운 값으로의 변경 등이 있겠다..,
Rails는 친절하게도 Draper라는 gem 을 통해 아래와 같이 Decorator 패턴을 손쉽게 구현할 수 있다. (https://github.com/drapergem/draper) . Model Class이름을 딴 Decorator로 잘 명명만 해주면 알아서 해당 모델에 대한 Decorator 클래스로 잡아주는 참 편리한.. 잼이다.
간단하게 구현한 코드를 보자.. 매우 간단한 기능이다. 질문 관리 어드민인데, 질문 테이블 중 게시 유무라는 Attribute는 모델 내에는 true/ false Boolean 값을 갖는데, 이를 View에서 알기 쉽게 on/off 로 바꿔주자.
<Decorator - admin_ask_decorator.rb>
내가 만든 모델 클래스 이름은 AdminAsk이기 때문에 모델 클래스이름 + Decorator로 아래와 같이 Class이름을 지엇다. 단/복수 구분까지 해서 이름을 지어줘야함.. (레일즈는 Naming Convention이 매우 매우 중요하다)
class AdminAskDecorator < ApplicationDecorator
delegate_all
def bool_to_val
if object.post_open == true
"On"
else
"Off"
end
end
end
<Controller - asks_controller.rb>
컨트롤러에 내가 Decorator 패턴이 필요하다고 생각이 드는 method에 추가해주자. 여기서는 index 리스트에서만 Decorator 패턴을 적용하고자 하므로, index method의 마지막 부분에 '.decorate' 매쏘드를 사용하였다.( 17 L)
.decorate 매쏘드가 Implementation of Decorator 정도..
class Admin::AsksController < AdminController
before_action :set_page_title
before_action :set_ask, except: [:index, :create, :new]
def index
@asks = AdminAsk.all
@asks = @asks.search_by_ask_type(params[:ask_type]) if params[:ask_type].present?
@asks = @asks.search_by_open(params[:post_open]) if params[:post_open].present?
@asks = @asks.search_by_category(params[:code_id]) if params[:code_id].present?
@asks = @asks.search_by_condition(params[:condition], params[:q]) if params[:condition].present? && params[:q].present?
@asks = @asks.order(id: :desc)
@asks = @asks.page(params[:page]).per(params[:per_page])
@total_count = @asks.size
@asks = @asks.decorate # AdminAsk Decorate Pattern의 적용
add_breadcrumb t('pages.admin.ask.list'), 'asks'
end
< Model - admin_ask.rb>
Admin_ask Model 파일로 이미 Decorator패턴에 모델 데이터의 가공을 위임했기 때문에 코드는 그대로이다. (그냥 아무것도 없다는 걸 보여주려고 올린다.)
class AdminAsk < ApplicationRecord
acts_as_paranoid
enum ask_type: {ask_home: 0, ask_today_q: 1, ask_profile: 2}
enum code_id: {
sat: 38, language: 55, public_servant: 69, employment: 85,
life:128, middle_school: 157, specialist: 158,
elementary_school: 165, startup: 169, certificate: 177}
scope :search_by_ask_type, -> (ask_type) { where("ask_type =?", ask_type) }
scope :search_by_open, -> (post_open) { where("post_open = ?", post_open)}
scope :search_by_category, -> (code_id) { where("code_id = ?", code_id)}
scope :search_by_condition, -> (condition, query) { where("#{condition} LIKE :query", query: "%#{query}%") }
end
< V - _ask.html.erb >
View 단에서는 controller 내에서 적용한 Decorate 에 의해 Decorator 클래스 내에서 잘 정의된 method를 그대로 끌어다 쓰면 적용할 수 있다.. 'bool_to_val' 라는 boolean을 value로 적용해주는 method로 출력을 바꿔줌..
<tr>
<td><%= item_count(ask_counter) %></td>
<td><%= ask.id %></td>
<td><%= t("activerecord.attributes.ask.ask_type_name.#{ask.ask_type}") %></td>
<td>
<% if ask.code_id.present? %>
<%= t("activerecord.attributes.ask.code_name.#{ask.code_id}") %>
<% else %>
<%= "(없음)" %>
<% end %>
</td>
<td><a href="<%= admin_ask_path ask.id %>"><%= ask.body %></a></td>
<td><%= ask.bool_to_val %></td>
<td><%= ask.created_at.strftime("%Y-%m-%d %H:%M") %></td>
<td><%= ask.updated_at.strftime("%Y-%m-%d %H:%M") %></td>
</tr>
< Client View>
boolean에서 On / Off 로 View에 적용..
Why Decorator in Rails?
→ 굳이 Decorator 를 이용하는 이유는 보통 MVC 패턴에서 Controller부분이 상당히 무거워지는 경우가 있는데, 적어도 View에 의존하고 있는 부분은 최대한 분리시켜 Controller를 가볍게 만들어주는 역할을 한다. 그래서 보통 Controller class가 너무 비대해지면서 View에 의존하는 가공 Method들이 많아질 때,
Decorator 패턴을 이용해 의존성을 분리하면 깔끔해진다. 내가 느끼기에는 Rails 의 기본 뼈대인 웹에서의 MVC 를 더욱 선명하게 분리시켜주는 역할을 해주고 있다. Controller에서는 그저 Model에서 생성된 Obj를 View로 쏴주고, View에서는 무슨 일이 일어나는지 신경 쓸 필요가 더 없어지기 때문이다. ( 이마저도 Decorator 에서 구현되므로..)
View에서 랜더링되는 속도는 큰 차이를 모르겠다 아직.. (적어도 어드민 index에서 10~20개 정도 보여주는 경우에 각 obj마다 decorate 패턴이 적용되더라도 큰 차이는 없다.. 이걸 컨트롤러 내부에서 다 처리하느냐 / 바깥에서 다른 클래스를 끌어와서 처리하느냐의 차이?)