Create a new Edge Rails project

Posted by Brian in News, Rails, snacks (April 24th, 2008)

In a previous post, I provided scripts that made the creation of a new Edge Rails project easy. Since then, Rails has moved from Subversion to Git, which means that the scripts I provided no longer work as expected. Fortunately, very little has changed and I was able to make the script a little bit better so that it acts like the original rails command.

Prerequisites

First, you’re going to need git. I could have written the script to grab the latest version via a zipfile, but I wanted something that was fast and worked on all platforms. Windows can unzip files, but then I’d have to make Windows users go grab commandline tools to unzip files.

Installing Git

Mac users with XCode and Macports installed can do it with

sudo port install git-core +svn

Windows users can install Msysgit.

Linux users should install Git using their package manager or from source.
Here’s the script. Instructions for running it are after the code.

#!/bin/ruby
git_repo = "git://github.com/rails/rails.git"

help = %Q{
Rails Info:
    -v, --version                    Show the Rails version number and quit.
    -h, --help                       Show this help message and quit.
 
General Options:
    -p, --pretend                    Run but do not make any changes.
        --force                      Overwrite files that already exist.
    -s, --skip                       Skip files that already exist.
    -q, --quiet                      Suppress normal output.
    -t, --backtrace                  Debugging: show backtrace on errors.
 
Description:
    The 'edge_rails' command creates a new Rails application with a default
    directory structure and configuration at the path you specify, using the
    very latest version of Rails.
 
Example:
    edge_rails ~/Code/Ruby/weblog
 
    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
    See the README in the newly created application to get going.    
 
}

require 'fileutils'
 
if ARGV.empty?
  puts help
  exit
end
 
dir = ARGV.shift
args = ARGV.join (" ")
 
FileUtils::mkdir(dir)
FileUtils::mkdir("#{dir}/vendor")

puts "Exporting EdgeRails from #{git_repo}"
# system "svn export http://svn.rubyonrails.org/rails/trunk #{dir}/vendor/rails"
system "git clone --depth=1 #{git_repo} #{dir}/vendor/rails"
system "rm -rf #{dir}/vendor/rails/.git*"
 
system "ruby #{dir}/vendor/rails/railties/bin/rails #{dir} #{args}"

How this works for Mac and Linux users

Save the script to your home folder as edge_rails, and set the execute bit:

  chmod 644 ~/edge_rails

Run it with

  ~/edge_rails your_app

You could symlink it to your /usr/local/bin if you are feeling clever.

How this works for the Windows crowd

Save this script as c:\ruby\bin\edge_rails.bat

@echo off
goto endofruby
#!/bin/ruby
git_repo = "git://github.com/rails/rails.git"

help = %Q{
Rails Info:
    -v, --version                    Show the Rails version number and quit.
    -h, --help                       Show this help message and quit.
 
General Options:
    -p, --pretend                    Run but do not make any changes.
        --force                      Overwrite files that already exist.
    -s, --skip                       Skip files that already exist.
    -q, --quiet                      Suppress normal output.
    -t, --backtrace                  Debugging: show backtrace on errors.
 
Description:
    The 'edge_rails' command creates a new Rails application with a default
    directory structure and configuration at the path you specify, using the
    very latest version of Rails.
 
Example:
    edge_rails ~/Code/Ruby/weblog
 
    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
    See the README in the newly created application to get going.    
 
}

require 'fileutils'
 
if ARGV.empty?
  puts help
  exit
end
 
dir = ARGV.shift
args = ARGV.join (" ")
 
FileUtils::mkdir(dir)
FileUtils::mkdir("#{dir}/vendor")

puts "Exporting EdgeRails from #{git_repo}"
# system "svn export http://svn.rubyonrails.org/rails/trunk #{dir}/vendor/rails"
system "git clone --depth=1 #{git_repo} #{dir}/vendor/rails"
system "rm -rf #{dir}/vendor/rails/.git*"
 
system "ruby #{dir}/vendor/rails/railties/bin/rails #{dir} #{args}"
__END__
:endofruby
"%~d0%~p0ruby" -x "%~f0" %*

Now, open a new command prompt and type

edge_rails my_new_app

If it doesn’t work, check that you have Git installed properly and have added Git’s command line utilities to your path.

Working with Docbook on Windows

Posted by Brian in Howto, snacks, tips (March 3rd, 2008)

Setting up a toolchain for working with Docbook on Windows often requires setting things up using Cygwin. Many people are just simply not willing to do that. This tutorial will show you how to set up a native environment to work with Docbook, and show you how to make CHM and PDF files on Windows.

Thanks to http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/docbooksys/segmentedhtml/ch03s03.html#DocBookSys-Chapter3-XML-Install-libxml-Windows for much of this information.

Getting the tools

The tools you need to work with Docbook XML and XSTL are all available on Windows. The first thing you need to do is visit http://www.zlatkovic.com/pub/libxml/ (new window) and grab the latest versions of

  • libxml2
  • libxslt
  • iconv

Download each and unzip the contents of the folder to c:\windows or another location on your path. For reference, these files are the ones you’re looking for:

iconv.exe
libexslt.dll
libxml2.dll
libxslt.dll
xmlcatalog.exe
xmllint.exe
xsltproc.exe

If you feel better about putting these in their own folder, that’s fine as long as you add the new folder to your path.

Getting the Stylesheets

In order to build a book, you need to have the XSLT stylesheets so you can transform your XML into a pretty-looking book with a table of contents and nicely formatted text.
Download the docbook-xml-ns files from sourceforge: http://sourceforge.net/project/showfiles.php?group_id=21935

Unpack to your c:\ drive and then rename the extracted folder to c:\docbook-xsl

Generating PDFs

In order to create a PDF, you have to first convert to the FO format and then use a Java library to convert the FO to a PDF. Apache FOP does this for you. You’ll need to have a JRE (Java Runtime) installed though. Visit http://java.sun.com/ for that.

Get FOP to build PDFs

Download FOP at http://www.uniontransit.com/apache/xmlgraphics/fop/fop-0.94-bin-jdk1.4.zip and unzip it to a temp location. Copy all .jar files in build/ and lib/ to your Java installation’s lib/ext folder. On my system it’s C:\Program Files\Java\jre1.5.0_11\lib\ext. Your system will differ depending on your installed version of Java.

Next, download OFFO-hyphenation from http://offo.sourceforge.net/index.html and grab the offo-hyphenation-fop-stable.zip file from the downloads page and put the jar files in the same folder as the FOP files.

Building your first book

Create a project folder called “my_book” and create a new file called “book.xml” in this folder.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
                 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<book>
  
  <bookinfo>
  <title>My Simple Book</title>
  </bookinfo>

  <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="chapter1.xml"/>
 
</book>

Then create a chapter for your book. Create the file chapter1.xml in your project folder with this content:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
                 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
 
<chapter id="chapter1">
  <title>Introduction</title>
  <para>This is just a simple book.</para>
 
</chapter>
 

Notice that the chapter and book each have their own doctype, This is really important. Each chapter file needs to have this structure in order to work properly.

Generating HTML from the document

The easiest way to use Docbook is to export to HTML. Execute this command to create an HTML version of your book:

   xsltproc --xinclude --output book.html c:/docbook/xsl/html/docbook.xsl book.xml

Creating a makefile to build the PDF

The PDF creation process is similar to the HTML process but it does require two steps. You need to first convert the document to the FO file format. Then you use FOP to convert it to the PDF. We can automate this by using Ruby.

Create a Ruby file in your project folder called “make”. You’ll use this file to build the PDF of your book.

file = ARGV[0]
cmd1 = "xsltproc --xinclude --output #{file}.fo c:/docbook-xsl/fo/docbook.xsl #{file}.xml "
cmd2 = "java org.apache.fop.cli.Main -fo #{file}.fo -pdf #{file}.pdf"
 
puts "Building FO file"
`#{cmd1}`
 
puts "Building PDF"
`#{cmd2}`
 
puts "Cleaning up"
`del #{file}.fo`
 
puts "Done"
 

Now, build your book:

ruby make book

Creating a Help File

Generating a Windows HTML Help file (CHM) is pretty similar to the way you make a PDF. You first need to make the HLP file using xsltproc, and then you use a commandline tool to build the CHM.

Grab a copy of Microsoft’s HTML Help Workshop here and install it. Open a command prompt and copy the hhc file to the c:\windows directory so that the file is on your path.

copy "c:Program FilesHTML Help Workshop"hhc.exe c:windows

Next, we can use Ruby to make a file to create another build file. Create a file called “make_chm” in your project folder.

file = ARGV[0]
cmd1 = "xsltproc --xinclude c:/docbook-xsl/htmlhelp/htmlhelp.xsl #{file}.xml"
cmd2 = "hhc htmlhelp.hhp"
 
puts "Building HLP temporary files"
`#{cmd1}`
 
puts "Building CHM"
`#{cmd2}`
 
puts "Cleaning up"
`rename htmlhelp.chm #{file}.chm`
`del *.hhp`
`del *.hhc`
`del *.html`
 
puts "Done"
 

Summary

Docbook is a really great way to create books, tutorials, and documentation in a format that can be transformed into various other formats. It’s extremly easy to work with in Windows too!

Working with Docbook on the Mac

Posted by Brian in Howto, snacks, tips (March 3rd, 2008)

Docbook allows you to prepare documentation by using XML markup. You can create PDFs or HTML exports of your work, and it’s really nice for collaborating with others, as you can work in a code repository easily. In this article, you’ll learn how to build a PDF using Docbook XSLT.

Getting the Stylesheets

In order to build a book, you need to have the XSLT stylesheets so you can transform your XML into a pretty-looking book with a table of contents and nicely formatted text.
Download the docbook-xml-ns files from sourceforge: http://sourceforge.net/project/showfiles.php?group_id=21935

Unpack to your home directory and rename the folder to ~/docbook-xsl

Generating PDFs with Apache FOP

In order to create a PDF, you have to first convert to the FO format and then use a Java library to convert the FO to a PDF. Apache FOP does this for you.
Get FOP. - http://www.uniontransit.com/apache/xmlgraphics/fop/fop-0.94-bin-jdk1.4.zip

Unzip to temp location and copy all .jar files in the build/ and lib/ folders to ~/Library/Java/Extensions. Create that folder if it isn’t there for you already.

Finally, download OFFO from http://offo.sourceforge.net/index.html and grab the offo-hyphenation-fop-stable.zip file from the downloads page and put the jar files in ~/Library/Java/Extensions. This enables hyphenation support.

Building your first book

Create a project folder called “my_book” and create a new file called “book.xml” in this folder.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
                 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<book>
  
  <bookinfo>
  <title>My Simple Book</title>
  </bookinfo>

  <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="chapter1.xml"/>
 
</book>

Then creaet a chapter for your book. Create the file chapter1.xml in your project folder with this content:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
                 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
 
<chapter id="chapter1">
  <title>Introduction</title>
  <para>This is just a simple book.</para>
 
</chapter>
 

Notice that the chapter and book each have their own doctype, This is really important. Each chapter file needs to have this structure in order to work properly.

Generating HTML from the document

The easiest way to use Docbook is to export to HTML. Execute this command to create an HTML version of your book:

   xsltproc --xinclude --output book.html c:/docbook/xsl/html/docbook.xsl book.xml

Creating a makefile to build a PDF

The PDF creation process is similar to the HTML process but it does require two steps. You need to first convert the document to the FO file format. Then you use FOP to convert it to the PDF. We can automate this by using Ruby.

Create a Ruby makefile in your project folder called “make”. You’ll use this file to build the PDF of your book.

file = ARGV[0]
cmd1 = "xsltproc --xinclude --output #{file}.fo ~/docbook-xsl/fo/docbook.xsl #{file}.xml "
cmd2 = "java org.apache.fop.cli.Main -fo #{file}.fo -pdf #{file}.pdf"
 
puts "Building FO file"
`#{cmd1}`
 
puts "Building PDF"
`#{cmd2}`
 
puts "Cleaning up"
`rm #{file}.fo`
 
puts "Done"
`open #{file}.pdf`
 

Now, build your book. In your project folder, type

ruby make book

Summary

Now you have a good introduction to how to work with the Docbook format. You may want to use a Textmate bundle to make editing the XML a little easier, but the syntax really isn’t that hard.

Up next… creating CHM files using Docbook on Windows.

Mini-CMS for your Rails application

Posted by Brian in Rails, snacks, tips (March 3rd, 2008)

I’ve worked on several applications where an end user may need to change text on various sections of a web site. I’ve used this technique a few times and it’s worked out well so I thought I’d share.

We’re going to create a model called Content which will be backed by a database. When a request comes in, we can look at the requested controller and action name to look up all of the content sections for a page. Some pages may have more than one content section.

This solution is intended to be implemented and modified by developers. An end user would not be able to add new content regions to a system.

Create the model and table

ruby script/generate model Content

Next, modify the migration:

class CreateContents < ActiveRecord::Migration
  def self.up
    create_table :contents do |t|
      t.string :controller, :action, :name
      t.text :body
      t.timestamps
    end
  end      
 
  def self.down
    drop_table :contents
  end
end
 

The name field is what our end user will use to locate and edit a section. They don’t need to know the controller and action name. Instead, they’ll probably look for a content region like “Home Page main content”. In order to make this work, the user will only be able to edit the body field. The other fields will be handled by the system.

Now, open up the content model. We’re going to add a few methods here to easily grab the content out so we can use it in views. Out of the box, ActiveRecord allows us to find all the sections for a controller and action name by doing

@contents = Content.find_all_by_controller_and_action("public", "index")

At first glance, that looks pretty cool, but how do you identify the individual regions? You’d have to loop through that collection every time you want to output something. If you only have one content area per page, this could be good enough. However, I like things to be a little more spelled-out.

Add this method to the content class:

  def self.get_content_for(controller, action)
    c = Content.find_all_by_controller_and_action(controller, action)
    content = Hash.new
    c.each do |record|
      content[record.name] = record.body
    end
    content
  end'

Now, to fetch all of the sections for the page, we only need to call

@contents = Content.get_content_for "public", "index"

This class method fetches all of the content sections and then puts then into a hash which you can afccess via the name.

Trying it out

Open up the console and put two content regions in:

   ruby script/console
Content.create :controller=>"public", :action=>"index", :name=>"intro", :body => "Hello world"
Content.create :controller=>"public", :action=>"index", :name=>"main_content", :body => "Main content goes here"

Now quickly generate a new controller and action

ruby script/generate controller public index

Add this to the public controller

before_filter :get_content

Now open application.rb and add this method:

def get_content
  @content = Content.get_content_for(params[:controller], params[:action])
end

Finally, open up app/views/public/index.html.erb and change its contents.

 
<h1>Home page</h1>
 
<%=@contents["intro"] %>
 
<h2>About us</h2>
<%=@contents["main_content"] %>
 

Summary

I’ve used this approach in a few sites and it’s served me well. I’d love to hear how you’ve done the same. Post ideas and suggestions in the comments.

Creating a new Edge Rails project in Windows (and *nix / OSX too!)

Posted by Brian in Rails, Howto, snacks (September 20th, 2007)

Rails has moved to Git so the scripts here no longer work! Visit the updated article instead.

I’ve been working with Edge Rails a lot lately, in preparation for a book I’m working on. Creating a new Rails project with Edge is a bit tricky—you need to have Edge Rails in your app’s vendor/rails folder before you can create your app. It usually involves the following steps:

  • Create the project’s folder
  • Create the vendor folder
  • Export Edge Rails to the vendor/rails folder
  • Create the Rails project using the vendor/rails/railities/bin/rails script instead of the normal one.

It seems that every time I want to do this, I have to go look at my notes and remember how. Not any more.

edge_rails

On Windows, paste this script into a new file and save the file to c:\ruby\bin\edge_rails.

