The Art of Change

The Art of Change is our soap box. Come here for news about Integrate, our inspirations, and our opinions.

I try to stray away from political discourse, as a rule, on this blog. After all, it is my company’s website and the discussion doesn’t really have a place here. However, after reading The Wall Street Journal Article, ‘Sick and Getting Sicker’, I feel it may be appropriate…or at least germane…to add my two cents to the discussion (for what it’s worth).

So much is made about the health care issue and the cost to businesses, but not much is added to the discussion that has a bearing on real life. It ends up being about tax dollars and spending and ‘BIG’ government and ’small’ government. It’s never about who’s affected by action or inaction. As a small business owner, insurance is a big deal. Health insurance is an even bigger deal.

It’s not all ponies and rainbows

I’m constantly worried about when the other shoe will drop, as I’m sure many business owners are. Will my new client pay? Will I get that new contract? What happens if we make a mistake? Insurance is supposed to help us all feel a little better about doing business. My business insurance helps. My Errors and Omissions policy makes me feel better too, but it’s not nearly as important, psychologically, as health insurance.

Thankfully, I’m insured by wife’s excellent policy, but I can’t offer insurance to my employees, so I’ve taken to hiring freelancers and contractors to fill the gap, but the talent pool is shallow and it’s not easy to ramp up for a big project when you staff a project this way. This ham strings my efforts to get and keep amazing people (no matter how amazing I may be…which my Mom tells me I am…so I must be), as it does for the people in the article. More importantly, it wears down the fiber of an organization. It’s yet another hurdle to creating and building a business. A hurdle that seems unnecessary when you look at the way European countries care for their population.

So much lip service is given to the culture of small business in this country. It’s apparently essential to making the economy rebound. It apparently employs the most people in the US. It apparently speaks about the culture of this country and our rugged individuality. If it is apparently all these things, then why are small business left hanging in the wind. Why is the discourse disproportionally skewed toward big business? I guess it’s hard to have a seat at the debate when small businesses are made up of so many disparate voices. I don’t think it’s an excuse.

The cost of any progressive action will be high, but the cost of inaction will be much, much higher. Insure us all and keep our country moving forward.

Congratulations BellKor’s Pragmatic Chaos!. You’ve earned your million and Netflix just became that much cooler.

This is by far, the most frustrating error I’ve had the unfortunate luck to run into…and it all seems so simple in retrospect. If you’ve ever set a color using the style.color method in javascript, you may have encountered a compatibility problem where it works in Firefox and Safari, but not in Internet Explorer (this is not a bash IE blog post, by the way).

The Error

If you’re using Scriptaculous, like I am in Ruby on Rails:

1
 $('item_id').style.color = '#09SF0C';

or if you’re using the document object:

1
document.getElementById('item_id').style.color = '#09SF0C';

The action will result in an error in an ‘Invalid Object’ error in Internet Explorer. Safari and Firefox will do their best, but not consistently.

The Solution

I thought, at first, that it was a coding issue where IE would treat style.color differently then the other browsers (although I could find no evidence to support that assumption), so I edited the code and tried every different combination setting the color style, but to no avail.

It turns out that no browser really likes this color #09SF0C (which is a green by the way). So, after two hours of ridiculous code editing, I started playing with the color and found a new green (#336633) that all browsers liked just fine. Code works, color changes. In retrospect, it’s all so simple…and I feel pretty bad that I didn’t think of it in the first 15 minutes. The least I could do is save someone a couple of hours. Use one of the hex colors here.

- Chris

We’re working on a current project that utilizes observers to update activity and notification feeds, and we’ve run into a few stumbling blocks while implementing our solution and I wanted to share our experiences after scouring the internet for the proper execution method. It turns out that the Cache Sweeper is just what the doctor ordered.

1) Standard ActiveRecord::Observers wouldn’t work

