'Development'에 해당되는 글 30건

  1. [2008/11/22] NAVER Developer Center
  2. [2008/11/15] SpringnoteFS
  3. [2008/10/17] 로드된 DLL 파일 찾기
  4. [2008/07/02] Rails 2.1 : ActiveSupport::Cache
  5. [2008/06/30] MySQL Gem Error (1)
  6. [2008/04/17] Passenger (a.k.a. mod_rails)
  7. [2008/04/16] Rails 2.1 : Dirty Objects
  8. [2008/04/11] make_resourceful #1
  9. [2008/04/04] Rails 2.1 : Gem Dependencies (1)
  10. [2007/12/23] Rails2.0에서 scaffold_resource (1)

NAVER Developer Center

[Development]
사용자 삽입 이미지


국내 최대의 포털인 네이버에서 Developer Center를 오픈했다.
그 동안 네이버에서 개발된 각종 리소스들을 오픈소스로 공개하고, 개발자들의 참여를 유도하고 있다.
오픈소스 소개페이지에서 다양한 프로젝트들의 설명을 볼 수 있는데, 재미있는 것들이 많다.
마치 어릴 적에 자고 일어났더니 방 안에 장난감이 가득 쌓여있을 때의 느낌이랄까...
한 동안 심심할 일은 없겠다. 하나씩 차근차근 살펴보면서 배워가야겠다.

그 중에서도 CUBRID를 Rails에서 활용하는 방안을 고민 중이다.
CUBRID의 서브 프로젝트인 CUBRID-ruby 프로젝트도 진행 중이니 조만간 CUBRID를 활용한 Rails 서비스도 나오지 않을까 싶다.

2008/11/22 15:17 2008/11/22 15:17

SpringnoteFS

[Development]
Ruby 개발환경을 Ubuntu로 변경하다.
이번 주 초에 내 노트북인 Xnote R200의 비스타에 짜증을 느꼈다. 가장 많이 사용하는 Ruby, Rails 개발환경이 너무 느리고 불편한 것이 화근이었다. 결국 불 같은 화를 참지 못 하고, 이틀 간의 사투 끝에 Ubuntu 8.10을 설치하였다. (완전히 MS의 싹을 잘라버리려 했으나, 인터넷 뱅킹과 회사 회계 관련된 사이트들 때문에 멀티 부팅으로 타협을 봤다.) Ubuntu를 설치했던 과정들은 다른 문서로 정리할 것이다.

신규 프로젝트 전의 몇일...
다음 주부터는 올해 말까지 진행될 프로젝트가 본격적으로 시작된다. 따라서 이번 주 이틀에서 사흘 정도가 2008년 막판 스퍼트를 위한 마지막 휴식 기간이다. 평소 같았으면 다른 더 재미있는 짓을 했겠지만, 우분투로 깔끔한 개발환경 구축해 놓은 것이 마음에 걸렸다. 뭔가 개발은 하고 싶고, 거기다 시간 여유 있고... 이렇게 뭐 할 만한 것을 찾던 중, 예전에 잠깐 생각했던 Springnote Filesystem을 떠올렸다.

SpingnoteFS : Springnote Filesystem
스프링노트는 RESTful Rails를 훌륭하게 구현한 국내 최고의 Rails Application이다. SpringnoteResources와 같은 ActiveResource 기반의 편리한 API까지 제공되므로 잘 활용한다면 다양한 mashup이 가능하다. 예전에 GmailFS를 보고 여러 다양한 리소스를 저장공간으로 활용하는 파일시스템을 만들어 보았던 경험이 있기 때문에, SpringnoteFS는 자연스럽게 개발될 수 있었다.

기본적인 파일 시스템의 CRUD를 목표로 이틀간 꼬박 만들어서 0.1 버전을 만들 수 있었다. OAuth를 다뤄본 적이 없어서 OAuth 인증방식에 대한 공부와 삽질로 하루를 보냈다. 이틀날은 실제 마운트된 파일 시스템에서의 CRUD를 구현하고 코드를 정리하면서 프로젝트를 마무리지었다.

급하게 만든거라 버그도 많을 것이고, 노틸러스와 같은 파일시스템 GUI에서는 정상적으로 파일 복사, 이동이 되지 않는 것도 확인했다. 리눅스를 사용하시면서 스프링노트를 자신만의 저장공간으로 활용해보고 싶으신 분들은 http://inocrazy.springnote.com/pages/2119958 에서 친절한 설명과 함께 코드를 받아서 사용해 보시길...
2008/11/15 05:36 2008/11/15 05:36

로드된 DLL 파일 찾기

[Development]

Cygwin Error!!!

개발 PC에는 여러 환경에서의 개발을 위해서 Cygwin이 설치되어 있다. 그런데 어느 날부터 Command Prompt에서 Cygwin에 관련된 명령어들이 작동하지 않았다.


...cygwin system shared memory version mismatch detected...


이라는 에러 메시지를 출력하면서 실행이 멈추었다. Cygwin의 FAQ를 찾아보니, 시스템 상에 (정확히는 PATH 환경변수로 정의된 디렉토리 안에) 하나의 DLL 파일만 존재해야만 한다고 한다. 결국 다른 버전의 Cygwin DLL (cygwin1.dll) 파일이 먼저 로드되어서 충돌을 일으킨 것이다.


어느 프로세스가 로드한 것일까?

로컬 검색을 통해서 OpenSSH 디렉토리에 cygwin1.dll 파일이 존재한다는 것을 확인하고 삭제하려고 하였다. 그러나 이미 다른 프로세스가 DLL 파일을 로드한 상태였기 때문에 삭제할 수 없었다. 이를 해결하려면 어느 프로세스가 해당 DLL을 로드하였는지 찾아서 해당 프로세스를 죽이고, cygwin1.dll 파일을 삭제해야만 했다. 이를 위해서 tasklist 명령어가 사용되었다.


tasklist.png


tasklist는 Unix 시스템의 ps에 해당하는 명령어로 응용 프로그램이나 관련 작업/프로세스의 목록을 출력해준다. 여기에 /m [모듈] 옵션을 추가해서 [모듈]과 패턴이 일치하는 이름을 갖는 DLL을 로드한 프로세스를 볼 수 있다.


