Capistrano deploy file for Thin & NginX & SCM Git

지금 프로젝트에서 사용중인 Capistrano Deploy File.

잡다한 작업은 plugin에 맡기니 일이 확실히 줄어든다. Capistrano Bells Plugin 사용.

# pty setting
default_run_options[:pty] = true

# General configuration settiongs, required for all recipes!
set :application, "#{application_name}"
role :web, "#{webserver_domain}"
role :app, "#{appserver_domain}"
role :db, "#{dbserver_domain}", :primary => true

# User Setting
set :user, "production"

# Deployment Settings
set :repository, "#{repository_location}"
set :scm, :git
set :deploy_to, "#{deploy_location}"
set :deploy_via, :remote_cache
set :config_files, %w()

# Change this to :thin if you want to use Thin instead.
set :app_server, :thin

# =============================================================
# Application Server Settings (Thin or Mongrel)
# =============================================================
set :thin_servers, 3
set :thin_port, 7007
set :thin_environment, 'production'
set :thin_address, '127.0.0.1'
set :thin_conf, "#{shared_path}/config/thin.yml"

# =============================================================
# Nginx Settings
# =============================================================
set :nginx_sites_available, "/etc/nginx/sites-available"
set :nginx_sites_enabled, "/etc/nginx/sites-enabled"

after "deploy:update_code", "db:symlink"
after "db:symlink", "attachment:symlink"

namespace :db do
  desc "Make symlink for database yaml"
  task :symlink do
    run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
  end
end

namespace :attachment do
  desc "Make symlink for attachment directory"
  task :symlink do
    run "ln -nfs #{shared_path}/attachments/article_images #{release_path}/public/images/article_images"
  end
end

Posted by Kenny Thu, 24 Apr 2008 08:41:00 GMT


Rails with NginX File Upload

Swfupload를 이용한 파일 업로드 시에 생겼던 문제.

사실 알고보면 단순한 문제인데, 원인 파악을 잘못해서 고생했었다.

413 에러가 계속 나는 문제였는데, 이 413에러를 Swfupload 쪽에서 보내는 걸로 착각한것. 서버에는 호출됐다는 로그가 안남고, swfupload 쪽에서는 계속 413에러를 내고, 해당 에러 메시지를 swfupload 쪽에서 찾아보면 UI_NO_CLASS_FOUND가 뜨고.. ;;; 대체 이게 뭔 에런지 거의 여덟시간을 헤멤.

결론은…. 너무나도 당연하게도 HTTP 413 메시지였다. ;;; Request entity too large. 맘 편하게 그냥 처음부터 HTTP 에러 메시지로 갔으면 쉬웠을 것을.. 괜히 swfupload랑 연결시켜서 생각하느라 시간낭비한 상황.

당연히, nginx의 vhost 설정에 clientmaxbody_size 50M 한줄 추가로 해결. ;;

좀 쉽게 가도 될 것을…

Posted by Kenny Thu, 24 Apr 2008 03:05:00 GMT


지금 사용중인 .irbrc 파일.

레일스 개발을 하면서 script/console을 쓰게 되는 경우도 있고, 간단한 ruby code 테스트에는 irb를 습관적으로 많이 사용하기에 .irbrc 파일을 재정의 해서 사용중이다.

레일스 관련 약간의 설정, 그리고 일상적으로 많이 쓰이는 것들을 require한 것 뿐. ^^; 백업 용도로 블로그에 정리!

require 'rubygems'
require 'irb/completion'
require 'irb/ext/save-history'
require 'map_by_method'
require 'what_methods'
require 'pp'

IRB.conf[:AUTO_INDENT]=true
IRB.conf[:SAVE_HISTORY] = 100
IRB.conf[:HISTORY_FILE] = "#{ENV['HOME']}/.irb-save-history"
IRB.conf[:PROMPT_MODE]  = :SIMPLE

