Intridea's Scalr on TechCrunch and AWS

Posted 04/03/2008 by naffis 0 CommentsAdd Your Comment

Since open sourcing Scalr late yesterday we've been mentioned on TechCrunch and the Amazon Web Services Blog

We're extremely happy with the response so far. If anyone is interested in contributing to the project please visit the Scalr Project on Google Code.

0 comments »

ActsAsConference

Posted 01/16/2008 by naffis 4 CommentsAdd Your Comment

Josh Owens and I will be presenting at the upcoming acts_as_conference February 8th and 9th in Orlando Florida.

Adding Media to Your Rails Application

Adding media such as audio and video to a web application can be costly and time consuming. In this talk we'll cover the fundamentals and break down several solutions for uploading large files and transcoding audio and video in Rails.

Presentation slides and files

4 comments »

Tracking Views in Rails

Posted 05/22/2007 by naffis 3 CommentsAdd Your Comment

ActsAsViewable is plugin that allows you to track page and asset views in your Rails application. For example, you can use it to track how many times a page is visited or how many times a particular image is viewed.

Trac: http://trac.intridea.com/trac/public/wiki/ActsAsViewable

Subversion repository: http://svn.intridea.com/svn/public/acts_as_viewable

Installation:
script/plugin install http://svn.intridea.com/svn/public/acts_as_viewable

OR

cd vendor/plugins
svn co http://svn.intridea.com/svn/public/acts_as_viewable

Create the tables where views will be tracked:

class CreateViewings < ActiveRecord::Migration
  def self.up
    create_table :viewings do |t|
      t.column :viewable_type,  :string
      t.column :viewable_id,    :integer
      t.column :views,          :integer,   :default => 0
      t.column :created_at,     :datetime, :null => false
      t.column :updated_at,     :datetime
    end
  end

  def self.down
    drop_table :viewings
  end
end
Set the objects you want to track views for:
class SomeAsset < ActiveRecord::Base
  acts_as_viewable
end

Now you can increment views for these objects wherever you need to. For example in the show action of our SomeAssetController:

class SomeAssetController < ApplicationController
  def show
    @some_asset = SomeAsset.find(params[:id])
    @some_asset.increment_views
  end
end

To get the number of views:

@some_asset.views
3 comments »

Automatically Expiring Sessions in Rails

Posted 05/22/2007 by naffis 4 CommentsAdd Your Comment

SessionExpiration is plugin that allows you to expire sessions after X seconds of inactivity. Useful for when you want to automatically log out users if they’re idle.

Trac: http://trac.intridea.com/trac/public

Subversion repository: http://svn.intridea.com/svn/public/session_expiration/

Installation:
script/plugin install http://svn.intridea.com/svn/public/session_expiration

OR

cd vendor/plugins
svn co http://svn.intridea.com/svn/public/session_expiration

Specify when to expire session in your ApplicationController to do it site wide or you can do it for specific controllers:

class ApplicationController
  expire_session_in 5.minutes
end

If you want to run a method when the session expires use this:

class ApplicationController
  expire_session_in 5.minutes, :after_expiration => :some_method

  def some_method
    flash[:notice] = "You have been logged out due to inactivity"
  end
end

Ajax uploads? Image manipulation & drag-and-drop sorting.

Posted 12/11/2006 by dnaffis 14 CommentsAdd Your Comment

Wouldn’t it be nice to allow uploads in a cool Ajaxy way? Well, because of security restrictions it’s just not possible. There are however ways to create the same effect.

Here’s a quick demo of an ajax-ish image upload as well as some image manipulation functionality, and drag and drop sorting. I’m not sure this will work on all browsers but it’s been tested successfully with most. This was created about 4 months ago and I never had time to polish any of it up so take what you can from it.

http://www.naffis.com/demos/image_demo

First our layout (layouts/image_demo.rhtml):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/strict.dtd">
<html>
    <head>
        <title>Image Demo</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        
        <%= javascript_include_tag :defaults %>
        <%= stylesheet_link_tag 'image_demo' %>
    </head>
    <body>
        <div id="titlebar">Image Demo</div>

        <%= render :partial => "upload_form" %>    

        <div id="centercontent">
            <%= yield %>
        </div>
        <div id="next">
            <%= link_to "Create Animated Gif", :action => "animate" %>
        </div>
        <div id="bottom">
            &copy; naffis.com 2006 
        </div>

    </body>
</html>

We’re going to extend the form_remote_tag to handle file uploads.

Drop this in your lib directory (lib/remote_uploads.rb):

