메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

한빛랩스 - 지식에 가능성을 머지하다 / 강의 콘텐츠 무료로 수강하시고 피드백을 남겨주세요. ▶︎

IT/모바일

Ruport: 루비를 위한 업무 레포팅(2)

한빛미디어

|

2008-05-22

|

by HANBIT

11,376

제공 : 한빛 네트워크
저자 : Gregory Brown and Michael Milner
역자 : 이대엽
원문 : Ruport: Business Reporting for Ruby

형식자 템플릿(Formatter Template)

템플릿은 개별 형식자로부터 기본 형식화 값을 분리함으로써 일종의 추상화를 제공한다. 결국 Ruport의 형식자가 (만약 있다면) 구현할 템플릿 옵션을 고를 수 있으며, 템플릿의 일반적인 목표는 형식화 옵션에 대한 정형화된 인터페이스를 제공하는 것이다.

템플릿을 생성하는 것은 간단하므로 어떻게 템플릿을 사용하여 현재 PDF 보고서를 조작하는지를 간략하게 살펴볼 것이다. 다음 템플릿은 add_text에 의해 그려진 텍스트를 가운데에 위치시키고 크기를 늘린다.
app/reports/templates.rb
Ruport::Formatter::Template.create(:default) do |format|
  format.text = {
    :font_size => 16,
    :justification => :center
  }
end
이 코드를 동작하게 만들려면 명시적으로 environment.rb에 require를 추가해야 할 필요가 있는데, 클래스 탐색(class lookup)을 이용하여 자동으로 파일을 로딩할 수 없을 것이기 때문이다.
  require "app/reports/templates"
일단 require가 설정되면 기반 코드는 변경할 필요 없이 다음과 같은 PDF를 다시 만들어낼 수 있다:



이 템플릿이 기본적으로 모든 컨트롤러에서 사용될 필요는 없다. 그렇게 할 필요가 없다면 여러분은 템플릿에 이름을 준 후 출력결과를 렌더링할 때 이름으로 해당 템플릿을 참조할 수가 있을 것이다.
# 템플릿 내부
Ruport::Formatter::Template.create(:book_list) do |format|
  # ...
end

# 출력결과를 렌더링할 때
BookReport.render_pdf(:template => :book_list)
다른 템플릿으로부터 템플릿을 파생할 수도 있다:
Ruport::Formatter::Template.create(:small_centered, :base => :default) do |format|
  format.text.merge!(:font_size => 10)
end
마지막으로 템플릿을 이용하여 작업할 때 컨트롤러에서 모든 템플릿을 무시하게 만들려면 그렇게 할 수도 있다:
BookReport.render_pdf(:template => false)
Ruport의 형식자는 광범위한 템플릿 옵션을 지원하는데, 템플릿 옵션은 API 문서에서 찾아볼 수 있다.

지금까지 한 내용은 템플릿의 일면만을 다룬 것인데, 템플릿에 관해서는 알아둘 만한 가치가 있다. 여러분은 루비로 자신만의 형식자를 만들 수도 있으며, 훅(hook)을 구현함으로써 몇 가지 템플릿 옵션에 대해 알게 되어 보고서를 상당히 유연하게 만들 수 있을 것이다. 그렇지만 지금은 기본적인 사항만을 알아볼 것이며, 다음으로 레일즈 애플리케이션에서 이러한 보고서를 동작시키는 것에 대한 내용으로 넘어가도록 하자.

레일즈 컨트롤러와 뷰 연결하기

지금까지 기본적인 보고서를 만들어 보았으므로 레일즈 컨트롤러에서 어떻게 보고서를 생성하는지에 대해 알 수도 있을 것이다. 아마 보고서 형식을 레일즈 프로젝트에 통합하는 가장 쉬운 방법은 HTML일 것이다. 보고서가 HTML 출력결과를 만들어낼 것이므로 여러분은 원하는 뷰 가운데 하나로 해당 HTML을 삽입할 수가 있다.

레일즈 컨트롤러에서 index 액션을 만들어 HTML 보고서를 생성한 다음 인스턴스 변수에 저장해 보기로 하자.
  class BooksController < ApplicationController
    def index
      @book_report = BookReport.render_html
    end
  end
이렇게 한 후 이에 대응하는 뷰는 다음과 같이 단순해 질 수 있다:
  <%= @book_report %>
Bibliophile의 유일한 뷰는 약간 복잡해 보일 뿐인데, 대부분 형식을 지정한 것 때문이다. 동일한 기본 보고서를 가운데로 정렬해서 볼 수도 있을 것이다:



