Ruby on Rails Tips and Techs
디자인 패턴에 관심이 있는 분들은 MVC(Model-View-Controller)와 MVP(Model-View-Presenter)에 대해 들어보셨을 겁니다. 그리고 이보다 조금 더 관심을 가지고 계신 분들이 들어보셨을 법 한 패턴이 하나 더 있습니다. 바로 MVVM(Model-View-ViewModel)이죠.
MVVM은 마틴 파울러가 주장한 설계 패턴 중 하나인데, 레일즈 개발자가 이해하기 쉽도록 축약하자면 ”웹 서버에서 받는 요청들의 대부분은 컨트롤러를 거칠 필요가 없다.” 라는 이론에 기인합니다. 그래서 컨트롤러를 대신해서 모델을 뷰에 맞게 2차 가공한 일명 뷰모델(ViewModel)이라는 게 존재하고, 이 뷰모델이 컨트롤러를 대신하는 원리이죠.
오늘 소개할 decent_exposure는 이 MVVM의 개념을 레일즈에 적용한 젬 중 하나입니다. 다음의 예를 살펴보죠.
class ProductsController < ApplicationController
expose(:product)
# ...
end
위 코드는 decent_exposure의 expose라는 메소드를 호출한 상품(Product) 컨트롤러입니다.
언뜻 보면 별 내용이 없지만, 코드를 이해하기 쉽도록 풀어보겠습니다.
class ProductsController < ApplicationController
def product
@product ||= params[:id] ? Product.find(params[:id]) : Product.new(params[:product])
end
helper_method :product
hide_action :product
# ...
end
product라는 특이한 메소드가 하나 있고, 이 메소드는 뷰를 위한 헬퍼로 등록되어 있습니다.
언뜻 쓸모 없어보이는 이 메소드는, 그러나 굉장히 유용하며 다양한 기능을 포함하고 있는 마법 상자와도 같습니다. 이제 컨트롤러 코드를 고쳐볼까요?
# Before
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
end
def new
@product = Product.new
end
def edit
@product = Product.find(params[:id])
end
def create
@product = Product.new(params[:product])
if @product.save
redirect_to @product, notice: 'Product was successfully created.'
else
render action: "new"
end
end
def update
@product = Product.find(params[:id])
if @product.update_attributes(params[:product])
redirect_to @product, notice: 'Product was successfully updated.'
else
render action: "edit"
end
end
def destroy
@product = Product.find(params[:id])
@product.destroy
redirect_to products_url
end
end
이제 expose를 이용해 보겠습니다. 코드는 아래와 같이 바뀝니다.
# After
class ProductsController < ApplicationController
expose :product
def create
if product.save
redirect_to products_path, :notice => "Successfully created product."
else
render :new
end
end
def update
if product.save
redirect_to products_path, :notice => "Successfully updated product."
else
render :edit
end
end
def destroy
product.destroy
redirect_to products_url, :notice => "Successfully destroyed product."
end
end
코드가 줄어들었죠? 이해하기 어렵다면 맨 위에 있는 expose의 해석본과 함께 살펴보세요. 뷰에서는 @product대신 product를 이용하면 이전 코드와 동일한 역할을 수행합니다.
이처럼 우리 작성하는 레일즈 액션 중에서 컨트롤러를 거치지 않고 바로 뷰로 넘겨도 문제없는 액선들이 다수 존재합니다. decent_exposure는 이러한 코드들의 중복 요소를 제거하는데 매우 유용하죠.
다음은 decent_exposure의 위키에 있는 또 다른 예제입니다.
# Before
class PeopleController < ApplicationController
respond_to :html, :xml, :json
before_filter :get_company
def index
@people = @company.people.where(params.except(:action, :controller, :format))
end
def show
@person = @company.people.find(params[:id])
end
def new
@person = @company.people.build
end
def edit
@person = @company.people.find(params[:id])
end
def create
@person = @company.people.build(params[:person])
@person.save
respond_with(@person)
end
def update
@person = @company.people.find(params[:id])
@person.update_attributes(params[:person])
respond_with(@person)
end
def destroy
@person = @company.people.find(params[:id])
@person.destroy
respond_with(@person)
end
private
def get_company
@company = Company.find(params[:company_id])
end
end
# After
class PeopleController < ApplicationController
respond_to :html, :xml, :json
expose(:company) { Company.find(params[:company_id]) }
expose(:people) do
company.people.where(params.except(:action, :controller, :format))
end
expose(:person)
def create
person.save
respond_with(person)
end
def update
person.save
respond_with(person)
end
def destroy
person.destroy
respond_with(person)
end
end
매우 쉽고 간편하죠? 레일즈 코드를 RESTful하게 작성했다면 지금 당장이라도 decent_exposure를 이용해 프로젝트의 코드 규모를 줄일 수 있습니다.
하지만 decent_exposure는 레일즈 커뮤니티에서도 많은 논란이 있는 젬입니다. 일단 MVVM의 본질적인 부분을 다루지 못했다는 의견이 첫번째, 그리고 레일즈의 내부 구현상 MVC 외의 패턴(이는 MVP와 MVVM 모두를 가리킵니다)을 완벽하게 적용하기 어렵다는 의견이 두번째입니다.
그렇기에 decent_exposure를 사용할 때 이를 유념하고 이용해 주세요.
-
- 출처 & 관련자료 -
GitHub : voxdolo/decent_exposure