make_resourceful #1

Last Modified on 2008/04/11 11:09 by 크레이지DK

RESTful Rails

RailsREST 기반의 개발 방식은 개발의 일관성과 편의성을 제공해준다. REST 기반의 개발에서는 웹을 페이지의 관점이 아닌 resource 관점에서 바라본다. 게시판에 글을 작성하고, 읽는 것이 아니라, 글이라는 resource를 생성하고,  가져오는 것이 된다. 또한 웹사이트의 요소를 resource로서 정확하게 정의하기만 하면, 해당 resource에 대한 CRUD 관점에서의 접근을 통해서 메소드명이나 경로명 등이 바로 결정되게 된다. 따라서 개발자는 그런 것들에 대해서 고민할 필요없이 핵심 코드에만 집중할 수 있게 된다. 예를 들어 블로그 어플리케이션을 개발하는 경우를 생각해보자. 만약 Rails가 아닌 다른 웹프레임워크(PHP, ASP 등)로 개발을 하거나, Rails에서 RESTful하지 않게 개발하는 경우에는, Post를 등록하고, 보여주고, 수정, 삭제하는 기능을 위해서 각각의 메소드명을 고민해야하고, 그에 대응하는 경로명을 고민해야한다. 하지만 Rails의 REST 기반의 개발 방식에서는 Post라는 resource가 결정된다면, 그에 대한 CRUD 메소드 및 경로가 정해진다. 다음은 Post resource를 처리하는 PostsController의 전형적인 소스 코드이다.

 

  1. class PostsController < ApplicationController
      # GET /posts
      # GET /posts.xml
      def index
        @posts = Post.find(:all)

        respond_to do |format|
          format.html # index.html.erb
          format.xml  { render :xml => @posts }
        end
      end

      # GET /posts/1
      # GET /posts/1.xml
      def show
        @post = Post.find(params[:id])

        respond_to do |format|
          format.html # show.html.erb
          format.xml  { render :xml => @post }
        end
      end

      # GET /posts/new
      # GET /posts/new.xml
      def new
        @post = Post.new

        respond_to do |format|
          format.html # new.html.erb
          format.xml  { render :xml => @post }
        end
      end

      # GET /posts/1/edit
      def edit
        @post = Post.find(params[:id])
      end

      # POST /posts
      # POST /posts.xml
      def create
        @post = Post.new(params[:post])

        respond_to do |format|
          if @post.save
            flash[:notice] = 'Post was successfully created.'
            format.html { redirect_to(@post) }
            format.xml  { render :xml => @post, :status => :created, :location => @post }
          else
            format.html { render :action => "new" }
            format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
          end
        end
      end

      # PUT /posts/1
      # PUT /posts/1.xml
      def update
        @post = Post.find(params[:id])

        respond_to do |format|
          if @post.update_attributes(params[:post])
            flash[:notice] = 'Post was successfully updated.'
            format.html { redirect_to(@post) }
            format.xml  { head :ok }
          else
            format.html { render :action => "edit" }
            format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
          end
        end
      end

      # DELETE /posts/1
      # DELETE /posts/1.xml
      def destroy
        @post = Post.find(params[:id])
        @post.destroy

        respond_to do |format|
          format.html { redirect_to(posts_url) }
          format.xml  { head :ok }
        end
      end
    end

 

각각의 메소드의 역할 및 접근 경로는 다음과 같다.

메소드명 타입 역할 접근경로
index collection 리소스 collection에 대한 처리 GET /posts
show member 특정 리소스에 대한 처리 GET /posts/1
new new 신규 리소스를 추가하기 위한 폼 GET /posts/new
edit member 특정 리소스를 수정하기 위한 폼 GET /posts/1/edit
create collection 리소스의 생성 POST /posts
update member 특정 리소스의 수정 PUT /posts/1
destroy member 특정 리소스의 삭제 DELETE /posts/1

더 자세한 RESTful Rails와 REST Routing에 대해서는 다른 문서에서 상세히 살펴보기로 하고, 여기서는 오늘의 주제인 make_resourceful 플러그인에 집중해보자.

 

 

DRY를 위한 make_resourceful

