Use Modules, not Inheritence to share behavior in Ruby

Posted by Brian in Howto, Rails, snacks (August 10th, 2009)

When working in Rails, I often find that several classes have the same behavior. A common approach to share code between objects is the tried-and-true method of inheritance, wherein you create a base class and then extend your subclasses from the parent. The child classes get the behaviors from the inheritance. Basically, Object Oriented Programming 101.

Languages like Java and Ruby only let you inherit from one base class, though, so that isn’t gonna work. However, Ruby programmers can use modules to “mix in” behavior.

Mixing it up and Mixing it in

Let’s say we have a Person class.

class Person
  def sleep
    puts "zzz"
  end
end

Now let’s say we need to define a Ninja. We could create a Ninja class and inherit from Person, but that’s not really practical. Ruby developers really don’t like changing the type of a class just because its behavior changes.

Instead, let’s create a Ninja module, and put the behaviors in that module.

module Ninja
  def attack
    "You are dead."
  end
end

We can then use this multiple ways.

Mixing it in to the parent class

In a situation where we would like every Person to be a ninja, we simply use the include directive:

  class Person
    include Ninja
  end

Every instance of the class now has the Ninja behaviors. When we include a module, we are mixing in its methods to the instance. We could also use the extend method, but this mixes the methods in as class methods.

Notice here that we redefined the class definition. This appends to the existing class; it does not overwrite it. This can be a dangerous technique if used improperly because it can be difficult to track down. This is the simplest form of monkeypatching.

There’s an alternative method.

Mixing in to an instance

We can simply alter the instance of an object, applying these behaviors to a specific instance and affecting nothing else.

  @brian = Person.new
  @brian.extend Ninja

Here, we use the extend method because we need to apply these as class methods to the object instance. In Ruby, classes are objects themselves. Our instance needs to be extended.

A more practical exaple – Geolocation

I was working on a project this weekend where I had several models that needed latitude and longitude data pulled in via Google’s web service. I used the GeoKit gem and the Geokit-Rails plugins to make this happen, but I soon noticed I was adding the same code to multiple classes.

acts_as_mappable
after_validation_on_create :geocode_address

def geocode_address
  geo=Geokit::Geocoders::MultiGeocoder.geocode (address)
  errors.add(:address, "Could not Geocode address") if !geo.success
  self.lat, self.lng = geo.lat,geo.lng if geo.success
end

It seemed immediately apparent that this should go in a module which could be included into my classes. However, I wanted to also make the two class method calls – the after_validation_on_create callback and the acts_as_mappable macro.

Ruby has a facility for that type of situation

self included

def self.included(base)
#
end

This method is called when a module is included into a class, and it gives you access to the class that’s doing the including. You can use this as a handle to call any class methods. With that, my geolocation module looks like this:

module Geolocation
  
  def self.included(base)
    base.acts_as_mappable
    base.after_validation_on_create :geocode_address
  end

  def geocode_address
    geo=Geokit::Geocoders::MultiGeocoder.geocode (address)
    errors.add(:address, "Could not Geocode address") if !geo.success
    self.lat, self.lng = geo.lat,geo.lng if geo.success
  end
  
end

So now any class that needs geolocation just needs to look like this:

class Business
  include Geolocation
end

class Nonprofit
  include Geolocation
end

Summary

The above problem could have been solved by using a parent class like MappableOjbect that included the code, but it then makes putting in additional behavior more difficult. Using modules to share code is the preferred way to attach behaviors to objects in your applications.

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.




  
  
  My Simple Book
  
 
  


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





  Introduction
  This is just a simple book.



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 Files\HTML 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.




  
  
  My Simple Book
  
 
  


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





  Introduction
  This is just a simple book.



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.


Home page

<%=@contents["intro"] %>

About us

<%=@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 Howto, Rails, 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 script\generate 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 script\generate 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)

Update 2009-11-15 This is out of date. There’s no reason to read further. Rubygems does OS detection automatically now.

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 confusing 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| %>
      

<%=@document.title %>

<% end %> <% else %>

There are no items to show.

<% 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| %>
   

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

<% 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|| %>
		

<%=link_to document.title, :action=>"show", :id=>document %>

<% 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

		

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

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("

You have #{collection.size} items.

", 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("

You have #{collection.size} items.

", 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

Next Page »