Rails Note #16: rails3, devise, paperclip, uploadify, formtastic

February 10th, 2011 rupert Comments off

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 => 0 ),
 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 => 0 ),
 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

Rails Note #15: Rails 3, Devise, Cucumber

February 9th, 2011 rupert Comments off

1. Installed rvm (Ruby Version Manager). See Episode 200. Rails3 Beta and RVM

rupert:tsa rupert$ rvm ruby-1.9.2-p0
rupert:tsa rupert$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.5.0]

To use ruby-1.9.2-p0 as default

rupert:rails3 rupert$ rvm ruby-1.9.2-p0 --default
rupert:rails3 rupert$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.5.0]

2. Install rails3

gem install rails

3. Create a rails app tsa (the sample app)

rails new tsa -d mysql

4. Start webrick. Yup, script/server is gone

rupert:tsa rupert$ rails server -e development
=> Booting WEBrick
=> Rails 3.0.3 application starting in production on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2010-12-06 20:47:27] INFO  WEBrick 1.3.1
[2010-12-06 20:47:27] INFO  ruby 1.9.2 (2010-08-18) [x86_64-darwin10.5.0]
[2010-12-06 20:47:27] INFO  WEBrick::HTTPServer#start: pid=1348 port=3000

Note: Upon checking http://127.0.0.1:3000/ I get a “Routing Error”

5. Update your GemFile with the necessary gems.
Let’s use devise as a login authentication system.

source 'http://rubygems.org'
 
gem 'rails', '3.0.3'
 
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
 
gem 'mysql2'
gem 'devise', :git => "git://github.com/plataformatec/devise.git", :branch => "master"

Then run bundle install. This will install devise gem.

rupert:tsa rupert$ bundle install
Fetching git://github.com/plataformatec/devise.git
remote: Counting objects: 10326, done.
remote: Compressing objects: 100% (3745/3745), done.
remote: Total 10326 (delta 6603), reused 9755 (delta 6163)
Receiving objects: 100% (10326/10326), 1.24 MiB | 314 KiB/s, done.
Resolving deltas: 100% (6603/6603), done.
Fetching source index for http://rubygems.org/

Where are the gems installed?

rupert:tsa rupert$ bundle show devise
/Users/rupert/.rvm/gems/ruby-1.9.2-p0/bundler/gems/devise-b50fd1a72e71

6. Create the necessary databases in mysql. See config/database.yml

CREATE DATABASE tsa_development;

7. Run devise generator.

rupert:tsa rupert$ rails generate devise:install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
 
===============================================================================
 
Some setup you must do manually if you havent 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>
 
===============================================================================

8. Generate a controller in Rails3.

rupert:tsa rupert$ rails generate controller main index
      create  app/controllers/main_controller.rb
       route  get "main/index"
      invoke  erb
      create    app/views/main
      create    app/views/main/index.html.erb
      invoke  test_unit
      create    test/functional/main_controller_test.rb
      invoke  helper
      create    app/helpers/main_helper.rb
      invoke    test_unit
      create      test/unit/helpers/main_helper_test.rb

Update routes.rb for the root_url

  root :to => "main#index"

9. Create a User model with devise.

rupert:tsa rupert$ rails generate 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/20101206104321_devise_create_users.rb
      insert    app/models/user.rb
       route  devise_for :users
rupert:tsa rupert$

10. Run migration for User model

rupert:tsa rupert$ export RAILS_ENV=development
rupert:tsa rupert$ rake db:migrate
(in /Users/rupert/projects/rails3/tsa)
==  DeviseCreateUsers: migrating ==============================================
-- create_table(:users)
   -> 0.0805s
-- add_index(:users, :email, {:unique=>true})
   -> 0.1368s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.1484s
==  DeviseCreateUsers: migrated (0.3662s) =====================================

11. Using cucumber

sudo port install libxml2 libxslt

12. Update Gemfile

group :test, :development do
  gem 'rspec'
  gem 'rspec-rails'
  gem 'cucumber'
  gem 'cucumber-rails'
  gem 'capybara'
  gem 'launchy'
end

13. Rspec

rupert:tsa rupert$ rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  autotest
      create  autotest/discover.rb

14. Cucumber

rupert:tsa rupert$ rails g cucumber:install --rspec --capybara
      create  config/cucumber.yml
      create  script/cucumber
       chmod  script/cucumber
      create  features/step_definitions
      create  features/step_definitions/web_steps.rb
      create  features/support
      create  features/support/paths.rb
      create  features/support/env.rb
       exist  lib/tasks
      create  lib/tasks/cucumber.rake
        gsub  config/database.yml
        gsub  config/database.yml
       force  config/database.yml

