Setup

dev:rails3 rupert$ rails new photogallery
dev:rails3 rupert$ cd photogallery
dev:photogallery rupert$ rm public/index.html
dev:photogallery rupert$ vim Gemfile 
  1 source 'http://rubygems.org'
  2 
  3 gem 'rails', '3.0.3'
  4 
  5 # Bundle edge Rails instead:
  6 # gem 'rails', :git => 'git://github.com/rails/rails.git'
  7 
  8 gem 'sqlite3-ruby', :require => 'sqlite3'
  9 gem 'devise', :git => "git://github.com/plataformatec/devise.git", :branch => "master"
 10 gem 'formtastic', '~> 1.1.0'
 11 gem 'paperclip'
 12 gem 'mime-types', :require => 'mime/types'

Paperclip needs imagemagick to work. No need to install by source

sudo apt-get install imagemagick
dev:photogallery rupert$ bundle install
Updating git://github.com/plataformatec/devise.git
Fetching source index for http://rubygems.org/
Using rake (0.8.7) 
Using abstract (1.0.0) 
Using activesupport (3.0.3) 
Using builder (2.1.2) 
Using i18n (0.5.0) 
Using activemodel (3.0.3) 
Using erubis (2.6.6) 
Using rack (1.2.1) 
Using rack-mount (0.6.13) 
Using rack-test (0.5.7) 
Using tzinfo (0.3.23) 
Using actionpack (3.0.3) 
Using mime-types (1.16) 
Using polyglot (0.3.1) 
Using treetop (1.4.9) 
Using mail (2.2.14) 
Using actionmailer (3.0.3) 
Using arel (2.0.6) 
Using activerecord (3.0.3) 
Using activeresource (3.0.3) 
Using bcrypt-ruby (2.1.4) 
Using bundler (1.0.7) 
Using orm_adapter (0.0.4) 
Using warden (1.0.3) 
Using devise (1.2.rc) from git://github.com/plataformatec/devise.git (at master) 
Using formtastic (1.1.0) 
Using paperclip (2.3.8) 
Using thor (0.14.6) 
Using railties (3.0.3) 
Using rails (3.0.3) 
Using sqlite3-ruby (1.3.2) 
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.

Devise

dev:photogallery rupert$ rails g devise:install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
 
===============================================================================
 
Some setup you must do manually if you haven't yet:
 
  1. Setup default url options for your specific environment. Here is an
     example of development environment:
 
       config.action_mailer.default_url_options = { :host => 'localhost:3000' }
 
     This is a required Rails configuration. In production it must be the
     actual host of your application
 
  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:
 
       root :to => "home#index"
 
  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:
 
       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>
 
===============================================================================
dev:photogallery rupert$
dev:photogallery rupert$ rails g devise User
      invoke  active_record
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      create    db/migrate/20110210032416_devise_create_users.rb
      insert    app/models/user.rb
       route  devise_for :users
  1 class User < ActiveRecord::Base
  2   # Include default devise modules. Others available are:
  3   # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
  4   devise :database_authenticatable, :registerable,
  5          :recoverable, :rememberable, :trackable, :validatable
  6 
  7   # Setup accessible (or protected) attributes for your model
  8   attr_accessible :email, :password, :password_confirmation, :remember_me
  9 
 10   has_many :pictures
 11 end
dev:photogallery rupert$ vim app/controllers/home_controller.rb
  1 class HomeController < ApplicationController
  2   def index
  3   end
  4 end
dev:photogallery rupert$ mkdir -p app/views/home
dev:photogallery rupert$ vim app/views/home/index.html.erb
  1 <h1>Welcome to Photo Gallery</h1>
  2 <%= render :partial => 'shared/header' %>
dev:photogallery rupert$ mkdir -p app/views/shared
  1 <ul>
  2   <% if user_signed_in? %>
  3     <li><%= link_to "Log Out", destroy_user_session_path %></li>
  4     <li><%= link_to "My Pictures", pictures_path(:user_id => current_user.id) %></li>
  5   <% else %>
  6     <li><%= link_to "Log In", new_user_session_path %></li>
  7     <li><%= link_to "Sign Up", new_user_registration_path %></li>
  8   <% end %>
  9 </ul>
dev:photogallery rupert$ vim config/routes.rb
  1 Photogallery::Application.routes.draw do
  2   devise_for :users
  3 
  4   resources :pictures
  5 
  6   root :to => "home#index"
  7 
  8 end

