레일스 엔진 젬 시작하기

By Ryan Cook

June 23rd, 2011

레일스3가 릴리스된 이후 개발자들은 루비젬으로 패키징할 수 있는 새롭고 깔끔한 방식으로 레일스 엔진을 작성해 왔습니다. 레일스 엔진이란 패키지 형태의 하나의 어플리케이으로 다른 어플리케이션 안에서 실행되거나 마운트될 수 있습니다. 하나의 엔진은 자체 모델, 뷰, 컨트롤러, 제너레이터, 그리고 웹으로 공개서비스되는 정적 페이지 파일을 가질 수 있습니다.

하나의 엔진을 만들면 반복해서 사용할 수 있기 때문에 많은 양의 코드 작성을 좋아하지 않을 경우 이것은 대단한 소식이 아닐 수 없습니다. 소규모 사업을 위한 웹사이트를 많이 만들어야 하는 경우를 생각해 봅시다. 일반적으로 필요로하는 것은 특정 회사의 모든 직원들에 대한 목록을 보여주고 각 직원에 대한 기본 정보를 보여주는 웹페이지입니다. 이러한 기능은 거의 변경되지 않아서 공통적으로 필요로 하는 항목군으로 모아 볼 수 있어서 레일스 엔진 젬으로 개발하기에 딱 좋은 개발 대상이 됩니다.

본 게시물에서, 우리는 직원들에 대한 목록을 보여주는 데이터베이스 연동 팀 페이지를 만들기 위해서 사용하게 될 레일스 엔진을 만드는 과정을 살펴 볼 것입니다.

[노트 : 프로 웹디자이너와 개발자들이 반드시 가져야 할 것: “Printed Smashing Books Bundle”에는 일상 코딩 작업에서 실질적인 내용들이 가득합니다. 지금 당장 이 bundle을 구해 보시기 바랍니다!]

Enginex 엔진X

레일스의 핵심 인물인 Jose Valim은 Enginex라고 하는 툴을 개발했는데, 이것은 레일스3 호환 엔진젬을 만들 수 있는 골격을 만들어 줍니다. 이 툴은 엔진젬 개발자들이 직면하게 되는 많은 예기치 않았던 문제점들을 해결해 줍니다. 이 툴은 테스트용 레일스 어플리케이션을 포함해서 시작점을 위한 기본적인 설정을 대신해 줍니다.

엔진젬을 만들기 위해서 먼저, 일반적인 프로젝트 디렉토리로 이동하여 다음의 명령행을 실행합니다:

$ gem install enginex
$ enginex team_page

이렇게 해서 표준 엔진X 골격을 가지는 team_page 디렉토리에 하나의 프로젝트를 가지게 될 것입니다.

Set-Up 설치

젬을 셋업하기 위해서 우리는 몇가지 파일을 변경할 것입니다. 먼저, team_age.gemsepc과 Gemfile 파일 두가지는 약간만 변경하면 됩니다.

# CURRENT FILE :: team_page.gemspec
require File.expand_path("../lib/team_page/version", __FILE__)

# Provide a simple gemspec so that you can easily use your
# Enginex project in your Rails apps through Git.
Gem::Specification.new do |s|F
  s.name                      = "team_page"
  s.version                   = TeamPage::VERSION
  s.platform                  = Gem::Platform::RUBY
  s.authors                   = [ "Your Name" ]
  s.email                     = [ "your@email.com" ]
  s.homepage                  = "http://yourwebsite.com"
  s.description               = "A simple Rails 3 engine gem that adds a team page to any Rails 3 application."
  s.summary                   = "team_page-#{s.version}"

  s.rubyforge_project         = "team_page"
  s.required_rubygems_version = "> 1.3.6"

  s.add_dependency "activesupport" , "~> 3.0.7"
  s.add_dependency "rails"         , "~> 3.0.7"

  s.files = `git ls-files`.split("\n")
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
  s.require_path = 'lib'
end
# CURRENT FILE :: Gemfile
source "http://rubygems.org"
# Specify any dependencies in the gemspec
gemspec