# Just for Rails...
if rails_env = ENV['RAILS_ENV']
  rails_root = File.basename(Dir.pwd)
  IRB.conf[:PROMPT] ||= {}
  IRB.conf[:PROMPT][:RAILS] = {
    :PROMPT_I => "#{rails_root}> ",
    :PROMPT_S => "#{rails_root}* ",
    :PROMPT_C => "#{rails_root}? ",
    :RETURN   => "=> %s\n"
  }
  IRB.conf[:PROMPT_MODE] = :RAILS

  # Called after the irb session is initialized and Rails has
  # been loaded (props: Mike Clark).
  IRB.conf[:IRB_RC] = Proc.new do
    ActiveRecord::Base.logger = Logger.new(STDOUT)
    ActiveRecord::Base.instance_eval { alias :[] :find }
  end
end

Posted by Kenny Thu, 24 Apr 2008 01:51:00 GMT


POJO, DTO, VO에 관하여..

OKJSP의 DTO, POJO, VO 의 차이점이 무엇인가요? 라는 글에 답변으로 달았던 글입니다.

일단.. DTO, VO는 따로 떼 놓고.. POJO는 Plain Old Java Object 라고 부릅니다. 원래는 Java Language Specification을 제외한 어떤 규칙에도 제약받지 않는 녀석(그러니까, 기본 Java Spec에 있는 것을 제외한 특정 클래스를 상속 받거나, 인터페이스를 구현하거나, Annotation을 지정하는..)을 POJO라 그러는데, 사실 이렇게 만들기는 힘들어서, 심각한 수준의 제약만 아니면 POJO-compliant라고 하긴 합니다. (결국, DTO나 VO같은 특정 부분에 사용되는 것을 지칭하는게 아닙니다.)

DTO랑 VO는.. 사실 좀 역사가 복잡합니다. ;;

Core J2EE Patterns 라는 책에서는… Value Object랑 Transfer Object를 동일한 뜻으로 사용합니다만(Transfer Object at Core J2EE Patterns).. 반대로 Martin Fowler는 저서 Patterns of Enterprise Application Architecture에서 약간 다른 의미로 이야기 합니다. DTO는 메소드 호출 횟수를 줄이기 위해 데이터를 담고 있는 녀석으로, VO는 값이 같으면 동일 오브젝트라고 볼 수 있는 녀석으로 표현을 하고 있죠.

예를 들자면

DTO a = new DTO(1);
DTO b = new DTO(1);

이라고 했을때,

a != b
이지만,

VO a = VO(1);
VO b = VO(1);

이라고 했을때

a == b
라고 정의하는 형태라고 보시면 됩니다.

개인적으로는, 구분의 용이성과 용도가 갈리는 경우가 분명히 존재한다는 면에서, 마틴 파울러의 정의에 한표를 던집니다만.. ;; 사실 저거 구분하는것도 일이라.. 보통은 동일한 의미로 사용합니다.

Posted by Kenny Wed, 23 Apr 2008 12:00:00 GMT


Attachment_fu model With Custom Filename, Square Cropping.

이번 작업을 하면서 필요했던 부분.

Attachment_fu에서 저장되는 파일 명 변경과 함께, 특정 이미지를 골라서 정해준 위치에 맞게 Cropping을 하는 기능이 필요했다. Cropping 관련 메소드는 Kropper에서 빌려옴. ^^