The problem with the ActiveRecord::Observer class is that it doesn’t know enough. It’s great if you’re doing email notification based on a simple create, update, destroy action, but unless you’re passing a user model in when you perform those actions you won’t have visibility into who’s doing the creating or destroying. Enter the Cache Sweeper observer. It does a great job of observing models and controllers at the same time. Using the Cache Sweeper we were able to identify the current user through the sessions parameter we use. For instance, we were able to put this code in place and see who was doing the action.

1
2
3
4
5
6
7
8
9
10
11
12
13
class ActionSweeper < ActionController::Caching::Sweeper
  observe :comment, :connection, :topic, :post, :membership, :video, :album, :item, :entry
 
  #have to use the after_save hook for created records because the permission isn't saved and can't be interpolated by the activities model 
  def after_save(object)
    Activity.create(:user_id => controller.session[:user], :object => object, :action => 'after_create') if controller.params[:action] == "create"
    Activity.create(:user_id => controller.session[:user], :object => object, :action => 'after_update') if controller.params[:action] == "update"
  end
 
  def after_destroy(object)
    Activity.create(:user_id => controller.session[:user], :object => object, :action => 'after_destroy')
  end
end

You may ask why we’re using the after_save method instead of the after_create and after_update methods. We have a plugin that the activity model references and that plugin is tied to the after_save method for some of the referenced models. If we use the after_save method, the observer fires before the plugin has a chance to save the corresponding models. By using the after_save method in this way, we get around it. I’d like it to be a little cleaner, but it works.

2) We can observe controller actions

Because the Cache Sweeper class also observes controllers, we were able to tie in to specific actions, like the show action. Some might say we could observe the after_find callback. Unfortunately, the after_find callback fires every time a record is found no matter how many records there are. So a Record.find(:all) call would fire the after_find method. Because we need to know when someone views a record, but we don’t want to incur the overhead of 100 after_find callbacks, we use the Cache Sweeper to observe the Show method of a controller which will only fire once. With that knowledge, we can use the code below to watch a controller’s show action:

1
2
3
4
5
class NotificationSweeper < ActionController::Caching::Sweeper
  def after_blogs_show
   Notification.hide(@entry)
  end
end

To observe the controller, simply name your method like so [before|after]_[controller name]_[controller action].

It’s not perfect, of course. You do have to add cache_sweeper :notification_sweeper to each controller you want to observe, which is a real drag. It is however the only way I could find that gives us access to the params array and session information which is necessary if we want to know who’s doing what.

Update

Looking to access an instance variable in the Cache Sweeper. Just use:

1
entry = assigns("entry")

If only making websites required the use of a capacitive, translucent touchscreen. Check it out.
http://gizmodo.com/5128041/seriously-guys-this-time-were-getting-closer-to-the-minority-report-screen

Light-Seeking Robot using the Arduino

May 25th, 2009 by Export

I’ve always been interested in physical computing. Even though I love creating software, I’ve always felt that creating websites and software doesn’t go far enough to satiate my creative desire. Enter the Arduino, a great open-source micro processor and prototyping board for around $30. This post marks the first, in what I hope will be a long line of tutorials and projects using the Arduino to develop robots and sensor driven applications that I hope to release into the wild. This post details a light-seeking robot with an Arduino brain, kind of the “Hello World!” application for autonomous robotics.

The Parts

I got a number of parts from several different sources. I had some parts lying around, but I’ll also provide links where you too can buy yourself all the light-seeking robot parts your heart desires.

Prototype I – Proof of Concept

While I was waiting for my chassis to arrive, I put together an initial prototype to test the circuit and code. The first prototype was rough, to say the least, and it didn’t move, but the code worked. I used the prototype board you see in the picture (available at Radio Shack) as a base to mount (tape) the motors to. I put tape on the axles to see if the code was actually making the motors turn the right way. It was.

Initial Prototype and Code Testing Platform

And here’s a video, showing it work in all it’s glory.

