Blog

Rails multiple data uploader (Part I)

Rails multiple data uploader (Part I)

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.

24.05.2010
Matthias Frick
Ruby on Rails
1 Kommentar

Über den Autor

Matthias Frick
Matthias Frick, MSc.

Er ist ein langjähriger Ruby-on-Rails Entwickler und leitet das Unternehmen Frick-Web.

1 Kommentar zu "Rails multiple data uploader (Part I)"

  1. Oumali Desto
    Oumali Desto 25.01.2012
    Keep on writing such good articles! Thanks, helped a lot!

Kommentar verfassen