참조

이 글은 스프링노트에서 작성되었습니다.

2008/10/17 16:30 2008/10/17 16:30
TAG. ,

Rails 2.1 : ActiveSupport::Cache

[Development]

원문(http://inocrazy.com/docs/10)에서 좀 더 깔끔하게 보실 수 있습니다.

개요

Rails 2.1에서는 ActiveSupport::Cache 모듈이 추가되면서 간편하게 cache를 설정하고 사용하는 것이 가능해졌다. cache를 실제로 저장하고 처리하는 caching store도 여러가지가 제공되므로 자신의 편의에 맞는 것을 사용할 수 있게 되었다. 이번 문서에서는 Rails의 새로운 Cache 기능에 대해서 자세히 살펴보자.



ActiveSupport::Cache::Store

ActiveSupport::Cache에서 가장 중요한 클래스는 Store 클래스이다. 이 클래스는 다양한 caching store를 위한 인터페이스 역할을 한다. FileStore, MemCacheStore 등의 클래스가 이 클래스를 상속해서 실질적인 처리를 구현하고 있다. Store 클래스의 대표적인 인스턴스 메소드는 다음과 같다.

메소드 내용
read(key, options = nil) key에 해당하는 값을 읽어온다.
write(key, value, options = nil) key에 해당하는 값을 저장한다.
fetch(key, options = nil) key에 해당하는 값이 저장되어 있으면 읽어오고, 값이 없을 경우 전달되는 block을 실행시킨 결과를 저장 후 리턴한다.
delete(key. options = nil) key에 해당하는 값을 삭제한다.
delete_matched(matcher, options = nil)                  정규식 matcher와 matching되는 결과만을 삭제한다. MemCacheStore에서는 구현되어 있지 않다.
exist?(key, options = nil) key에 해당하는 값이 존재하는지 여부를 리턴한다.
increment(key, amount = 1) key에 해당하는 값이 존재하는 경우, amount만큼 증가시킨다.
decrement(key, amount = 1) key에 해당하는 값이 존재하는 경우, amount만큼 감소시킨다.

어플리케이션 개발 시에 cache의 처리 상황을 파악하기 쉽도록, 각 메소드가 실행되는 경우 debug 수준의 로그를 남기게 된다. 따라서 처리된 결과값이 cache로부터 가져온 것인지, 혹은 DB를 access해서 가져온 것인지를 모니터링하면서 개발을 진행할 수 있다.


또한 ActiveSupport::Cache에는 ThreadSafety라는 모듈이 선언되어 있다. 모듈명에서도 알 수 있듯이 이 모듈은 thread safety를 위해서, read, write, delete 메소드를 Mutex#synchronize 메소드로 감싸는 wrapping 메소드를 제공한다. caching store에서 thread safety를 가능하게 하기 위해서는 Store#threadsafe! 메소드를 실행하면 된다.


위의 기본적인 CRUD 관련 메소드뿐만 아니라, 두 개의 클래스 메소드도 제공한다. lookup_store(*store_option)은 인수로 전달된 option에 해당하는 caching store 인스턴스를 리턴한다. environment.rb의 config.cache_store 설정에서 내부적으로 사용되는 메소드이다. 사용가능한 인수들은 config.cache_store 설정을 살펴보면서 정리하자.


  1. new_cache = ActiveSupport::Cache.lookup_store(:file_store, 'tmp/new_cache')

또한 expand_cache_key(key, namespace = nil) 클래스 메소드는 전달된 key를 확장하게 된다. Store 클래스에서 각 cache는 결국 hash 형식으로 처리되게 된다. 따라서 여러가지 caching되는 값들이 중복되지 않기 위해서는 유일한 key를 필요로 하게 된다. 이를 위해서 이 메소드에서는 다음과 같은 형식으로 키를 확장한다.


key => [namespace/][RAILS_CACHE_ID or RAILS_APP_VERSION/]<cache_key or to_param>


각 파트들은 해당 값이 인수로 전달되거나 설정된 경우에 추가되게 된다. 세번째 파트에서 key가 :cache_key라는 메소드가 있는 경우, 해당 값을 설정하게 된다. ActiveRecord::Base에는 cache_key라는 메소드가 추가되어서 쉽게 caching 처리가 가능해졌다. (<model_name>/<id>-<updated_at> 형식) 만약 key가 Array로 전달된 경우는 모든 요소들에 대해서 expance_cache_key 메소드가 실행된 결과가 to_param 형식으로 리턴된다.



Caching Stores

Rails 2.1에서는 5개의 caching store을 제공한다. 각각은 다음과 같다.

Caching Store 내용
FileStore cache를 개별 파일에 저장하고 읽어온다.
MemoryStore cache를 hash 변수에 저장하고 읽어온다.
DRbStore 메모리 대신 DRb server에 cache를 사용하는 것을 제외하곤, MemoryStore와 동일하다.
MemCacheStore memcache를 사용하여 저장하고 읽어온다
CompressedMemCacheStore read/write 시에 Gzip을 이용해서 압축과 해제를 하는 것을 제외하곤, MemCacheStore와 동일하다.

위와 같이 5가지의 caching store를 자신의 환경에 맞게 선택해서 사용하는 것이 가능하다. 저장공간을 기준으로 나누었을 때, 크게 File, Memory, Shared Memory에 저장하는 경우로 나누어볼 수 있다.


FileStore

FileStore에서는 각각의 cache를 개별 파일에 저장하고 읽어오게 된다. 추가적인 config.cache_store 설정이 없을 경우, tmp/cache 디렉토리가 존재한다면 Rails 2.1에서는 기본적으로 FileStore를 기본 caching store로 설정하게 된다. 예를 들어, cache가 저장되는 곳이 tmp/cache이고, inocrazy=Crazy For Innovation! 이라는 key=value를 저장한다고 가정하면, 실제 cache는 tmp/cache/inocrazy.cache 파일에 "Crazy For Innovation!"이라는 값을 쓰게 된다.


FileStore의 장점은 추가적인 설정없이 간편하게 사용할 수 있다는 것이다. 반면 매번 cache를 읽고 쓸 때마다 File IO가 발생하게 되므로, 상대적으로 속도가 느리다. 또한 app. server가 여러 대인 경우, cache가 각 서버에 저장되므로 정상적으로 사용할 수 없다.


MemoryStore

MemoryStore는 일반적인 hash 변수에 cache를 저장하고 읽어온다. FileStore와 같이 간편하게 사용할 수 있고, 또한 FileStore에 비해 빠르다는 장점이 있다. 하지만 마찬가지로 여러 대의 app. server에서는 사용할 수 없다.


DRbStore

DRbStore는 cache 저장을 위해서 DRb server를 사용한다는 점을 제외하고는 MemoryStore와 동일하다. (실제로 MemoryStore를 상속받아서 구현되었다.) MemoryStore의 장점에, 분산된 app. server에서도 사용할 수 있다는 추가적인 장점을 가지고 있다.


MemCacheStore

memcache를 이용해서 cache를 처리한다. 여러 대의 memcache server를 이용해서 효율적인 caching 처리를 할 수 있다. Rails 2.1에서는 ActiveSupport에 memcache-client 1.5.0이 포함되므로써 추가적으로 gem을 설치할 필요가 없게 되었다.


CompressedMemCacheStore

CompressedMemCacheStore는 MemCacheStore를 상속하여 구현한 클래스로서, read/write 시에 ActiveSupport::Gzip을 이용하여 압축과 해제를 추가적으로 하게 된다.



MemCacheStore 자세히 살펴보기

MemCacheStore는 실제 서비스에서 가장 많이 사용되므로 여기서 좀 더 자세히 살펴보자.


MemCache 설정

MemCacheStore는 다음과 같이 서버 목록과 option들을 인수로 전달받는다.


  1. cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, '192.168.10.1:11211', '192.168.10.2:11212', :namespace => 'inocrazy')