As it says in the video, I initially gave the robot seeking and avoiding behavior (you can see it in the code below in the function foundSource()), but I realized that there are times when the robot will never find it’s source (especially if that source is the sun) and that it was needless behavior. I left the runAway variable in the code through so you could easily turn the robot into a photophobe. You could also mount a switch on the robot that changes the behavior from photovore to photophobe as well.

Prototype II

Once I got the chassis from Pololu, I put together a better prototype, one that moves and turns around when it runs into something…you know, typical robot behavior. In the following pictures, you can see the very crude method of assembly, but it works…and that was really the point.
Prototype II on the Chassis
I literally moved the prototype board in the first picture above to the top of the chassis and hooked up the motors to the motor shield. I added a small switch for the motor power so I could stop screwing and unscrewing the motor’s power supply. Everything is just taped down. This is, again, more of a proof of concept than a finished robot. I plan on making a custom motor driver for the chassis and better mount for the Arduino and other sensors.

Back-up sensor under the plow
To give the push button more surface area, I added a plow to the front, with an “ultra-strong” electrical tape hinge. Obviously, when the robot runs into something, it pushes the button and the turnAround() function executes.

Inside of the tracked chassis
There’s not a lot of room in the chassis, but you could replace the AA battery pack with a 9V to give you some more space. But there are mounting holes on the corners which is probably a preferable location for your robot’s brains.

Here it is in action:

The Circuit

The circuit is fairly simple, especially because I’m using the Motor Shield. This circuit diagram illustrates the sensor circuitry and status led you see on the white bread board above. The motors are attached to the M1 and M2 points on the motor shield (positive leads toward the inside of the board). I used OmniGraffle to draw up the circuit, but I’m still looking for better templates so my circuits are prettier, more accurate, etc, but this drawing should illustrate the connections just fine.

Light Seeking Robot

The Code

You program the Arduino in Wiring, which is basically a library in C++. Forgive me any trespasses below. C++ is not a language I’ve worked in a lot, and the code below could most likely use some optimization. I originally had many delays written into the loop because that’s what I saw a lot of other code doing. That resulted in erratic behavior and a lot of running into stuff even though the object’s shadow should have caused the robot to avoid it. When I removed the delay and averaged the light sensor readings, I got a very responsive robot. I’ve also left a lot of code in the source because I’m working on building a robot base that I can use over and over again as I install more sensors and create more interesting behavior.

#include 
#define NUMREADINGS 5
#define LEFTSENSOR 3
#define RIGHTSENSOR 5
#define BUMPSENSOR 2
#define TOPSPEED 200
 
int i;
int valLefat;
int valRight;
int valCenter;
int oldLeft = 0;
int oldRight = 0;
int sensitivity = 40;
int threshold = 50;
int topThreshold = 650;
boolean runAway = false;
 
int leftReadings[NUMREADINGS];
int rightReadings[NUMREADINGS];
int index = 0;                            // the index of the current reading
int leftTotal = 0;                            // the running total
int leftAverage = 0;                          // the average
int rightTotal = 0;                            // the running total
int rightAverage = 0;                          // the average                      
 
AF_DCMotor rightMotor(2, MOTOR12_8KHZ); // create motor #2, 64KHz pwm
AF_DCMotor leftMotor(1, MOTOR12_8KHZ); // create motor #1, 64KHz pwm
 
void setup()
{
  Serial.begin(9600);
  rightMotor.setSpeed(TOPSPEED);
  leftMotor.setSpeed(TOPSPEED);
  pinMode(BUMPSENSOR, INPUT);
  moveForward();
  delay(500);
  for (int i = 0; i &lt; NUMREADINGS; i++){
    leftReadings[i] = 0;                      // initialize all the readings to 0
    rightReadings[i] = 0;
  }
}
 
void loop()
{
  checkForBump();
  averageReadings();
  checkLightandMove();
}
 
void checkLightandMove(){
  if(valLeft &gt; threshold &amp;&amp; valRight &gt; threshold){
    if((valLeft &gt; valRight) &amp;&amp; (valLeft - valRight &gt; sensitivity)){
      if(runAway == false){
        turnRight();
      }else{
        turnLeft();
      }
    }else if((valLeft &lt; valRight) &amp;&amp; (valRight - valLeft &gt; sensitivity)){
      if(runAway == false){
        turnLeft();
      }else{
        turnRight();
      }
    }else{
        moveForward();
    }
 
  }else{
    //turnAround();
    stop();
  }
 
  if(valLeft &gt; topThreshold &amp;&amp; valRight &gt; topThreshold){
    runAway = false;
  }
  //delay(500);
}
 
void checkForBump(){
  int bumped = digitalRead(BUMPSENSOR);
  Serial.println(digitalRead(BUMPSENSOR));
  if(bumped == HIGH){
    Serial.println();
    turnAround();
  }
}
 
boolean foundSource(){
  return oldLeft &gt; valLeft &amp;&amp; oldRight &gt; valRight;  //the robot has reached the source of the light, or the point of maximum brightness
}
 
void moveForward(){
  Serial.println("Move Forward");
  rightMotor.run(FORWARD);
  leftMotor.run(FORWARD);
}
 
void speedUp(){
  for (i=0; i==TOPSPEED; i++) {
    rightMotor.setSpeed(i);
    leftMotor.setSpeed(i);
  }
}
 
void slowToStop(){
  for (i=TOPSPEED; i==0; i--) {
    rightMotor.setSpeed(i);
    leftMotor.setSpeed(i);
  }
  rightMotor.run(RELEASE);
  leftMotor.run(RELEASE);
}
 
void turnLeft(){
  Serial.println("Turn Left");
  rightMotor.run(BACKWARD);
  leftMotor.run(FORWARD);
}
 
void turnRight(){
  Serial.println("Turn Right");
  rightMotor.run(FORWARD);
  leftMotor.run(BACKWARD);
}
 
void stop(){
  Serial.println("Stop");
  rightMotor.run(RELEASE);
  leftMotor.run(RELEASE);
  delay(500);
}
 
void turnAround(){
   slowToStop();
   delay(500);
   moveBackward();
   delay(2000);
   turnLeft();
   delay(2000);
   stop();
   //runAway = true;
}
 
void moveBackward(){
  Serial.println("Move Backward");
  rightMotor.run(RELEASE);
  leftMotor.run(RELEASE);
  rightMotor.run(BACKWARD);
  leftMotor.run(BACKWARD);
}
 
void averageReadings(){
  leftTotal -= leftReadings[index];               // subtract the last reading
  leftReadings[index] = analogRead(LEFTSENSOR); // read from the sensor
  leftTotal += leftReadings[index];               // add the reading to the total
 
  rightTotal -= rightReadings[index];               // subtract the last reading
  rightReadings[index] = analogRead(RIGHTSENSOR); // read from the sensor
  rightTotal += rightReadings[index];               // add the reading to the total
 
  index = (index + 1);                    // advance to the next index
 
  if (index &gt;= NUMREADINGS)               // if we're at the end of the array...
    index = 0;                            // ...wrap around to the beginning
 
  valLeft = leftTotal / NUMREADINGS;
  valRight = rightTotal / NUMREADINGS;         
 
  Serial.print(valLeft, DEC); // prints the left sensor value
  Serial.print(" | ");
  Serial.println(valRight, DEC); // prints the right sensor value
}

Oh MySQL, What will become of you?

May 22nd, 2009 by Chris

I’ve long been a fan of MySQL. We use it to power Event Clipboard, integratechange.com, and many of our other projects. I was concerned when MySQL was bought by Sun, but I thought, at least Sun has a history of embracing open source initiatives.

My one fear was that MySQL would lose direction and become a behemoth of epic proportions. In short, I thought it was going become Java (not to start a flame war, but I could never embrace Java). Now that Sun has been purchased by Oracle, I fear that my fears will be realized. I hope not, but this article in Infoworld, MySQL: Forked beyond repair? doesn’t help me feel better about the situation.

If the originators of MySQL are jumping ship and creating disparate derivatives, what will be the one source we can trust. I don’t necessarily trust Oracle to shepherd the open-source process, which would ultimately lend credibility to MySQL and engender a period of adoption by enterprises. From a purely bottom-line perspective, It doesn’t make sense for Oracle to promote something which naturally competes with it’s already profitable offerings.

If indeed, MySQL is being cast out into the wilderness of so many open source products, it may have to wander the desert for a little while as the most prolific contributors coalesce around an authoritative source of code. This may be an opening for a new company to come in and fill the gap that has been left by MySQL’s changing hands, or Oracle may do what we all hope and keep MySQL’s vibrant community going strong. I think it will end up in the wilderness for some time and a new source will come out on top.

For the time being, we may not see any real changes that affect us, but it may be time to start dusting of the PostgresSQL skills.

And he has the video to prove it. What a fantastic use of interactive software and augmented reality.

levelHead v1.0, 3 cube speed-run (spoiler!) from Julian Oliver on Vimeo.

Google Chrome Videos

April 30th, 2009 by Chris

I’m not a google chrome user because I’m on a Mac, but I can appreciate some interesting advertising videos when I see them.
Check it out.

Saddest Code of the Week

April 28th, 2009 by Chris

I’m helping a client debug a legacy application and found this wonderful piece of code. The worst part is that it wasn’t written in 1980. This is from a .NET application (not VB6), and it has made me very sad.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
        Public Overridable Sub page_init()
            Dim num3 As Integer
            Dim exception As Exception
            Dim num4 As Integer
            Dim num5 As Integer
            Try 
            Label_0000:
                num3 = 0
                Me.whichlevel(0) = "System User"
            Label_0010:
                num3 = 1
                Me.whichlevel(1) = "Administrator"
            Label_0020:
                num3 = 2
                Me.usrid = Me.getcookie("xlaANLadmin", "userid")
            Label_0039:
                num3 = 3
                Me.connectionstr = ConfigurationSettings.AppSettings.Item("xlaANLconnectionstr")
            Label_0051:
                num3 = 4
                If (StringType.StrCmp(Me.Request.QueryString.Item("qut"), "", False) = 0) Then
                    goto Label_02AB
                End If
            Label_007A:
                ProjectData.ClearProjectError
                num5 = 1
            Label_0082:
                num3 = 6
                Dim str3 As String = Strings.UCase(Strings.Replace(Me.appsettings.xla_id, "-", "", 1, -1, 0))
            Label_00A8:
                num3 = 7
                Dim str As String = Strings.Mid(str3, &H10, (Strings.Len(str3) - 15))
            Label_00BD:
                num3 = 8
                str3 = Strings.Left(str3, 15)
            Label_00C9:
                num3 = 9
                Dim num As Integer = 1
            Label_00CF:
                num3 = 10
                Dim str2 As String = ""
            Label_00D9:
                num3 = 11
                Dim str4 As String = ""
            Label_00E4:
                num3 = 12
                Dim num6 As Integer = Strings.Len(str3)
                Dim num2 As Integer = 1
                goto Label_0150
            Label_00F5:
                num3 = 13
                num = (num + 4)
            Label_00FD:
                num3 = 14
                If (num <= 15) Then
                    goto Label_010F
                End If
            Label_0106:
                num3 = 15
                num = (num - 15)
            Label_010F:
                num3 = &H10
                str4 = Strings.Mid(str3, num, 1)
            Label_011D:
                num3 = &H11
                If Information.IsNumeric(str4) Then
                    goto Label_0156
                End If
            Label_012C:
                num3 = &H13
                str2 = (str2 & StringType.FromInteger((Strings.Asc(str4) - &H41)))
            Label_0146:
                num3 = 20
                num2 += 1
            Label_0150:
                If (num2 <= num6) Then
                    goto Label_00F5
                End If
            Label_0156:
                num3 = &H15
                Me.Response.Write("ANL5.0.NET-2005.04.31<br>")
            Label_016A:
                num3 = &H16
                Me.Response.Write(String.Concat(New String() { "Licensed to : ", Me.appsettings.license, " (", str2, ") ", str }))
            Label_01BC:
                num3 = &H17
                Me.Response.Write("")
            Label_01D0:
                num3 = &H18
                Me.Response.End
                goto Label_02AB
            Label_01E4:
                num4 = 0
                Select Case (num4 + 1)
                    Case 0
                        goto Label_0000
                    Case 1
                        goto Label_0010
                    Case 2
                        goto Label_0020
                    Case 3
                        goto Label_0039
                    Case 4
                        goto Label_0051
                    Case 5
                        goto Label_007A
                    Case 6
                        goto Label_0082
                    Case 7
                        goto Label_00A8
                    Case 8
                        goto Label_00BD
                    Case 9
                        goto Label_00C9
                    Case 10
                        goto Label_00CF
                    Case 11
                        goto Label_00D9
                    Case 12
                        goto Label_00E4
                    Case 13
                        goto Label_00F5
                    Case 14
                        goto Label_00FD
                    Case 15
                        goto Label_0106
                    Case &H10
                        goto Label_010F
                    Case &H11
                        goto Label_011D
                    Case &H12
                        goto Label_012A
                    Case &H13
                        goto Label_012C
                    Case 20
                        goto Label_0146
                    Case &H15
                        goto Label_0156
                    Case &H16
                        goto Label_016A
                    Case &H17
                        goto Label_01BC
                    Case &H18
                        goto Label_01D0
                    Case &H19, &H1A
                        goto Label_02AB
                End Select
            Catch obj1 As Object When (?)
                Dim exception1 As Exception = DirectCast(obj1, Exception)
                ProjectData.SetProjectError(exception1)
                exception = exception1
                If (num4 = 0) Then
                    num4 = num3
                    Select Case num5
                        Case 1
                            goto Label_01E4
                    End Select
                    Throw
                End If
            End Try
            Throw exception
        Label_02AB:
            If (num4 <> 0) Then
                ProjectData.ClearProjectError
            End If
        End Sub

Open-source Software Myths

April 24th, 2009 by Chris

I am an advocate of open-source software. Indeed, we deploy (almost exclusively of late) many different open-source software solutions for our clients and our own projects. But I often run into issues when talking about open-source software with clients, and it sounds a little something like this:

Client: Isn’t there an open-source product that performs that function?

Me: There are a couple of products that perform the same operation, yes.

Client: Well, why do I have to pay for something that’s already been done and is open-source?

And so it goes. If you’ve ever developed software using open-source solutions (and my guess is you have), you’ve undoubtedly run into the same tortured logic around the myths of open-source software.

Open-source software is free.
Sure, you can download them for free, install them for free, and deploy them for free, but thats about all you can do…for free. Once you need to work them into a cohesive project, they start to cost you time or money. It’s true that good open-source solutions can lower your costs. For example, the restful_authentication plugin from Rick Olson makes it quick and easy to implement an authentication function in a Ruby on Rails, but restful_authentication alone does not make a complete system. It’s always part of a larger application and using the plugin saves the developers time and the client money, but it must be integrated, and that will always cost time or money.

Open-source software is the complete solution.
This is rarely the case. Even when you deploy a Wordpress site, you still need to create the theme and customize the installation (even slightly) and that’s going to cost money. It will definitely save you a ton of development costs to launch a Wordpress site instead of rolling your own CMS (I know some people debate whether or not Wordpress is a true CMS), but it still costs money. This fact is exaggerated even more greatly when you’re building a custom application in Ruby on Rails or CakePHP. There are literally thousands (maybe hundreds, I’ve never counted) of open-source plugins available for both frameworks, but they solve particular problems and hardly ever make up a complete application.

Open-source software is high quality.
This couldn’t be further from the truth. In fact, I would argue that high-quality open-source software is far more rare than people think. Any Tom, Dick, or Lisa can create an open-source project and release into the wild. It doesn’t have to be tested, and it doesn’t even have to work. To declare something open-source, all some has to do is say it is so (like we did, but PopCrit is well tested). No testing is necessary. While I do agree that the community votes with their feet, they can’t vote on everything so there are a lot of unknowns when using an open-source solution in a project. Choosing one solution over another could be the worst decision in the entire project, and cost you and the client even more time or money.

There are probably even more myths, but I think these are the most pervasive and caustic for custom software developers. I am a true believer in open-source, but not for the reason that they save money or effort. I believe in open-source because, every once and while, the projects can change the direction of entire industries (MySQL), and from time to time, they save me time and energy.

-Chris

Google Releases Analytics API

April 22nd, 2009 by Chris

Check out the documentation: http://code.google.com/apis/analytics/docs/

Safari 4 Beta vs Firefox 3

April 9th, 2009 by Chris

I’ve been using the Safari 4 Beta for several weeks as a Firefox 3 replacement for my everyday browsing needs, and I’ve been, for the most part pleasantly surprised. I wasn’t always the biggest Safari fan, but when I saw some of the new features and the promised speed, I couldn’t help but make it my default browser for a try out. Here’s my opinion of the experience, for what it’s worth. And yes, I know it’s unfair to do a comparison on a beta product but I’m doing it anyway.

Tabs

Safari Tabs
Safari Tabs

Firefox Tabs
Firefox Tabs

There has been much hubbub around the internets about the tabs in the new Safari. I don’t mind the positioning of the tabs too much, but I do have a problem with the way they function, and because this is a comparison between Safari and Firefox, I’ll compare. In Firefox, you click on a tab and drag it to the position you want. In Safari, if you click and drag the tab, you drag the whole window. If you want to move the tab, you have to click the upper right corner and then drag. It’s not that big a deal, but reducing the clickable area on the tab for (I think) my most common practice of grouping tabs infuriates me to no end. In that way, I think the new tab placement unfortunately misses the user’s expectations of how a tab functions. Firefox does what I expect of it when it comes to tabs, and that’s all I really ask for.

Winner: Firefox

Instant Gratification

Top Sites
Top Sites

Fast Dial
Fast Dial

Safari has implemented the Top Sites feature, which I love very much. It’s a pleasant way to begin your browsing day, and it often helps me find what I’m looking for faster than simply remembering where that API doc was. I have a couple of problems with it that I hope will be ironed out in the production release of the browser. One: Just because I tried a hundred times to buy tickets to The Dead Weather show on TicketMaster, doesn’t mean it’s one of my top sites. Two: This really isn’t a criticism, but can the Top Sites feature give me some of my time back. Every time I open a tab, it’s there telling me what I’m missing on other sites I view and it forces me to click through taking whole chunks of time from me that I will never, ever get back. Top Sites feature, you are addictive.

In Firefox, you can approximate the Top Sites functions with the Fast Dial add-on (a la Opera). It requires manual configuration and the site list never changes from the ones you select. This is a bigger productivity boost than Top Sites in Safari. I can keep my important sites always at hand and that helps keep me focused. That said, Fast Dial is nowhere near as sexy as Top Sites.

Winner: Safari, but only because it’s prettier.

History

Safari History
Safari History

Firefox History
Firefox History

