1. Skip to navigation
  2. Skip to content

The ELC Community Blog

A knowledge exchange on Ruby on Rails and Agile Development

January 30, 2008

by josh

Spec refactoring

While trolling through old code, I came across the following specification:

describe Build do
  describe "passed?" do
     it "returns true for 1" do
       build = Build.new(:status => 1)
       build.passed?.should be_true
     end
  end
end

The problem with this test is it only tests for success. Realizing that it's equally--if not more--important to test for failure than it is success, I decided to ask the team for opinions on what this test should look like.

After bouncing this around with the team, we decided to refactor it as such:

describe Build
 describe "passed?" do
     before(:each) do
       @build= Build.new
     end

    it "returns false when status has not been set" do
      @build.passed?.should be_false
    end

     it "returns false when status is 0"
       @build.status= 0
       @build.passed?.should be_false
    end

     it "returns true when status is 1"
       @build.status= 1
       @build.passed?.should be_true
    end
 end
end

As you can see, we were able to find 2 failure conditions and only one success.

This is a very simple example but we think it has a broad application to testing in general.

January 30, 2008

by Cary Dunn

replaceaface.com beta ultra version 0.113 build 18.2

replaceaface.com - putting faces on bodies and stuff



With replaceaface, you can mashup images by mixing and matching faces and bodies in a sweet online flash app. Totally photoshopped, check it out!


Choose a body photo
by selecting a hot bod, uploading a new one, or taking one from your webcam.

Choose a face photo
by uploading a pic of your broski to humiliate, or taking one of your own ugly mug from your webcam to throw on a rockin bod.

Erase the face on the body image
to make a hole for your face image.

Resize and position the face
to fit the bod.

Doodle a bit and send it!


If anyone would like any tutorials on features let me know!

January 28, 2008

by josh

Tony Pitale wrote:
Very nice. Looking forward to reading the follow-up on STDOUT and STDERR.


Executing Shell Commands in Ruby

When in need of executing shell commands, it's easy enough in ruby to use exec(), system(), the ubiquitous backtick or %x{...}, but what's the difference between these?

irb(main):001:0> `ls`
=> "one\nthree\ntwo\n"
irb(main):001:0> %x{ls}
=> "one\nthree\ntwo\n"

Ok, so the backticks and %x{} are exactly the same. What about system()?

irb(main):003:0> system('ls')
one   three two
=> true

Nice, no '\n'. And now exec():

irb(main):004:0> exec('ls')
one   three two
bash$

Ah! it kicked me out of irb, meaning it kills the current Thread. Good to know!

Return Values

Process::Status Objects

irb(main):001:0> system('ls')
one   three two
=> true
irb(main):002:0> $?
=> #<Process::Status: pid=79599,exited(0)>
irb(main):004:0> 

$? accesses the status of the last system executed command, whether you use the backticks, system() or %{}.

Use '.exitstatus' to get the status:

irb(main):001:0>irb(main):001:0> system('ls')
one   three two
=> true
irb(main):002:0> $? # a magic ruby method
=> #<Process::Status: pid=79606,exited(0)>
irb(main):003:0> $?.exitstatus
=> 0
irb(main):004:0> 

Contrary to what you might think, 0 means success, 1 means failure. If you want to make the $? more readable, require English:

irb(main):001:0> system('ls')
one   three two
=> true
irb(main):002:0> $?
=> #<Process::Status: pid=79615,exited(0)>
irb(main):003:0> require 'English'
=> true
irb(main):004:0> $CHILD_STATUS
=> #<Process::Status: pid=79615,exited(0)>
irb(main):005:0> $CHILD_STATUS.exitstatus
=> 0
irb(main):006:0>

Parsing STDERR and STDOUT can only be done by logging and parsing logs. More to come...

Also posted to fr.ivolo.us

January 25, 2008

by Cary Dunn

Brendan wrote:
Awesome tutorial…very creative use of red5, it’s nice to see something other than chat tools…...


Saad wrote:
For now, we only want to customize these files for our current app. We need...


Tutorial - Red5, AS3, FC4, and Shared Fridge Magnets!



Jargon...zZzZz

Red5 is an Open Source Flash Server written in Java that supports: Streaming Audio/Video (FLV and MP3), Recording Client Streams (FLV only), Shared Objects, Live Stream Publishing, Remoting”

Red5 is essentially an open source alternative to Adobe’s Flash Media Server. Both technologies leverage Adobe’s RTMP (Real Time Message Protocol) to stream small binary fragments in real time. The packets are multiplexed and therefore only one persistent connection is needed for each user. Objects are serialized via AMF (Action Message Format) which is what we will be using in this tutorial to create SharedObjects, which as the name suggests, are objects (in this case refrigerator magnets!) that are shared amongst all viewers of the application.