memcache는 기본적으로 여러 대의 분산된 memcache server를 이용할 수 있으므로, 해당 서버들의 목록을 설정할 수 있다. 또한 각 서버의 weight를 설정함으로써 처리 빈도를 조절할 수 있다. 서버는 hostname[:port][:weight]와 같은 형식으로 설정한다.


또한 사용가능한 옵션들은 다음과 같다.

옵션 내용
:namespace cache의 namespace를 설정한다. memcache server는 여러 서비스가 함께 사용하게 되므로 서비스 간에 키의 중복이 발생할 수 있다. 이 namespace를 접두어로 붙임으로써, 유일한 키로 설정되도록 한다.
:readonly memcache를 읽기 전용으로 사용한다. 따라서 cache를 쓰려고 하는 경우, 예외를 발생시키게 된다.
:multithread thread safety를 위해서 cache access를 Mutex로 wrapping한다.
read/write 옵션

MemCacheStore에서 read/write 메소드의 경우, 다음과 같은 추가적인 옵션을 사용할 수 있다.

옵션 내용
:raw memcache-client에서는 값을 저장하고 가져오는 경우, Marshal#dump와 Marshal#load 메소드를 사용한다. 만약 :raw가 true로 설정되게 되면, marshaling되지 않은 raw value로 저장하고 읽어오게 된다. (기본값 false)
:unless_exist write 메소드에서 사용되는 옵션으로 :unless_exist가 true인 경우, 값이 존재하지 않는 경우에만 값을 저장하게 된다. 기본적으로는 값이 존재하는 경우, 기존값을 대체한다. (기본값 false)
:expires_in write 메소드에서 사용되는 옵션으로 cache가 유효한 시간을 초 단위로 설정한다. cache가 일정 시간에 한번씩 업데이트되어야 하는 경우에 이 옵션을 사용할 수 있다.
  1. cache.write('inocrazy', 'Crazy For Innovation!', :raw => true)
  2. # :unless_exist가 true로 설정되었고, 이미 값이 존재하므로 저장되지 않는다.
    cache.write('inocrazy', 'InoCrazy', :unless_exist => true, :expires_in => 10.miutes)
    cache.read('inocrazy', :raw => true)
    => 'Crazy For Innovation!


Rails에서 Cache

지금까지 ActiveSupport::Cache에 대해서 자세히 살펴보았다. 이제 Rails에서 실제로 caching store를 설정하고, 사용하는 방법에 대해서 살펴보자. 특정 caching store를 설정하기 위해서는 config/environment.rb (또는 config/environments 디렉토리의 환경별 설정파일)에서 다음과 같이 설정할 수 있다.


  1. # FileStore 사용. '/tmp/cache'를 cache 저장소로 설정.
    config.cache_store = :file_store, '/tmp/cache'
    # MemoryStore 사용
    config.cache_store = :memory_store
    # DRbStore 사용, 192.168.10.1:9192의 drb server로 설정.
    config.cache_store = :drb_store, 'druby::/192.168.10.1:9192'
    # MemCacheStore 사용. 192.168.10.1:11211, 192.168.10.2:11212를 memcache server로 설정
    config.cache_store = :mem_cache_server, '192.168.10.1:11211', '192.168.10.2:11212', :namespace => 'inocrazy'
    # CompressedMemCacheStore 사용
    config.cache_store = :compressed_mem_cache_store, 'localhost'

위에서 언급하였듯이, 위의 설정은 내부적으로 ActiveSupport::Cache.lookup_store 메소드를 통해서 cache 인스턴스를 설정하게 된다. 위의 설정이 존재하지 않는 경우의 기본 caching store는 FileStoreMemoryStore 중 하나를 사용하게 된다. tmp/cache 디렉토리가 존재할 경우, FileStore가 사용되며, 그렇지 않을 경우 MemoryStore가 기본이 된다. Rails에서는 기본적으로 global cache를 생성한다. 실제 코드에서 이를 사용하기 위해서는 RAILS_CACHE 전역변수나 Rails.cache를 통해서 접근할 수 있다.


  1. # 'date'라는 key를 갖는 cache에 쓰고 읽는다.
    Rails.cache.write('date', Date.today)
    Rails.cache.read('date')
    => '2008-07-02'

    # 'time'이라는 cache가 존재하지 않으므로 block의 실행결과를 cache에 저장하고, 리턴한다.
    Rails.cache.fetch('time') { Time.now }
    => Wed Jul 02 02:44:34 +0900 2008
    # 이미 'time'이라는 cache가 존재하므로 cache에서 값을 읽어온다.
    Rails.cache.fetch('time') { Time.now }
    => Wed Jul 02 02:44:34 +0900 2008