class ArticleImage < ActiveRecord::Base
  belongs_to :article

  @@thumbs = {
    :large => '180x180!',
    :medium => '120x120!',
    :small => '90x90!'
  }

  has_attachment :content_type => :image,
                 :storage => :file_system,
                 :max_size => 20.megabytes,
                 :path_prefix => "public/images/#{table_name}",
                 :resize_to => '600x400>',
                 :processor => "Rmagick",
                 :thumbnails => {
                   :large => '180x180!',
                   :medium => '120x120!',
                   :small => '90x90!'
                 }

  validates_as_attachment

  # Customizing Filename.
  def full_filename(thumbnail = nil)
    file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
    @thumb = self.thumbnail unless (@thumb = thumbnail)
    asset = self.parent_id.nil? ? self.id : self.parent_id
    File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(@thumb, asset)))
  end

  # Filename Customizing Method
  def thumbnail_name_for(thumbnail = nil, asset = nil)
    suffix = "_#{thumbnail}" unless thumbnail.blank?
    extension = filename.scan(/\.\w+$/)
    return "#{asset}#{suffix}#{extension}"
  end

  # crops the attached image according to the passed left, top, width, and height params.
  # if resize_to_stencil is true, the image is resized to stencil_w x stencil_h.
  # after cropping and resizing, the resulting image is saved back to disk.
  # any thumbnail image the image may have is automatically updated.
  def crop!( options = {} )
    # setup the default params
    options[:crop_left]   ||= 0
    options[:crop_top]    ||= 0
    options[:crop_width]  ||= 100
    options[:crop_height] ||= 100
    options[:stencil_width]  ||= 100
    options[:stencil_height] ||= 100
    options[:resize_to_stencil] ||= false
    # passed params could be strings, so convert them to ints/booleans
    crop_l = options[:crop_left].to_i
    crop_t = options[:crop_top].to_i
    crop_w = options[:crop_width].to_i
    crop_h = options[:crop_height].to_i
    stencil_w = options[:stencil_width].to_i
    stencil_h = options[:stencil_height].to_i
    resize_to_stencil = false if (options[:resize_to_stencil] == false) || (options[:resize_to_stencil] == "false")
    resize_to_stencil = true if (options[:resize_to_stencil] == true) || (options[:resize_to_stencil] == "true")

    # call the appropriate crop method for the image processor we're using with attachment_fu
    if self.class.include?(Technoweenie::AttachmentFu::Processors::RmagickProcessor)
      crop_with_rmagick! crop_l, crop_t, crop_w, crop_h, stencil_w, stencil_h, resize_to_stencil
    else
      raise "You're using an unsupported image processor."
    end
  end

  private

  def crop_with_rmagick!(crop_l, crop_t, crop_w, crop_h, stencil_w, stencil_h, resize_to_stencil)
    # create a temporary cropped version of our image
    cropped_img = nil
    self.with_image do |img|
      if (crop_w <= 0) || (crop_h <= 0) || (crop_l + crop_w <= 0) || (crop_t + crop_h <= 0) || (crop_l >= img.base_columns) || (crop_t >= img.base_rows)
        raise InvalidCropRect
      end
      cropped_img = img.crop(crop_l, crop_t, crop_w, crop_h, true )
      cropped_img.resize!(stencil_w, stencil_h) if resize_to_stencil
    end
    # write the cropped image to a temp file, which attachment_fu will use
    # to replace the existing image file (and thumbnail, if any) on save
    self.temp_path = write_to_temp_file(cropped_img.to_blob)
    @@thumbs.each { |suffix, size| create_or_update_thumbnail(self.temp_path, suffix, *size) }
    #callback_with_args :after_resize, cropped_img
  end

end

Posted by Kenny Wed, 23 Apr 2008 02:10:00 GMT


Attachment_fu model Spec for RSpec

OKJSP에 남겨놨던 글을 좀 자세하게 블로그에 정리.

환경은 다음과 같다.

  • Ruby on Rails 2.0.2
  • RSpec 1.1.3
  • Spec::Rails 1.1.3
  • Attachment_fu 2008-03-17

사실 원래 목적은, 이번 플젝을 진행하면서 소소하게 BDD를 적용해 보다가, Plugin으로 처리되는 파일 업로드를 위한 Spec 파일을 만드는것.

역시나 언제나 처럼 구글에는 개발할 때 필요한 거의 모든게 있다. ^^

require File.dirname(__FILE__) + '/../spec_helper'

describe ArticleImage do
  before(:all) do
    SAMPLE_FILE = RAILS_ROOT + '/tmp/rails.png'

    @article_image = ArticleImage.new
    @article_image.uploaded_data =
      ActionController::TestUploadedFile.new(SAMPLE_FILE, 'image/png')
    @article_image.send :process_attachment
  end

  it "should be valid" do
    @article_image.should be_valid
  end
end

Posted by Kenny Tue, 22 Apr 2008 14:36:00 GMT


Working With Rails가 다시 오픈했습니다

원문

Working With Rails가 새로운 기능과 멋진 색상으로 돌아왔습니다. 아주 멋져 보여요. Rails Hackfests와 같은 행사를 주관해 주시는 것에 대해서도 감사드립니다.

Posted by Kenny Fri, 26 Oct 2007 10:58:00 GMT


MicroPlace가 오픈했습니다.

