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


블로그도 좀 쓰고 그럴까...

왜이렇게 블로그에 글을 올린다는건 부담스럽지…

위키나 다른 곳에 글을 올리는건 크게 부담스럽게 생각하지 않으면서, 꼭 블로그에 글 쓰는건 부담스러워 하는거 같같다. ;;

하루 한개씩 올리는 연습이라도 해야 하나…

Posted by Kenny Tue, 22 Apr 2008 11:50:00 GMT