» tagged pages
» logout
Rails
Return to Rails

Dave Naffis - Rails, Ruby, Randomness

(or Cancel)

(Editing anonymously: to be credited for your changes, login or register a new account)

other page actions:

Tags Applied to this Topic

1 person has tagged this page:

Ruby, Rails, and Randomness

Wednesday, December 13, 2006

AOHell, when will you cease to exist?

These are hilarious, especially if you’ve ever worked at AOL. To those fortunate folks who have never had the nauseating experience of working for perhaps the most poorly run company in the known universe, they have a layoff every 3-6 months. The most recent being today just in time for the holidays! Merry effing Christmas!

Here are some of my favorites:

Monday, December 11, 2006

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

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.

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.

Wednesday, October 25, 2006

Neglecting my blog

So yeah I’ve been neglecting my blog. I’ve been working way too many hours recently and haven’t had a moment to post anything. I have about 20 or so posts to make, a few code samples, and some plugins I’ll hopefully get around to releasing this weekend.

Wednesday, October 25, 2006

Problem with has_many :through

I recently ran into a problem using has_many :through relationships. The edge code works fine when using standard id’s but for those using legacy databases or non-standard id’s in your join table the code fails when trying to add or delete an association.

Something like this would fail:
create_table :books, :force => true do |t|
  t.column :name, :string
end
create_table :citations, :id => false, :force => true do |t| t.column :book1_id, :integer t.column :book2_id, :integer end
class Book < ActiveRecord::Base has_many :citations, :foreign_key => 'book1_id' has_many :references, :through => :citations, :source => :reference_of, :uniq => true end
class Citation < ActiveRecord::Base belongs_to :reference_of, :class_name => "Book", :foreign_key => :book2_id belongs_to :book1, :class_name => "Book", :foreign_key => :book1_id belongs_to :book2, :class_name => "Book", :foreign_key => :book2_id end
awdr = Book.create!(:name => "Agile Web Development with Rails") rfr = Book.create!(:name => "Ruby for Rails")
awdr.references << rfr awdr.delete(rfr)

There’s further information at http://dev.rubyonrails.org/ticket/6466

If you’re running into this problem you can patch your local version of rails. First freeze edge in your tree. Then create the file has_many_through_patch.rb in your lib directory with the following code:

module ActiveRecord
class HasManyThroughCantDisassociateNewRecords < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot disassociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.") end end
module Associations class HasManyThroughAssociation
# Construct attributes for :through pointing to owner and associate. def construct_join_attributes(associate) construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) end
# Remove +records+ from this association. Does not destroy +records+. def delete(*records) return if records.empty? records.each { |associate| raise_on_type_mismatch(associate) } through = @reflection.through_reflection raise ActiveRecord::HasManyThroughCantDisassociateNewRecords.new(@owner, through) if @owner.new_record?
load_target
klass = through.klass klass.transaction do flatten_deeper(records).each do |associate| raise_on_type_mismatch(associate) raise ActiveRecord::HasManyThroughCantDisassociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
@owner.send(@reflection.through_reflection.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate))) @target.delete(associate) end end
self end end end end
Then in your environment.rb add the following:
require 'has_many_through_patch'

You should be able to add and delete now until the patch is committed.

Friday, September 01, 2006

Ruby on Rails, Ajax & CSS Star Rating System

I’m sure everyone by now has seen those oh-so-Web 2.0 star rating features on hundreds of websites. Well I needed to implement one for a site I’m working on and I couldn’t find a complete example anywhere (not in RoR). So here it is. A complete Rails based Ajax and CSS star ratings sytem with some RJS thrown in for good measure.

I used Rogie’s very elegant CSS only star rating system found here CSS Star Rating Part Deux. I also used Chris Ingrassia’s acts_as_rateable plugin.

So here we go.

Get the CSS and change the image url’s