개발도상국의 노동자들에게 소액대출을 하고, 이자를 받는 사이트인 MicroPlace가 최근에 오픈하였습니다. MicroPlace에서 일하는 Josh Susser가 프로젝트에 대한 멋진 소개를 썼습니다.

“제가 아는한, MicroPlace는 Ruby on Rails로 구현된 최초의 SEC등록 온라인 중개사이트입니다. 시스템 전체적인 보안감사 부터 시작해서, 끔찍한 양의 규제들을 만나야 했습니다. 하지만 이런 문제들을 해결하는데 있어 루비 온 레일스로 인한 심각한 문제는 없었습니다.”

Josh는 MicroPlace가 Java로만 제작하던 eBay의 첫번째 레일스 애플리케이션이라고 이야기합니다. 멋져요. 모두 수고하셨습니다. 사이트의 개발 관점에서 더 많은 아티클들을 볼 수 있었으면 좋겠네요.

Posted by Kenny Thu, 25 Oct 2007 02:04:00 GMT


IntelliJ IDEA 7에 Ruby on Rails 지원이 추가되었습니다.

원문

IntelliJ IDEA는 오랜 기간 자바 개발자들 사이에서 최고의 IDE중 하나로 호평받았습니다. 이번 7.0 버전에서는, 자바 개발 뿐만 아니라 훌륭한 Ruby on Rails 지원 을 제공해 이미 Rails 지원을 포함하고 있는 Netbeans와 Eclipse를 추격합니다. Rails 지원을 소개하는 끝내주는 튜토리얼 비디오 도 확인해 보세요.

Posted by Kenny Mon, 15 Oct 2007 19:00:00 GMT


Capistrano 2.1

원문

생각했던 것 보다 많이 늦게 Capistrano 2.1이 출시되었습니다. (Capistrano는 여러대의 머신에서 병렬로 명령어 처리를 하기 위한 유틸리티입니다. 레일스 개발자들은 배포를 자동화하기 위해 Capistrano를 사용하고 있습니다.) 재밌을 만한 몇몇 기능을 포함해서 이번 릴리즈에는 꽤 많은 일을 해냈습니다. 언제나 처럼 RubyGems를 통해 설치해 주세요.

gem install capistrano

새로운 기능은 다음과 같습니다. (대충 중요도 순입니다.)

기본 PTY가 없습니다. 2.1이전에는, Capistrano는 각각의 명령어를 가상 터미널 장치(Pseudo-Terminal-Driver)를 통해 전달했습니다. 이 방식은 사용자의 프로필 스크립트가 실행되지 않는다는 부작용이 있었는데요, 이제는 아닙니다! 2.1부터 Capistrano는 명령어를 PTY를 통해 전달하지 않습니다. 따라서 사용자의 .profile(혹은 .bashrc 등의 개별 사용자 프로필 파일)이 정상적으로 로드됩니다. 다만, 어떤 시스템에서는 PTY가 할당되지 않았을 경우 명령어가 사용자의 입력을 받을 수 없는(non-interactive) 상황이 일어날 수 있습니다. svn이나 passwd같은 명령어가 생각했던 방식으로 동작하지 않는다면, capfile에 다음 설정을 입력하여 예전 방식을 사용하실 수 있습니다.

default_run_options[:pty] = true

sh Wrapping을 끌 수 있습니다. 어떤 호스트에서는 POSIX 셸이 Capistrano 2.0 부터 사용하는 독립적인 명렁어 실행을 할 수 없게 되어 있습니다. 그런 호스트에서 작업중이시라면 capfile에 다음 설정을 입력하면 됩니다.

default_run_options[:shell] = false

설정이 적용되면, Capistrano는 “sh -c” 명령으로 감싸는 대신 직접 명령을 실행할 겁니다. 이렇게 사용하신다면 원격지 사용자 계정의 기본 셸이 POSIX 호환이어야 합니다. 아닐경우 알수 없는 에러가 날테니까요.

Git SCM 지원. Garry Dolley, Geoffrey Grosenbach, Scott Chacon의 노력으로 Capistrano에 Git SCM 모듈이 추가되었습니다. Git를 쓰신다면, 이제 설정파일에 SCM을 Git로 지정해 줄 수 있습니다.

set :scm, :git