Paperclip

dev:photogallery rupert$ vim config/environments/development.rb
  1 # Be sure to restart your server when you modify this file.
  1 Photogallery::Application.configure do
  2   # Settings specified here will take precedence over those in config/application.rb
  3 
  4   # In the development environment your application's code is reloaded on
  5   # every request.  This slows down response time but is perfect for development
  6   # since you don't have to restart the webserver when you make code changes.
  7   config.cache_classes = false
  8 
  9   # Log error messages when you accidentally call methods on nil.
 10   config.whiny_nils = true
 11 
 12   # Show full error reports and disable caching
 13   config.consider_all_requests_local       = true
 14   config.action_view.debug_rjs             = true
 15   config.action_controller.perform_caching = false
 16 
 17   # Don't care if the mailer can't send
 18   config.action_mailer.raise_delivery_errors = false
 19 
 20   # Print deprecation notices to the Rails logger
 21   config.active_support.deprecation = :log
 22 
 23   # Only use best-standards-support built into browsers
 24   config.action_dispatch.best_standards_support = :builtin
 25 
 26   Paperclip.options[:command_path] = "/usr/local/ImageMagick/bin"
 27   Paperclip.options[:swallow_stderr] = false
 28 end
 29
dev:photogallery rupert$ rails g model Picture
      invoke  active_record
      create    db/migrate/20110210032526_create_pictures.rb
      create    app/models/picture.rb
      invoke    test_unit
      create      test/unit/picture_test.rb
      create      test/fixtures/pictures.yml
dev:photogallery rupert$ vim db/migrate/20110210032526_create_pictures.rb
  1 class CreatePictures < ActiveRecord::Migration
  2   def self.up
  3     create_table :pictures do |t|
  4       t.string :caption_title
  5       t.text :caption_description
  6       
  7       t.references :user
  8       
  9       t.timestamps
 10     end
 11   end
 12   
 13   def self.down
 14     drop_table :pictures
 15   end
 16 end
dev:photogallery rupert$ rails g migration AddPaperclipToPictures
      invoke  active_record
      create    db/migrate/20110210032654_add_paperclip_to_pictures.rb
dev:photogallery rupert$ vim db/migrate/20110210032654_add_paperclip_to_pictures.rb
  1 class AddPaperclipToPictures < ActiveRecord::Migration
  2   def self.up
  3     add_column :pictures, :image_file_name, :string
  4     add_column :pictures, :image_content_type, :string
  5     add_column :pictures, :image_file_size, :integer
  6     add_column :pictures, :image_updated_at, :datetime
  7   end
  8 
  9   def self.down
 10     remove_column :pictures, :image_file_name
 11     remove_column :pictures, :image_content_type
 12     remove_column :pictures, :image_file_size
 13     remove_column :pictures, :image_updated_at
 14   end
 15 end
dev:photogallery rupert$ vim app/models/picture.rb
  1 class Picture < ActiveRecord::Base
  2   belongs_to :user
  3 
  4   has_attached_file :image,
  5                     :styles => {
  6                       :thumb => ["100x100>", :jpg],
  7                       :pagesize => ["500x400>", :jpg],
  8                     },
  9                     :default_style => :pagesize,
 10                     :url => "/images/photogallery/:id/:style/:basename.:extension",
 11                     :path => "/wwwroot/images/photogallery/:id/:style/:basename.:extension"
 12 
 13   validates_attachment_presence :image
 14   validates_attachment_size :image, :less_than => 10.megabytes
 15 end
dev:images rupert$ mkdir -p /wwwroot/images/photogallery
dev:images rupert$ ln -s /wwwroot/images/photogallery photogallery
dev:photogallery rupert$ rake db:migrate
(in /Volumes/rupert/projects/rails3/photogallery)
==  DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
   -> 0.0084s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0679s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0007s
==  DeviseCreateUsers: migrated (0.0772s) =====================================
 
==  CreatePictures: migrating =================================================
-- create_table(:pictures)
   -> 0.0007s
==  CreatePictures: migrated (0.0008s) ========================================
 
==  AddPaperclipToPictures: migrating =========================================
-- add_column(:pictures, :image_file_name, :string)
   -> 0.0005s
-- add_column(:pictures, :image_content_type, :string)
   -> 0.0003s
-- add_column(:pictures, :image_file_size, :integer)
   -> 0.0003s
-- add_column(:pictures, :image_updated_at, :datetime)
   -> 0.0003s
