사실, 기본적인 파일 업로드는 특별한 플러그인이나 라이브러리를 사용하지 않아도 그리 어렵지 않게 구현할 수 있습니다. 그리고 파일 업로드를 직접 한 번 구현해 보는 것은 웹에서의 파일 업로딩 처리 흐름에 대해서 이해할 수 있는 좋은 계기가 될 것 입니다.
파일을 업로드를 구현하기 위한 첫 번째 단계는 html 페이지의 form 속에 file 필드를 추가하는 것이다.
<%= form_tag({:action=>'save_picture'}, :multipart => true)%>그런데 위의 내용에서 multipart 옵션을 빼먹으면 파일의 내용이 제대로 업로드 되지 않으므로 조심해야 한다. 위의 코드가 실제 어떤 html 코드가 되는 지 한 번 보고 넘어가자.
컨트롤러에서는 params를 통해서 전달된 파일의 내용을 읽어서 서버에 저장하게 되는데 보통은 파일 시스템에 파일로 저장한다. (물론 database 에 파일의 내용을 집어 넣을 수 도 있다. )
위의 form 에서 넘어온 파일 데이터는 params[:picture_file] 에 들어 있다. params[:picture_file]은 “IO” 객체이며, 이 객체에서 우리가 관심을 가지는 메소드는 original_filename() 과 content_type() 이다.
이제 IO 객체의 내용을 읽어서 파일로 저장해보자.
def create
org_filename = params[:picture_fle].original_filename
File.open(MYPATH+filename, “wb”){|f|f.write(params[”picture_file”].read)}
end
이 코드는 잘 동작하지만 실제로 상용화 하기에는 문제가 있다. 만약 서로 다른 사용자가 똑같은 이름의 파일을 업로드 한다면 나중 파일이 앞선 파일을 overwrite 하게된다.
이 문제를 해결하기 위해서는 매번 저장하는 파일의 이름이나 path를 unique 하게 해주어야 한다. 간단하게는 파일이름에 보통은 현재 시간값으로 prefix를 더해주는 방법이 있으며, 더 확실히 하고 싶으면 데이터베이스에서 얻을 수 있는 고유 id 값을 이용한다. 여기서는 시간 값을 이용해 보자.
org_filename = params[:picture_fle].original_filename
new_filename= Time.now.strftime(”%m%d%y%H%M%S”).to_s +”#{org_filename}
File.open(MYPATH+filename, “wb”){|f|f.write(params[”picture_file”].read)}
위에서와 같이 업로드된 파일을 서버상의 파일시스템에 저장하는 경우, 나중에 해당 파일에 대한 접근 경로를 알기위해서는 파일의 path와 파일명을 모델 객체에 저장하는 것이 보통이다.
picture = Picture.create :org_filename => org_filename,
:file_url => MY_URL_PATH + new_filename
이렇게 해서 기본이 끝났다. 그렇게 복잡하지는 않지만 몇 가지 신경쓸 것 들이 있어서 귀챦은 것은 사실이다. 또 사진 등을 업로드 하는 경우에는 썸네일을 추출해주거나 일정한 크기로 크기 변경을 해주는 과정이 추가로 필요하기도 하다.
물론, 이런 기능들을 제공해주는 여러가지 플러그인들이 Rails에 이미 존재하고 있는데 각각에 대해서 간단히 정리하면 다음과 같다.
레일스에서 파일 업로드를 위한 플러그인에는 다음과 같은 것들이 있으며 대부분 파일 저장과 이미지 크기 변경을 포함하는 비슷한 기능을 제공한다.
이 중에서 제일 간지나는 attachment_fu 에 대해서 좀 더 살펴보기로 하자.
Rick Olson 은 techno weenie 블로그의 운영자이며 Rails 코어팀 멤버이자 Mephisto 와 Beast 의 개발자이다. (오~~ Rick 에게 이 자리를 빌어 감사를 표하는 바이다.)
이제 attachment_fu 를 써봐야 겠다는 생각이 드는가? 그렇다면 일단 플러그인을 다운로드 받자.
script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/
README를 한 번 열어보라. 다음과 같은 내용이 쓰여있다. “어쨋든 잘 동작합니다. 사용법은 Mike Clark 의 블로그에 쓴 소개를 참고해주세요. “
Mick Clark 는 실제로 저자인 Rick 이 사용법을 제공하지 않아도 될 만큼 좋은 튜토리얼을 적었다.
저자도 포기한 사용법을 이 글에서 자세히 소개하는 것은 우스운 일이 될 수 있으니까 아주 간단하게 M,V,C 각 부분에서 구현한 코드의 주요 부분만 설명해보겠다.
자세한 내용은 역시 Mike Clark 의 블로그를 보시라.
form 은 앞에서 기본적으로 구현한 form 과 다른 것이 없다. 사실 어느 플러그인을 사용하더라도 동일할 것이다.
기억할 것은 두 가지이다. 첫째, Attachment_fu 에서는 파일 필드의 이름으로 “uploaded_data”를 사용한다. 둘째, 앞에서도 얘기했듯이 multipart 옵션을 받드시 사용해야 한다.
따라서 form 은 아래와 같이 된다. (MIck Clark 의 코드를 참고하였음).
<%= error_messages_for :mugshot %>
<% form_for(:mugshot, :url => mugshots_path,
:html => { :multipart => true }) do |f| -%> Upload A Mugshot: <%= f.file_field :uploaded_data %>
<%= submit_tag 'Create' %>
<% end -%>여기서 file_filed의 name 속성값이 ‘mugshot[uploaded_data]’ 가 된다는 사실을 기억하고 넘어가자.
attachment_fu 를 사용하기 위해서는 각 파일의 정보를 저장할 전용 모델을 만드는 것이 바람직하다.
모델의 schema 는 아래와 같이 한다. (MIck Clark 의 코드를 참고하였음).
class CreateMugshots < ActiveRecord::Migration
def self.up
create_table :mugshots do |t|
t.column :parent_id, :integer
t.column :content_type, :string
t.column :filename, :string
t.column :thumbnail, :string
t.column :size, :integer
t.column :width, :integer
t.column :height, :integer
end
end
def self.down
drop_table :mugshots
end
end파일의 저장을 위해서는 controller 에 다음과 같이 할 수 있겠다.
이 때, 파일을 나타내는 모델 객체(Mugshot)의 생성시에 전달되는 인자는 ‘uploaded_data’ 라는 값을 가지는 hash 이어야 한다.
def new
@mugshot = Mugshot.new
end
def create
@mugshot = Mugshot.new(params[:mugshot])
if @mugshot.save
flash[:notice] = 'Mugshot was successfully created.'
redirect_to mugshot_url(@mugshot)
else
render :action => :new
end
end