module ActionView
  module Helpers
    module PrototypeHelper
      alias_method :form_remote_tag_old, :form_remote_tag
      def form_remote_tag(options = {})
        if options[:html] && options[:html][:multipart]      
          uid = "a#{Time.now.to_f.hash}"                                
          <<-STR    
          <iframe name="#{uid}" id="#{uid}" src="about:blank" style="position:absolute;left:-100px;width:0px;height:0px;border:0px"></iframe>
          <form method="post" action="#{url_for options[:url].update({:iframe_remote => true})}" enctype="multipart/form-data" target="#{uid}" #{%(onsubmit="#{options[:loading]}") if options[:loading]}>
          STR
        else
          form_remote_tag_old(options)
        end
      end                             
    end
  end
end

Add the require in your environment.rb:

require 'remote_uploads.rb'

This will create a custom form for file uploads (multipart => true) that submits to a hidden iframe. If it’s not a file upload then it will revert to the standard form_remote_tag of PrototypeHelper.

Some boring half baked styles for our demo:

body {
  background-color:        #FFFFFF;
  background-image:        url(/maps/images/gradient.jpg);
  background-repeat:    no-repeat;
  color:                            #666666;
  font-family:                arial, sans;
  font-size:                    100%;
  line-height:                1.7em;
  margin:                            1em 2em;
}

#titlebar {
  font-size:                     1.2em;
  border-bottom:                    2px solid #333333;
  margin-bottom:                    1em;
  padding-bottom:                1em;
}

h2 {
  font-size:                     1.2em;
}

ul.navigation {
  background-color:        #333333;
  padding:                        0em 0.5em;
  list-style-type:        none;
}

ul.navigation li {
  border-right:                1px solid #666666;
  display:                        inline;
}

.navigation a {
  color:                            #FFFFFF;
  padding:                        0.5em;
}

.description {
  font-size:                    1.2em;
}

.upload {
  font-size:                    1.2em;
}

strong {
  background-color:        #FFFF99;
}

#centercontent {
  width: 100%;
  text-align: center;
  margin-bottom:                1em;
  padding-bottom:                1em;                
  margin-top:                    1em;
  padding-top:                1em;
}

#bottom {                
    width: 100%;
    float: left;
    text-align: center;
    border-top:                    2px solid #333333;
    margin-top:                    1em;
    padding-top:                1em;
}

div.float {
  width: 120px;
  padding: 10px;
  float: left;
}

div.spacer {
  clear: both;
}

div.float img {
  margin-left: 5px;
  }

div.float p {
  font-size: 9px;
  text-align: center;
  }            

#image-list ul {    
  list-style: none;
}

#image-list ul li {
  list-style: none;
  display: inline; 
    float: left;  
    width: 120px; 
    height: 120px; 
    padding: 10px;
  border: 1px solid #000;      
}