==  AddPaperclipToPictures: migrated (0.0016s) ================================
dev:photogallery rupert$ rails g controller pictures
      create  app/controllers/pictures_controller.rb
      invoke  erb
      create    app/views/pictures
      invoke  helper
      create    app/helpers/pictures_helper.rb
dev:photogallery rupert$ vim app/controllers/pictures_controller.rb 
  1 class PicturesController < ApplicationController
  2   def index
  3     @user = User.find(params[:user_id])
  4     @pictures = @user.pictures
  5   end
  6 
  7   def create
  8     #You can specify a sleep here to mimic a long response
  9     #sleep 5
 10     newparams = coerce(params)
 11 
 12     current_pictures = Picture.where(:user_id => params[:user_id]).all
 13 
 14     @picture = Picture.new(newparams[:picture])
 15     @picture.user_id = current_user.id
 16 
 17     if @picture.save
 18       flash[:notice] = "Picture was successfully created"
 19 
 20       respond_to do |format|
 21         format.html {redirect_to pictures_path(:user_id => @picture.user_id)}
 22         format.json {render :json => { :result => 'success', :picture => picture_path(@picture) } }
 23       end
 24     else
 25       flash[:alert] = "There is an error in saving the picture."
 26       respond_to do |format|
 27         format.html {redirect_to pictures_path(:user_id => @picture.user_id)}
 28         format.json {render :json => { :result => 'error', :error => flash[:alert] } }
 29       end
 30     end
 31   end
 32   
 33   def show
 34     @picture = Picture.find(params[:id], :include => :user)
 35     @total_pictures = Picture.find(:all, :conditions => { :user_id => @picture.user.id})
 36     render :template => 'pictures/show'
 37   end
 38   
 39   def destroy
 40     picture = Picture.find(params[:id])
 41     user_id = picture.user_id
 42     picture.destroy
 43     flash[:notice] = "Picture was successfully deleted"
 44     redirect_to pictures_path(:user_id => user_id)
 45   end
 46     
 47   private
 48     def coerce(params)
 49       if params[:picture].nil?
 50         h = Hash.new 
 51         h[:picture] = Hash.new
 52         h[:picture][:user_id] = params[:user_id]
 53         h[:picture][:image] = params[:Filedata]
 54         h[:picture][:image].content_type = MIME::Types.type_for(h[:picture] [:image].original_filename).to_s
 55         h
 56       else
 57         params
 58       end 
 59     end
 60 end

Uploadify

dev:photogallery rupert$ vim config/initializers/session_store.rb
  1 # Be sure to restart your server when you modify this file.
  2 
  3 Photogallery::Application.config.session_store :cookie_store, :key => '_photogallery_session'
  4 
  5 # Use the database for sessions instead of the cookie-based default,
  6 # which shouldn't be used to store highly confidential information
  7 # (create the session table with "rails generate session_migration")
  8 # Photogallery::Application.config.session_store :active_record_store
  9 Rails.application.config.middleware.insert_before(
 10   ActionDispatch::Session::CookieStore,
 11   FlashSessionCookieMiddleware,
 12   Rails.application.config.session_options[:key]
 13 )
dev:photogallery rupert$ mkdir app/middleware
dev:photogallery rupert$ vim app/middleware/flash_session_cookie_middleware.rb
  1 require 'rack/utils'
  2  
  3 class FlashSessionCookieMiddleware
  4   def initialize(app, session_key = '_session_id')
  5     @app = app
  6     @session_key = session_key
  7   end
  8  
  9   def call(env)
 10     if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
 11       req = Rack::Request.new(env)
 12       env['HTTP_COOKIE'] = [ @session_key,
 13                              req.params[@session_key] ].join('=').freeze unless req.params[@session_key].nil?
 14       env['HTTP_ACCEPT'] = "#{req.params['_http_accept']}".freeze unless req.params['_http_accept'].nil?
 15     end
 16  
 17     @app.call(env)
 18   end
 19 end