Red5 Boostrap

Download Red5

Once installed, navigate the Red5 root directory to get started.


Red5 Structure Rundown

Just the important stuff...

In the root Red5 directory you’ll find red5.jar which holds a pre-compiled jar holding the red5 java classes. You’ll use this later to compile your main application class.

The directory webapps is where you will be creating new Red5 applications. All new applications have a consistent structure which is laid out as a template for you in doc/templates


Red5 New Application Structure Rundown

Creating a new Red5 application is simple. We just create a new folder in webapps/.

We will create an app called elcMagnets so we create a directory for it and now have webapps/elcMagnets. Red5 expects us to also have a directory inside our new application named WEB-INF/ so we create that as well, and now have webapps/elcMagnets/WEB-INF.

There are 4 default configuration files in doc/templates/myapp/WEB-INF that we will modify and use in our app, so we can copy those over to our elcMagnets directory. (log4j.properties, red5-web.properties, red5-web.xml, web.xml)

You can read up about all the properties and handlers specified in these files here.

For now, we only want to customize these files for our current app. We need to change the webapp.contextPath specified in red5-web.properties to reflect our newly created app. Take note of the virtualHosts parameter and remember to deal with it on deployment to a remote host.

webapp.contextPath=/elcMagnets
webapp.virtualHosts=localhost, 127.0.0.1


The only other configuration file we will modify for now is red5-web.xml. For this we need to specify the handler that is invoked when users connect to the app. You can read up on handlers here.

For the purposes of this demo app we will only be creating a web.handler. We will end up writing Application.java as the web.handler inside the a package called package org.red5.server.webapp.elcMagnets so we will set our web.handler to org.red5.server.webapp.elcMagnets.Application.

<bean id=”web.handler” 
	    class=”org.red5.server.webapp.elcMagnets.Application” 
		singleton=true” />


Lastly we will comment out the sample service bean since we will not be using that in this demo.

<!-- <bean id=”myhandler.service” 
	    class=”the.path.to.my.ServiceHandler” 
		singleton=true” /> -->


Application.java

To keep things organized, we will create the directories lib/, classes/, src/ inside webapps/. Next we will create Application.java inside of src/.

As you recall, Application.java will serve as our web.handler, and therefore needs to extend ApplicationAdapter. (For more info on handlers, see the ‘HOWTO-NewApplications.txt’) In short, it allows you to stack functionality on top of existing events (connect, disconnect, join, leave, start, stop) or create your own.

Our example handler will be trivial, but illustrate calls to the Red5 server.

package org.red5.server.webapp.elcMagnets;

import org.red5.server.adapter.ApplicationAdapter;

public class Application extends ApplicationAdapter {
	public String high5Red5(int how_many)
	{
		String highFives = “”;
		if(how_many==0){ return “I dont like you anyways, bro.”; }
		for(int i=0;i<how_many;i++){ highFives += “*Smack*\n”; }
		return highFives;
	}
}


Later, we can directly call this handler in ActionScript...
nc.call("high5Red5",nr,5);
// Or more generally...
netconnection.call("function",responder,arguments.....)


Compiling Application.java

What we want to end up with is a nicely packaged up elcMagnets.jar located in lib/

So from src/ we compile Application.java to the classes/ directory.

javac -classpath ../../../../red5.jar -d ../classes Application.java


Now lets create our jar file. First we need our MANIFEST.MF. If you aren’t familiar with it...


MANIFEST.MF

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.7
Created-By: 1.5.0_06-b05 (Sun Microsystems Inc.)
Class-Path: ../../../../red5.jar

(you can try yum/port install ant, but seem to remember building it)


Compiling elcMagnets.jar

jar -cmf MANIFEST.MF ../lib/elcMagnets.jar ../classes/org/red5/server/webapp/elcMagnets/Application.class


OK! Done with Red5.

You should now be able to start up your Red5 server and have it load up our new elcMagnets application.

Flash App

Next we will create an AS3 flash app to interact with our Red5 application and utilize SharedObjects.

Fridge.as (Document Class)