We’re using Sean Treadway’s responds_to_parent plugin (http://sean.treadway.info/svn/plugins/responds_to_parent/) to execute our RJS generated javascript in the parent window instead of the iframe which the file upload is submitted to. There are other ways of doing this that use less code but the plugin is simple so why not use it?

Everything from this point on is pretty self explanitory. I can expand on it later but here’s the rest of the code.

Our index:

<div id="image-list">
    <ul id="sortable_list">
        <% for @asset in @assets %>
            <%= render :partial => "image_container", :locals => { :asset => @asset } %>
        <% end %>
    </ul>
</div>
<%= sortable_element('sortable_list', :constraint => false, :url => {:action => :update_positions}) %>

Some partials used above:

_image_container.rhtml

<li id="item_<%= @asset.id %>" class="float">
    <%= render :partial => "image_thumb", :locals => { :asset => @asset } %>
</li>

_image_thumb.rhtml

<%= image_tag @asset.thumbnail, :border => 2 %>
    <br>
    <%= link_to_remote(image_tag("arrow_rotate_anticlockwise.png", :border => 0), :url => {:action => "rotate", :id => @asset.id, :direction => "left"} ) %>
    &nbsp;
    <%= link_to_remote(image_tag("cross.png", :border => 0), :url => {:action => "remove", :id => @asset.id} ) %>
    &nbsp;
    <%= link_to_remote(image_tag("arrow_rotate_clockwise.png", :border => 0), :url => {:action => "rotate", :id => @asset.id, :direction => "right"} ) %>

_upload_form.rhtml

<%= form_remote_tag(:url => { 
        :controller => "image_demo", 
        :action => "create" },             
        :html => {:multipart => true}) %>
    <b>Picture:</b>&nbsp;
    <%= file_field_tag "asset" %>&nbsp;
    <%= submit_tag "Upload" %>&nbsp;
<%= end_form_tag %>

Our RJS to handle the create, remove, and rotate.

create.rjs

if @asset.new_record?
  page.alert "There was a problem uploading your file:\n" +
  @asset.errors.full_messages.join("\n")
else
  page.insert_html :top, 'sortable_list', :partial => 'image_container', :locals => { :asset => @asset } 
  page.visual_effect :highlight, "item_#{@asset.id}"
  page.sortable "sortable_list", :constraint => false, :url => { :action => :update_positions }
end

remove.rjs

page.remove "item_#{@asset_id}"
page.sortable "sortable_list", :constraint => false, :url => { :action => :update_positions }

rotate.rjs

page.replace_html "item_#{@asset.id}", :partial => 'image_thumb', :locals => { :asset => @asset } 
page.visual_effect :highlight, "item_#{@asset.id}"
page.sortable "sortable_list", :constraint => false, :url => { :action => :update_positions }

Our controller:

class ImageDemoController < ApplicationController 
  layout 'image_demo'

  def index
    session[:uid] = Time.now.to_i unless session[:uid]      
    @assets = Asset.find(:all, 
                         :conditions => ["user_id = ?", session[:uid].to_i],
    :order => "position")    
  end

  def create
    @asset = Asset.new()
    @asset.uploaded_file = params['asset']
    @asset.position = 0
    @asset.user_id = session[:uid].to_i
    @asset.save    
    responds_to_parent do
      render :action => 'create.rjs'
    end
    return
  end    

  def list
    @assets = Asset.find(:all, 
                         :conditions => ["user_id = ?", session[:uid].to_i],
    :order => "position")
  end  

  def update_positions
    params[:sortable_list].each_with_index do |id, position|
      Asset.update(id, :position => position)
    end
    render :nothing => true
  end

  def rotate
    @asset = Asset.find(params[:id])
    degrees = params[:direction] == "left" ? -90 : 90
    @asset.rotate(degrees)
  end

  def remove
    @asset_id = params[:id]
    Asset.delete(@asset_id)
  end

end

Our asset model:

require 'RMagick'

class Asset < ActiveRecord::Base 

  def uploaded_file=(incoming_file)
    content_type = incoming_file.content_type.chomp
    if content_type.rindex(/image\/[(jpe?g)||(gif)]/)
      self.name = base_part_of(incoming_file.original_filename)

      base_dir = "/some/path/you/like"           

      # save original file
      self.original = "image_demo_assets/o_#{Time.now.utc.to_i}#{rand(1000000)}."+self.name
      File.open(base_dir+self.original,File::CREAT|File::TRUNC|File::WRONLY,0666){ |f|
        f.write(incoming_file.read)
      }      

      self.resized = "image_demo_assets/r_#{Time.now.utc.to_i}#{rand(1000000)}."+self.name
      resized = Magick::Image.read(base_dir+self.original).first
      resized.change_geometry!('500x500') { |cols, rows, img|
        img.resize!(cols, rows)
      }
      resized.write(base_dir+self.resized) 

      self.thumbnail = "image_demo_assets/t_#{Time.now.utc.to_i}#{rand(1000000)}."+self.name        
      thumb = Magick::Image.read(base_dir+self.original).first
      thumb.change_geometry!('100x100') { |cols, rows, img|
        img.resize!(cols, rows)
      }
      thumb.write(base_dir+self.thumbnail)     

      self.save    
    end
  end

  def rotate(degrees) 
    base_dir = "/some/path/you/like"       
    #main photo
    image = Magick::ImageList.new(base_dir+self.original)
    image = image.rotate(degrees)
    image.write(base_dir+self.original)

    # resized
    resized = Magick::ImageList.new(base_dir+self.resized)
    resized = resized.rotate(degrees)
    resized.write(base_dir+self.resized)    

    # thumb
    thumb = Magick::ImageList.new(base_dir+self.thumbnail)
    thumb = thumb.rotate(degrees)
    thumb.write(base_dir+self.thumbnail)    
  end

  private

  def base_part_of(file_name)
    name = File.basename(file_name)
    name.gsub(/[^W._-]/, '')
    sanitize_filename(name)
  end

  # Fixes a 'feature' of IE where it passes the entire path instead of just the filename
  def sanitize_filename(value)
    #get only the filename (not the whole path)
    just_filename = value.gsub(/^.*(\\|\/)/, '')
    just_filename.gsub(/[^\w\.\-]/,'_') 
  end

end

Some suggestions:
  • Use form_for and get rid of some ugliness in the controller by using Asset.new(params[:asset]) instead of setting each value individually.
  • Use simply_helpful for generiting your DOM id’s.
  • Use acts_as_attachment for handing the storing of files.
  • Better validations (aaa will handle that too).
  • Rewrite the whole thing.

Again, this is a VERY quick-and-dirty demo written in about 20 minutes with so much room for improvement. If I had the time I would, but alas I hope it helps.

14 comments »

Dave is the cofounder of Intridea and leads Intridea's product development efforts.

Before Intridea, Dave spent years at both AOL and IMAKE and received a Masters in Systems Engineering from the University of Virginia.