dev:photogallery rupert$ rm -rf public/javascripts/*
dev:photogallery rupert$ cd doc/
dev:doc rupert$ wget http://www.uploadify.com/wp-content/uploads/Uploadify-v2.1.4.zip
--2011-02-10 14:58:26--  http://www.uploadify.com/wp-content/uploads/Uploadify-v2.1.4.zip
Resolving www.uploadify.com... 67.205.57.45
Connecting to www.uploadify.com|67.205.57.45|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 237327 (232K) [application/zip]
Saving to: `Uploadify-v2.1.4.zip'
 
100%[===========================================================================================================================================>] 237,327     88.9K/s   in 2.6s    
 
2011-02-10 14:58:29 (88.9 KB/s) - `Uploadify-v2.1.4.zip' saved [237327/237327]
dev:doc rupert$ unzip Uploadify-v2.1.4.zip
dev:doc rupert$ cd jquery.uploadify-v2.1.4/
 
dev:jquery.uploadify-v2.1.4 rupert$ mkdir -p ../../public/javascripts/uploadify
dev:jquery.uploadify-v2.1.4 rupert$ mkdir -p ../../public/uploadify
 
dev:jquery.uploadify-v2.1.4 rupert$ cp uploadify.css ../../public/stylesheets/
 
dev:jquery.uploadify-v2.1.4 rupert$ cp jquery.uploadify.v2.1.4.js ../../public/javascripts/uploadify/
dev:jquery.uploadify-v2.1.4 rupert$ cp swfobject.js ../../public/javascripts/uploadify/
 
dev:jquery.uploadify-v2.1.4 rupert$ cp -Rf uploadify.swf ../../public/uploadify/
dev:jquery.uploadify-v2.1.4 rupert$ cp -Rf cancel.png ../../public/uploadify/
 
dev:photogallery rupert$ cd ../../public/javascripts/
wget https://github.com/rails/jquery-ujs/raw/master/src/rails.js --no-check-certificate
dev:photogallery rupert$ vim app/views/layouts/pictures.html.erb
  1 <!DOCTYPE html>
  2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  3 <head>
  4   <title>Photo Gallery</title>
  5   
  6   <%= stylesheet_link_tag 'formtastic/formtastic' %>
  7   <%= stylesheet_link_tag 'formtastic/formtastic_changes' %>
  8 
  9   <%= stylesheet_link_tag 'uploadify' %>
 10 
 11   <%= javascript_include_tag 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js' %>
 12   
 13   <!-- 
 14     Taken from https://github.com/rails/jquery-ujs/raw/master/src/rails.js
 15     Do not remove jquery.rails.js as the delete links will not work 
 16   -->
 17   <%= javascript_include_tag "jquery.rails.js" %>
 18   
 19   <%= csrf_meta_tag %>
 20 </head>
 21 <body>
 22 <div class="container">
 23   <% flash.each do |name, msg| %>
 24     <hr/>
 25     <%= content_tag :div, msg, :class => "flash_#{name}" %>
 26   <% end %>
 27 
 28   <%= render :partial => 'shared/header' %>
 29 
 30   <hr/>
 31 
 32   <%= yield %>
 33 
 34 </div>
 35 
 36 </body>
 37 </html>
dev:photogallery rupert$ vim app/views/pictures/index.html.erb
  1 <h1><%= "Manage #{pluralize(@user.pictures.size, 'Picture')}" %></h1>
  2 
  3   <div class="upload_form">
  4     <%= form_for Picture.new(:user_id => @user.id), :html => {:multipart => true } do |f| %>
  5       <h2>Step 1: Choose your images</h2>
  6       <%= f.file_field :image %>
  7 
  8       <%= f.hidden_field :user_id, "value" => @user.id %>
  9 
 10       <h2>Step 2: Upload</h2>
 11       <%= f.submit 'Upload' %>
 12     <% end %>
 13 
 14     <% if simple_upload_form? %>
 15 
 16       <span class="simple_upload_link">
 17         If you want to see the Browse flash button above, click on the
 18         <%= link_to "Flash Upload", pictures_path(:user_id => @user.id) %>
 19       </span>
 20 
 21     <% else %>
 22 
 23       <!-- Important: Please see uploadify. It contains javascript functions to provide the BROWSE button, submit to a user with content_type json, and process the response. See     more details in uploadify -->
 24       <%= render :partial => "uploadify" %>
 25 
 26       <span class="simple_upload_link">
 27         If you cannot see the Browse flash button above, click on the
 28         <%= link_to "Simple Upload", pictures_path(:user_id => @user.id, :simple => 1) %>
 29       </span>
 30 
 31     <% end %>
 32   </div>
 33 
 34 <div class="clear"></div>
 35 
 36 <hr/>
 37 
 38 <div class="picture_container">
 39   <ul class="thumbs noscript">
 40     <%= render @pictures %>
 41   </ul>
 42 </div>
dev:photogallery rupert$ vim app/views/pictures/_uploadify.html.erb 
  1 <%= javascript_include_tag "uploadify/swfobject.js", "uploadify/jquery.uploadify.v2.1.4.js" %>
  2 
  3 <script type="text/javascript" charset="utf-8">
  4 <%- session_key = Rails.application.config.session_options[:key] -%> 
  5 $(document).ready(function() {
  6   
  7   $('#picture_image').click(function(event){ 
  8     event.preventDefault();
  9   }); 
 10   
 11   $('#picture_image').uploadify({
 12     buttonText: 'Browse',
 13     width: 110,
 14     uploader : '/images/uploadify/uploadify.swf',
 15     cancelImg : '/images/uploadify/cancel.png',
 16     multi : true,
 17     auto : true,
 18     script : 'pictures',
 19     //Function 'onComplete' below will process response from pictures_controller 'create'
 20     //format.json {render :json => { :result => 'success', :picture => admin_picture_path(@picture) } }
 21     onComplete : function(event, queueID, fileObj, response, data) { 
 22       var data = eval('(' + response + ')');
 23       if(data.result == 'success'){
 24         $.getScript(data.picture);
 25       }
 26       else{
 27         alert(data.error);
 28         //We can have a <hr/> before alert or notice using jquery
 29         $('#alert').html(data.error)
 30       }
 31     },
 32     scriptData : {
 33           '_http_accept': 'application/javascript',
 34           'format' : 'json',
 35           '_method': 'post',
 36           '<%= session_key %>' : encodeURIComponent('<%= u cookies[session_key] %>'),
 37           'authenticity_token': encodeURIComponent('<%= u form_authenticity_token %>'),
 38           'user_id' : '<%= @user.id %>'
 39         }
 40   });
 41   
 42   $('#picture_submit').click(function(event){ 
 43       event.preventDefault(); 
 44       $('#upload_photo').uploadifyUpload(); 
 45     });
 46     
 47 }); 
 48 </script>
dev:photogallery rupert$ vim app/helpers/pictures_helper.rb
  1 module PicturesHelper
  2   def simple_upload_form?
  3     if params[:simple] == nil
  4       return false
  5     else
  6       return true
  7     end
  8   end
  9 
 10   def link_to_picture(picture)
 11     link_to(
 12       image_tag( picture.image.url(:thumb), :size => '80x80', :border =>  ),
 13       picture.image.url(:pagesize),
 14       {
 15         :class => "thumb",
 16         :title => "#{picture.image_file_name}",
 17         :name => "#{picture.image_file_name}",
 18         :rel => "nofollow"
 19       }
 20     )
 21   end
 22 
 23   def link_to_web_photo(photo)
 24     link_to(
 25       image_tag( photo.thumb_path, :size => '80x80', :border =>  ),
 26       photo.full_path,
 27       {
 28         :class => "thumb",
 29         :title => "#{photo.thumb_path}",
 30         :name => "#{photo.thumb_path}",
 31         :rel => "nofollow"
 32       }
 33     )
 34   end
 35 end
dev:photogallery rupert$ vim app/views/pictures/_picture.html.erb
  1 <li>
  2   <p>
  3     <%= link_to_picture(picture)%><br/>
  4     <%= picture.caption_title %>
  5   </p>
  6   <%= link_to "Delete Picture", picture_path(picture), :confirm => "Are you sure?", :method => :delete %>
  7 </li>
dev:photogallery rupert$ vim app/views/pictures/show.js.erb
  1 $('h1').html('Manage <%= pluralize(@total_pictures.count, "Picture") %>');
  2 $('ul.thumbs').append('<%= escape_javascript(render @picture) %>');
dev:photogallery rupert$ vim app/views/pictures/edit.html.erb
  1 <% title 'Edit Caption' %>
  2 
  3 <%= render "shared/error_messages", :target => @picture %>
  4 
  5 <p style="text-align:center"><%= image_tag @picture.image.url(:pagesize) %></p>
  6 
  7 <%= semantic_form_for @picture do |f| %>
  8   <%= f.inputs do %>
  9     <%= f.input :caption_title %>
 10     <%= f.input :caption_description %>
 11   <% end %>
 12   
 13   <%= f.buttons do %>
 14     <%= f.commit_button %>
 15     <%= f.cancel_button pictures_path(:page_id => @picture.page.id) %>
 16   <% end %>
 17   
 18 <% end %>

Download photogallery.tar.gz