다른 형식으로 생성하는 그렇게 어렵진 않은데, 여러분은 요청된 데이터를 어떻게 반환할지 고려할 필요가 있다. 여러분이 직접 렌더링할 수는 없을 것이므로 레일즈의 send_data 메서드를 이용하여 브라우저로 결과를 흘러보낼(stream) 필요가 있다. 그렇지만 보고서 자체는 같은 방식으로 생성되므로 적절한 형식으로 대체하기만 하면 된다.

인쇄가능한 PDF 출력결과를 생성할 수 있는지 살펴보기로 하자. 우리는 PDF를 만들어 내기 위해 또 다른 메서드를 컨트롤러에 추가할 수 있다.
  class BooksController < ApplicationController
    def printable_list
      pdf = BookReport.render_pdf
      send_data pdf, :type => "application/pdf",
                     :filename => "books.pdf" 
    end
  end
여러분은 우리가 HTML 보고서에서 했던 것과 같은 방법으로 PDF 보고서를 렌더링하고 있다는 사실을 알 수 있을 것이다. 그렇지만 이번에는 결과를 변수에 저장한 다음 그 결과를 send_data 메서드의 데이터로 제공하고 있다. 그리고 콘텐트 타입(content type)과 파일명(filename)도 지정하고 있다. 그 밖에 이 메서드에 대한 링크를 추가하여 보고서의 PDF 버전을 생성할 수 있도록 만들 필요가 있을 것이다.

철두철미하게 하자면 아래는 보고서로부터 CSV 출력물을 생성하는 컨트롤러 액션이다. 이 코드는 다른 것과 동일한 패턴을 따른다.
  class BooksController < ApplicationController
    def csv_list
      csv = BookReport.render_csv
      send_data csv, :type => "text/csv",
                     :filename => "books.csv" 
    end
  end
보고서 생성을 레일즈 컨트롤러에 통합하는 것은 다른 여러 경우와 마찬가지로 간단하므로 여러분이 해야 할 일은 그뿐일 것이다.

보고서 데이터 필터링하기

Bibliophile의 화면을 자세히 살펴보면 보고서가 저자의 이름으로 필터링될 수 있다는 점을 알아챌 수도 있을 것이다. 이러한 기능은 레일즈 기반의 보고서 생성에서 매우 공통적인 것이라 할 수 있는데, 단일 필드를 기반으로 하는 필터링과 같이 단순한 것에서부터 보고서를 요약하기 위한 완전히 본격적인 쿼리 생성기를 만드는 것까지 다양하다.

이러한 예제가 대부분의 간단한 경우를 대표하긴 하지만, 일반적인 패턴을 구축하여 임의의 복잡한 필터링 시스템을 구현할 수도 있다.

이 기능을 사용하려면 레일즈 컨트롤러와 BookReport를 모두 변경해야 할 필요가 있다. 먼저 재미있는 부분인 Ruport 코드를 살펴보자:
class BookReport < Ruport::Controller

  stage :list

  def setup
    conditions = ["authors.id = ?", options.author] unless options.author.blank?
    self.data = Book.report_table(:all, :include => { :author => { :only => ["name"] } },
                                         :only => ["name", "author.name", "pages"],
                                         :order => "books.name",
                                         :conditions => conditions)
    data.rename_columns("name" => "Title", "author.name" => "Author")
  end

  formatter :html do
    build :list do
      output << textile("h3. Book List")
      output << data.to_html
    end
  end

  formatter :pdf do
    build :list do
      pad(10) { add_text "Book List" }
      draw_table data
    end
  end

  formatter :csv do
    build :list do
      output << data.to_csv
    end
  end
end
Ruport에 가해진 변경사항을 살펴보면 여러분은 바뀐 부분이 매우 적다는 것을 알 수 있을 것이다. 형식자의 경우에는 BookReport 컨트롤러에서 제공된 데이터를 모두 사용하기 때문에 바뀐 점이 없다. 새로운 코드는 setup()에만 들어 있는데, 이 코드는 단순히 기반이 되는 ActiveRecord#find 호출로 다시 전달될 몇 가지 조건을 만들 뿐이다. 아마도 유일하게 놀랄만한 사실은 보고서가 이제는 options.author 속성을 참조하고 있다는 것뿐일 것이다. script/console은 아래의 코드가 수행하는 바를 밝혀줄 것이다:
>> Author.find(1).name
=> "Umberto Eco" 
>> puts BookReport.render_csv(:author => 1)
Title,Author,pages
Baudolino,Umberto Eco,521
위 예제에서도 볼 수 있듯이 Ruport는 렌더링시에 전달된 옵션을 취해 그 옵션을 options 객체에 할당한다. 이 규칙에서 유일하게 제외되는 것은 특별한 키워드, 가령 :file, :data, :template와 같은 것들이다.