이것은 gemspec 파일의 일부 설정을 변경해서 Git로 커밋한 파일과 나중에 추가하게 될 실행파일들을 자동으로 사용할 수 있게 해 주고 젬에 명시하게 될 VERSION 상수를 사용할 수 있게 해 줍니다.

또한, bundle install을 실행하게 되면, Gemfile에서 gemspec을 호출하여, team_page.gemspec으로부터 모든 연관 젬들을 로드하게 될 것이며 이것은 규칙입니다.

다음으로는, 젬을 레일스 엔진으로 변환하기 위해 세개의 파일을 추가하거나 변경하게 됩니다. 우선, 최상위 팀 페이지 파일은 다음과 같습니다:

# CURRENT FILE :: lib/team_page.rb
# Requires
require "active_support/dependencies"

module TeamPage

  # Our host application root path
  # We set this when the engine is initialized
  mattr_accessor :app_root

  # Yield self on setup for nice config blocks
  def self.setup
    yield self
  end

end

# Require our engine
require "team_page/engine"

mattr 함수를 사용하기 위해는 ActiveSupport가 필요합니다. 또한 self.setup메소드를 이용해서 젬을 설정할 수 있는 멋진 방법이 있습니다. 엔진이 파일의 마지막에서 필요하게 되는데 모든 관련된 젬들이 이전에 명시되어야 함을 확인해야 합니다.

두번째로, 버전 파일을 다음과 같습니다:

# CURRENT FILE :: lib/team_page/version.rb
module TeamPage
  VERSION = "0.0.1
end

마지막으로, 엔진 파일은 다음과 같습니다:

# CURRENT FILE :: lib/team_page/engine.rb
module TeamPage

  class Engine < Rails::Engine

    initialize "team_page.load_app_instance_data" do |app|
      TeamPage.setup do |config|
        config.app_root = app.root
      end
    end

    initialize "team_page.load_static_assets" do |app|
      app.middleware.use ::ActionDispatch::Static, "#{root}/public"
    end

  end

end

이것은 두개의 레일스initialize 블럭을 정의하게 되는 이것은 호스트 레일스 어플리케이션의 루트 디렉토리로 연결하는 실마리를 제공해 줄 뿐만 아니라 젬의 루트public 디렉토리에 있는 파일을 제공하게 해 줍니다.

Data Model

젬에서 하나의 모델을 추가하기 위해서는 먼저 하나의 마이그레이션과 하나의 제너레이터 클래스를 호스트 레일스 어플리케이션으로 복사하도록 명시해 두어야 합니다. 이러한 과정은 레일스 3.1에서 훨씬 더 분명하게 처리될 것이지만, 현재로서는 몇가지 제너레이터 클래스를 만들어 줄 필요가 있습니다. 이러한 과정에 대한 훌륭한 리소스를 Nepal on Rails에서 찾을 수 있습니다.

먼저, 제너레이터 클래스를 추가해 보겠습니다:

# CURRENT FILE :: lib/generators/team_page/team_page_generator.rb
# Requires
require 'rails/generators'
require 'rails/generators/migration'

class TeamPageGenerator < Rails::Generators::Base
  include Rails::Generators::Migration
  def self.source_root
    @source_root ||= File.join(File.dirname(__FILE__), 'templates')
  end

  def self.next_migration_number(dirname)
    if ActiveRecord::Base.timestamped_migrations
      Time.new.utc.strftime("%Y%m%d%H%M%S")
    else
      "%.3d" % (current_migration_number(dirname) + 1)
    end
  end

  def create_migration_file
    migration_template 'migration.rb', 'db/migrate/create_team_members_table.rb'
  end
end

이 제너레이터를 추개해 줌으로써 레일스 어플리케이션내에서 rails g team_page 명령행을 실행해서 필요로 하는 마이그레이션 파일을 생성토록하여 team page가 작동하도록 해 줄 것입니다.

다음으로, 샘플 마이그레이션 파일을 만들게 될 것입니다:

# CURRENT FILE :: lib/generators/team_page/templates/migration.rb
class CreateTeamMembers < ActiveRecord::Migration
  def self.up
    create_table :team_members do |t|
      t.string :name
      t.string :twitter_url
      t.string :bio
      t.string :image_url
      t.timestamps
    end
  end

  def self.down
    drop_table :team_members
  end
end

마지막으로, 샘플 모델을 만들어서 team page 젬으로 명칭을 구분할 수 있게 합니다.

# CURRENT FILE :: app/models/team_page/team_member.rb
module TeamPage
  class TeamMember < ActiveRecord::Base
    attr_accessible :name , :twitter_url , :bio , :image_url
  end
end

지금까지 작업한 내용은 레일스 3 엔진젬을 시작하기 위한 단계를 따라해 보는 것이었습니다. 엔진으로 구성작업이 완료했고, 자체 마이그레이션 제너레이터를 만들었으며, 액티브레코드 모델도 만들었습니다.

이제, 젬이 호스트 레일스 어플리케이션이 사용하게 될 라우터, 컨트롤러, 뷰를 가지도록 셋업해 보겠습니다.

The Route 라우트

레일스 엔진젬은, 적절하게 세팅이 되면, 호스트 프로젝트의 config 와 app 디렉토리를 자동으로 로드합니다. 이것은 전체 레일스 어플리케이션과 똑같은 구조에서 코드를 작성할 수 있기 때문입니다.

따라서, 라우트를 설정하기 위해서 프로젝트의 config 디렉토리에 routes.rb 파일을 생성합니다. `team` 라우트를 생성하기 위해 다음과 같이 합니다:

# CURRENT FILE :: config/routes.rb
Rails.application.routes.draw do
  get "team" => "team_page/team#index" , :as => :team_page
end

여기서 작업한 내용을 검토해서 봄으로써 우리는 약간의 고통을 필할 수 있습니다. 먼저, 호스트 레일스 어플리케이션으로 요청되는/team 라우트를 제대로 해당 컨트롤러와 액션으로 연결할 수 있게 될 것입니다.

두번째로, 레일스에게, 엔진에서 만들게 될 네임스페이스가 정해진 컨트롤러의index 라우트로 요청을 보내도록 지시를 해 놓았습니다.

마지막으로, 라우트에 이름을 붙여 놓아 어플리케이션의 어디에서든 링크 헬퍼 메소드를 이용할 수 있게 되었습니다.

The Controller

컨트롤러는, 우리가 익숙해 있듯이,app 디랙토리에 위치합니다. 한가지 문제는, 모델에서 했듯이, 이 컨트롤러들을 team_page 디렉토리로 옮겨야 한다는 것입니다. 이것은 단순히 페이지상에 보여줄 모든 팀 멤버를 로드하게 될 것입니다.

# CURRENT FILE :: app/controllers/team_page/team_controller.rb
module TeamPage
  class TeamController < ::ApplicationController
    def index
      @team_members = TeamMember.all
    end
  end
end

알 수 있듯이, 호스트 레일스 어플리케이션에 있는 최상위 ::ApplicationController를 상속받았다는 것입니다.

The View 뷰

마무리하기 위해서, 뷰를 렌더링해야 할 필요가 있습니다. 컨트롤러에 별도로 사용하게 될 레이아웃을 명시하지 않았기 때문에, 디폴트로, 호스트 레일스 어플리케이션의 메인 어플리케이션 레이아웃을 이용하게 될 것입니다.

모델과 컨트롤러에서서 했듯이, team_page 디렉토리 상에 뷰가 위치하게 될 것입니다. 외부 의존을 최소한으로 줄이기를 원하기 때문에 우리는 여기서 HAML 대신에 ERB 형태로 뷰를 작성하게 될 것입니다.

<!-- CURRENT FILE :: app/views/team_page/index.html.erb -->
<ul>
  <% @team_members.each do |team_member| %>
  <li>
    <span>
      <%= link_to @team_member.name , @team_member.twitter_url %>
    </span>
    <%= @team_member.bio %>
    <%= image_tag @team_member.image_url , :class => "team-member-image" %>
  </li>
  <% end %>
</ul>