Accurev SCM 지원. Doug Barth의 노력으로, Accurev 사용자들도 Capistrano를 편하게 쓰실 수 있습니다. 설정파일의 SCM을 accurev로 지정하시면 됩니다.

set :scm, :accurev

레일스 플러그인을 지원합니다. 이제 capify로 생성된 Capfile에는 vender/plugins/*/recipes/*.rb를 자동 로드하는 부분이 있습니다. 이 기능이 필요하지만 이미 Capfile이 있고, 직접 수정한 부분을 잃어도 된다면 그냥 기존 Capfile을 지우고 capify명령을 다시 실행시키세요. 직접 수정한 부분이 중요하다면, “config/deploy”를 읽어오는 부분 이전에 다음 줄을 넣으시면 됩니다.

Dir['vender/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }

윈도우에서 안전한 파일 읽기. 이제 Capistrano는 파일을 읽을때마다 “b”옵션을 설정합니다. 따라서, 윈도우의 경우에도 바이너리 형식으로 정상적으로 읽을 수 있습니다.

Cap shell과 sudo. Capistrano shell은 이제 sudo 명령을 알아듣고, 정확하게 암호를 물어볼 겁니다.

의존성 확인을 위해 ‘match’를 사용. deploy:check:”match”라는 원격 의존성 확인 메소드가 추가되었습니다. 환경설정이 제대로 되어 있는지 확인하기 위해 명령어 출력 결과에 별도의 정규표현식을 적용하실 수 있습니다.

depend :remote, :match, "rake -V", /version 0\.7/

Namespaces#top. 어떤 태스크에서 다른 태스크를 실행시켜야 할 때가 있습니다. 그런데, 실행시킬 태스크의 부모 네임스페이스가 현재 네임스페이스의 이름과 유사하다면, 잘못된 작업이 실행될 수 있습니다. 이제 “top” 메소드를 이용해서, 네임스페이스 계층구조의 최상위로 접근하실 수 있습니다.

namespace :apache do
  namespace :deploy do
    task :restart do
      run "restart apache"
      top.deploy.restart
    end
  end
end

나머지 변경 사항들. 소소한 버그 수정이나 변경들도 있습니다.

  • 업로드 시에 기본 권한이 0660에서 0664로 변경되었습니다.
  • deploy:pending 태스크를 실행시켰을때 최근의 변경된 리비전을 참조하도록 해서 마지막으로 배포된 변경사항이 중복 출력되는 문제를 해결했습니다.
  • Subversion에 최근 리비전을 조회할때, “Revision” 대신 “Last Chaged Rev”를 사용합니다.
  • copy_test 시에 “stringio” 를 명시적으로 require하게 변경하였습니다.
  • Subversion#query_revision이 실패하였을시, 좀 더 도움되는 에러가 출력됩니다.
  • 실 운영 서버가 아닌 경우에는 update:revision을 실행시키지 마십시오.
  • subversion 연결시에 기본적으로 -password 옵션을 사용합니다. command-line에서 직접 암호를 보내길 원치 않는 사람들을 위해 :scmpreferprompt 변수가 추가되었습니다.
  • 예측이 가능한 sudo 작업에 대해서는 sudo -p 옵션을 사용합니다.
  • 동일 루비 프로세스에 대해 같은 작업을 해야 할때 필요한 설정들을 별도의 독립적인 설정 파일로 관리할 수 있게 하였습니다.
  • :scmauthcache 변수를 통해 subversion 접근 권한을 캐싱 할 수 있도록 하였습니다.
  • Don’t let a task trigger itself when used as the source for an “on” hook.
  • versiondir, currentdir, shared_dir 변수가 추가되어서 배포시의 디렉토리 구조를 변경할 수 있습니다.
  • deploy:restart 시에 프로세스를 실행시킬 사용자명을 :runner 변수에 추가할 수 있도록 변경되었습니다.
  • “-h” 옵션의 출력을 변경하였습니다. 이제 “-q”는 기본값이 아닙니다.

잘 사용하세요! 버그를 발견하시면 Rails trac 의 “Capistrano” 컴포넌트에 올려 주시길 부탁드립니다.

Posted by Kenny Sun, 14 Oct 2007 09:43:00 GMT