(I'm just going to post code, comment if you need any explanation.)

//	=======================================
// 	Cary Dunn <cdunn@elctech.com>
//	ELC Technologies http://www.elctech.com
//

package
{
	import flash.events.*;
	import flash.display.MovieClip;
	import flash.text.TextField;
	import flash.errors.IOError;
	import flash.system.Security;
	import caurina.transitions.Tweener;
	import flash.net.*;
	import com.elctech.Magnet;
	
	public class Fridge extends MovieClip
	{
		NetConnection.defaultObjectEncoding = ObjectEncoding.AMF0 ;
		private var nc:NetConnection = null;
		private var alphabet:Array = new Array;
		private const LETTERS:Array	= ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",",","!"]
		
		public function Fridge():void
		{
			nc = new NetConnection();
			nc.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler);
			var nr:Responder = new Responder(onHighFives,null);;
			nc.connect("rtmp://localhost/elcMagnets");
			nc.call("high5Red5",nr,5);
		}
		
		private function onHighFives(resp:String):void
		{
			trace(resp);
		}
		
		private function netStatusHandler(event:NetStatusEvent):void
		{
			trace(event.info.code);
			if(event.info.code == "NetConnection.Connect.Success")
			{
				createAlphabet();
			}
		}
		
		private function createAlphabet():void
		{
			for(var i:uint = 0; i < LETTERS.length; i++)
			{
				var new_magnet:Magnet = new com.elctech.Magnet(LETTERS[i],nc,i);
				addChild(new_magnet);
				alphabet.push(new_magnet);
			}
		}
	}
}

com.elctech.Magnet.as

//	=======================================
// 	Cary Dunn <cdunn@elctech.com>
//	ELC Technologies http://www.elctech.com
//

package com.elctech
{
	import flash.display.MovieClip;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.geom.Rectangle;
  import flash.events.*;
	import flash.display.Stage;
	import flash.text.TextField;
	import flash.net.*;
	import flash.utils.Timer;
	import caurina.transitions.Tweener;
	
	public class Magnet extends MovieClip
	{
		private var _id:uint = 0;
		private var _so = null;
		private var _nc:NetConnection = null;
		private var _l:String = null;
		static internal var dragging:Boolean = false;
		static const MAGNET_COLORS:Array = [0xFF3333, 0x3399FF, 0x339966, 0x993399, 0xFF9933]
		
		static internal const ELASTICITY:Number = .5;
		static internal const FRICTION:Number = .5;
		private var ax:Number = 0;
		private var ay:Number = 0;
    private var vx:Number = 0;
		private var vy:Number = 0;
		
		public function Magnet(l:String, nc:NetConnection, i:uint):void
		{
			this.letter.text = l;
			this.letter.textColor = MAGNET_COLORS[Math.round(Math.random()*(MAGNET_COLORS.length-1))];
			this.x = Math.round(Math.random()*390)+67;
			this.y = Math.round(Math.random()*260)+20;
			trace("New Magnet " + l);
			
			_l = l;
			_nc = nc;
			_id = i;
			
			_so = SharedObject.getRemote("letter_"+_id, _nc.uri, true);
			_so.addEventListener(SyncEvent.SYNC,syncEventHandler);
			_so.connect(_nc);
			
			this.buttonMode = true;
			
			this.addEventListener(MouseEvent.MOUSE_DOWN, function(evt:Event):void {
				dragging = true;
				stage.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
				evt.target.parent.addEventListener(Event.ENTER_FRAME, updatePosition);
			});
			
			this.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
		}
		
		private function stopDragging(evt:Event):void
		{
			this.removeEventListener(Event.ENTER_FRAME, updatePosition);
			stage.removeEventListener(MouseEvent.MOUSE_UP, stopDragging);
			dragging = false;
		}
		
		private function updatePosition(evt:Event):void
		{
      ax = (stage.mouseX-this.x+Math.random()*5)*ELASTICITY;
      ay = (stage.mouseY-this.y)*ELASTICITY;
      vx += ax;
      vy += ay;
      vx *= FRICTION;
      vy *= FRICTION;
      this.x += vx;
      this.y += vy;
      this.scaleX = (100-Math.abs(ax)*2)/100;
      this.scaleY = (100-Math.abs(ay)*2)/100;
      this.rotation = ax*3;

			_so.setProperty("currentPosition", {x: evt.target.x, y: evt.target.y});
		}
		
		private function syncEventHandler(evt:SyncEvent):void
		{
			trace("Sync for letter "+_l);
			if(dragging){ return; }
			
			Tweener.addTween(this, {x:_so.data.currentPosition.x, y:_so.data.currentPosition.y, time:0.5, transition:"easeOutBack"});
		}
	}
}

Download Project Source



Installing Red5 on FC4

This section documents my trials and tribulations with installing Red5 on an Amazon EC2 instance running a standard FC4 image. This is a nasty (but working) implementation of this.

Some of the rpms are unnecessary, but some of the yum packages were failing on me. Anyways, this might help someone...

$ yum install gcc
$ rpm -ivh ftp://rpmfind.net/linux/fedora/core/updates/4/i386/libstdc++-devel-4.0.2-8.fc4.i386.rpm
$ rpm -ivh ftp://rpmfind.net/linux/fedora/core/updates/4/i386/gcc-c++-4.0.2-8.fc4.i386.rpm
$ yum install fedora-rpmdevtools
$ fedora-buildrpmtree
$ cd /etc/yum.repos.d/
$ wget http://www.jpackage.org/jpackage.repo
---->>> download jdk 5.0 update .bin from <a href="http://java.sun.com/javase/downloads/index.jsp" target="_blank">sun</a>, scp it up to server (requires accepting of TOS)
---->>> cp jdk-....bin rpmbuild/SOURCES
$ wget http://mirrors.dotsrc.org/jpackage/1.7/generic/non-free/SRPMS/java-1.5.0-sun-1.5.0.14-1jpp.nosrc.rpm
$ rpmbuild --rebuild java-1.5.0-sun-1.5.0.14-1jpp.nosrc.rpm
$ cd ~/rpmbuild/RPMS/i586/
$ (echo config gpgcheck 0; echo localinstall java-1.5.0-sun*.rpm; echo run) > yum-cmd
$ yum shell yum-cmd
$ rm yum-cmd

$ cd ~/
wget http://archive.apache.org/dist/ant/binaries/apache-ant-1.7.0-bin.tar.gz
$ cd /usr/local/
$ tar -zxf ~/apache-ant-1.7.0-bin.tar.gz
$ mv apache-ant-1.7.0 ant

$ vim /etc/profile
PATH=$PATH:$HOME/bin:/usr/local/ant/bin
export PATH

$ source /etc/profile

$ tar -zxvf red5-0.6.3.tar.gz
$ cd red5-0.6.3
$ make
$ make install
$ /usr/lib/red5/red5.sh

At this point if you can hit http://{ip here}:5080 you are golden.

JRuby&Red5

January 25, 2008

by Cary Dunn

Setting up a Flash Media Server on EC2 (CentOS)

Windows Server? Yeah right. Redhat? No thanks. It may not be “supported” by the Flash Media Server, but by installing a few libraries you can get one up and running on CentOS in a few minutes…

yum install compat-libstdc++-296.i386
yum install compat-libstdc++-33.i386
cd /lib
ln -s libcrypto.so.*** libcrypto.so.4
ln -s libssl.so.*** libssl.so.4
Might be useful if trying to deploy on EC2 for instance…

January 24, 2008

by Yuanyi Zhang

AWS/S3 may cause rake task failed

I got a weird problem recently, the project runs well with script/server, but when I run a rake task, a 'uninitialized constant ***' error appeared. After spending me about 1h, I finally detected the problem was caused by AWS/S3 gem.

There's a rake file named 'mysql.rake' in my project, it requires 'aws/s3'. So when i running a rake task, AWS/S3 will be loaded before environment.rb, and it will define String#underscore before ActiveSupport, because ActiveSupport use Module to extend String, so AWS/S3 version will be used, but it's outdated:

# AWS/S3 version
def underscore
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
  gsub(/([a-z\d])([A-Z])/,'\1_\2').
  downcase
end unless public_method_defined? :underscore

# ActiveSupport Version
def underscore(camel_cased_word)
  camel_cased_word.to_s.gsub(/::/, '/').
    gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
    gsub(/([a-z\d])([A-Z])/,'\1_\2').
    tr("-", "_").
    downcase
end

You should notice the difference, if a plugin doesn't require all its lib files in init.rb and it uses name space (eg acts_as_paranoid), and you just have a rake task need AWS/S3, then you will have problems to run rake tasks. because Rails can't load necessary files for that plugin automatically with the outdated String#underscore.

The solutions are:

  • modify the weird plugin to require all lib files itself.
  • update AWS/S3 with the new version String#underscore

But if it's a large plugin, then solution#1 will require lots of time, It's what i encountered, so I chose the solution#2.

I've submit a patch to AWS/S3 ml, but before they merging it into the trunk, if you are unfortunately the man satisfying all above conditions, hope this post can save a little time for you.

January 23, 2008

by jmoline

Dustin Davis wrote:
This seems to work OK, but not quite as expected. Let’s say I create a...


Refreshing Rails Generated Views

It seems such a small thing, really, and it's so easy to take for granted. One of the things that I first came to love about Rails was the fact that it would generate standard views for your models. Sure, the HTML that the framework generates would charitably be described as skeletal; it requires a ton of work in order to look even remotely presentable. Even pushing design concerns to the side, you still have to account for the fact that all of your fields default to textboxes.

And yet, despite the headaches that accompany generated views, not having to type out every single field by hand is a beautiful thing. Of course, if that were all I had to say, I wouldn't have bothered to type this out. I mean, really, who'd want to read that?

So, I guess you're wondering why I called you all together, then. It's simple, really, I love generated views, but I also happen to prefer typing out my migration files rather than using command line arguments in the script/generate command. Unfortunately, there doesn't seem to be a native way to generate views from an existing migration, or, heaven forbid, a model that has been modified by multiple migrations.

In response to this dilemma, I decided to write a Rails Generator that would leverage the core view templates against field parameters that are created from an existing model.

And so, without further ado, I present the Refresh View plugin.

Install:

./script/plugin install http://refreshview.rubyforge.org/svn/tags/1.0/refresh_view/

Use:

Create your scaffold, modify the migration, then run the following command:

./script/generate refresh_view model_name

If you continue to make changes to the model's structure? Just keep refreshing the view. BUT, and this is important, the generator completely rewrites the view, so don't refresh the view if you've made changes you want to keep.

Happy Programming.

January 22, 2008

by josh

Reid wrote:
very nice!


Environment Scripts in merb

The time has come to run a script using cron or another background worker and I find myself reaching for script/runner only...

It's not there!

No worries...here's what you do:

#!/usr/bin/env ruby
require 'rubygems'
require 'merb'
require File.dirname(__FILE__) + '/../config/dependencies'
load_paths = []
load_paths.unshift(File.join(Merb.root , '/app/models'))
# add more load paths here, such as
# load_paths.unshift(File.join(Merb.root , '/app/controllers'))
load_paths.each do |path|
  Dir.glob("#{path}/*").each { |m| require m }
end

You can avoid everything from line 4 onward by using Merb::BootLoader.load_application but it will load the routes, which takes longer.

Check in your merb gem (lib/merb/boot_loader.rb) for more help.

Also posted to fr.ivolo.us

January 16, 2008

by Jeff Emminger

Stop wrote:
Thanks for pointing me to this valuable resource. I am going to apply the method...


Nathan wrote:
Yea, it would be really cool if that could be somehow built into this terminit...


Script Terminal with TermInit


Are you tired of typing the same commands in Terminal every day just to get your project going? Do you want to save, like *minutes* per day? Did you know that you only get so many keystrokes per lifetime before your hands are permanently crippled? OK, that last bit I might have just read somewhere but I digress.

Enter TermInit, stolen^H^H^H^H^H^H inspired by Solomon White's work here: http://onrails.org/articles/2007/11/28/scripting-the-leopard-terminal

...and all packaged up for easy use: https://wush.net/svn/public/terminit/

An example from the readme:

  # myproject.yml:
  - tab1: cd /foo/bar
  - tab2: 
    - mysql -u root
    - use foo_db
    - select * from bar;
  - tab3: echo "hello world"

Open Terminal and run it:

terminit.rb myproject

Et voila, you have three tabs opened in Terminal with the appropriate commands executed in each.

Save those keystrokes for real code!

January 16, 2008

by damian

Patching Rails - Rendering form partials

Yesterday we got a patch committed to Rails. This new enhancement provides a nicer and more conventional way of rendering form fields as a partial.

Instead of writing this not-so-DRY call to

   1  render
,

  <% form_for(@client) do |f| %>
    <%= render :partial => 'form', :locals => {:f => f} %>
    <%= submit_tag 'Create' %>
  <% end %>

... we can do something cleaner, like this:

  <% form_for(@client) do |f| %>
    <%= render :partial => f %>
    <%= submit_tag 'Create' %>
  <% end %>

Here, the rendered partial is

   1  clients/_form
and the local variable inside the partial is called
   1  form
.

If we used a different form builder,

  <% form_for(@client, :builder => LabellingFormBuilder) do |f| %>
    <%= render :partial => f %>
    <%= submit_tag 'Create' %>
  <% end %>

... the partial that gets rendered is

   1  clients/_labelling_form
and the local variable is called
   1  labelling_form
.

There have been other proposals, but in the end we agreed that the

   1  render
method is the one who knows about the different kinds of things that can be rendered as partials (take, for example,
   1  render :partial => @clients
).

The patch also changes the docs and provides unit tests for the new behavior.


home | services | Ruby on Rails Development | code | blog | company