First figure out which version of the CSS ratings you like. I used this example.

    /*             styles for the star rater                */    
    .star-rating{
        list-style:none;
        margin: 0px;
        padding:0px;
        width: 150px;
        height: 30px;
        position: relative;
        background: url(/images/star_rating.gif) top left repeat-x;        
    }
    .star-rating li{
        padding:0px;
        margin:0px;
        /*\*/
        float: left;
        /* */
    }
    .star-rating li a{
        display:block;
        width:30px;
        height: 30px;
        text-decoration: none;
        text-indent: -9000px;
        z-index: 20;
        position: absolute;
        padding: 0px;
    }
    .star-rating li a:hover{
        background: url(/images/star_rating.gif) left center;
        z-index: 2;
        left: 0px;
        border:none;
    }
    .star-rating a.one-star{
        left: 0px;
    }
    .star-rating a.one-star:hover{
        width:30px;
    }
    .star-rating a.two-stars{
        left:30px;
    }
    .star-rating a.two-stars:hover{
        width: 60px;
    }
    .star-rating a.three-stars{
        left: 60px;
    }
    .star-rating a.three-stars:hover{
        width: 90px;
    }
    .star-rating a.four-stars{
        left: 90px;
    }    
    .star-rating a.four-stars:hover{
        width: 120px;
    }
    .star-rating a.five-stars{
        left: 120px;
    }
    .star-rating a.five-stars:hover{
        width: 150px;
    }
    .star-rating li.current-rating{
        background: url(/images/star_rating.gif) left bottom;
        position: absolute;
        height: 30px;
        display: block;
        text-indent: -9000px;
        z-index: 1;
    }

Make sure you change your image url’s so that your Rails app can find them.

Get the images for your CSS

Grab the images used in your CSS and put them in your images directory. Here are both

.

Install the acts_as_rateable plugin.

Run the following from the root of your Rails app to install the plugin.

script/plugin install http://juixe.com/svn/acts_as_rateable

Create the tables used by acts_as_rateable

Create a file db/migrate/xxx_create_ratings.rb (xxx is 001 if it’s the first migration file you have).

class CreateRatings< ActiveRecord::Migration
def self.up create_table :ratings, :force => true do |t| t.column :rating, :integer, :default => 0 t.column :created_at, :datetime, :null => false t.column :rateable_type, :string, :limit => 15, :default => "", :null => false t.column :rateable_id, :integer, :default => 0, :null => false t.column :user_id, :integer, :default => 0, :null => false end
add_index :ratings, ["user_id"], :name => "fk_ratings_user" end
def self.down drop_table :ratings end
end

Run your migration.

rake migrate

You should now have the appropriate tables.

Make one of your models rateable

I was trying to add a rating system for the model Asset. Yours can obviously be whatever you like but from here on out I’ll be using Asset. So add acts_as_rateable to your model.

class Asset < ActiveRecord::Base
  acts_as_rateable
  ...
end

Create a controller to handle the rating submissions

Create the file /controllers/rating_controller.rb

class RatingController < ApplicationController
def rate @asset = Asset.find(params[:id]) Rating.delete_all(["rateable_type = 'Asset' AND rateable_id = ? AND user_id = ?", @asset.id, current_user.id]) @asset.add_rating Rating.new(:rating => params[:rating], :user_id => current_user.id) end
end

Two things to note here. First I’m associating ratings to users. I’ve already implemented a user/permission system for my site using the model User. Use whatever is appropriate for you. You can modify this whole example to work without associating ratings to users, the acts_as_rateable plugin will handle it just fine. However, I’m not going to get into that here.

Since I am associating ratings to users it would be bad to have a user skew the results by storing multiple ratings for a single Asset. Hence the delete. I’m telling it to delete all ratings for the rateable_type ‘Asset’ and the id (rateable_id) of the Asset. The rateable_type of Asset is handled by the plugin and stored in the ratings table.

Create your views

Create the partial /views/rating/_rating.rhtml

