Rails Note #16: rails3, devise, paperclip, uploadify, formtastic
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