15. Write your first feature

Feature: Home Page
  In order to make sure the home page works
  As a normal user
  I want to see the home page
 
Scenario: Show Main Sections
  When I go to the home page
  Then show me the page
  Then I should see "Login"

From Harold Jimenez:

“The Given step is where you set up the context of your scenario. Every scenario starts with a blank slate, so it is important to create a state in your application for example by creating data in the database, or by navigating to a specific page.

The When step is where you exercise the application in order to accomplish what needs testing. In the case of a web app like twiddr, this is usually where you fill in forms, press buttons, click links, or otherwise interact with the system in some way.

Finally, the Then step is where you verify the result, and it’s where we check that the correct pages are rendered, that we see a success or error message, or anything that could help us verify that the prior action was successful. As we move along with creating our own features, this will become much clearer.”

16. See it fail in cucumber

rake features  (or)
cucumber (or)
bundle exec cucumber features/home.feature
rupert:tsa rupert$ cucumber
Using the default profile...
F--
 
(::) failed steps (::)
 
Can't find mapping from "the home page" to a path.
Now, go and add a mapping in /Users/rupert/projects/rails3/tsa/tsa/features/support/paths.rb (RuntimeError)
./features/support/paths.rb:26:in `rescue in path_to'
./features/support/paths.rb:20:in `path_to'
./features/step_definitions/web_steps.rb:24:in `/^(?:|I )go to (.+)$/'
features/home.feature:7:in `When I go to the home page'
 
Failing Scenarios:
cucumber features/home.feature:6 # Scenario: Show Main Sections
 
1 scenario (1 failed)
3 steps (1 failed, 2 skipped)
0m0.062s

17. Defining “the home page” in the support/paths.rb

    when /the home page/
      root_path

and in the routes.rb

root :to => "home#index"

Other examples:

when /the profile page for "([^\"]+)"/
  user = User.find_by_twiddr_name!($1)
  user_path(user)

Note: When you see the error

