Posts tagged ruby

Feb 23, 2010

Setting up watchr and Rails

ruby rails watchr autotest | comments

Autotest is a great idea. When it worked for me, I loved it. But I hardly ever used it. Our complicated codebase choked on it - something to do with accounts as subdomains or double-loading of test_helper when running the test suite or some such. Frankly, I don’t remember the details, but I do remember the multiple two-hour wild goose chases to try to get the lovely software to work for me. Unfortunately I wasn’t up to the task.

And the Growl integration problems? Oi!

So this afternoon I decided to give watchr a try. I wasn’t hopeful and time-boxed myself at an hour. No rat hole for me, no sir.

Imagine my surprise when 45 minutes later I not only had watchr working, but I placed it just so in my environment. I could use my script on any Rails app on my system with full growl notification and more!

Automatic testing with Rails

The steps for me to setup watchr were fairly straightforward. On the command line (your symlink will vary, natch):

gem install watchr
ln -s /Users/barry/.gem/ruby/1.8/gems/watchr-0.6/bin/watchr /usr/local/bin/watchr

I am using this Rails watchr script - I haven’t modified it as of yet. I placed it in ~/.watchr/rails.rb.

I placed the following two images into ~/.watchr_images/passed.png and ~/.watchr_images/failed.png respectively:

Rails tests passed

Rails tests failed

To run watchr on my Rails app, from the code directory:

watchr ~/.watchr/rails.rb

To make this less super-annoying I set a bash alias:

alias watch="watchr ~/.watchr/rails.rb"  # default is Rails cause that's my most frequent workspace

So tomorrow morning I’ll open a terminal window and type:

watch

Life is good!

(My watchr links at Delicious.)


Feb 17, 2010

Co-op Capistrano deployment script

Co-op Harvest capistrano ruby | comments

Yesterday at Harvest we launched a little API-accessible robot for our team communication app, Co-op. There are many uses for this API hook, from deploy notices to code commits to fun daily team affirmations. You are only limited by your imagination!

Our first use of the Cobot was to get deployment messages right in our Co-op workstream, including git commit messages. This is what we see today:

Archive: Tuesday, Feb 16 at Team Iridesco @ Harvest Co-op

And our Capistrano deployment script (hat tip to John Nunemaker for the idea):

