Мы живем в мире Web 2.0, где контент создают пользователи. Современное веб-приложение трудно представить без системы, позволяющей в том или ином виде загружать пользователям файлы на сервер. Далее мне хотелось бы показать, как можно организовать работу с файлами в Rails, используя возможности СУБД PostgreSQL.
В PostgreSQL иммется возможность хранения больших файлов, которая дает возможность потокового доступа к данным, хранящимся в специальной структуре, предназначенной для хранения "больших" объектов (PGLarge). Подробнее, о том как работать с такими объектами, можно прочесть в документации к PostgreSQL: http://www.postgresql.org/docs/8.1/static/largeobjects.html
Далее, я продемонстрирую код, который позволяет работать с описанными выше объектами в Rails. В первую очередь, важно учтонить, что будет использоваться адаптер 'postgres', потому что многие другие распространенные сегодня адаптеры для работой с СУБД PostgreSQL не имели возможности работать с PGLarge.
Сгенерим модель Lofile, которая и будет отвественна за хранение загружаемых файлов:
ruby script/generate model Lofile filename:string content_type:string loid:integer
Дополним модель методами, которые позволять манипулировать PGLarge объектами:
def lofile=(lofile_field)
return if lofile_field.blank? or (buf=lofile_field.read).blank?
self.filename = base_part_of(lofile_field.original_filename)
self.content_type = lofile_field.content_type.chomp
self.connection.execute("BEGIN")
begin
lo = self.connection.raw_connection.lo_create()
lo.open(PGlarge::INV_WRITE)
self.loid = lo.oid lo.write(buf)
lo.close
rescue PGError => e
self.connection.execute("ROLLBACK")
self.loid = nil
end
self.connection.execute("END")
end
def lofile
""
end
def data
connection.raw_connection.exec("BEGIN")
lo = connection.raw_connection.lo_open(loid, PGlarge::INV_READ)
content = lo.read
lo.close
connection.raw_connection.exec("END")
content
end
def data=(text)
connection.raw_connection.exec("BEGIN")
lo = connection.raw_connection.lo_open(loid, PGlarge::INV_WRITE)
content = lo.write(text)
lo.close
connection.raw_connection.exec("END")
content
end
def base_part_of(file_name)
name = File.basename(file_name)
name.gsub(/[^\w._-]/, '')
end
Таким образом, с помощью методов data и data= получаем и устанавливаем соотвественно те данные, которые загружает пользовательа, lofile и lofile= достает эти данные из HTTP-запроса. То есть надо создать во view нечто подобное:
<% form_for @lofile, :url=>{...}, :html=>{:multipart=>true} do |f| %> <%= f.error_messages %>
<%= f.label :lofile%> <%= f.file_field :lofile, :size => 80 %
<%= f.submit "Загрузить" %>
<% end %>
Остался контроллер:
def create
@lofile = Lofile.new(params[:lofile])
if @lofile.save
flash[:notice]="Файл успешно загружен"
redirect_to ...
else
render :action => 'new'
end
end
Ну а отдавать пользователю файл тоже очень просто. К примеру, так:
send_data(@lofile.data, :filename => @lofile.name, :type => @lofile.content_type, :disposition => "inline")
Ruslan Voloshin