Ich habe für unser Qualifikationsprojekt 2b einen Dateienupload programmiert. Mit diesem ist es möglich gleichzeitig mehrere Dateien, Bilder, etc. zu einem Model hochzuladen und anschließend zu diesem Anzeigen zu lassen.
Ich verwende dabei ein Railsplugin namens Paperclip. Dieses beinhaltet den eigentlichen Dateienupload. Nähere Informationen dazu befinden sich zB hier: Paperclip auf github
In meinem Beispiel verwende ich den Upload für ein Model “page”, dieses händelt mir bei meinem Content Management System statische Seiten und deren Content inklusive Bildern und weiteren Daten. Da das Model “page” in Beziehung mit einem Model names “category” steht, habe ich es in dieses Tutorial ebenfalls inkludiert.
Vorgehensweise:
1. Paperclip Plugin installieren
ruby script/plugin install git://github.com/thoughtbot/paperclip.git
2. Migrations für Category, Page und Upload
generiert category.rb
ruby script/generate migration Category
generiert page.rb
ruby script/generate migration Page
generiert upload.rb
ruby script/generate migration Upload
3. Anpassungen der Migrations
Die Migrations folgendermaßen anpassen für category.rb:
class CreateCategories < ActiveRecord::Migration def self.up create_table :categories do |t| t.string :name t.timestamps end end def self.down drop_table :categories end end |
Ich habe hier dem Model “category” lediglich einen Namen übertragen.
Die Migrations folgendermaßen anpassen für page.rb:
class CreatePages < ActiveRecord::Migration def self.up create_table :pages do |t| t.string :title t.text :body t.string :permalink t.integer :category_id t.timestamps end end def self.down drop_table :pages end end |
Ich habe hier dem Model “page” einen title, body, permalink und eine category_id übergeben.
Die Migrations folgendermaßen anpassen für upload.rb:
class CreateUploads < ActiveRecord::Migration def self.up create_table :uploads do |t| t.integer :page_id t.string :data_file_name t.string :data_content_type t.integer :data_file_size t.datetime :data_updated_at t.datetime :created_at t.datetime :updated_at t.timestamps end end def self.down drop_table :uploads end end |
Hier übergebe ich dem Model “upload” eine page_id, data_file_name, data_content_type, data_file_size, data_updated_at, created_at, updated_at
data_file_name, data_content_type, data_file_size und data_updated_at sind Paperclip spezifisch. Hier sind außer data_file_name alle optional, data_file_name muss jedoch gegeben sein, damit das Plugin damit umgehen und die Uploads behandeln kann. Diese Migration könnte auch mit einem Paperclip generator erstellt werden.
4. Migrations für die Datenbank durchführen
rake db:migrate
5. Models anpassen
category.rb
class Category < ActiveRecord::Base # relations from category has_many :pages # validations validates_format_of :name, :with => /[a-z]/ validates_uniqueness_of :name validates_presence_of :name validates_length_of :name, :minimum => 3, :too_short => "Kategorienname zu kurz, mindestens 3 Buchstaben" end |
Hier werden die Beziehungen richtig gesetzt und weiters ein paar Validierungen durchgeführt.
page.rb
class Page < ActiveRecord::Base #relations from page belongs_to :category has_many :uploads, :dependent => :destroy # acts_as_textiled acts_as_textiled :body # for nested-attributes accepts_nested_attributes_for :uploads, :reject_if => proc { |attributes| attributes['data_file_name'].blank? }, :allow_destroy => true # validations validates_format_of :title, :with => /[a-z]/ validates_uniqueness_of :title validates_presence_of :title, :permalink, :body validates_length_of :title, :minimum => 5, :too_short => 'Titel zu kurz, mindestens 5 Buchstaben' validates_length_of :body, :minimum => 20, :too_short => 'Content zu kurz, mindestens 5 Buchstaben' has_permalink :title end |
Hier werden ebenfalls die Beziehungen richtig gesetzt und weiters ein paar Validierungen durchgeführt.
has_permalink wird für einen permanenten Link benutzt, mehr dazu unter: http://www.seoonrails.com/even-better-looking-urls-with-permalink_fu.html
acts_as_textiled wird für die bessere textuelle Darstellung benutzt. Kann optional hier weggelassen werden, gleich wie has_permalink auch weggelassen werden könnte. Mehr Informationen zu acts_as_textiled ist hier zu finden: http://github.com/defunkt/acts_as_textiled
accepts_nested_attributes_for händelt den gleichzeitigen Upload von mehreren Bildern, Dateien, etc. beim erstellen einer Page ohne neu laden zu müssen. Dies ist äußerst wichtig und muss beinhaltet werden.
Mit :reject_if => proc { |attributes| attributes['data_file_name'].blank? } wird der Fall ausgeschlossen, dass leere Datensätze in die Datenbank gespeichert werden, wenn keine zusätzlichen Dateien beim erstellen einer Page mitgegeben werden.
:allow_destroy => true erlaubt beim löschen einer Page das gleichzeitige mitlöschen der dazugehörenden Uploads.
Upload braucht kein eigenes Model
6. Controllers generieren
Nun brauchen wir noch Controller für category und page. upload braucht keinen controller
categories_controller.rb
Den categories_controller kann man einfach standardmäßig mit einem scaffold Generator erstellen.
class CategoriesController < ApplicationController # index category def index @categories = Category.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @categories } end end # show category def show @category = Category.find(params[:id]) @page = Page.find(:all, :conditions => {:category_id => (params[:id])}) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @category } end end # new category def new @category = Category.new respond_to do |format| format.html # new.html.erb format.xml { render :xml => @category } end end # edit category def edit @category = Category.find(params[:id]) end # create category def create @category = Category.new(params[:category]) respond_to do |format| if @category.save flash[:notice] = 'Kategorie wurde erfolgreich erstellt.' format.html { redirect_to(@category) } format.xml { render :xml => @category, :status => :created, :location => @category } else format.html { render :action => 'new' } format.xml { render :xml => @category.errors, :status => :unprocessable_entity } end end end # update category def update @category = Category.find(params[:id]) respond_to do |format| if @category.update_attributes(params[:category]) flash[:notice] = 'Kategorie wurde erfolgreich aktualisiert.' format.html { redirect_to(@category) } format.xml { head :ok } else format.html { render :action => 'edit' } format.xml { render :xml => @category.errors, :status => :unprocessable_entity } end end end # delete category def destroy @category = Category.find(params[:id]) @category.destroy respond_to do |format| format.html { redirect_to(categories_url) } format.xml { head :ok } end end end |
pages_controller.rb
Den pages_controller kann man ebenfalls einfach standardmäßig mit einem scaffold Generator erstellen und anschließend anpassen.
In diesem controller ist folgendes bei “new” und “edit” wichtig: 4.times { @page.uploads.build }
Dies erstellt mir zusätzlich zu einer neuen Page 4x eine Upload-Instanz, diese kann anschließend in den Views verwendet und hergenommen werden.
class PagesController < ApplicationController # index pages def index @pages = Page.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @pages } end end # show page def show @page = Page.find(params[:id]) respond_to do |format| format.html # show.html.erb format.xml { render :xml => @page } end end # new page def new @page = Page.new 4.times { @page.uploads.build } # added for paperclip and nested attributes respond_to do |format| format.html # new.html.erb format.xml { render :xml => @page } end end # edit page def edit @page = Page.find(params[:id]) 4.times { @page.uploads.build } # added for paperclip and nested attributes end # create page def create @page = Page.new(params[:page]) respond_to do |format| if @page.save flash[:notice] = 'Seite wurde erfolgreich erstellt.' format.html { redirect_to(@page) } format.xml { render :xml => @page, :status => :created, :location => @page } else format.html { render :action => 'new' } format.xml { render :xml => @page.errors, :status => :unprocessable_entity } end end end # update page def update @page = Page.find(params[:id]) respond_to do |format| if @page.update_attributes(params[:page]) flash[:notice] = 'Seite wurde erfolgreich aktualisiert.' format.html { redirect_to(@page) } format.xml { head :ok } else format.html { render :action => 'edit' } format.xml { render :xml => @page.errors, :status => :unprocessable_entity } end end end # delete page def destroy @page = Page.find(params[:id]) @page.destroy respond_to do |format| format.html { redirect_to(pages_url) } format.xml { head :ok } end end # for reach the sites via the permalink posibility def permalink_pages @page = Page.find_by_permalink(params[:permalink]) respond_to do |format| format.html { render :action => 'show' } format.xml { render :xml => @page.to_xml } end end end |
Dies war Part I dieses Tutorial.
Part II wird demnächst herausgegeben.
1 Kommentar zu "Rails multiple data uploader (Part I)"
Kommentar verfassen