This is by far the killer feature of the Safari 4 Beta. What would happen if you took Mac’s incredible cover flow navigation and used it do something useful. Well, you’d have the new history search for Safari 4. To those who know me, I don’t like cover flow other than for it’s sexiness. I think it is a feature best avoided for file and music navigation. However, when it comes to navigating your web history, I can think of no better way than a visual reminder of what the page looked like.

History in Firefox is dealt the same old tired manner that has always been. A long list of stuff you’ve seen. Most of the time I can’t remember what the title of the article was, but I can remember the color of the header. If only there was a way to see what I saw when I saw it. And there is, just not in Firefox.

Winner: Safari

Speed and Stability

Apple has made much about Safari being the fastest web browser, and there are times where I can see a notable difference between Firefox and Safari load time. Most of the time, it’s inconsequential at best. I will say this though (and I hope this will be fixed in the production release), Safari will sometimes just hang…forever. And then you have refresh the page a couple of times to get the site you’re looking at loaded. It can be very frustrating to say the least, but it is in Beta so I’m giving Safari leeway on this one.

Firefox on the other hand is old reliable. It will sometimes load a page a little more slowly than Safari, but not slow enough that it bothers me. I have run into some issues with Firefox completely spazzing out and borking my router…especially on Google maps. I’m not going to pretend to understand why Firefox spazzes out on my Mac while I’m viewing Google Maps and why in turn I need to restart my router after it does so, but that problem alone caused me to move to Safari as my default browser.

Winner: Right now, it’s a tie and I hope Safari can fix these issues (God knows I’ve sent enough bug reports to Apple about it).

Developing Web Sites

Safari Activity
Safari Activity

Firefox Firebug
Firefox Firebug

We develop web applications at Integrate so we spend all day in a browser. Obviously, we check compatibility on all major browsers so we are, by our nature, browser agnostic when it comes to development. However, nothing can quite compare to the Firebug and Web Developer add-ons for Firefox. If you haven’t started using them yet, shame on you. While I do love the Activity panel in Safari, if I’m working on a site, 9 times out of 10, I’ll be working on it in Firefox.

Winner: Firefox

My Final Two Cents

There are a lot of things I didn’t talk about (security being one of them), but most of what I concern myself with on a day to day is usability. For that I’m going to give it to Safari 4. I would definitely have chosen Firefox when compared to Safari 3, but the History browsing alone is enough for me. If you haven’t already downloaded the Safari 4 Beta, I’d do it if I were you. You won’t be sorry. Don’t worry, Firefox will keep chugging along right next to Safari in your dock.

- Chris

We have plans to release several plugins to the open source community. PopCrit is the first of these. PopCrit grew out of our need to create rails conditions dynamically at run-time, usually coupled with a form like the one below.

Dynamic Search

To cope with creating rails conditions, I’m sure you’ve had to do or seen this done before:

1
2
3
4
if @f_name.to_s.length != 0
  crit += " AND first_name like ?"
  crit_values += ["#{@f_name}%"]
end

Multiply that by 10 fields in a robust dynamic query form and your code begins to smell. We hate it too, so we created the PopCrit plugin (previously a simple library in our applications) to manage those nasty dynamic queries users love so much.

With the PopCrit plugin, you can simply use the new process_criteria_params method provided. It will take your forms parameters and convert it into Rails loving conditions. Now you just have to do the following:

@cards = Infocard.find(:all, :conditions=>process_criteria_params(params[:dynamic]))

There’s more to it, so feel free to check out our repository at http://github.com/chazlett/popcrit/tree/master.

Or install it, play around, and feel free to contribute.

script/plugin install git://github.com/chazlett/popcrit.git

-Chris

Add Git branch name to your Bash Prompt

March 25th, 2009 by Chris

This is great timesaver for those of you who use Git (and it’s easy branching). This little bit of shell script magic will display the current git branch in green. Thanks to ‘has_many :beers’ for this great bash customization.
Check it out.

Event Clipboard