<%= number_with_precision(asset.rating, 1) %>/5 Stars<br>
<ul class='star-rating'>
    <li class='current-rating' style='width:<%= (asset.rating * 30).to_i -%>px;'>
          Currently <%= number_with_precision(asset.rating, 1) %>/5 Stars.
        </li>
    <li>
        <%= link_to_remote( "1", {:url => { :controller => "rating_demo", 
            :action => "rate", :id => asset.id, :rating => 1}},
            :class => 'one-star', :name => '1 star out of 5') %>
    </li>
    <li>
        <%= link_to_remote( "2", {:url => { :controller => "rating_demo", 
            :action => "rate", :id => asset.id, :rating => 2}},
            :class => 'two-stars', :name => '2 stars out of 5') %>    
    </li>
    <li>
        <%= link_to_remote( "3", {:url => { :controller => "rating_demo", 
            :action => "rate", :id => asset.id, :rating => 3}},
            :class => 'three-stars', :name => '3 stars out of 5') %>
    </li>
    <li>
        <%= link_to_remote( "4", {:url => { :controller => "rating_demo", 
            :action => "rate", :id => asset.id, :rating => 4}},
            :class => 'four-stars', :name => '4 stars out of 5') %>    
    </li>
    <li>
        <%= link_to_remote( "5", {:url => { :controller => "rating_demo", 
            :action => "rate", :id => asset.id, :rating => 5}},
            :class => 'five-stars', :name => '5 stars out of 5') %>
    </li>
</ul>

Obviously it’s using Ajax with the prototype helper link_to_remote to submit the user’s rating. One thing to note. Where you see width:<= (asset.rating * 30).to_i ->px;’ you’ll have to modify this to correspond with the images you chose to use. The one I’m using has images which are 30px wide. If you chose the smaller star images then you’ll have to modify this calculation to correspond to your image width. By the way, this is the line that handles the display of the current rating.

And now a little RJS

Create the file /views/rating/rate.rjs

page.replace_html "star-ratings-block", :partial => 'rating/rating', :locals => { :asset => @asset } 

This will replace the star ratings with the partial we created previously in order to reflect any rating changes made by the submission.

And finally put it on your page

Render the partial in one of your views.

<div id="star-ratings-block">
    <%= render :partial => "rating/rating", :locals => { :asset => @asset } %>
</div>

This needs @asset (or whatver you’re going to be using) in order to function.

Done

Now wasn’t that easy? Gotta love rails. 10 minutes of coding and you have a complete Ajax and CSS star rating system just like the pros use.

I could very well have skipped something so let me know if you have any problems.

Friday, September 01, 2006

Washington DC Ruby on Rails Users Group

I’m creating a Washington DC area Ruby on Rails Users Group. If you’re interested in getting involved check out www.dcrug.org for more information.

Friday, September 01, 2006

RejectMail.com - Free Receive Only Email

RejectMail.com is a free receive only email service.

Give out any address @rejectmail.com and then come to rejectmail.com to check your email or use the RSS feed provided for updates.

It’s a bit ugly right now but hopefully when I have some time I’ll clean it up and add a few more features.

Again, this was written entirely with Ruby on Rails.

Friday, September 01, 2006

Receiving Emails and Attachments with Rails

Does your Rails app need to handle incoming emails with the attachments? All of the examples I’ve seen so far show you how to insert email attachments into the DB. Here’s a quick example that uses RailsCron to poll a POP3 account every minute for new emails and stores the attachments on the filesystem. If you need help using or running RailsCron see my previous posts about the topic.

The Agile book has a good example that kicks off a runner script but I think this method is far more efficient than having your mail system kick off a separate runner every time a new email is received, especially if you’re dealing in high volume.

It also handles non-responsive or slow responding POP3 servers by setting a high timeout length and retrying a handful of times before it gives up.

This class will check for and hand off any incoming emails:

require 'net/pop'
class EmailQueue < ActiveRecord::Base background :poll_mail, :every => 1.minute, :concurrent => false
def self.poll_mail retrycount = 0 begin timeout(600) do Net::POP3.start("yourdomain.com", nil, "username", "password") do |pop| if pop.mails.empty? logger.info "NO MAIL" else pop.mails.each do |email| begin logger.info "receiving mail..." AssetSubmitHandler.receive(email.pop) email.delete rescue Exception => e logger.error "Error receiving email at " + Time.now.to_s + "::: " + e.message end end end end end rescue TimeoutError if(retrycount < 5) retrycount+=1 retry else logger.info("ERROR Timeout error in poll_mail attempt #" + retrycount.to_s) nil end end rescue Exception => exception SystemNotifier.deliver_exception_notification(exception) logger.info("Error in poll_mail") logger.info(exception.class.to_s + " " + exception.message.to_s + " " + exception.backtrace.to_s) end
end

And here’s the code that handles the email:

class AssetSubmitHandler < ActionMailer::Base  
# content type should be validated to image/gif, image/jpg, or image/jpeg def receive(email) if email.has_attachments? email.attachments.each do |attachment| asset = Asset.new asset.submitter = email.from.first asset.name = base_part_of(attachment.original_filename) asset.content_type = attachment.content_type.chomp
base_dir = "/home/someapp/www/"
# save original file asset.original = "assets/o_#{Time.now.utc.to_i}#{rand(1000000)}."+asset.name File.open(base_dir+asset.original,File::CREAT|File::TRUNC|File::WRONLY,0666){ |f| f.write(attachment.read) }
asset.save end end end
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

Friday, September 01, 2006

My site in an O'Reilly book

I just found out recently that one of my webpages was featured in an O’Reilly book:

Google Maps Hacks: Tips & Tools for Geographic Searching and Remixing



Ok, so it’s a pretty simple page but nonetheless I think it’s pretty cool.

Friday, September 01, 2006

Rails Security Issue and Fix

If you’re running Ruby on Rails you should do an update immediately to get the fix for a newly found security issue.

To upgrade run:
gem install rails --include-dependencies

Rails 1.0 and earlier versions as well as Rails 1.1.3 aren’t affected.

If you want to freeze your particular version of Rails for an app go to the root directory of that app and run:

rake rails:freeze:gems

Note: The last release of rails (1.1.5) only solves part of the security problem. Update rails today to get 1.1.6.

Friday, September 01, 2006

Rider Jones

Check out Rider Jones out of Vancouver.

There’s a lot of garbage on MySpace to wade through some times but once in a while you come across some great music. I think their MySpace page is burned into my screen at this point. Can’t wait until I get the EP’s.

Friday, September 01, 2006

WhatMyFriendsLike.com


In addition to MySpace Maps I also created WhatMyFriendsLike.com

It lets you create a ranked list of all the music, movies and books your MySpace friends like.

This one was also written entirely in Ruby on Rails sharing most of the code with MySpace Maps.

Friday, September 01, 2006

Multiple RailsCron Instances

Just to follow up on my last post about using RailsCron.

If you want to run multiple instances of RailsCron you need to change the startup tasks in the rake file.

In my particular case I wanted a separate instance running for each of the sites that required it. I’m running them all as Daemons so I changed the cron_start target in my rake file.

From the root of of your app edit the rake file:
vi vendor/plugins/trunk/tasks/startup.rake
Change
desc "Starts RailsCron as a daemon" 
task :cron_start do
  if `#{sudo "ps x | grep RailsCron | grep -v grep"}`.strip.blank?
    mode = ENV['RAILS_ENV'] || "development" 
    puts `#{sudo "nohup ruby script/runner -e #{mode} \"RailsCron.start\" > /dev/null 2>&1 &"}`
  else
    puts "RailsCron already started" 
  end
end
To the following:
desc "Starts RailsCron as a daemon" 
task :cron_start do
    mode = ENV['RAILS_ENV'] || "development" 
    puts `#{sudo "nohup ruby script/runner -e #{mode} \"RailsCron.start\" > /dev/null 2>&1 &"}`
end

This essentially removes the check for an existing RailsCron process and lets you start a new one. Be careful.

Now you can run rake cron_start from the root of each app that requires a RailsCron instance. Running rake cron_stop will stop all the RailsCron processes so you’ll have to start each one up again.

Username:
Password:
(or Cancel)