undefined local variable or method `node' for #<Capybara::Driver::RackTest::Node:0x00000101151ad8> (NameError)"

You need to comment in features/support/env.rb

#require 'cucumber/rails/capybara_javascript_emulation'

For more info, read https://rspec.lighthouseapp.com/projects/16211-cucumber/tickets/674

18. Then show me the page will open up the browser

file://localhost/Users/rupert/projects/rails3/tsa/tsa/tmp/capybara/capybara-20101209222149.html

19. Automatic Testing with “autotest”. When you save a file, autotest automatically runs cucumber. To avoid autotest infinite loop with cucumber, make sure your IDE (i.e rubymine) does not save automatically in the file system. You can exclude files and directories from autotest with a “.autotest” file

Autotest.add_hook :initialize do |at|
  at.add_exception(%r{^\./\.git})
  at.add_exception(%r{^\./db})
  at.add_exception(%r{^\./log})
  at.add_exception(%r{^\./tmp})
  at.add_exception(%r{^\./.idea})
  at.add_exception(%r{^\./rerun\.txt})
  at.add_exception(%r{^\./Gemfile\.lock})
end

Now you can run, autotest from the terminal. Save a file and see autotest run.

autotest

20. If you have javascript AJAX calls included in cucumber scenarios, you need to append them with @javascript tag. Below is a sample which selects “Australia” from the “Country” and waits for the ajax request to finish before selecting the “State” drop down. More info on capybara github.

@javascript
Scenario: Creating a Fleet
  And I select "Australia" from "Country"
  And I wait for the ajax request to finish
  And I select "Victoria" from "State"

How does the step “I wait for the ajax request to finish” looks like? http://stackoverflow.com/questions/7286254/cucumber-wait-for-ajaxsuccess

When /^I wait for the ajax request to finish$/ do
  start_time = Time.now
  page.evaluate_script('jQuery.isReady&&jQuery.active==0').class.should_not eql(String) until page.evaluate_script('jQuery.isReady&&jQuery.active==0') or (start_time + 5.seconds) < Time.now
    sleep 1
  end
end
Categories: rails, ruby Tags: , ,

Rails3 Notes

December 19th, 2010 rupert Comments off

Controllers

Note: redirect_to

redirect_to admin_destinations_path( :country_id => @destination.country_id )

Note: Creating a controller under Admin namespace

$ rails generate model Country
$ rails g controller Admin::Countries
      create  app/controllers/admin/countries_controller.rb
      invoke  erb
      create    app/views/admin/countries
      invoke  rspec
      create    spec/controllers/admin/countries_controller_spec.rb
      invoke  helper
      create    app/helpers/admin/countries_helper.rb
      invoke    rspec
      create      spec/helpers/admin/countries_helper_spec.rb

Note: Change class Admin::CountriesController < ApplicationController to use

class Admin::CountriesController < Admin::ApplicationController
  def new
    @country = Country.new
  end
end
 
class Admin::ApplicationController < ApplicationController
  before_filter :authenticate_user!
 
  layout 'admin'  
end

Models

Note: Validation in Rails3

  #(rails2):
  #validates_presence_of :country_name
 
  #(rails3):
  validates :country_name, :presence => true

Note: My foreign key does not save?
When you have an association between models usually defined by foreign keys and sometimes it does not update, make sure you add the key in the model’s :attr_accessible

attr_accessible :destination_name, :description, :country_id, :destination_type_id

SEO Friendly URLS
http://www.seoonrails.com/to_param-for-better-looking-urls.html
http://nithinbekal.com/2010/03/01/rails-seo-friendly-urls-using-to_param/

class Poi < ActiveRecord::Base
  def to_param
    "#{id}-#{name.gsub(/[^a-z0-9]+/i, '-')}"
  end
end

In the view, its still the same:

<%= link_to poi.name, poi %>

Views

Note: Deleting a model from a view from a REST route

This is dependent on prototype or jquery.rails.js

<li><%= link_to "Destroy", admin_poi_path, :confirm => 'Are you sure?', :method => :delete %></li>

The “:delete” is a symbol of HTTP verb (POST, DELETE, PUT). :method => symbol of HTTP verb – This modifier will dynamically create an HTML form and immediately submit the form for processing using the HTTP verb specified. Useful for having links perform a POST operation in dangerous actions like deleting a record (which search bots can follow while spidering your site). Supported verbs are :post, :delete and :put. Note that if the user has JavaScript disabled, the request will fall back to using GET. If :href => ‘#’ is used and the user has JavaScript disabled clicking the link will have no effect. If you are relying on the POST behavior, you should check for it in your controller’s action by using the request object’s methods for post?, delete? or put?.

              admin_pois GET    /admin/pois(.:format)                  {:action=>"index", :controller=>"admin/pois"}
                         POST   /admin/pois(.:format)                  {:action=>"create", :controller=>"admin/pois"}
           new_admin_poi GET    /admin/pois/new(.:format)              {:action=>"new", :controller=>"admin/pois"}
          edit_admin_poi GET    /admin/pois/:id/edit(.:format)         {:action=>"edit", :controller=>"admin/pois"}
               admin_poi GET    /admin/pois/:id(.:format)              {:action=>"show", :controller=>"admin/pois"}
                         PUT    /admin/pois/:id(.:format)              {:action=>"update", :controller=>"admin/pois"}
                         DELETE /admin/pois/:id(.:format)              {:action=>"destroy", :controller=>"admin/pois"}

Note: Displaying in_groups_of vertical

http://railscasts.com/episodes/28-in-groups-of

<table cellpadding="2" cellspacing="2">
  <% @destinations.in_groups_of((@destinations.length/3).ceil).transpose.each do |destinations| %>
    <tr>
      <% destinations.each do |destination| %>
        <td>
            <%= render destination %>
        </td>
      <% end %>
    </tr>
  <% end %>  
</table>
 
<% @destinations.in_groups(5, false) do |group| %>
  <ul class="column_list">
    <%= render :partial => "destinations/destination", :collection => group %>
  </ul>
<% end %>

Others

Migrate a mysql to sqlite database

1. Install yaml_db.git from
http://www.kartar.net/2008/08/migrating-a-rails-database-from-sqlite3-to-mysql/

2. Have the mysql database ready. We will configure it on production.

3. config/database.yml

development:
  adapter: sqlite3
  database: db/mytp-1.0.4.db
  pool: 5
  timeout: 5000
 
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000
 
production:
  adapter: mysql
  database: tp_production
  username: rupert
  password: ******
  host: localhost

4. Dump the database from production with rake command below. We should have as output: data.yml
rake db:dump RAILS_ENV=production

5. Load data.yml to SQLite. Ensure you have given a database name for sqlite. We should have as output: mytp-1.0.4.db under db/
rake db:load RAILS_ENV=development

Adding ar_mailer to Rails

1. environment.rb:

  config.gem "adzap-ar_mailer", :lib => 'action_mailer/ar_mailer'
  config.action_mailer.delivery_method = :activerecord
#production mailing settings
ActionMailer::Base.smtp_settings = {
   :address => "127.0.0.1",
   :port => 25,
   :domain => "mytravelphilippines.com",
   :authentication => :login,
   :user_name => "info",
   :password => "******",
}
 
ActionMailer::Base.default_content_type = "text/plain"

2. Emailer.rb is still

class Emailer < ActionMailer::Base

3. Run

rupert@2rmobile:/opt/rails/tp/current$ ar_sendmail -ov
expired 0 emails from the queue
found 1 emails to send
sent email 00000000002 from info@mytravelphilippines.com to rupert@2rmobile.com: #<Net::SMTP::Response:0xb702279c @string="250 Message accepted.\n", @status="250">
Categories: rails Tags: ,

Postgres PostGIS CheatSheet v2

November 11th, 2010 rupert Comments off

This is a quick-command list of Postres. If you want detailed instructions, please visit the Postgres Manual.

How do I know the version of Postgis?

SELECT POSTGIS_FULL_VERSION();

How do I Show all databases?
1. Using “psql -l”

2. Using

postgres=# \l
List of databases
Name       |  Owner   | Encoding
------------------+----------+----------
postgis          | postgres | UTF8
postgres         | postgres | UTF8
template0        | postgres | UTF8
template1        | postgres | UTF8
template_postgis | postgres | UTF8
(5 rows)

Note: Do not drop template databases if not necessary.

How do I run a script from the prompt?

psql -d cybersoftbj -u user -f myfile.sql

Its very useful in reloading user-defined functions.

How do I create a user/role?

CREATE ROLE lbs WITH LOGIN PASSWORD 'mypassword' SUPERUSER INHERIT CREATEDB CREATEROLE;

How do I change the password for a user/role?

ALTER ROLE lbs PASSWORD 'mynewpassword';

How to provide/restrict access privileges to tables?

GRANT SELECT ON TABLE TABLE TO user;
REVOKE SELECT ON TABLE TABLE FROM user;

How to dump database in a text file?

pg_dump -U lbs -d cybersoftbjv1 -h 127.0.0.1 -W &gt; cybersoftbjv1.sql

How to dump database cleanly?

% pg_dump -c  -d -E UTF8 -h 127.0.0.1 -U lbs -W platform_v1 &gt; platform_v1.sql

How to rename a database?

ALTER DATABASE beijing_app RENAME TO beijing_app_20080801;

How to update using two tables?

UPDATE road_for_update u
SET the_geom = r.the_geom
FROM roads r
WHERE r.rd_id = u.rd_id;

DROP TABLE

DROP TABLE IF EXISTS "my_table";

How to change a column type with Cast?

ALTER TABLE roads ALTER COLUMN class_new TYPE integer USING class_new::integer;

How to add a geometry column to a table?

SELECT AddGeometryColumn('public', 'poi', 'the_geom', 4326, 'POINT', 2)

Changing column names with spaces?

ALTER TABLE class_aroundme RENAME "level 1" TO level_1;

Setting kernel shmmax for postgres

% sysctl -w kernel.shmmax=134217728

Note: For permanent changes see /etc/sysctl.cfg

11. How to backup table(s) from pg_dump?

% pg_dump poi_beijing -t class -t poi_class -f $BACKUPDIR/test_$MYDATE.sql

Change integer primary key to serial

CREATE SEQUENCE seq_job_id INCREMENT 1 MINVALUE 1000 MAXVALUE 2147483648 START 1000 CACHE 1;
 
ALTER TABLE job ALTER COLUMN job_id SET DEFAULT NEXTVAL('seq_job_id'::regclass);
 
SELECT * FROM job
 
SELECT NEXTVAL('seq_job_id')

Date and Time Function Helper: date_add

CREATE OR REPLACE FUNCTION date_add(diffType Character Varying(15), incrementValue bigint, inputDateTime timestamp without time zone) RETURNS timestamp AS $$
DECLARE
   YEAR_CONST Char(15) := 'year';
   MONTH_CONST Char(15) := 'month';
   DAY_CONST Char(15) := 'day';
   HOUR_CONST Char(15) := 'hour';
   MIN_CONST Char(15) := 'minute';
   SEC_CONST Char(15) := 'second';
 
   dateTemp timestamp without time zone;
   intervals interval;
BEGIN
   IF lower($1) = lower(YEAR_CONST) THEN
       SELECT cast(cast(incrementvalue AS character varying) || ' year' AS interval) INTO intervals;
   ELSEIF lower($1) = lower(MONTH_CONST) THEN
       SELECT cast(cast(incrementvalue AS character varying) || ' months' AS interval) INTO intervals;
   ELSEIF lower($1) = lower(DAY_CONST) THEN
       SELECT cast(cast(incrementvalue AS character varying) || ' day' AS interval) INTO intervals;
   ELSEIF lower($1) = lower(HOUR_CONST) THEN
       SELECT cast(cast(incrementvalue AS character varying) || ' hours' AS interval) INTO intervals;
   ELSEIF lower($1) = lower(MIN_CONST) THEN
       SELECT cast(cast(incrementvalue AS character varying) || ' minutes' AS interval) INTO intervals;
   ELSEIF lower($1) = lower(SEC_CONST) THEN
       SELECT cast(cast(incrementvalue AS character varying) || ' seconds' AS interval) INTO intervals;            
   END IF;
 
   dateTemp:= inputDateTime + intervals;
 
   RETURN dateTemp;
END;
$$ LANGUAGE plpgsql;

How to set the current timezone in postgres?

# Session based only
SET time zone 'utc';
SELECT  current_setting('TIMEZONE');
# Permanent
# Edit /usr/local/var/postgres/postgresql.conf (#postgres installed via homebrew)
timezone = 'UTC'

Date/Time Functions

SELECT current_setting('TIMEZONE'); 
--"Australia/Victoria"
 
SELECT Now(), 
timezone('UTC', now()), 
EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0)),
to_timestamp(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0))),
to_timestamp(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0)))::TIMESTAMP
 
--"2011-11-14 09:29:14.249427+11" 
--"2011-11-13 22:29:14.249427"
--1321223354
--"2011-11-14 09:29:14+11"
--"2011-11-14 09:29:14"

How to specify the id of a sequence to prevent SQL Error: PGRES_FATAL_ERROR:ERROR: duplicate key value violates unique constraint “gps_histories_pkey”?
This happens when the maximum number of records in gps_histories is not in sync with the sequence id.

SELECT SETVAL('dfms_4000.gps_histories_id_seq', (SELECT MAX(id) FROM dfms_4000.gps_histories)+1)
Categories: postgis, postgres Tags: ,

iPhone Dev Note #23: Downgrade an iPhone to iOS 3.1

October 16th, 2010 rupert Comments off

One of the main reasons of downgrading the iOS version is for binary app testing. XCode3.2.3 allows you to compile with the latest iOS SDK using the Base SDK and set the targetiPhone version in the iPhone OS Deployment Target.

xcode-1.png

xcode-2.png

On the iPhone simulator, you could only test your application using 3.2 or 4.x. So, to test for devices > 3.1, we actually need a dedicated device for it. The iPhone3G that I passed on to my wife will suit the scenario.

Before doing anything, it is necessary to make backups.
1. Currently the iPhone 3G is on 4.1. Let’s Backup/Sync. Just in case we need to go back to 4.1. (Note: you can’t use this backup when 3.1 is loaded, yeah it sucks, find out later on.)

2. Contacts. Sync the contacts with Google Contacts. From there, we could have them exported to CSV later on just in case something goes wrong.

Below are the steps taken to downgrade iPhone 3G to iOS3.1:

1. Download the firmware from http://www.iclarified.com/entry/index.php?enid=750. I downloaded 3.1.0 (3G): iPhone1,2_3.1_7C144_Restore.ipsw

2. From XCode’s Organizer, choose the 3.1.0 and hit Restore.

3. During the restore process: “the baseband cannot be rolled back.”

baseband.gif

On the device, you will only see the Apple Logo and the progress bar to its full bar. Don’t panic.

device.jpg

4. Hit OK.

5. QUIT XCode. The device blacks out and you will see the connect to Itunes graphic.
iTunes.JPG

6. Install iRecovery and libusb-package.

iRecovery-1.png

Now we need to tell the device to auto-boot again, save the settings. Enter the ff commands:

iRecoverty-2.png

7. Disconnect the USB. Turn off the device (hold home+power button). Turn on device (power button). Open iTunes. We should be greeted with the Activation Screen.

activation.png

8. Restore from the last backup/sync doesn’t work.

backup-fail.png

9. Restore contacts from Google Contacts or accept bashing from wife? I’ll go with the google contacts sync. :)

10. Check version from settings
iphone-3.1.jpg

References:
http://www.iclarified.com/entry/index.php?enid=750
http://www.funkyspacemonkey.com/downgrade-iphone-os-40-313-mac-windows

Categories: iphone Tags: