We’re sponsoring the Rails Rumble

Posted by Brian in News, Rails (August 27th, 2007)

The Rails Rumble (http://www.railsrumble.com/) is coming up. Flex your development muscles and create an application in 48 hours. Go it alone or get a team of four together to build something incredible. The contest takes place September 8th and 9th, 2007. Registration opens at 12:00 EDT on August 28th, 2007.

We’re giving away a 30GB Ipod choice of an 80GB iPod classic or an 8GB iPod Touch to the person who develops the best app all by themselves.

Rails Functional testing with Stubs and BDD

Posted by Brian in Rails, snacks, tips (July 26th, 2007)

One of the things that bothers many people about functional tests with Rails is the seemingly unnecessary duplication of test coverage.

Consider this test, a very common “create new user” functional test:

def test_create
   post :create, {:user=>{:username => "brian",
                         :password =>"1234", 
                         :password_confirmation=>"1234", 
                         :email=>"hoganbp@uwec.edu"}}
   assert assigns(:user)
   assert_redirected_to :action=>"list"
   
   
end
 

This is an example of test-driven development at the controller level. You pass in the POST parameters so that the controller can create the user object and save it to the database. This then triggers the redirect.

But this is really kinda bad. You already know that you can save records, you’ve got unit tests that prove that your validation works. You’re really interested in how the controller responds when it creates a valid record.

Behaviors

Behavioral driven development (BDD) is a technique receiving a lot of attention lately in the Rails community. The idea is to write your tests looking at the behavior you’re trying to test, as opposed to just testing methods.

Take a look at the behavior of the controller. What’s it supposed to do?

When a new user is created successfully, it should go to the list action.

So… let’s rename that scaffolded functional test to

   def test_should_redirect_to_list_when_user_is_created
 
   end

In BDD, you use words like should to specify what should happen. See, already your test is less ambiguous. Eventually you’re going to have to test that it “should_render_registration_form_when_creation_fails” or something like that.

Stubs

We already know from unit tests we wrote that we can save user records. We don’t need to test that again. Good tests don’t cover more than one piece of functionality anyway. How do we get around to it?

Enter http://rubyforge.org/projects/mocha/. Mocha is a mocking library. You can read more about it in the documentation, but I’ll show you how to use the stubbing features to bypass creating records.

Install mocha.

  gem install mocha

Open up your test/test_helper.rb and add

  require 'mocha'

to the top of the file.

This simple bit of code added to our test makes it all work:

   User.any_instance.stubs(:save).returns(true)

You would read that as “Any instance I create of User should have its save method always return true when I call it.”

That means our functional test becomes

def test_should_redirect_to_list_when_user_is_created
   User.any_instance.stubs(:save).returns(true)
   post :create
   assert_redirected_to :action=>"list"
   
end

Wrapping up

Stubbing helps you decouple your methods, and BDD helps you think about your process rather than your code. I encourage you to read more about both. Keep in mind you still want to test other aspects of your controllers, and sometimes you might want to ensure that certain things work certain ways. For example, you might still need @user to contain real data when you do searches so your views will render properly. This is just an example of the power of stubbing.

We’re doing audits too!

Posted by Brian in News (July 17th, 2007)

I’ve noticed that some other firms are starting to market this service, so we’re throwing our hat in the ring too. We’ve been doing this as part of our consulting for about a year now, helping a couple of teams learn how to write better code. This is the next logical step.

It never hurts to have a second set of eyes look over your code before you roll out that brand new application. I routinely have others look over my code before I release it because I know I’m going to miss something. Whether you hired out the project or did it yourself internally, you just might benefit from this service.

We’ll look for things like security vulnerabilities, code that’s not covered by tests, and help you identify trouble spots with your app long before your customers find them.

Contact us today to see how we can help.

Making it easier to install gems on Windows

Posted by Brian in Rails, snacks (July 12th, 2007)

When you install gems, you’re usually asked what version you want to install. Windows users have to choose the one that says ‘mswin32′. This can be really, really awkward for people who are new to this. You have to admit that this is comfusing for a first-time Rails on Windows user:

Select which gem to install for your platform (i386-mswin32)
 1. mongrel 1.0.1 (mswin32)
 2. mongrel 1.0.1 (ruby)
 3. mongrel 1.0 (mswin32)
 4. mongrel 1.0 (ruby)
 5. Skip this gem
 6. Cancel installation
>

I’ve had lots of students say that they thought they should choose the Ruby one because that’s what they’re programming in.

I found a great example at http://revolutiononrails.blogspot.com/2007/06/code-digest-2.html by Todd Fisher that explained how to skip the os and version selection on Linux, so I modified it slightly to work with Windows and I’ve added my own method to make installation of gems easier for Windows users.

Here’s how it works: You install gems via a new command on Windows to skip selection of OS and version number.
This will install the gem with dependencies, without documentation. I made the choice to skip doc generation because it’s much faster, but you can modify the script below to take that out, or even make it optional, based on parameters you pass in.

Examples of use:

   gem_install rails
   gem_install rails mongrel mongrel_service capistrano redcloth tzinfo zentest rcov

Here’s the source code for the file. Save this to c:\ruby\bin\gem_install.bat or anywhere on your PATH.

 
gem_install.bar source code:
 
@echo off
goto endofruby
 
#!/bin/ruby
# Cobbled together by Brian Hogan from scripts found at
# http://svn.bountysource.com/fishplate/scripts/debian_install.pl
# and http://revolutiononrails.blogspot.com/2007/06/code-digest-2.html by Todd Fisher.
# Many thanks to their hard work for their solutions that finally made this possible.
 
    require 'rubygems'
    Gem.manage_gems
 
    # Patch gems to only install the latest Windows version of the gems.
    Gem::RemoteInstaller.class_eval do
      
      alias_method :find_gem_to_install_without_ruby_only_platform, :find_gem_to_install
      
      def find_gem_to_install( gem_name, version_requirement, caches = nil )
        if caches # old version of rubygems used to pass a caches object
          caches.each {|k,v| caches[k].each { |name,spect| caches[k].remove_spec(name) unless spec.platform == 'ruby' } }
          find_gem_to_install_without_ruby_only_platform( gem_name, version_requirement, caches )
        else
          Gem::StreamUI.class_eval do
            
            alias_method :choose_from_list_without_choosing_ruby_only, :choose_from_list
            def choose_from_list( question, list )
              result = nil
              result_index = -1
              list.each_with_index do  |item,index|
                if item.match(/(mswin32)/)
                  result_index = index
                  result = item
                  break
                end
              end
              return [result, result_index]
            end
            
          end
          
          find_gem_to_install_without_ruby_only_platform( gem_name, version_requirement )
        
        end
      end
    
    end
    # end patching of Gems.
 
   
 
    # Install multiple gems with dependencies and without documentation.
    # pass in an array of gem names to install.
    #
    #   install_gems "mongrel", "redcloth", "mongrel_service", "tzinfo", "capistrano"
    #
    #
    def install_gems(*attrs)
        attrs.flatten!
        puts attrs.inspect
        attrs.each do |gem_name|
            puts "installing gem #{gem_name}"
            Gem:: GemRunner.new.run(['install', gem_name, '--include-dependencies', '--no-rdoc', '--no-ri'])
        end
    
    end
 
    
if ARGV[0] == "help"
  puts "Install gems via this tool on Windows to skip selection of OS and version number.  This will install the gem with dependencies, without documentation."
  puts "Examples:"
  puts "   gem_install rails"
  puts "   gem_install rails mongrel mongrel_service"
  puts "Report bugs to info@napcs.com ."
else
    install_gems ARGV
end
    
 
__END__
 
:endofruby
"ruby" -x "%~f0" %*
 

That’s it. It works great so far and I’m going to be embedding this into the Eclipse for Rails installation. You Mac users should have no trouble taking this concept to your platform. It just involves changing the regex from mswin32 to ruby in the Ruby portion of the script and then copying the Ruby code into its own .rb file that you mark executable.

I should say something about how the whole thing works. When you run the batch file, it skips over the Ruby code because of the goto command and then actually passes itself to the Ruby interpreter.

   ruby -x gem_install.bat rails mongrel

The additional parameters you send to the batch file are also passed along to the Ruby script as well. The Ruby interpreter ignores everything in the file until it gets to the shebang line (#!/bin/ruby). It starts executing code from this point on, but then it stops when it gets to __END__. Everything else in the file is ignored.

This method is how we Windows users get to do things like rake or gem or even rails Linux users can just set a Ruby script to be executable. Windows users have a little more trouble doing that so we cheat.

Before I go, I should say that I could not have done this without Curt Hibbs, who uses this batch-file-to-ruby approach in the One-Click Ruby Installer. He’s a swell guy.

If you’ve got comments, I’d love to hear them. Let me know what you think, or if there are ways I can improve this.

Using Ruby Blocks to make custom helpers in Rails

Posted by Brian in Rails, snacks (July 2nd, 2007)

A ruby block is one of the most powerful things about the language. I think it’s one of the most important concepts in the entire language.

To illustrate the point, I’ll explain blocks a bit by using an example we can all relate to.

Have you ever written code that looks like this?

<% if @documents.size > 0 %>
   <% documents.each do |@document| %>
      <p><%=@document.title %></p>
   <% end %>
<% else %>
   <p>There are no items to show.</p>
<% end %>

You check to see if any results are in the collection so you can display a nice friendly message to people saying
that you didn’t find anything.

We can use Ruby’s blocks to shorten that code and make it a bit more generic.

What are blocks?

Methods in Ruby can take arguments (or parameters, if you prefer),
but they can also take blocks of code. Ruby can then execute this
code you pass in within the scope of the method. That sounds pretty abstract,
but it’s nothing more than just allowing your code to be wrapped by some other code.

Using a helper that accepts your code as a block, you can do something like this in one of your views:

<% display_items_from @documents, "There are no documents to display" do |document| %>
   <p><%=link_to document, :action=>"show", :id=>document %></p>
<% end %>

We expect that this method will check the size of the @documents collection, and if it has results, it will loop over the collection of documents and then execute the block of code we pase in. If the collection of documents is empty, we will display the message.

The first iteration - Displaying the items

The code is actually really simple to implement. Let’s handle the easy case first… the case
where we actually have documents to show.

def display_items_from(collection, blank_message="There is nothing to display")
  if collection.size > 0
    collection.each do |item|
      yield item
    end
  end
end

This is where Ruby gets really abstract. That method declaration doesn’t mention anything about handling a block! It turns out that I can pass a block to any method, and Ruby will use it if I use the yield keyword. I don’t have to declare the block as a parameter in most cases.

When I do

  <% display_items_from @documents, "There are no documents to display" do |document|| %>
    <p><%=link_to document.title, :action=>"show", :id=>document %></p>
  <% end %>

in my view, I am passing document in as a local variable that the helper method can bind to.

Look at this section of code:

    collection.each do |item|
      yield item
    end

The yield statement is what does the actual output. I specified

    <p><%=link_to document, :action=>"show", :id=>document %></p>

so this will execute the block of code I passed in for each item in the collection,
binding item with my local variable document and using yield to execute the block of code I passed.

To reiterate, you use blocks in Ruby to run a bunch of code in the context and scope of another method.

The other case - Nothing to return

Rails has a few wonderful helper methods we can use to make this helper more useful. Right now, you can get the same result by using a partial with the :collection parameter.
But what we really want is a nice, clean way to iterate over our collection if it has items, and display a friendly message to our users if there were no items in the collection.

You’d think that would be easy… I could just do

def display_items_from(collection, blank_message="There is nothing to display")
 
  if collection.size > 0
    collection.each do |item|
         yield item
    end
  else
    blank_message
  end
 
end

There’s a slight problem with that approach though… I will never see that blank message on the screen anywhere. Remember, this type of
helper method is invoked using the regular ERb evaluation mechanism of <% .. %> and not the ERb output
mechanism (<%= .. %>). There’s nothing in our code that will output blank_message variable.

Ok, so what if I did this?

  else
    yield blank_message
  end

Would that work?

No, of course not. Yield operates on the stuff I passed in, so it’s going to try to call document.title and I’ll get an exception.

It turns out the solution requires the use of the concat method. The concat method takes two parameters: the string to output, and the block or proc to bind the output to.

  else
    concat(blank_message, block.binding)
  end

There’s just one small problem…. I’ll get an exception when I call this because
block was never defined. Remember when I said that you don’t have to declare
that you’re passing a block to a method in Ruby, and that it will ignore the block of code you pass if
the method doesn’t call for it? Well, that’s true, except in the case where you actually need to do
something with the block, like bind to it.

All I have to do is change the method declaration just slightly by adding
&block as the third parameter. The ampersand is what specifies the block,
and it absolutely must come last in the list of method arguments.

Our updated helper method now looks like this:

def display_items_from(collection, blank_message="There is nothing to display", &block)
  if collection.size > 0
    collection.each do |item|
         yield item
    end
  else
    concat(blank_message, block.binding)
  end
end

Handling Errors

We should always make sure that this method is called with a block. We can use the built-in method called block_given? which returns false if the developer
didn’t pass a block to the method.

    raise ArgumentError, "You need to provide a block." unless block_given?

You could make that more helpful by placing some nice instructions in that message.

Displaying a record count

If I wanted to display a count of the number of results you found in the output above the
code I passed, all I have to do is make
use of the concat method again, and place it above the yield statement.

    concat("<p>You have #{collection.size} items.</p>", block.binding)
    
    collection.each do |item|
         yield item
    end

Wrapping up

The final method looks like this:

 
def display_items_from(collection, blank_message="There is nothing to display", &block)
 
    raise ArgumentError, "You need to provide a block." unless block_given?
 
  if collection.size > 0
    concat("<p>You have #{collection.size} items.</p>", block.binding)
    
    collection.each do |item|
         yield item
    end
    
  else
    concat(blank_message, block.binding)
  end
 
end

Now that I’ve covered this in more detail, the wheels should be spinning in your head. Think about
all of the possible helpers you could write to reduce your view code?

  • Easily make a navigation bar of links
  • Create a helper to assist with the creation of rounded-corner areas
  • Come up with a neat mechanism to show admin-only content

I hope this was helpful. Don’t hesitate to ask questions in the comments. If there’s anything I can make more clear,
let me know and I’ll do what I can. I was extremely influenced by Bruce Williams and Marcel Molina Jr.’s presentation at RailsConf. You can read the
slides at http://www.codefluency.com/assets/2007/5/18/VisForVexing.pdf

ActiveMerchant and Authorize.Net

Posted by Brian in Rails, Howto, snacks (June 4th, 2007)

I’ve been working on payment processing with AuthorizeNet, following the documentation for the ActiveMerchant plugin, as well as an excellent book on the subject. However, I find that both sources really lack the true implementation details, so I figured I’d write it up here.

Step 1: Get a developer account.

Go get a developer account from Authorize.Net . They’ll ask you some questions and you’ll get an account in about 24 hours.

Step 2. Get your API login and transaction IDs.

This is really not documented well. ActiveMerchant’s docs all say to create a transaction with

   gateway = AuthorizeNetGateway.new({
       :login => "user",
       :password=>"password})

However, you don’t use the username and password for AuthorizeNet. You use a separate set of credentials..

1. Log into the Merchant Interface
2. Select Settings from the Main Menu
3. Click on API Login ID and Transaction Key in the Security section
4. Type in the answer to the secret question configured on setup
5. Click Submit

Then copy the credentials… you’ll need them later.

Step 3. Install ActiveMerchant

VIsit www.activemerchant.org for installation instructions.

Step 4. Add a configuration file

I suggest creating a file called “config.yml” in your config/ folder. It comes in handy for a lot of things. I’ll store the API login and transaction key in this file.

production:
  auth_net_user: asdfga
  auth_net_pass: 1234412355
development:
  auth_net_user: asdfga
  auth_net_pass: 1234412355
test:
  auth_net_user: asdfga
  auth_net_pass: 1234412355

Remember, that’s a yml file, so make sure you don’t use tabs, and be sure to get your spacing right.

Step 5. Reservation model

My particular application is a reservation system. Regular readers of my blog know I’m really into test-driven development and this is where I really found the docs lacking. Even the book I read, which pushes for TDD the whole way through, never once touched on how to test the Authorize.Net gateway.

I like unit tests a lot, and so rather than use the controller to do the payment processing, I use the model. I want to find a reservation that’s already been created, set the credit card details, and call a method called “process” that will return true if it worked and false if it doesn’t. The process method will set the status to 1 if it was successful.

 
def test_should_process_order_successfully
 
    r = Reservation.find 1
    r.status = 0  # want to make sure this has a "pending" status.
    r.step = "checkout"    # need to set this - validations for cc info run only if this is set
    r.customer_ip = '192.168.1.155'
    r.card_type = "Visa"
    r.card_number="4779139500118580"
    r.card_verification_value = "410"
    r.card_expiration_month = "10"
    r.card_expiration_year = "2008"
    r.billing_city = "Springfield"
    r.billing_state = "NT"
    r.billing_zip =" 54703"
    r.billing_address ="123 Fake Street"
    assert r.process
    assert_equal(1, r.status)
end

With that set, I can add the following code to my model.

#  id                     :integer(11)   not null, primary key
#  customer_email         :string(255)   
#  confirmation_number    :string(255)   
#  payment_transaction_id :string(255)   
#  created_at             :datetime      
#  confirmed_at           :datetime      
#  processed_at           :datetime      
#  cancelled_at           :datetime      
#  phone_number           :string(255)   
#  billing_address        :string(255)   
#  billing_city           :string(255)   
#  billing_state          :string(255)   
#  billing_zip            :string(255)   
#  user_id                :integer(11)   
#  status                 :integer(11)   default(0)
#  passengers             :integer(11)   
#  total_cost             :integer(10)   
#
class Reservation < ActiveRecord::Base
include ActiveMerchant::Billing
belongs_to :user
validates_presence_of :billing_address,
    :billing_city,
    :billing_state,
    :billing_zip,
    :customer_ip,
    :card_type,
    :card_verification_value, :card_number, :card_expiration_month, :card_expiration_year, 
    :if=> Proc.new{|record| record.step == "checkout"}
 
  # cc fields as accessors so we don't store anything bad.
  attr_accessor :card_type, :card_expiration_month, :card_expiration_year, :card_number, :card_verification_value, :step
 
  # Process the payment
  def process
    self.processed_at = Time.now
    begin
      process_payment
    rescue => e
      logger.error("reservation #{self.confirmation_number} failed with error message #{e} ")
      self.error_message = "#{e}"
      self.status = 7  #failed
    end
    save!
    self.status == 1
        
  end
    
  protected
 
    def process_payment
     
     # this forces the system to use the testing server, which is what all dev accounts use.
     ActiveMerchant::Billing::Base.mode = :test if RAILS_ENV != "production"
 
     # read api key and  transaction # from config file
    c = YAML::load(File.open("#{RAILS_ROOT}/config/config.yml")) 
    user = c[RAILS_ENV]['auth_net_user']
    pass = c[RAILS_ENV]['auth_net_pass']
 
     creditcard = ActiveMerchant::Billing::CreditCard.new(
       :first_name => self.user.first_name,
       :last_name => self.user.last_name,
       :number=> self.card_number,
       :verification_value =>card_verification_value,
       :type => self.card_type,
       :month => self.card_expiration_month,
       :year => self.card_expiration_year
     )
     
     if creditcard.valid?
         options = {:name => self.user.full_name,
            :email => self.user.email,
            :phone => self.phone_number,
            :ip => self.customer_ip,
        :card_code => self.card_verification_value,
             :order_id => self.confirmation_number,
        :description => "Conference reservation",
             :billing_address=>{
               :address1 => self.billing_address,
               :city => self.billing_city,
               :state => self.billing_state,
               :zip => self.billing_zip,
               :country => "US"}
             }
          
         }
          
     
                      
              gateway = AuthorizeNetGateway.new({:login => user, :password=>pass})
           
           
           response = gateway.purchase(self.total_cost_in_cents, creditcard, options)
 
           if response.success?
             self.status = 1
             self.confirmed_at = Time.now
             self.error_message = nil
           else
             self.status = 7
             self.error_message = response.message
           end
        else
          self.status = 7
 
         self.error_message = "Invalid credit card."
       end
 

  end
end
 
  

Run the unit test - it should pass without issue.

Now, you just need to make your controller take in the fields from a form. That should be pretty simple. Set up your form fields like

  <% form_for "reservation", @reservation, :url=>{:action=>"process_order"} do |f| %>
     <p>Card number: <%= f.text_field "card_number" %></p>
     ... other fields
    <%=submit_tag "Check out" %>
  <% end %>
 
 def process_order
   # get  id from session - no passing ids around in the url for me! 
   @reservation = Reservation.find(session[:reservation_id]
 
   @reservation.customer_ip = request.remote_ip
   @reservation.step = "checkout"   # for validation, remember?
        if @reservation.update_attributes(params[:reservation])
          if @reservation.process
            redirect_to :action=>"finished"
          else
            @reservation.errors.add_to_base @reservation.error_message
            render :action=>"checkout"    
          end
        else
          render :action=>"checkout"  
        end
 end

Step 6. Hooking it all up

You’re using a developer account, so you won’t actually see the transaction on the AuthorizeNet side of things unless you turn testing mode off. This part took me forever to figure out.

1. Log into the Merchant Interface
2. Select Account from the Main Menu
3. Click Test Mode
4. Press Turn OFF test

Now, re-run your unit test and then view the Reports page in the Merchant Interface. You will see a new transaction.

Testing without the Internet

If you’re not really into testing online, you can still get your test to pass. You can use 0 or 1 for the credit card number…. 0 means an invalid card, and 1 means a successful card. This comes in handy when doing a functional test later on.

Taking it Live

Change your details to use your permanent account’s transaction key and id. Then remove the :test mode stuff by running your app in production mode. Put your real gateway in test mode and then run through your app, using some fake credit cards from AuthorizeNet.

Mastercard: 5424000000000015
Visa: 4007000000027
Discover: 6011000000000012
American Express: 370000000000002

Once you’re absolutely sure that everything’s working, turn off the test mode on the production account, and you’re good to go!

Wrapping up

I hope that helps some of you. I spent a lot of time looking over the documentation and various examples out there. The developer account works a bit differently than the real account, so that was a major stumbling block.

Feedback is welcome, as always!

MySQL gem on OSX

Posted by Brian in Rails, Howto, snacks (June 1st, 2007)

When I got the Macbook Pro in March, I immediately installed Ruby, Rails, Subversion, etc, using this tutorial from Hivelogic. http://hivelogic.com/narrative/articles/ruby-rails-mongrel-mysql-osx
They’ve done a good job of keeping it up to date, but one problem I noticed is that the MySQL gem instructions don’t work anymore. When the article was first written, it contained steps to remedy a minor installation problem. However, enough users reported that the problem no longer existed so it was removed.

Today, I encountered that problem again, and thought I’d share the solution. If you’re getting an error when you do

  asudo gem install mysql -- --with-mysql-dir=/usr/local/mysql

You’ll need to do the following to fix it.

First, look at where the gem says it’s left its files.

  Gem files will remain installed in /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7 for inspection.

So in my case, I’ll do

  cd /usr/local/lib/ruby/gems/1.8/gems/mysql-2.7

Then I’ll edit the file ‘mysql.c’ in that folder and add one line at the top of the file:

  #define ulong unsigned long

Then, I just run

  sudo make
  sudo make install

The original fix for this comes from
http://jlaine.net/2006/10/3/installing-ruby-mysql-driver-on-os-x but the instructions there are a little backwards. I hope this solution will help others having a similar problem.

RailsConf 2007 and presentation

Posted by Brian in News (May 21st, 2007)

RailsConf2007 was fun. I hoped for more advanced panels, as it really seemed to be geared towards beginners. There were some excellent talks though, and I did learn some exciting things.

For those of you who attended my presentation, thanks for the great support. Your questions and enthusiasm really made it worth the effort. I hope to be able to make the screencasts available soon, complete with annotations and a voiceover.

It was also great to meat Jeff Cohen and the Softies guys. That was probably my favorite part of the conference because they were the friendliest bunch of people who actually seemed interested in talking to and learning from others. We had a great conversation and I would love to work more with them.

Rails for Windows Shortcut available now!

Posted by Brian in News, Rails, Projects (May 14th, 2007)

Rails for Windows Shortcut

If you’re looking to get started with Ruby on Rails and you’re a Windows user, this book will walk you through setting up some of the tools you’ll need, as well as show you how connect to Microsoft SQL Server and set up Capistrano. Of course, this is targeted at people who are new to Ruby on Rails and come from the Windows platform.

Curt Hibbs wrote the first chapter, where he showcases how to use RadRails and InstantRails to create a quick and easy setup.

The book covers

  • InstantRails and RadRails
  • Installation with the One Click Ruby Installer
  • Installing RMagick
  • Working with MySQL and SQL Server
  • Setting up a simple Subversion repository on Windows
  • Using SQLite and scaffold_resource to rapidly prototype a simple application
  • Using Capistrano
    … and more.

So check it out, won’t you?

Why I’m using a Mac

Posted by Brian in News, Rails, Usability, web (May 9th, 2007)

I’m a Windows user. Anyone who reads this blog can pretty much figure that much out. I started this company in 1995, fixing computers and trying to put the “personal” in computer service.
Over the years, I’ve removed countless viruses, uninstalled lots of spyware, formatted hard drives, recovered files, and occasionally had to send a computer back to a customer as “unfixable” because
of some unknown hardware or software glitch. Windows is popular and well-known, and people who know it well can have some pretty good job security.

Shortly after I started this company, I shifted focus towards the Internet, developing web sites. I bucked the trend of my Mac-using
counterparts back then and did all of my design work on Windows. I even did video editing there. I embraced ASP, and when that got old, I started using PHP, but deployed on Windows.

When Ruby on Rails came along and I got involved in that community, I was determined more than ever to bring Rails to the Windows platform. I’m speaking about Rails on Windows at RailsConf 2007 this year, and I’ve just finished a book on
Rails for Windows users which will be published by O’Reilly.

But I started using a Mac for development this Spring and couldn’t be happier. Here’s why.

  1. Ruby runs faster.

    Ruby runs at least ten times faster on a Mac, which means my unit tests, Rake tasks, and anything else I do with Ruby take no time to run. On Windows, I pay at least a 15 second penalty just to start a task or a test. That basically means I either sit and wait for things to happen, or I just start neglecting my tests to save time. Not a good place to be if you’re trying to write good code.

  2. It’s Linux that runs Photoshop

    Many web-based languages like PHP, Python, and Ruby run extremely well on Linux. However, you can’t use industry-standard tools like Photoshop on Linux without a few glitches and a lot of work. I’ve done Photoshop on Wine and I am not impressed. On my Mac, I can use Flash, Photoshop, Illustrator, Dreamweaver, and anything else, because there are native supported ports of those programs for my system.

  3. I can run Windows apps too!

    I need Office, and I need to run IIS so I can support ASP, .Net, and other Microsoft-based technologies. My Macbook Pro has Windows XP installed as a second boot option, but I also run Windows XP on Parallels, a virtual machine that lets me use my Windows apps side-by-side with my Mac programs. Parallels can run pretty much any other operating system, so I also have Windows Server 2003 installed for testing.

    When I go to conferences, I only have to bring one computer.

  4. I can surf in peace

    No IE means no spyware. I typically use Firefox on Windows anyway, but the complete absense of IE on this machine makes me feel much safer.

  5. More time working, less time tweaking

    I don’t have to stop working in order to make something do what I want. The Mac’s OS gets out of your way and lets you work. It’s a little difficult to transistion from a PC to a Mac at first, but some of the built-in features are great. The Dashboard gives me quick access to GMail, my Wordpress blog, the weather, my Backpack account, and even a color-picker.

    When I got my Macbook, I was using it and actually being productive with it in only a few hours. I haven’t used a Mac since I was a kid.

  6. Textmate

    Windows users have e, but I have TextMate, and I don’t need anything else. TextMate is more than just a text-editor; it’s an IDE. I can integrate with Subversion, I can run Rails generator commands, I can run unit tests, and I can have Prototype and Script.aculo.us methods auto-complete while I type. I can validate HTML, preview in a browser, and then upload it back to my repository.

  7. I can use more advanced tools

    I do most of my deployment on shared hosts with Mongrel and mongrel_cluster. I can run these on my Mac without incident. I also use the excellent load-testing program httper, which does not run on Windows. There are countless other tools out there too.

    I also get to use QuickSilver, which means I can chain commands and navigate through my files and programs much faster.

  8. Apple has awesome support

    I used my new Macbook Pro for gaming one night just to try it out. My games ran flawlessly when I boot into Windows XP via Bootcamp. While I was playing, one of my keys came dislodged. I called up Apple, and within 10 minutes I had them sending me a new keyboard. That’s cool. Try getting that from another company. I’ve worked with tech support offices for the last 13 years, and I rarely get service like that. Good luck getting that from some of the larger PC manufacturers unless you have an enterprise support contract.

  9. I can zoom.

    OS X has a built-in zoom feature that I use to not only enlarge the screen when I need it, but also to inspect images for artifacts and imperfections.

So there you have it, a long-time Windows user who is using a Mac for software development. If you’re going to preach to others about “using the right tool for the job” then you owe it to yourself to see what a Mac can do for you.

If you have switched, I’d love to hear your story. If you’re thinking about getting one, let’s hear what’s holding you back from taking the plunge.

« Previous PageNext Page »