• Twitter
  • RSS
  • Archive

Rails Maniac

Ruby on Rails Tips and Techs

  • Note

    8th October 2011

    decent exposure

    디자인 패턴에 관심이 있는 분들은 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

    Wikipedia : Model View ViewModel

    RailsCasts : Decent Exposure

    루비 루비 온 레일즈 Ruby Ruby on Rails MVVM Decent Exposure
Next
contact me on twitter or E-mail

Tumblr Themes created by Obox

hit counter
hit counter