정리

Rails 2.1에서는 기존에 복잡한 설정과 추가적인 gem/plugin으로 구현해야 했던, cache 처리를 ActiveSupport::Cache를 통해서 간편하게 사용할 수 있게 되었다. 이 cache를 적절히 사용함으로써 Rails 서비스의 Performance를 더욱 향상시켜보자. ^^


참조




이 글은 스프링노트에서 작성되었습니다.

2008/07/02 04:23 2008/07/02 04:23

MySQL Gem Error

[Development]

원문(http://inocrazy.com/docs/9)에서 좀 더 깔끔하게 보실 수 있습니다.

Rails Log

Rails의 Log 파일을 보면 다음과 같은 메시지가 있는 경우를 볼 수 있다.

WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. Please install the C-based MySQL library instead (gem install mysql).


또는 Edge Rails를 사용한다면 다음과 같은 메시지가 나올 것이다.

!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.


위 메시지들은 Rails에서 MySQL 라이브러리를 로드하지 못 할 경우 출력되는 메시지이다. 이럴 경우 Rails에 포함된 Ruby 기반의 MySQL 라이브러리를 사용하게 된다. development 환경에서는 크게 문제가 되지 않지만, production 환경에서는 서비스의 성능에 영향을 미치게 된다. 결국 이 문제를 해결하기 위해서는 MySQL gem이 정상적으로 설치되었고, 로드되는가를 확인해봐야 한다.


Windows의 경우

Windows 환경에서 MySQL이 설치되었고, MySQL gem도 정상적으로 설치되었다고 가정하자. 그럼에도 불구하고, 위의 메시지가 출력된다면 native MySQL driver (libmySQL.dll) 파일을 의심해 볼 수 있다.


우선 (환경변수의 path로 잡혔음에도 불구하고) mysql/bin 디렉토리의 libmySQL.dll 파일을 접근할 수 없는 경우가 있다.  이 경우에는 해당 파일을 ruby/bin 디렉로리에 복사한 후, 정상적으로 로드되는가를 확인해보아야 한다.


위의 방법으로 해결이 되지 않은 경우, 특정 버전의 libmySQL.dll의 버그인 경우를 의심해볼 수 있다. libmySQL.dll 2.7.3과 특정 버전의 MySQL이 정상적으로 작동하지 않는 경우가 있다고 한다. 따라서 이 경우에는 MySQL을 최신 버전으로 재설치함으로써 문제를 해결할 수 있다.


Linux의 경우

mysql gem 설치 시에 Shared Object 파일을 컴파일하게 되는데, 이 때 에러로 설치가 되지 않은 경우가 있다. 설치 시 아래와 같은 메시지가 출력된다면 MySQL의 위치를 파악하지 못 해서, Makefile의 생성이 실패한 것이다.


/usr/local/bin/ruby extconf.rb install mysql
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lm... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lz... yes
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lsocket... no
checking for mysql_query() in -lmysqlclient... no
checking for main() in -lnsl... yes
checking for mysql_query() in -lmysqlclient... no

따라서 적절하게 자신의 mysql 설정을 옵션으로 추가해서 Gem을 설치한다. gem 설치시 Native extension을 빌드하는 경우, -- --build-flags 형식의 옵션을 통해서 build 옵션을 전달할 수 있다.

  1. $ gem install mysql -- \
    > --with-mysql-config=/usr/local/mysql/bin/mysql_config

테스트

위의 과정을 통해서 MySQL Gem이 정상적으로 설치되었다면, 테스트를 통해서 확인한다.

  1. $ irb
    irb(main):001:0> require 'mysql'
    => true
    irb(main):002:0> Mysql::VERSION
    => 20700

참조

이 글은 스프링노트에서 작성되었습니다.

2008/06/30 01:56 2008/06/30 01:56
TAG. , ,

Passenger (a.k.a. mod_rails)

[Development]

원문(http://inocrazy.com/docs/8)에서 좀 더 깔끔하게 보실 수 있습니다.

Phusion Passenger (a.k.a. mod_rails)

Phusion Passenger는 네덜란드의 Phusion사에서 개발된 Apache web server의 module이다.(mod_rails로도 알려져있다.) PHP Application을 배포하는 것처럼, Rails Application에서 upload-and-go(업로드 후, 바로 실행)를 가능하게 해준다. 이 문서에서는 기존 Rails Deployment의 문제점을 살펴본 후, Passenger의 설치와 설정 방법에 대해서 알아볼 것이다.



Deployment : Rails vs PHP

Rails를 실전에 도입하는데 있어서 큰 장벽 중의 하나는 배포 환경의 문제이다. 최적화된 성능을 위해서 여러가지 선택을 해야만 한다. 우선 어떤 웹서버를 사용할 것인가(Apache 2.2 + mod_proxy_balancer or Nginx)를 결정해야 하고, 어플리케이션의 규모에 적절한 Mongrel 클라이언트의 개수를 결정해야 한다. 또한 이 Mongrel을 감시하기 위한 모니터링 데몬인 Monit이나 God 등도 설정해 주어야 한다. Mongrel의 경우, 하나의 클라이언트가 수십 메가씩 메모리를 점유하기 때문에 단순히 호스팅을 받아서는 구성할 수도 없다. 이 과정을 조금이라도 편하게 하기위해서 Capistrano를 도입해서 배포를 자동화할 수 있지만, 그를 위해서 또 다른 설정을 해야만 한다. 문제는 이러한 복잡한 설정을 통해서도 만족할만한 성능을 얻기가 어렵다는 것이다.


PHP의 경우, 개발한 어플리케이션을 호스팅 서버에 올리기만 하면 모든 배포가 끝나게 된다. 서버가 여러 대인 경우에도 rsync 한방이면 깔끔하게 정리된다. 가장 중요한 것은 이렇게 간단한 배포 방식에도 불구하고 꽤 괜찮은 성능을 보여준다는 것이다. 그에 반해서 Rails의 개발자는 너무도 많은 설정과 고민을 해야만 하고, 그에 반해 성능은 만족스럽지 못하다. Rails의 이러한 고민들이 어느날 혜성처럼 등장한 Phusion Passenger에 의해 해결되었다.



설치하기

Passenger는 두 가지 형태의 설치방법을 제공한다. gem 형태로 설치한 후에 빌드할 수도 있고, 소스코드를 다운받아서 빌드할 수도 있다. 설치방법은 두 방식 모두 비슷하므로, 여기서는 좀 더 쉬운 방식인 gem을 이용한 설치방법을 살펴보자. (소스로 설치하는 경우, 설치가 완료된 후에도 설치된 mod_passenger.so 모듈이나 passenger-spawn-server 실행파일에 접근해야 하기 때문에, 소스 디렉토리를 삭제하면 안된다.)


  1. passenger gem을 설치한다.

    1. $ gem install passenger

  2. passenger module을 빌드한다.

    1. $ passenger-install-apache2-module

Passenger에서는 사용하기 쉬운 passenger installer를 제공한다. shell에서 passenger-install-apache2-module 명령어를 실행시키면 아래 화면과 같이 installer의 시작 화면이 표시된다. 설치는 스텝별로 설명과 함께 진행되기 때문에 크게 어려운 점은 없을 것이다. Passenger 설치 시에 주의해야할 사항 몇 가지를 정리해보자.


passenger01.png

passenger 설치 시작 화면


Apache bin 디렉토리를 PATH에 설정하기

passenger installer의 첫번째 단계는 설치에 필요한 소프트웨어를 찾는 것이다. 이때 Apache2 development header나 APR development header가 필요한데, Apache의 bin 디렉토리가 PATH에 설정되어있지 않을 경우, 아래와 같이 not found 메시지를 보여주게 된다. 또한 친절하게도 해당 문제에 대한 해결책까지 함께 보여준다. 하지만 Apache의 bin 디렉토리를 PATH에 추가함으로써 간단하게 해결할 수 있다.


passenger03.png

Apache 관련 header를 찾을 수 없다는 메시지


  1. $ export PATH=/usr/local/apache2/bin:$PATH

boost/non_type.hpp 파일 문제

이 문제는 내 환경에서만 발생하는 것인지 모르지만, 여러 대의 서버에서 테스트한 결과 동일한 현상이 발생하여 정리해둔다. 위의 Apache 설정을 한 후에 passenger installer를 다시 실행시키면 필요한 소프트웨어가 모두 발견되고, Apache module을 컴파일하고 설치하는 스텝으로 넘어가게 된다. 그런데 컴파일 중 아래와 같은 오류 메시지가 발생하였다.


passenger04.png


소스코드를 살펴보면 Boost C++ Library의 non_type.hpp 라는 파일을 찾지 못 해서 발생하는 에러인데, 해당 파일이 있어야할 ext/boost 디렉토리에 존재하지 않았다. 결국 이 문제를 해결하기 위해서 Boost C++ Library를 다운받아서 non_type.hpp 파일을 ext/boost 디렉토리에 복사한 후, 설치를 완료할 수 있었다.


설치가 완료된 후 아래와 같이 Apache의 httpd.conf 파일을 설정하는 방법에 대한 설명이 나오게 된다.


passenger05.png



설정하기

httpd.conf 파일을 열어서 Passenger module의 설정을 추가한다.

  1. LoadModule passenger_module /usr/local/lib/ruby/gems/1.8/gems/passenger-1.0.1/ext/apache2/mod_passenger.so
    RailsSpawnServer /usr/local/lib/ruby/gems/1.8/gems/passenger-1.0.1/bin/passenger-spawn-server
    RailsRuby /usr/local/bin/ruby

VirtualHost의 Root로 설정하기

기본적으로 PHP Application을 설정하는 것과 동일하다. 여기서는 inocrazy.com 이라는 Rails Application을 설정하는 것을 가정해보자. Document Root는 /home/inocrazy/www/current/public 이며, inocrazy.com을 도메인으로 설정할 것이다. httpd.conf 파일에 VirtualHost 설정을 추가한다.

  1. <VirtualHost *:80>
       ServerName inocrazy.com
       DocumentRoot /home/inocrazy/www/current/public
    </VirtualHost>

Sub 디렉토리로 설정하기

기존의 superkdk.com 사이트의 inocrazy라는 디렉토리에 inocrazy.com Rails Application을 설정하는 경우를 생각해보자. superkdk.com 사이트의 Document Root는 /home/superkdk/www이다.  우선적으로 할 일은 superkdk.com의 Document Root에 inocrazy라는 symlink를 만드는 것이다.

  1. $ ln -s /home/inocrazy/www/current/public /home/superkdk/www/inocrazy

그 후에 RailsBaseURI 옵션을 VirtualHost 설정에 추가해주면 된다.

  1. <VirtualHost *:80>
       ServerName superkdk.com
       DocumentRoot /home/superkdk/www
       RailsBaseURI /inocrazy
    </VirtualHost>


설정 옵션들

Passenger에서 사용할 수 있는 옵션들에 대해서 살펴보자.


RailsSpawnServer <filename>

Passenger spawn server의 경로를 설정한다. 위에서 살펴본 것처럼 설치 시에 정확한 경로를 알려주므로 해당 경로로 설정해두면 된다. 만약 이 옵션이 설정되지 않을 경우, $PATH 경로에서 passenger-spawn-server 라는 실행파일을 찾게된다.


RailsBaseURI <uri>

위에서 언급한 것처럼 Sub 디렉토리에 Rails Application을 설정하는 경우 설정한다. 각 VirtualHost마다 설정할 수 있다.


RailsAutoDetect <on|off>

Virtual Host의 Document Root가 Rails Application인지를 자동으로 찾아낼지 여부를 설정한다. 기본값은 on이다.


RailsAllowModRewrite <on|off>

Passenger가 mod_rewrite 규칙들을 override할지 여부를 설정한다. 기본값을 off이다.


RailsRuby <filename>

사용할 ruby 인터프리터의 경로를 설정한다.


RailsEnv <string>

기본적인 RAILS_ENV 변수값을 명시한다. 주의해야할 것은 이 옵션이 Global 옵션이라는 것이다. 따라서 각 Virtual Host마다 RAILS_ENV를 설정하기 위해서는, 각 Rails Application의 config/environment.rb에서 RAILS_ENV= 로 설정하는 방법 밖에 없다. (개인적으로 이 옵션은 VirtualHost 옵션으로 변경되어야 한다고 생각된다.)


RailsMaxPoolSize <integer>

동시에 실행될 Rails Application의 인스턴스 수를 설정한다. 이 값을 높게 설정할 경우, 메모리 사용량은 그만큼 높아지지만, 동시접속 처리능력은 (당연히) 증가하게 된다. Global 옵션으로서 기본값은 20이다.

(PhusionRuby Enterprise Edition을 사용하면 메모리 사용량을 33% 정도 감소시킬 수 있다고 한다.)


RailsPoolIdelTime <integer>

Rails Application의 인스턴스의 유휴시간을 설정한다. 해당 유휴 시간 동안 아무 일도 하지않은 인스턴스는 메모리 보존을 위해서 shutdown되게 된다. 매뉴얼에서는 하나의 Rails 웹페이지에서 머무는 평균 시간의 2배 정도를 설정하는 것을 추천하고 있다. Global 옵션으로서 기본값은 120이다.


RailsUserSwitching <on|off>

사용자 전환을 실행할지 여부를 설정한다. 기본값은 on이다.


RailsDefaultUser <username>

사용자 전환이 실행될 경우, 전환한 사용자 계정을 설정한다. 기본값은 nobody이다.



Rails Application을 재시작하기

설정을 변경하거나 새롭게 배포한 후에는 Rails Application을 재시작해야한다. Mongrel 기반에서는 Mongrel 클라이언트를 모두 재시작하는 방법을 사용하였다. 그러면서 정확하게 죽지않는 개새끼(mongrel) 문제도 발생하곤 했다. Passenger에서는 두 가지 재시작 방법을 제공한다.


  1. Apache 재시작하기
  2. tmp/restart.txt 시간 갱신하기

첫 번째 방법은 (당연하지만) Apache를 재시작하는 것이다. 하지만 여러 개의 VirtualHost가 존재하는 서버에서 하나의 Rails Application을 재시작하기 위해서 매번 Apache를 재시작시키는 것은 부담되는 일이다.  이를 위해 Passenger에서는 특정 Rails Application만 재시작할 수 있는 방법을 제공한다. tmp 디렉토리에 restart.txt 파일을 생성하거나 갱신할 경우, 해당 Rails Application을 재시작한다. shell에서 touch 명령어를 통해서 간단하게 restart.txt를 생성하거나 갱신할 수 있다.


  1. $ touch tmp/restart.txt


결론

지금까지 Passenger의 기본적인 설치방법과 설정방법에 대해서 살펴보았다. Passenger의 등장으로 인해서 그동안 Rails Deployment에 있어서 장애로 여겨졌던 많은 부분들이 해소될 수 있을 것 같다. PHP만큼 쉽게 배포될 수 있는 환경이 구축됨에 따라서 Rails 개발자들도 좀 더 확산될 수 있을 것 같다. 또한 기존의 Rails Deployment 방식도 Passenger 기반으로 재설정되어야 할 것이다.


참조



이 글은 스프링노트에서 작성되었습니다.

2008/04/17 03:29 2008/04/17 03:29

Rails 2.1 : Dirty Objects

[Development]

원문(http://inocrazy.com/docs/7)에서 좀 더 깔끔하게 보실 수 있습니다.

속성의 변화를 추적하기 - ActiveRecord::Dirty

다음과 같은 경우를 생각해보자. Book이라는 모델이 있고, Book은 category를 나타내는 category_id라는 컬럼을 가지고 있다. Book의 category가 변경될 때마다 (즉, category_id 값이 변할 때마다) 특정 counter 테이블을 변경시켜줘야 한다면 어떻게 구현할 수 있을까? Book이 update되는 경우에 category_id가 변경되었는지 여부를 확인해서 counter 테이블을 갱신해야 할 것이다. 막상 구현하려고 하면, Book의 before_update callback을 이용해야하는 것은 바로 감이 오지만, category_id의 변경여부를 모니터링하는 방법은 바로 해결책을 내놓기가 어렵다.


즉, 위의 예처럼 모델의 특정 속성의 변경 여부를 모니터링할 필요가 있을 때, 쉽게 사용할 수 있는 방법이 Changeset 9127에서 추가되었다. 물론 기존에도 동일한 기능을 하는 acts_as_modified 라는 플러그인이 존재하였으나, Rails에 기능으로서 포함됨으로써 보다 편하게 사용할 수 있게 되었다. ActiveRecord::Dirty 모듈은 ActiveRecord::Base 클래스에 mix-in 되면서, '_changed?', '_change', '_will_change!', '_was' 접미사를 갖는 suffixed attribute method(접미사가 붙은 속성메소드)를 추가한다. suffixed attribute method는 ActiveRecord::AttributeMethodsattribute_method_suffix 클래스 메소드에 의해서 쉽게 추가할 수 있는데, 추후 다른 문서에서 ActiveRecord::AttributeMethods에 대해서 구체적으로 살펴볼 것이다.


ActiveRecord::Dirty에 의해 추가되는 메소드는 다음과 같다.

  • obj.<attr>_changed?
  • obj.<attr>_was
  • obj.<attr>_change
  • obj.<attr>_will_change!
  • obj.changed?
  • obj.changed
  • obj.changes

사용법 파악하기

위에서 열거한 메소드들을 하나씩 살펴보면서 사용법을 파악해보자. Person이라는 모델이 name이라는 속성을 갖는다고 가정하자. (이 단락의 소스코드들은 하나의 코드를 편의상 나누어둔 것이다.)


  1. person = Person.find_by_name('Dongkyu Kim')
  2. person.name          # => 'Dongkyu Kim'
  3. person.name_changed? # => false

'Dongkyu Kim'이라는 name 속성을 갖는 레코드를 가져온다. <attr>_changed? 메소드는 해당 속성의 변경 여부를 알려준다. 단, 이 변경 여부는 저장되기 전에만 확인할 수 있다. 저장이 되면서, 내부에서 변경정보를 유지하고 있는 @changed_attributes 속성이 리셋되기 때문이다.


  1. person.name = 'Kim Dongkyu'
  2. person.name_changed? # => true
  3. person.name_was      # => 'Dongkyu Kim'
  4. person.name_change   # => ['Dongkyu Kim', 'Kim Dongkyu']

name이 변경되었기 때문에 name_changed?는 true를 리턴한다. <attr>_was 메소드는 변경 이전의 값을 알려준다. 또한 <attr>_change 메소드를 통해서 변경 이전 값과 이후의 값을 Array로 받을 수 있다.


  1. person.changed?  # => true
  2. person.changed   # => ['name']
  3. person.changes   # => { 'name' => ['Donkyu Kim', 'Kim Dongkyu'] }

obj.changed? 메소드는 하나의 속성이라도 변경되었을 경우, true를 리턴한다. obj.changed 메소드는 변경된 속성명을 Array로 알려준다. 또한 obj.changes 메소드는 변경된 속성명과 함께 해당 속성의 변경 이력을 Hash 값으로 전달해준다.


  1. person.save
  2. person.changed?      # => false
  3. person.name_changed? # => false

위에서 언급하였듯이 일단 저장하게 되면, 저장 이전의 변경 정보는 알 수 없게 된다.


몇 가지 주의사항들

위에서 설명한 메소드들은 전혀 어려울 것 없이 코드에서 사용할 수 있다. 이 단락에서는 ActiveRecord::Dirty의 메소드를 사용하는데 있어서 주의해야할 몇 가지를 언급하면서 문서를 마치고자 한다.


<attr>= writer로 속성을 변경하지 않는 경우

ActiveRecord::Dirty의 메소드들은 기본적으로 <attr>= writer로 값을 변경하는 경우에, 변경 정보를 모니터링할 수 있다. 따라서 다른 메소드로 속성 값을 변경하는 경우에는, <attr>_will_change! 메소드로 해당 속성이 변경될 것임을 미리 알려주어야만 속성의 변경 여부를 파악할 수 있다.


  1. person = Person.find_by_name('Dongkyu Kim')
  2. person.name_will_change!  # => name 속성이 다른 메소드로 변경될 것임을 알림.
  3. person.name.upcase!       # => <attr>= writer로 변경하지 않음.
  4. person.name_changed?      # => true

같은 값으로 변경하는 경우에는?

만약 속성의 값을 같은 값으로 설정하는 경우, 또는 A에서 B로 변경했다가 다시 A로 변경하는 경우의 changed?의 결과는 어떻게 될까?


  1. person = Person.find_by_name('Dongkyu Kim')
  2. person.name                     # => 'Dongkyu Kim'
  3. person.name_changed?            # => false
  4. person.name = 'Dongkyu Kim'     # 같은 값으로 설정한다.
  5. person.name_changed?            # => false
  6. person.name = 'Kim Dongkyu'
  7. person.name_changed?            # => true
  8. person.name_change              # => ['Dongkyu Kim', 'Kim Dongkyu']
  9. person.name = 'Dongkyu Kim'
  10. person.name_changed?            # => true
  11. person.name_change              # => ['Dongkyu Kim', 'Dongkyu Kim']
  12. person.changed?                 # => true

위의 예제에서 보는 것과 같이 같은 값으로 변경하는 경우에는 변경된 것으로 인식하지 않는다. 하지만 한번이라도 다른 값으로 변경된 속성은 changed?를 true로 리턴하게 된다.


참조

이 글은 스프링노트에서 작성되었습니다.

2008/04/16 03:47 2008/04/16 03:47

make_resourceful #1

[Development]

원문(http://inocrazy.com/docs/6)에서 좀 더 깔끔하게 보실 수 있습니다.

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의 동작 방식을 변경하는 법을 알아볼 것이다.

이 글은 스프링노트에서 작성되었습니다.

2008/04/11 11:04 2008/04/11 11:04

Rails 2.1 : Gem Dependencies

[Development]

원문(http://inocrazy.com/docs/4)에서 좀 더 깔끔하게 보실 수 있습니다.

Rails와 Gem

Rails를 개발하다보면 외부의 Gem에 의존하게 되는 경우가 있다. 예를 들어 RSS를 읽어와서 item의 title과 link를 추출하는 경우에, 빠르고 간편한 처리를 위해 hpricot을 사용할 수 있을 것이다. 또한 업로드하는 파일의 thumbnail을 생성하기 위해서 rmagick gem을 필요로 하게된다. 이 경우 Rails Application은 hpricotrmagick이라는 gem에 의존적이게 된다. 따라서 해당 gem이 설치되어 있지 않거나, 필요로 하는 버전 이하의 gem이 설치되어 있을 경우, 정상적인 결과를 보장할 수 없게 된다. 기존의 Rails에서는 이러한 Gem 의존성 문제를 프로그램적으로 해결할 수 있는 방법이 없었다. 그래서 Rails 2.1에서 Gem 의존성을 정의하고, 그에 따라서 Gem을 자동으로 로드해주는 기능이 추가되게 되었다.


다음 코드는 Rails 2.1에서의 config/environments.rb 파일 중, 초기화와 관련된 부분이다.

  1. Rails::Initializer.run do |config|
      config.gems "bj"
      config.gems "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
      config.gems "aws-s3", :lib => "aws/s3"
    end

위와 같이 초기화 블럭에서 config.gems GEMNAME <options> 형식으로 Gem 의존성을 정의할 수 있다. 위의 코드를 통해서 이 Rails Application은 "bj", "hpricot", "aws-s3" 이라는 gem들에 의존하고 있다는 것을 알 수 있다. #gems는 Rails::Configuration 클래스에 새로 추가된 메소드로서 주어진 옵션으로 Rails::GemDependency 클래스의 인스턴스를 생성해주는 메소드이다. 위와 같이 의존성이 정의된 gem은 Rails Application이 가동될 때, 해당 gem의 존재 여부와 버전의 적합성을 판단하여 gem을 로드할 수 없을 경우, 에러 메시지를 발생시킨게 된다.


Rails::GemDependency

다음은 Rails::GemDependeny의 initialize 메소드다. 아래의 코드에서 Gem 의존성을 설정할 때 설정할 수 있는 옵션의 종류를 알 수 있다.

  1. def initialize(name, options = {})
      @name     = name.to_s
      if options[:version]
       @requirement = Gem::Requirement.create(options[:version])
       @version     = @requirement.instance_variable_get("@requirements").first.last
      end
      @lib      = options[:lib]
      @source   = options[:source]
      @loaded   = false
      @load_paths_added = false
      @unpack_directory = nil
    end

  1. :version

    우선 version 정보를 설정할 수 있다. Ruby 코드에서 특정 버전의 gem을 필요로 하는 경우, 다음과 같이 표기하였다. 위 코드에서 '~>3.0' 부분이 gem에 대한 버전 정보로서, GemDependency에서도 동일한 표기법으로 버전 정보를 설정한다.

    1. gem 'RedCloth', '~> 3.0'   # gem을 $LOAD_PATH에 추가.
    2. require 'RedCloth'

    version 정보로 Gem::Requirement create 메소드(factory method)인스턴스를생성한후에, 인스턴수변수에서 Gem::Version 정보를추출한다.



  2. :lib

    gem 이름과 require로 불러오는 이름이 다를 경우, :lib으로 라이브러리명을 명시할 수 있다.

    1. gem 'aws-s3', '>= 0.4.0'
      # require 'aws/s3'
      config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0'

  3. :source

    gem을 특정 repository에서 install해야하는 경우, :source로 명시할 수 있다. 명시한 source 정보는 gems:install rake task에서 사용되게 된다.

    1. config.gems "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"

Gems Rake Task

정의된 gem 의존성을 통해서, 설치되지 않은 gem을 확인하고, 설치할 수 있는 Rake Task도 추가되었다. 특히 신규 서버에 Rails Application을 설치하는 경우(ex. App. server 추가), 기존에는 Capistrano 등의 배포툴에서 Gem 의존성을 보장하는 코드를 작성해서 일괄적으로 gem을 설치하곤 했다. 하지만 gems rake task를 통해서 보다 간편하고 정확하게 Gem 의존성을 보장할 수 있게 되었다. 아래의 이미지에서 rake -T 명령어로 gems rake task가 추가된 것을 볼 수 있다.


rake_gems(2).png


  1. rake gems

    Rails에서 의존하고있는 gem의 목록을 보여준다. 아래의 이미지에서 Rails Application이 hpricotspringnote_resources gem에 의존하고 있다는 것을 볼 수 있다. 만약 의존하고 있는 gem을 발견하지 못 할 경우, gem 이름 앞의 체크표시([*])가 표시되지 않으며, 에러 메시지를 보여준다.

    rake_gems(1).png


  2. rake gems:install

    필요로 하는 모든 gem을 설치한다. 만약 source 정보를 명시하였다면 해당 source로 부터 gem을 설치한다.


  3. rake gems:unpack <GEM=[GEMNAME]>

    vendor/gems 디렉토리에 gem을 unpack한다. 특정 Gem의 source 코드가 궁금한 경우, gem repository에 직접 접근해서 소스코드를 보지 않고, 현재 디렉토리에 unpack해서 확인하는 경우가 많다.

    1. gem unpack GEMNAME

    그와 같이 repository에 있는 gem을 수정해서 자신만의 버전으로 사용하거나 하는 경우, unpack을 통해서 따로 설치한 gem을 사용할 수 있다. 이 경우 우선순위는 vendor/gems 에 설치된 gem이 갖게된다.


    Changeset 9140까지는 GEM=[GEMNAME]을 명시하여, 특정 Gem만 unpack할 수 있었으나, Changeset 9215부터 gem 이름을 명시하지 않을 경우, 모든 gem을 unpack하도록 기능 추가되었다.


  4. rake gems:build <GEM=[GEMNAME]>

    Chageset 9215에서 추가된 task로서 unpack된 gem(들)을 빌드한다. (native extension)


참조

이 글은 스프링노트에서 작성되었습니다.

2008/04/04 21:36 2008/04/04 21:36

Rails2.0에서 scaffold_resource

[Development]

    Rails 1.2.x에서는 scaffold_resource라는 generator 있었다. 예를 들어 post라는 model

    scaffold_resource 생성하기 위해서는 다음과 같은 명령어가 사용되었다.


    $> script/generate scaffold_resource post title:string content:text created_at:datetime


    위의 명령어는 다음과 같은 요소들을 생성하게 된다.

    • Model (app/models/post.rb)
    • RESTful controller (app/controllers/posts_controller.rb)
    • Views (app/views/posts/)
    • Migration (db/migrate/001_create_posts.rb)
    • RESTful route (map.resources :posts)
    • Test Suite

    Rails 2.0에서는 scaffold_resource 라는 generator 삭제되고, scaffold generator 동일한 역할을 하게 되었다. "script/generate scaffold <model> <columns>"라는 명령어를 실행시킬 경우, 위와 동일한 요소들을 생성하게 된다.


    Rails 1.2.x에서 사용되던 "script/generate scaffold <model> <controller>" 같은 형태의 명령어는 사용할 없게 되었다.


    scaffold 도움말

    scaffold 도움말

2007/12/23 05:17 2007/12/23 05:17