@echo off
goto endofruby
#!/bin/ruby
 
require 'fileutils'
dir = ARGV[0]
dbtype = ARGV[1] rescue nil
FileUtils::mkdir(dir)
FileUtils::mkdir("#{dir}/vendor")
 
puts "Exporting EdgeRails from http://svn.rubyonrails.org/rails/trunk"
system "svn export http://svn.rubyonrails.org/rails/trunk #{dir}/vendor/rails"
 
system "ruby #{dir}/vendor/rails/railties/bin/rails #{dir} #{dbtype}"
 
__END__
:endofruby
"%~d0%~p0ruby" -x "%~f0" %*
 

Now, to create a new Edge Rails project, simply do

edge_rails my_app_name

or

edge_rails my_app_name --database=sqlite3

Right now, I’m not supporting any of the other command line options. If you want them, figure out how to do it. It’ll be a good exercise for you.

If you want to do this on a non-Windows system, just save the script below to a file called edge_rails, place the file on your path, and be sure to make it executable. You may need to change the first line of the script to correctly point to your ruby installation. Use

which ruby

to locate your Ruby path.

Here’s the script with all the Windows goodness removed.

#!/bin/ruby
 
require 'fileutils'
dir = ARGV[0]
dbtype = ARGV[1] rescue nil
FileUtils::mkdir(dir)
FileUtils::mkdir("#{dir}/vendor")
 
puts "Exporting EdgeRails from http://svn.rubyonrails.org/rails/trunk"
system "svn export http://svn.rubyonrails.org/rails/trunk #{dir}/vendor/rails"
 
system "ruby #{dir}/vendor/rails/railties/bin/rails #{dir} #{dbtype}"
 

Hope that makes someone else’s life easier too.

Adding RSS to your Rails application

Posted by Brian in Rails, snacks, tips (September 8th, 2007)

Rails makes it really simple to expose your data as XML. It also makes it pretty easy to support any other type of format.

While you can do this in Rails 1.2, this writeup will focus on doing it with Edge Rails.

Assume I have a Project model, a Projects controller, and I’m using a RESTful design…

  rails my_projects_rss --database=sqlite3
  cd my_projects_rss
  ruby scriptgenerate scaffold Project title:string description:string
  rake db:migrate
  ruby script/server

Create a new file at apps/views/projects called index.rss.builder.

Place this code in that file:

xml.rss('version' => '2.0') do
  xml.channel do 
    xml.title(@page_title)
    xml.link(projects_url)
    xml.description(@page_title)
    @projects.each { |project|
      xml.item do 
        xml.title(project.title)
        xml.link(project_url(project))
        xml.description(project.description)
        xml.pubDate(project.updated_at.rfc822)
      end
    }
  end
end

Now… take the index action in your projects controller and change it from this:

  # GET /projects
  # GET /projects.xml
  def index

    @projects = Project.find :all
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @projects}
    end
 
  end

to this:

  # GET /projects
  # GET /projects.xml
  # get /projects.rss
  def index
  
    @projects = Project.find :all, :order => "update_at desc"
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @projects}
      format.rss do 
        @page_title = "Journal entries"
        render :layout=>false
      end
 
    end
 
  end

Start up your server and add some projects. You can then navigate to http://localhost:3000/projects.rss and see the feed.

Now we’re not going to talk about how to cache the feed, but that’s easy enough to figure out.

How do I do this right now, without using Edge rails

The steps are simple.

  1. Use this to generate your resource scaffold:
          ruby scriptgenerate scaffold_resource Project title:string description:string
      
  2. Name the above mentioned RSS template file projects_rss.rxml
  3. Use this for your controller action.
      # GET /projects
      # GET /projects.xml
      # get /projects.rss
      def index
      
        @projects = Project.find :all, :order => "update_at desc"
     
        respond_to do |format|
          format.html # index.html.erb
          format.xml  { render :text => @projects.to_xml}
          format.rss do 
            @page_title = "Projects"
            render :action=>"projects_rss", :layout=>false
          end
     
        end
     
      end
    

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.

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!

Next Page »