Getting Started With Tests 테스트 검증하기

분명한 것은, 작성한 젬에 대한 유닛 또는 전체 테스트를 아직 작성하지 않았습니다는 것입니다. 본 게시물의 내용을 완성하게 될면 레일스3 엔진젬을 잘 이해하게 될 것입니다. enginex 툴은 기본 레일스 어플리케이션 구조와 함께 자동으로test 디렉토리도 생성해 줍니다.

test_helper.rb 파일이 문제가 없다는 것을 확인한 상태에서 시작해 보겠습니다.

# CURRENT FILE :: test/test_helper.rb
# Configure Rails Environment
ENV["RAILS_ENV"] = "test"
ENV["RAILS_ROOT"] = File.expand_path("../dummy",  __FILE__)

require File.expand_path("../dummy/config/environment.rb",  __FILE__)
require "rails/test_help"

ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.default_url_options[:host] = "test.com"

Rails.backtrace_cleaner.remove_silencers!

# Run any available migration
ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)

# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }

한가지 주목해야 할 것은, 테스트 셋업 헬퍼 메소드에서 드문상황인데, 테스트 헬퍼 메소드에서 어디에도 로컬 젬 코드를 필요로 하지 않는다는 것입니다. Bundler를 이용할 것이기 때문에, 젬은 실제로 test/dummy/config/application.rb

에서 Gemfile을 통해서 더미 레일스 어플리케이션에서 필요로 합니다. 그 외에도 레일스 환경을 셋업하고 어플리케이션을 부팅하고 모든 마이그레이션을 실행하게 될 것입니다. 아래에서 샘플 유닛 테스트 코드를 볼 수 있습니다.

# CURRENT FILE :: test/team_page_test.rb
require 'test_helper'

class TeamPageTest < ActiveSupport::TestCase

  test "truth" do
    assert_kind_of Module, TeamPage
  end

  test 'setup block yields self' do
    TeamPage.setup do |config|
      assert_equal TeamPage, config
    end
  end

end

레일스 어플리케이션에서 엔진을 테스트하고 통합하는 방법을 계속해서 알아보기 위해서는, test/dummy/ 레일스 어플리케이션으로 가서 서버를 부팅하거나 콘솔에서 작업을 해 보기 바랍니다.

Resources And Tips

여기에서는 제대로 따라하고 있다는 것을 확인할 수 있는 몇가지 방법을 제공해 줍니다. 아래에는 본 게시물에 있는 코드 예재를 따라한 후에 엔진젬이 가져야 하는 디렉토리를 구조를 보여 줍니다.

## DIRECTORY STRUCTURE
#

- team_page/
  - app/
    - controllers/
      - team_page/
        + team_controller.rb
    - models/
      - team_page/
        + team_member.rb
    - views/
      - team_page/
        + index.html.erb
  - config/
    + routes.rb
  - lib/
    - team_page.rb
    - generators/
      - team_page/
        + team_page_generator.rb
        - templates/
          + migration.rb
    - team_page/
      + engine.rb
      + version.rb
  - test/
    + team_page_test.rb
    + test_helper.rb
  + team_page.gemspec
  + Gemfile
  + Gemfile.lock

아래에 간단한 스크립트를 통해서 데이터베이스에 팀 멤버 몇 사람을 로드할 수 있습니다.

## DATA SEED
#
# => Method 1
# Copy the code into your application's db/seeds.rb file.
#
# => Method 2
# If you would like to run this code in the engine that you are
# developing, place it in the seeds file inside of the dummy app
# contained in your integration tests.
#
# With either of the above methods, be sure to run the following from
# your command line…
#
#     rake db:seed
#

5.times do |i|

  TeamPage::TeamMember.create!( {
    :name        => "Team Member #{i}",
    :twitter_url => "http://twitter.com/team_member_#{i}",
    :bio         => "A really fancy team member!",
    :image_url   => "http://bit.ly/muSWki"
  } )

end

글쓴이: 최효성

외과전문의,웹프로그래밍,컴퓨터 일러스트레이션 / Surgeon, Medical Illustration, Web Programmer

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중