이렇게 하는 것은 매우 유용한데, 이를 통해 임의 옵션을 컨트롤러와 형식자에 전달할 수가 있기 때문이다. 여러분이 의아해할 경우를 대비해서 설명하는 것이지만, 이러한 값에 options[:author]와 같은 해시와 유사한 방식으로도 접근할 수 있다는 것은 아무런 소용도 없다.

이미 우리에게는 동작하는 필터링 메커니즘이 있으므로 남은 것은 바로 레일즈 컨텍스트 내에서 필터링 메커니즘이 작동하게 하는 것뿐이다:
class BooksController < ApplicationController
  def index
    session[:author] = params[:author]
    @book_report = render_book_list_as :html
    @authors = Author.find(:all)
  end

  def printable_list
    pdf = render_book_list_as :pdf
    send_data pdf, :type => "application/pdf",
                   :filename => "books.pdf" 
  end

  def csv_list
    csv = render_book_list_as :csv
    send_data csv, :type => "text/csv",
                   :filename => "books.csv" 
  end

  protected

  def render_book_list_as(format)
    BookReport.render(format, :author => session[:author])
  end

end
여러분도 알겠지만 여기서 바뀐 부분은 특별할 게 없다. HTML 보고서를 보여주는 메인 인덱스 페이지는 드롭다운 메뉴에서 선택된 저자를 세션에 저장한다. 그런 다음 이 값은 HTML이나 CSV, PDF 형식이 렌더링될 때 전달된다. 우리는 불필요한 중복을 피하기 위한 간단한 도우미 메서드를 만들어 두었으며, 코드의 나머지 부분은 이전과 같다.

명백히 필터링은 이보다 더 복잡해질 수도 있다. 여기에서는 더 복잡한 내용에 대해 다루지 않을 텐데, 왜냐하면 더 복잡한 내용은 Ruport 보다는 레일즈 코드가 될 것이기 때문이다. 그렇지만 acts_as_reportable에서 데이터 필터를 구현하는데 유용할만한 몇 가지 추가 옵션을 지원한다는 사실을 언급해둘 가치는 있다. 여러분이 이러한 작업을 하고 있다면 report_table의 :filters와 :transforms 옵션에 대한 문서를 꼭 살펴보도록 한다.

지금까지는 빙산의 일각일 뿐

접근법을 간단하고 쉽게 유지하기 위해 이 기사에서는 갖가지 Ruport의 고급 기능을 다루지는 않았다. 우리는 시스템의 주요 구성요소를 모두 보여주었지만, 대부분의 고급 기능, 특히 다소 전문적인 것들은 일부러 생략하였다.

우리는 이 기사의 간단한 예제가 Ruport가 여러분에게 해줄 수 있는 것의 일부만을 보여주었고, Ruport의 가능성을 계속해서 탐구하기 위한 시작지점을 제공해 주었길 바란다. 이것이 재미있다고 생각된다면 Ruport API 문서를 찾아보거나, 온라인상으로 찾아볼 수 있는 HTML 버전도 무료로 제공하는 Ruport Book을 살펴볼 것을 고려해 보도록 한다.

마지막으로 참여를 주저하지 마라! 메일링 리스트는 소프트웨어를 배우는 가장 좋은 자원 중 하나이며, 사용자들의 커뮤니티 참여를 환영하며 Ruport가 더 나아지도록 도움을 주길 바란다. Ruport로 하는 작업을 즐기고, 즐거운 해킹이 되길!


저자 그레고리 브라운(Gregory Brown)은 Ruby Report의 원저자이다.

저자 마이클 밀너(Michael Milner)는 루비 커뮤니티의 정회원이다. 그는 현재 Ruport 프로젝트의 리드 개발자이며, Ruport에 대한 레일즈 통합을 제공하는 Ruport/Rails 플러그인의 유지보수를 담당하고 있기도 하다. 그는 직장에서 루비를 사용하여 일하고 있으며 레일즈를 이용하여 대규모 웹 애플리케이션을 개발하고 있다.
TAG :
댓글 입력
자료실

최근 본 상품0