REST 기반의 Rails Application을 개발하다보면 위와 같은 Controller 코드를 반복적으로 사용하게 된다. 이는 Rails의 DRY(Don't Repeat Yourself) 정신에 맞지 않는다. 이러한 중복을 제거해주는 플러그인이 바로 make_resourceful 플러그인이다. 다음은 make_resourceful 플러그인을 이용해서 위의 소스코드와 동일한 기능을 하는 PostsController를 구현한 것이다.

 

  1. class PostsController < ApplicationController
      make_resourceful do
        actions :all
      end
    end

 

위의 코드가 끝이다! make_resourceful이란 클래스 메소드에 설정을 위한 블럭을 전달해주기만 하면, 기본적인 7개의 메소드들이 생성되게 된다. 그러면 make_resourceful 플러그인의 구체적인 사용법을 살펴보자.

 

설치하기

플러그인은 다음 명령어로 설치할 수 있다. 

 

  1. $ ./script/plugin install http://svn.hamptoncatlin.com/make_resourceful/tags/make_resourceful

 

resourceful_scaffold

make_resourceful 플러그인에서는 resourceful_scaffold 라는 생성자도 함께 제공한다. 다음 명령어로 모델, 뷰, 테스트 및 make_resourceful 컨트롤러를 생성할 후 있다.

 

  1. $ ./script/generate resourceful_scaffold ResourceName attribute1:type1 attribute2:type2 ...

 

 

기본 action 커스터마이징하기 - Resourceful::Builder

make_resourceful 블럭은 Resourceful::Builder 클래스의 인스턴스의 문맥(context)에서 평가되게 된다. Resourceful::Builder 클래스는 make_resourceful에 의해서 생성되는 기본적인 action을 customizing하기 위해서 몇 개의 메소드를 제공한다. 이제 각 메소드의 사용법을 살펴보자.

 

actions(*available_actions)

actions 메소드는 위의 예제에서 이미 보았던 메소드이다. 이 actions 메소드에 전달된 action들만이 정의되게 된다. 인수로 :all 이 전달되는 경우, 기본 action 7개 (index, show, new, edit, create, update, destroy)가 모두 정의된다. 기본 action들은 Resourceful::Default::Actions에 정의되어 있다.

 

  1. make_resourceful do
  2.   actions :all  #=> 모든 메소드
  3.   actions :show, :new, :create  #=> show, new, create action만 정의.
  4. end

 

다음은 Resourceful::Default::Actions에 정의되어 있는 기본 action들이다. 내부적으로는 actions 메소드에 의해서 설정된 메소드를 제외한 나머지 메소드들이 remove_method에 의해서 제거된 후에, controller에 mix-in 되게 된다.

  1. module Actions
      # GET /foos
      def index
        load_objects
        before :index
        response_for :index
      end

      # GET /foos/12
      def show
        load_object
        before :show
        response_for :show
      rescue
        response_for :show_fails
      end

      # POST /foos
      def create
        build_object
        load_object
        before :create
        if current_object.save
          save_succeeded!
          after :create
          response_for :create
        else
          save_failed!
          after :create_fails
          response_for :create_fails
        end
      end

      # PUT /foos/12
      def update
        load_object
        before :update
        if current_object.update_attributes object_parameters
          save_succeeded!
          after :update
          response_for :update
        else
          save_failed!
          after :update_fails
          response_for :update_fails
        end
      end

      # GET /foos/new
      def new
        build_object
        load_object
        before :new
        response_for :new
      end

      # GET /foos/12/edit
      def edit
        load_object
        before :edit
        response_for :edit
      end

      # DELETE /foos/12
      def destroy
        load_object
        before :destroy
        if current_object.destroy
          after :destroy
          response_for :destroy
        else
          after :destroy_fails
          response_for :destroy_fails
        end
      end
    end

 

before(*events, &block)

before 메소드는 특정 action의 앞에서 실행될 코드 블럭을 정의한다. 기본 메소드 7개 모두 인수로 전달될 수 있다. 위의 기본 action들의 정의에서도 볼 수 있듯이, before 메소드에 의해서 전달되는 블럭은 객체가 로드(load_object or load_objects)와 데이터베이스 처리 혹은 response 사이에서 실행되게 된다. (action 이전에 실행되는 before_filter와는 다른 것이므로 혼동하지 말자.)

 

  1. before :show, :edit do
  2.   @page_title = current_object.title
  3. end

위의 코드는 show, edit 액션에서 response 직전에 @page_title이라는 인스턴스 변수를 설정하라는 것을 의미한다.

 

after(*events, &block)

after 메소드는 특정 action의 뒤에서 실행될 코드 블럭을 정의한다. 위의 기본 action들의 정의를 살펴보면, after 메소드는 create, update, destroy에서만 정의된 것을 볼 수 있다. after 메소드에는 다음 두 종류의 인수가 전달될 수 있다.

  • :create, :update, :destory : 각각의 데이터베이스 처리가 성공한 후에 실행되게 되는 블럭을 정의한다.
  • :create_fails, :update_fails, :destroy_fails : 각각의 데이터베이스 처리가 실패했을 경우, 실행할 블럭을 정의한다.

 

  1. after :destroy_fails do
  2.   flash[:error] = '삭제하는데 실패하였습니다.'
  3. end

위의 코드는 destory action에서 데이터베이스 처리가 실패한 경우, flash 메시지를 설정하는 소스 코드이다.

 

response_for(*actions, &block)

response_for 메소드는 기본 response 대신에 사용될 코드 블럭을 정의한다. 블럭은 format 인수를 가질 수도 있고, 갖지 않을 수도 있다. 블럭이 format 인수를 가질 경우에는 Rails의 respond_to 메소드와 동일한 문법이 적용된다. 블럭이 인수를 갖지 않는 경우에는 HTML에 대한 응답만을 의미한다.

 

  1. response_for :index do |format|
  2.   format.html
  3.   format.atom do
  4.     headers['Content-Type'] = 'application/atom+xml; charset=utf-8'
  5.     render :action => 'atom', :layout => false
  6.   end
  7. end

위의 코드는 index action의 기본 response 대신에 atom 확장자를 지원하는 형태로 기능을 변경하였다.

 

  1. response_for :new do
  2.   render :action => 'edit'
  3. end
  4.  
  5. response_for :new do |format|
  6.   format.html { render :action => 'edit' }
  7. end

위의 두 코드는 동일한 기능을 한다.

 

 

지금까지 make_resourceful 블럭에서 기본 action을 customizing하기 위해 사용되는 몇 개의 Builder 메소드를 살펴보았다. 2부에서는 리소스의 인스턴스 변수에 대한 간단한 접근방식을 제공하는 Accessors와 쉬운 경로설정을 돕는 URL helper들에 대해서 살펴볼 것이다. 또한 hidden 파라미터를 통해서 response의 동작 방식을 변경하는 법을 알아볼 것이다.