Rails on me :)
Ruby on Rails : Phusion Passenger 설치하기
Phusion Passenger 설치과정을 간단하게 기록하려한다. 본 예제는 Ubuntu 환경이며, Apache2 가 깔려있다는 전제에서 시작된다.
os : Ubuntu 9.10 Karmic Koala
ruby : 1.8 version
> sudo gem install passenger
만약,
Error installing passenger
Failed to build gem native extension
위 메세지가 보인다면,
> sudo apt-get install ruby1.8-dev
를 설치해주면 된다.
이제 다시 passenger 를 설치해보자.
> sudo gem install passenger
Apache2 관련 passenger 모듈 설치하기
> sudo passenger-install-apache2-module
설치과정은 아래 단계를 거치며 진행된다. enter 를 치며, 다음 단계로 진행하면 된다.
step 1.
Welcome to the Phusion Passenger Apache 2 module installer, v2.2.11.
This installer will guide you through the entire installation process. It
shouldn't take more than 3 minutes in total.
Here's what you can expect from the installation process:
1. The Apache 2 module will be installed for you.
2. You'll learn how to configure Apache.
3. You'll learn how to deploy a Ruby on Rails application.
Don't worry if anything goes wrong. This installer will advise you on how to
solve any problems.
Press Enter to continue, or Ctrl-C to abort.
step2.
Checking for required software...
* GNU C++ compiler... not found
* Ruby development headers... found
* OpenSSL support for Ruby... found
* RubyGems... found
* Rake... found at /usr/bin/rake
* rack... found
* Apache 2... found at /usr/sbin/apache2
* Apache 2 development headers... not found
* Apache Portable Runtime (APR) development headers... not found
* Apache Portable Runtime Utility (APU) development headers... not found
Some required software is not installed.
But don't worry, this installer will tell you how to install them.
Press Enter to continue, or Ctrl-C to abort.
자기 환경에 설치되어 있지 않은 프로그램에 대한 정보를 친절하게 보여준다. Enter 를 치면 다음단계에서 설치방법에 대해 친절하게 설명해준다.
step 3.
Installation instructions for required software
* To install GNU C++ compiler:
Please run apt-get install build-essential as root.
* To install Apache 2 development headers:
Please run apt-get install apache2-prefork-dev as root.
* To install Apache Portable Runtime (APR) development headers:
Please run apt-get install libapr1-dev as root.
* To install Apache Portable Runtime Utility (APU) development headers:
Please run apt-get install libaprutil1-dev as root.
If the aforementioned instructions didn't solve your problem, then please take
a look at the Users Guide:
/usr/lib/ruby/gems/1.8/gems/passenger-2.2.11/doc/Users guide Apache.html
위의 메세지대로 하나씩 설치를 한후, 다시
sudo passenger-install-apache2-module 명령을 실행하자.
step 4.
The Apache 2 module was successfully installed.
Please edit your Apache configuration file, and add these lines:
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.2.11/ext/apache2/mod_passenger.so
PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.2.11
PassengerRuby /usr/bin/ruby1.8
After you restart Apache, you are ready to deploy any number of Ruby on Rails
applications on Apache, without any further Ruby on Rails-specific
configuration!
Press ENTER to continue.
성공이다. 바탕색이 칠해진 부분은 나중에 apache 설정에 넣어야할 부분임으로 복사해두자.
step 5.
Deploying a Ruby on Rails application: an example
Suppose you have a Rails application in /somewhere. Add a virtual host to your Apache configuration file and set its DocumentRoot to /somewhere/public:
<virtualhost *:80="">
ServerName www.yourhost.com
DocumentRoot /somewhere/public # <-- be sure to point to 'public'!
<directory public="" somewhere="">
AllowOverride all # <-- relax Apache security settings
Options -MultiViews # <-- MultiViews must be turned off
</directory>
</virtualhost>
And that's it! You may also want to check the Users Guide for security and
optimization tips, troubleshooting and other useful information:
/usr/lib/ruby/gems/1.8/gems/passenger-2.2.11/doc/Users guide Apache.html
마지막 단계에서는 apache 의 virtual host 설정에 관한 예제도 보여준다.
자기 환경에서 Apache Configure 파일을 찾는 방법은 다음과 같다.
> apache2ctl -V | grep SERVER_CONFIG_FILE
output> -D SERVER_CONFIG_FILE="/etc/apache2/apache2.conf"
표시된 경로에 있는 파일을 열고(이때, 반드시 sudo 를 입력하여 root 계정으로 편집해야한다),
Phusion Passenger 설치과정에서 복사한 문구를 파일 끝에 넣는다.
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.2.11/ext/apache2/mod_passenger.so
PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.2.11
PassengerRuby /usr/bin/ruby1.8
이제 virtualhost 설정을 해주자.
> sudo vim /etc/apache2/sites-available/default
파일에 virtualhost 설정해준다
<virtualhost *:80="">
ServerName www.yourhost.com
DocumentRoot /somewhere/public # <-- be sure to point to 'public'!
<directory /somewhere/public >
AllowOverride all # <-- relax Apache security settings
Options -MultiViews # <-- MultiViews must be turned off
</directory>
</virtualhost>
DocumentRoot 선언에 rails 어플리케이션의 public 폴더를 명시하는 점에 주목하자.
참고로, # 주석은 실제 입력시 빼야 문법오류가 안뜬다
설정이 완료되었으면, apache 를 재시작하자.
> sudo /etc/init.d/apache2 restart
이제
http://localhost/ 로 접속했을 때, rails 어플리케이션이 생성된 폴더가 root 로 잡히는걸 확인할 수 있다.
라벨: Phusion Passenger, Ruby on Rails
Ruby on Rails : Action Controller 예외처리(404, 500) 다루기
사용자가 브라우저에 존재하지 않는 경로를 요청하거나, 혹은 잘못된 ID값을 get 방식으로 전달할 때 Rails 는 exception 을 일으킨다. Rails API의 ActionController::Rescue 모듈에 관한 설명에는 다음과 같이 나와있다.
Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view is already implemented by the Action Controller, but the public view should be tailored to your specific application.
The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such file exists, an empty response is sent with the correct status code.
You can override what constitutes a local request by overriding the local_request? method in your own controller. Custom rescue behavior is achieved by overriding the rescue_action_in_public and rescue_action_locally methods.
위의 설명대로, rescue_action_in_public 와 rescue_action_locally 함수를 오버라이딩 함으로써 잘못수행된 요청에 대한 처리를 할수 있다.
rescue_action 메서드 명뒤에 붙은 public 과 local 은 어떤 차이가 있을까? 잘못된 수행에 의해 exception 이 발생할때, Rails 는 local_request? 함수를 통해 먼저 이 exception이 localhost 에서 발생했는지를 체크한다. 만약, true 일 경우, rescue_action_locally 를 호출하며 rescue_action_public 은 호출하지 않는다.
위의 exception 발생환경에 따른 분기는 유용할지도 모르나, 내 경우 모든 경우가 localhost 에서 발생했기에, rescue_action_in_public 을 언제 활용할 수 있는지는 모르겠다. 아래의 예제는 두 환경을 동일하게 처리하도록 코딩된 예제이다.
ApplicationController 에 다음 메서드를 오버라이딩 한다.
def rescue_action_in_public(exception)
case exception
when ActiveRecord::RecordNotFound, ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction
#render_404
render :file => "#{RAILS_ROOT}/public/404.html", :layout => false, :status => 404
else
#render_500
render :file => "#{RAILS_ROOT}/public/500.html", :layout => false, :status => 500
end
end
def rescue_action_locally(exception)
rescue_action_in_public(exception)
end
라벨: 404, 500, Controller, Exception, Ruby on Rails
Ruby on Rails : Active Record Validation 메세지 한글화 하기
Rails 가 제공하는 Model단의 validation 기능과 사용자 페이지(View)에 메세지를 보여주는 기능은 무척 유용하다. 만약, 가격 필드에 문자값을 넣으면 validates_numericality_of 메서드를 이용하여 검증하면 다음과 같은 메세지를 뿌려준다.
class Order < ActiveRecord::Base
validates_numericality_of :price
end
--------------------------------------------------------
"Price is not a number" <== view page 에서 보여지는 메세지.
위 메세지를 한글화 하려면 옵션을 주면 다음과 같이 보이게 할수 있다.
class Order < ActiveRecord::Base
validates_numericality_of :price, :message => "항목은 숫자만 입력가능 합니다."
end
"Price 항목은 숫자만 입력가능 합니다."
문제는 저 Price 이다. 이는 속성의 이름이 default 로 문장의 맨앞에 나와있으며, 이를 바꿀 방법이없다. 그렇기에 아쉽지만 좀더 사용자 친화적인 메세지로 바꾸려면 validates_numericality_of 메서드를 포기하고 직접 만드는 수밖에 없다. 다음처럼 말이다.
class Order < ActiveRecord::Base
validate :price_must_be_a_number
protected
def price_must_be_a_number
begin
Kernel.Float(price)
rescue ArgumentError, TypeError
errors.add('price', '항목은 숫자만 입력해주세요.')
end
end
end
Kernel.Float 는 값이 float 타입인지를 판별하는 여러 방법중 하나이다. 이 방법이 아닌 다르게 체크해도 상관없다. 한데 문제가 있다. 사용자가 숫자가 아닌 문자열을 입력했을 때, price_must_be_a_number 메서드의 price 속성에 떨어지는값이 0.0인것이다 ! Rails 자체적으로 숫자입력이 필요한곳에 문자입력을 했을때 자동으로 0.0으로바꾸는것이다. 그렇기에 사용자가 문자를 입력해도 위의 validation이 통과되버리게된다.
해결방법을 찾아 며칠 전전긍긍한 끝에 Rails API 에서 해답을 얻을 수 있었다 !
http://api.rubyonrails.org/classes/ActiveRecord/Base.html 에 문제를 푸는 열쇠가 담겨있다. 다음과 같은 내용이 있다.
Accessing attributes before they have been typecasted
Sometimes you want to be able to read the raw attribute data without havingthe column-determined typecast run its course first. That can be done by using the_before_type_cast accessors that all attributeshave. For example, if your Account model has a balance attribute,you can call account.balance_before_type_castoraccount.id_before_type_cast.
This is especially useful in validation situations where the user mightsupply a string for an integer field and you want to display the originalstring back in an error message. Accessing the attribute normally wouldtypecast the string to 0, which isn‘t what you want.
_before_type_cast 를 통해, 케스팅되기 전의 값을 알수 있는 것이다! 이제 다음과 같이 validation 체크가 가능하다.
validate :price_must_be_a_number
protected
def price_must_be_a_number
begin
Kernel.Float(price_before_type_cast)
rescue ArgumentError, TypeError
errors.add('price', '가격항목에는 숫자만 입력해주세요.')
end
end
"Price 가격항목에는 숫자만 입력해주세요."
자, 이제 한글 validation 메세지를 보여주는 마지막 단계이다. errors.add 함수에는 세개의 인자값을 받는데, 다음처럼 마지막 인자인 option 설정을 통해 메세지에서 Price 부분을 제거할 수 있다.
validate :price_must_be_a_number
protected
def price_must_be_a_number
begin
Kernel.Float(price_before_type_cast)
rescue ArgumentError, TypeError
errors.add('price', '가격항목에는 숫자만 입력해주세요.', {:attribute => '' })
end
end
"가격항목에는 숫자만 입력해주세요."
위의 방식을 응용하면 모든 입력폼에 관한 한글 validation 메세지를 사용자들에게 제공해줄 수 있게 된다. :)
라벨: Model, Ruby on Rails, Validation
Ruby on Rails : has_and_belongs_to_many Association 의 마법
Ruby on rails 에서는 모델간의 관계를 정의하기 위해 has_many , belongs_to 등의 관계선언을 하게 된다.
이 본문에서 쓰고자 하는건 다대다 관계를 나타내는 :has_and_belongs_to_many 에 관해서다. 이 선언을 통해 다대다(N:N) 관계에서 Rails 가 제공하는 놀라운 마법을 경험하게 될것이다.
menus 와 posts 라는 두 테이블이 있다고 가정하자. 한개의 포스트(post)는 여러 메뉴에 속할 수 있고, 메뉴(menu)는 여러 post 들과 관련되어 있다면, 이 둘의 다대다 관계에 놓이게 된다. 이 두 테이블의 관계를 맺어주기 위해선 이들 사이에 join table 이 필요한데, Rails 의 네이밍 관습을 따르기 위해서 menus_posts 라는 이름으로 join table 을 생성해야 한다.
이제 각 테이블을 나타내는 모델 클래스에서 관계를 맺어주자.
class Menu < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :menus
end
class MenusPost < ActiveRecord::Base
# 선언없음.
end
MenusPost 모델에는 아무 설정도 하지 않았으며 또 이 모델 class 를 별도로 생성할 필요도 없는데, 이는 join table 이기에 Menu 와 Post 모델에서의 선언만으로 이 세 테이블의 관계를 Rails 는 이미 인식하고 있기 때문이다.
자, 이제 Rails 의 마법을 경험해보자.
Post 를 등록할 때, 여러 menu 들을 선택할 수 있도록 하기 위해선 일반적으로 입력폼에 각 메뉴들의 값을 checkbox 로 뿌려주어 사용자가 다중 선택할 수 있도록 제공될 것이다. 다음과 같이 말이다.
<input type="checkbox" name="post[menu_id][]" value="7" />
<!-- name 속성값에 "post[menu_id]" 뒤에 반드시 "[]" 를 붙여주어야 폼섭밋시 check 된 값들이 배열문자로 전달된다는 점에 유의하자. -->
만약 위와 같이 checkbox 를 제공하였다면, 생성(Create)시 menu_id 라는 속성이 존재하지 않는다는 에러메세지를 보게 될것이다. 이는 분명 당연한 결과이다. post 테이블에는 menu_id 라는 필드가 존재하지 않기 때문이다.
하지만 menu_id 에 s 를 붙여 다음과 같이 checkbox 를 제공하면,
<input type='checkbox' name='post[menu_ids][]' value='7' />
Create 가 성공적으로 수행되게 되는데, 여기에 우리가 모르는 Rails 의 마법이 발휘되었기 때문이다.
menu_id 라는 필드는 존재하지 않기에 에러를 발생시켰는데, menu_ids 라는 필드 역시도 존재하지 않음에도 Rails 는 (다대다 관계에 의해) post 가 여러 개의 menu_id 를 가질 수 있음을 알고 있었기에 에러를 발생시키지 않았다.
더욱 놀라운 점은, menus_posts Join table 의 내용을 들여다 보면 자동으로 입력된 값을 보게 된다는 것이다.
sql> select * from menus_posts;
+-----------------------+
+ menu_id | post_id +
+-----------------------+
+ 7 | 1 +
+-----------------------+
+ 9 | 1 +
+-----------------------+
위 쿼리의 결과에서 post_id 가 1 인 것은 방금 생성한 post 를 가리키고, menu_id 값 7, 8 은 post 생성시 체크한 menu 의 값들이다.
join talbe 에 값을 입력하는 코드를 넣지 않았음에도 Rails 가 모델의 관계를 인지하여 자동으로 값을 넣어준 것이다!
자, 그럼 수정(update)시에는 또 어떤 마법을 부리는지 살펴보자.
수정화면에서 생성시 선택했던 menu checkbox 들을 해제하고 다른 값들로 체크한 후 update 를 하면, Rails 는 자동으로 두가지 일을 수행한다.
첫째, join table 에서 기존에 입력된 값(post_id 가 1인 레코드)들을 모두 지운다.
둘쨰, 새로 선택한 값을 입력한다.
수행후 결과는 다음과 같다.
sql> select * from menus_posts;
+-----------------------+
+ menu_id | post_id +
+-----------------------+
+ 2 | 1 +
+-----------------------+
+ 4 | 1 +
+-----------------------+
어떤가? 놀랍지 않은가?
라벨: has_and_belongs_to_many, Model, Ruby on Rails