namespace :co_op do
  desc "Update Co-op with most recent deployment messages."
  task :update do
    require 'net/http'
    require 'base64'
    require 'cgi'
    require 'json'

    app = "Harvest"
    key = "withheld_for_your_protection"

    headers = {
      "Accept"        => "application/json",
      "Content-Type"  => "application/json; charset=utf-8",
      "User-Agent"    => "Co-op Deployment"
    }

    begin
      connection = Net::HTTP.new("coopapp.com", 80)
      connection.open_timeout = 10
      logs = (`git log #{previous_revision}..#{current_revision}`).
               scan(/\n\s\s\s.+\n(?:\n|$)?/).
               map{|l| l.strip}.
               reject {|t| t.match(/^Merge branch \'/)}
      logs_size = logs.size
      logs = logs[0,6]
      logs << '...' if logs_size > 6
      msg = "- #{logs.join('<br />- ')}"
      who_am_i = `whoami`.chomp.capitalize
      connection.post('/groups/5/notes', 
                      {:status => "#{who_am_i} deployed #{app}: " + 
                                  "<br />#{msg}", 
                       :key => key}.to_json, 
                      headers)
      puts 'Deploy notice sent to Co-op'
    rescue Timeout::Error => e
      puts "Timeout after 10s: Seems like Co-op missed your update."
      puts "Use \"cap co_op:update\" to update Co-op " +
           "status later w/o deploying"
    end
  end
end

after  "deploy", "co_op:update"

Jul 14, 2009

So you're motivated by a tech conference. Now what?

blogging conference futureruby motivation oss technology | comments

Den Heinemeier Hansson
 
Photo by _heycarsten

Attending a great tech conference is a singularly motivating experience. If you have been to a few conferences you realize the motivation wanes alarmingly fast once you sit down in that old office chair and get back to work. While disturbing, this is a natural experience. No matter how swell your work environment, it is very difficult to recapture that “all things are possible with code” vibe the conference provided. Better to admit this and deal with it.

The most difficult conference lightning for me to bottle is the motivation for self improvement. I walk away from a conference wanting nebulous things like “build a software as a service site”, “build a mobile application” or “contribute more to open source.” I honestly get depressed when time and again I fail to execute on these goals. My hyper-motivated attitude sets me up for failure.

First, get the most out of attending the conference

Working the conference floor has been discussed frequently. I’m just going to bullet point these things and get to the meat.

  • Be prepared to talk about yourself
  • Be prepared to talk about a variety of other people and companies
  • Be prepared to be uncomfortable
  • If you are an introvert, head to a conference room and strike up a conversation with someone sitting alone. Avoid people making love to their laptops.
  • Be prepared to ask someone to lunch
  • Take notes, like with pen and paper. Put down the laptop from time to time.

Things I like to note down:

  • Information pulled directly from the talk
  • Unrelated ideas that pop into my head during the talk
  • Audience reactions to the talk
  • Good and bad aspects of the presentation and presenter
  • Information about people I meet

Capture conference motivation by reliving it

A conference wears you out, particularly if it has very good, or very bad, parties. When you get home, get some sleep, eliminate the pile of emails, and then get some more sleep. Then sit down and process the information you gathered and emotions you experienced during the conference. Plan to do this within a few days of returning from the conference, or you simply will not do it. After a week, you probably will not have much ingrained recollection of the event save for a general label of “it was great” or “it was OK”. (Note: The less great the conference, the more quickly you will complete this process.)

The simplest bits to capture from a conference are to-do items like books to read, software to trial, etc. Just get them in your to-do listing app of choice. Put them near the top as they are currently as relevant as they will ever be.

Follow up with folks you met. Don’t be a networking whore; get in touch with people you truly connected with. Follow those people you met in passing on Twitter, if you must.

Share your thoughts about the conference. Consider not only informational aspects of the event, but also the emotion of both you and the audience. Maybe this is a blog post, though I prefer to share the info with colleagues or journal it for my own benefit. My thoughts are usually too personal and scattered for an interesting blog post.

Let your experience generate something useful to share with others. For me this is typically when I put together a blog post. I might write about a single aspect of the conference itself, or more likely I’ll write on a subject I was motivated to research. You may wish to pay it forward by working on an OSS project or putting together a presentation for your local users group. It is entirely possible your reflections will generate multiple projects with which you can put your motivation into action.

Rather than set an impractical dream as a goal, be motivated by bringing the things that opened your eyes to others. Through blog posts, software contributions and local presentations you will explore your own ability to learn and you may just inspire the next guy. And everyone’s dream will become a little more possible.


Jan 19, 2009

Rails date calculations could stand some timezone love

programming rails ruby | comments

New Rails handles a lot of timezone stuff for you. Set the appropriate time zone for the request and get an object from the DB, it’s created_at date will be translated from UTC to the request’s time zone.

Rails doesn’t do anything with the Ruby enhancements that create time objects from dates. Like:

Date.today.end_of_day

That’ll be in the server’s time zone. Ick.

I needed to find the beginning of a day as related to the request’s timezone. I moved the fix into some date extensions:

class ::Date
  def beginning_of_day_in_zone
    Time.zone.parse(self.to_s)
  end
  alias_method :at_beginning_of_day_in_zone, :beginning_of_day_in_zone
  alias_method :midnight_in_zone, :beginning_of_day_in_zone
  alias_method :at_midnight_in_zone, :beginning_of_day_in_zone

  def end_of_day_in_zone
    Time.zone.parse((self+1).to_s) - 1
  end
end

So now we have:

Time.zone = "Auckland"
=> "Auckland"
> Date.today.beginning_of_day
=> Tue Dec 09 00:00:00 -0600 2008
> Date.today.beginning_of_day_in_zone
=> Tue, 09 Dec 2008 00:00:00 NZDT +13:00

Realistically, I think Rails could be improved in this area. Or maybe I’m missing out on a better way to take a date object and make it zone-aware for generating related times. I spent more than an hour reading through the Rails date and time extensions and realized I need to keep my timezone pain relegated to the work week. For the time being, the above is a nice crutch for my current needs.


Nov 11, 2008

Announcing time warp ruby gem

change time gem rails ruby time manipulation time warp | comments

When writing tests, it is often desirable to bend time to test limits and edges of the day. It is especially useful to warp time to test results across the timezones of the world. Manipulating time is also useful to assure a day of the week, month or year every time the test runs.

Some may say “Why not just mock Time#now?” I see the point, but I find mocking around with baseline Ruby classes to be asking for trouble. Eventually unusual behavior will rear its head and a day will be lost debugging tests - the most excruciating debugging one can be subjected to.

For all of your time manipulation needs, give time warp a try in gem or plugin form.


Oct 16, 2008

Fading flash message

flash message programming rails ruby | comments

Rails apps love flash messages. Little notes providing information, confirmation or warnings to the user. Typically implemented in a partial like so:

<% if flash[:warning] -%>
   <div id='warning'><%= flash[:warning] -%></div>
<% end -%>
<% if flash[:notice] -%>
  <div id='notice'><%= flash[:notice] -%></div>
<% end -%>

There are many cases where the message does not need to stick around for long. Consider a page with lots of AJAX interactions. Perhaps the user sends a message, which causes a page reload with a flash confirmation note (e.g. “You’re message was sent.”). After this the user will remain on the page for a relatively long time, maybe inline editing some properties or settings with AJAX tools.

It sure would be nice if that flash message would fade into the sunset after a few seconds and give back its valuable real estate, yes? Especially without forcing every flash message to disappear like Bobby Fischer.

Ask and receive, my friends. Introducing the fading_flash_message method, to be added to your nearest ApplicationController:

def fading_flash_message(text, seconds=3)
  text +
    <<-EOJS
      <script type='text/javascript'>
        Event.observe(window, 'load',function() { 
          setTimeout(function() {
            message_id = $('notice') ? 'notice' : 'warning';
            new Effect.Fade(message_id);
          }, #{seconds*1000});
        }, false);
      </script>
    EOJS
end

Setting a fading flash message in your controller is simple:

flash[:notice] = fading_flash_message("Thank you for your message.", 5)

This is change we can believe in, my friends.

Update

For Rails 3, this still works with a simple inclusion of the RawOutputHelper.

include ActionView::Helpers::RawOutputHelper
def fading_flash_message(text, seconds=3)
  raw text +
    <<-EOJS
      <script type='text/javascript'>
        Event.observe(window, 'load',function() { 
          setTimeout(function() {
            message_id = $('notice') ? 'notice' : 'warning';
            new Effect.Fade(message_id);
          }, #{seconds*1000});
        }, false);
      </script>
    EOJS
end

Aug 29, 2008

Windy City Rails

conference rails ruby | comments

I am all registered for Windy City Rails, taking place in Chicago on September 20th. Looking forward to a shorter conference, along with an interesting drive with fellow Minnesota Rubyists. Come join us - only $99!


Aug 21, 2008

Blogiversaire

programming rails ruby | comments

Four years baby. Soon the blog will be toting a lunch pail and school bag, off to decades of education. Next thing I know, my little baby will be married and have little blogs of its own. Blog, you grow up so fast!

Top visited blog posts written in the past year:

As you can see, the popularity of this blog is directly related to what problems I’ve had with Ruby on Rails and people who search Google for the same issues. This is kind of unfortunate in some ways. It makes the blog pretty much narrative-free. But I’m still happy to have helped those who needed it.

While 2008‘s posts have been much more rare, I think there are some gems in there. Going forward, I hope to include a mix of posts explaining a solved problem, displaying a new technique, and generally talking about the unique work life I live with day-to-day.


Jul 22, 2008

Post Redirect Get in Rails

post/redirect/get rails ruby | comments

Post St.
 
Photo by Terry Bain

Post/Redirect/Get (PRG) is a web application design pattern used to supply users with nice, GET-generated results pages that bookmark and reload with ease.

Say you have a results page with a filtering form at the top. The user might alter filtering options, and submit a form to winnow down her results. This is a POST, meaning params will be hidden from the user (not in the URL), making direct bookmarks to the filtered results impossible. Also, reloading the page will result in a “POSTDATA” warning message (“Do you want to resubmit POST data or would you rather we do a hurkey-jerky dance to confuse you?”) to the user. These are not things people like to deal with. Post/Redirect/Get says that your application should accept the POST command, and redirect to the results page using a GET request.

This is really quite simple to implement in Rails. Take this example action filter for an expense report controller, reduced to its bare essentials:

class ExpenseReportsController < ApplicationController
  def filtered
    redirect_to fitered_expense_report_path(params) and return if request.post?
    
    # Actual action work here.
  end
end

Very easy - the trick is really knowing the pattern more than anything. There has been some discussion on adapting this to deal with validation failures. In my experience, I’ve only made use of this pattern in non-RESTful controllers. You are sure to do better with it than I.


Jul 15, 2008

Fixing slow rake on Leopard

mysql rails rake ruby ruby on rails | comments

Hay Rake Third Version
 
Photo by JBAT

Update: I highly recommend going with Ruby Enterprise Edition and Evan Weaver's Ruby GC settings for much improved Ruby speed.

In the past few months, as a result of my clumsiness, I’ve been working on a new, Leopard-enabled MacBook. When replacing my wet and dying Tiger MacBook of yore, my one hope was that my daily executions of rake to test Harvest would execute a little faster. A 20% increase in processor speed doesn’t buy one a whole lot when web programming. The one place I figured I’d see a bump is the constant processor use of rake.

Wrong. So, so, wrong.

After moving to a new MacBook with Leopard installed, I experienced a test suite that ran 2-3 times slower than on my old machine. This became the difference between testing frequently and hardly at all; the difference between using and not using autotest. Pain to the max.

This post is going to get long, so I’m going to reveal now. For me, the issue was resolved by leaving the canonical Hivelogic world and reverting to the default Leopard installation of Ruby. Test speed returned to better-than-normal, and all at the price of only 6 hours of troubleshooting!

MySQL: Like shooting fish in a Superior-sized barrel

As always, my first suspicion was MySQL. I looked through my local Rails logs for particularly slow calls into MySQL. The initial thought was that a few bad-apple tests were not tuned very well for my environment. What I found was everything was slow and there were too many noticeably poor speeds to point to a few SQL calls as the culprits.

Next I compared my my.cnf file to my compadre‘s. I noticed no major smells in my file. Since I’m a DB dummy, more important was Dee’s estimation that indeed my configuration file looked alright. We even went so far as comparing MySQL variables from within the MySQL shell:

mysql> show variables;

Finally, I upgraded MySQL from 5.0.51 to 5.0.51b and reinstalled the ruby-mysql gem. No dice.

Sanity check: Benchmark the MacBook

Dee suggested I verify that my new MacBook itself wasn’t a lemon. I was doubtful since I’d noticed no other areas where it performed worse than its predecessor. Still, this was a great place to regroup. Benchmarking each MacBook put my mind in the right place to finish off my troubleshooting and uncover my problem.

I used Xbench to compare the general performance of my new and old MacBooks. Benchmark results favored the new machine everywhere except OpenGL. After reading the FAQ, it’s pretty clear OpenGL is a false positive for the old MacBook.

Rails: A general comparison between two machines

So I knew Rails was running slow when hitting the DB, but what about all by itself? I simply ran rake environment on both laptops and took a mental count. The command took ~3 seconds on the old machine and 6-7 seconds on the new one.

This was really enough, but I did run some model finders in each environment as a sort of sanity check. Performance results were in line with everything you’ve read so far.

Ruby: ActiveRecord-less and slow as ever

To hit the real meat-and-potatoes, I executed some Ruby-only code on both systems, benchmarking the whole way. Old Tiger:

$ ruby script/performance/benchmarker "(1..100).inject(1){|sum, n| sum*n}" 
            user     system      total        real
#1      0.000000   0.000000   0.000000 (  0.000217)

New Leopard:

$ ruby script/performance/benchmarker "(1..100).inject(1){|sum, n| sum*n}" 
            user     system      total        real
#1      0.000000   0.000000   0.000000 (  0.001347)

Over six times slower. New shit has come to light!

Sanity check II: Another system test

I wanted to run something from the command line that proved the new hotness was outperforming the old hoopty. Enter a little shell script built to copy an empty file to and from a folder 10,000 times:

#!/bin/sh
echo "start at `date +%s`" 
touch ./file.txt
mkdir ./fileholder

LIMIT=1000

for ((a=1; a <= LIMIT ; a++))
do
  mv ./file.txt fileholder/
  mv ./fileholder/file.txt ./
done

rm ./file.txt
rm -rf ./fileholder

echo "end at `date +%s`" 
exit 0

Rejoice! The new machine took 32 seconds to execute. The old one executed in 78 seconds. The winnowing process was imperfect, but I believe a winner has been crowned: Ruby.

Ruby: From source to simple

I pwnd Ruby
 
Photo by tychay

Both machines in question were on Ruby 1.8.6. The new one on patchlevel 114, the old one on 0. I attempted to install Ruby from source for patchlevel 230 (ACK!) and 111. No rake performance improvements were achieved. I also own a Leopard Mac Mini, installed from the Hivelogic instructions, and experienced the same rake downfalls (Sanity check III). Time to try Leopard’s default Ruby (patchlevel 114).

I did not want to remove /usr/local/bin from my path because I have other wonderful things installed there. Advisable or not, I simply renamed key binaries:

mv /usr/local/bin/ruby /usr/local/bin/ruby.bak
mv /usr/local/bin/rake /usr/local/bin/rake.bak
mv /usr/local/bin/irb /usr/local/bin/irb.bak

From there, all my gems needed reinstalled. gem install failed on my first try, but worked thereafter. The ruby-mysql gem had special needs (assuming a Hivelogic-style install of MySQL), which were addressed with:

sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config

rake clean. Barry happy.

I can’t say I’m comfortable browsing my filesystem for gem source like I was with /usr/local, but I imagine that will come with time. My suspicion is the Hivelogic instructions can still work in Leopard given the appropriate compilation switches. I’m not the person to provide such information - perhaps someone already has?


Feb 28, 2008

Move server configuration files into your repository

capistrano rails ruby scm svn Ruby Rails SVN SCM Capistrano | comments

Dung repository
 
Photo by Chambo25

Are you a programmer? Do you hate any process that involves SSH’ing into a server and editing files directly in production? Would you like to get rid of this non-source controlled hackery? Damn right you would!

If you’re deploying an application to a server, get those app-specific configuration files in your source control system! I happen to be a Ruby on Rails programmer, so the two files I’m looking to control are the Apache and Mongrel configuration files. Here are two sample locations:

Maruku could not parse this XML/HTML: 
<pre>/usr/local/apache2/conf/apps/appname.conf
/etc/mongrel_cluster/appname.yml

Meat

Via terminal, browse into your local Rails app directory. Then, create a couple directories to store this configuration:

mkdir ./config/apache
mkdir ./config/mongrel

Then copy the configuration files you have sitting on your server into the local directory (I’m using SCP and assuming you know the rest). Again, locally:

scp bjhess@server_or_ip:/usr/local/apache2/conf/apps/appname.conf ./config/apache/
scp bjhess@server_or_ip:/etc/mongrel_cluster/appname.yml ./config/mongrel/

Potatoes

Now you’re all ready to commit these configuration repositories to your SVN/git/mercurial repository. But wait, you also want to actually use these versions of the configuration files in production, no? Well, let’s do it. Rename the original configuration files on the remote server:

mv /usr/local/apache2/conf/apps/appname.conf /usr/local/apache2/conf/apps/appname.conf.bak
mv /etc/mongrel_cluster/appname.yml /etc/mongrel_cluster/appname.yml.bak

Then simply update your deployment script to create symlinks from the expected configuration file location to the new location. I’m using Capistrano (version OLD), so adjust accordingly:

desc "Tasks to complete after code update"
task :after_update_code do
  #            LINK TARGET                                   LINK SOURCE
  run "ln -nfs #{current_release}/config/apache/appname.conf /usr/local/apache2/conf/apps/appname.conf"
  run "ln -nfs #{current_release}/config/mongrel/appname.yml /etc/mongrel_cluster/appname.yml"
end

Conclusion

This is great, isn’t it? The programmer in you loves having limitless history of configuration changes, not to mention the ability to use whatever local editor you choose. Wonderful day!

But what about the whole-server configuration files? Unless you commit to one app per server, I don’t think base Apache config files or server-level logrotate config files should be in a specific app’s repository. Perhaps a new repository could be setup to deal with these files? I imagine Capistrano could even be used for the bare-bones deployment.

Or maybe just backing up these general files is enough to keep your mind at ease. Just be sure to think about it. That’ll put you one step ahead of the rest

Update: Be sure to deal with symlinks upon rollbacks, something I just got burnt by:

desc "Tasks to complete after code rollback"
task :after_rollback do
  run "ln -nfs #{previous_release}/config/apache/appname.conf /usr/local/apache2/conf/apps/appname.conf"
end

Jul 11, 2007

Automatically pull photos off your flash card with Ruby

ruby digital camera ruby digital camera | comments

For several years now, I’ve been using a VBA script on my Windows PC to pull photo files off my compact flash card, rename them, place them into handy folders, and copy them out to my photo storage area. When I switched to Mac, I knew the VBA script was going to be short lived. This weekend I finally laid it to rest.

The result I was looking for with this script is that all photo files on the compact flash card end up in a destination directory, placed in folders named YYYY-MM-DD. The files themselves would be named YYYY-MM-DD(hh.mm.ss). Some may prefer a deeper folder structure, like YYYY/MM/DD for instance. My script could be easily updated to do such a thing.

Since I’m now a Ruby on Rails coder, I figured this would be a great time to learn about using Ruby for scripting. I’ve always heard that Ruby is great for such a purposes, and it turns out that statement is very true.

Before I added the handy option parser to the Ruby script, there were only 18 lines to do what 61 lines of (poorly written) VBA code used to do. Actually, the Ruby script did even more than the original script would do. The original script could only copy from a single directory at a time. The Ruby script copies all photo files from all subdirectories. Basic usage is:

ruby copypics.rb ./source_directory/ ./destination_directory/

The way the script is written, those trailing ‘/’ characters are required. To ease the chance of typos, I suggest an alias (in OS X, add an alias to ~/.bash_profile). I have aliases for grabbing pictures from my card reader as well as The Kid’s kidd-o Fisher Price digital camera:

alias pics="ruby ~/code/copypics.rb /Volumes/Card_Reader/ photos_directory/"
alias picskid="ruby ~/code/copypics.rb /Volumes/Kid_Camera/ kid_photos_directory/"

Note: You’ll need to be explicit about the script location, source, and destination directories when defining your alias.

Using an excellent options parser, found at about.com of all places, the script is able to support the -v (–verbose) and -h (–help) flags. Verbose mode will print out the source and destination of all copied pictures. You would call the script with the verbose flag like so:

ruby copypics.rb -v ./source_directory/ ./destination_directory/

Source code:

copypics.rb getpics.vbs (shudder)