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.

Slides from “Learning To Walk In Shoes” presentation

Posted by Brian in Howto, News (May 14th, 2009)

As promised, here’s the slide deck from my talk “Learning To Walk In Shoes” from April’s Twin Cities Code Camp. After the slides, we went over some demo applications, and you can get those from Github so you can play around with them yourself.

Learning To Walk In Shoes

Using Git to fork and contribute to a Rubyforge project

Posted by Brian in Howto, News, Rails, tips (January 13th, 2009)

If you’ve never comtributed to an open-source project before, there’s no better time to start. This article will walk you through forking an open-source project on RubyForge and making changes to it using Git. At the end, you’ll create a unified diff that can be sent back to the original author.

My current project FeelMySkills helps creative professionals promote themselves by creating an online portfolio that shows what they can do rather than who they know. I wanted the site to let a user export their profile page as a PDF, and so I found the amazing HTMLDoc gem which takes simple HTML pages and converts them to PDF documents.

While developing the site, I discovered that there is a bug in PDF::HTMLDoc that pops up when you embed images into the PDF. It turns out that it has to do with extra whitespace getting into the content which causes HTMLDoc to choke. The fix is really simple, and after I fixed it I found that there is already a patch for this posted to Rubyforge but it hasn’t been applied. I assume that the reason it’s not applied is that there were no tests supplied with the patch.

Let’s get the code, write the test, and submit the patch!

GITing the code

The HTMLDoc project (http://htmldoc.rubyforge.org) has a Subversion repository that we can use as our master branch. Now, if you’ve never used Git before, don’t worry about it because we’re only going to use it here as a really easy way to create patches.

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.

Forking the code from Subversion

The git svn command lets you pull and push to a Subversion repository and is perfect for watching projects that don’t use Github yet.

Visit the project page at http://rubyforge.org/projects/htmldoc/ and click the link on the bottom for SCM Repository. That page lists the repo as http://htmldoc.rubyforge.org/svn.

Grab the trunk for HTMLDoc from RubyForge.

git svn clone http://htmldoc.rubyforge.org/svn/trunk htmldoc

Test before you start

If you want to start things off on the wrong foot, just start hacking away at the code. A much better approach is to see what tests are broken before you start working. Good Ruby projects should have a test suite that completely passes.

In the htmldoc folder, run rake which will run the test suite for this app.
Unfortunately the test suite shows an error:

Loaded suite -e
Started
..E........
Finished in 3.500649 seconds.

  1) Error:
test_get_command_pages(BasicTest):
NoMethodError: undefined method `path' for nil:NilClass
    ./test/basic_test.rb:91:in `test_get_command_pages'

11 tests, 361 assertions, 0 failures, 1 errors

However, on further inspection, the error is because of a path issue. The tests pass when you run them individually:

cd test
ruby basic_test.rb && ruby generation_test.rb
cd ..

See? Everything works!

Loaded suite basic_test
Started
......
Finished in 0.010653 seconds.

6 tests, 324 assertions, 0 failures, 0 errors
Loaded suite generation_test
Started
.....
Finished in 3.403335 seconds.

5 tests, 38 assertions, 0 failures, 0 errors

Creating a new branch for your changes

We’ll want to keep our work in a new branch. This makes it easy for us to create the patch later, as we can make Git give us the difference between the master branch, which we forked from Rubyforge, with our new branch which will contain our fixes.

$ git checkout -b fix_images

Writing a new test

Before you start hacking away on a new feature, you should write a test to prove that things are really broken. In this case, a PDF that contains a reference to an image causes things to break. A test that tries to put an image reference into the PDF data should break. Add this test to test/generation_test.rb

  def test_generation_results_with_image
    pdf = PDF::HTMLDoc.new
    pdf.set_option :webpage, true
    pdf.set_option :toc, false
    pdf << "

Random title

something you have to follow either.

Run this test

cd test
ruby generation_test.rb -n test_generation_results_with_image
cd ..

and you'll see that things don't work as expected:

Loaded suite generation_test
Started
F
Finished in 0.349939 seconds.

  1) Failure:
test_generation_results_with_image(GenerationTest) [generation_test.rb:43]:

expected to be kind_of?
 but was
.

So we can reproduce the bug, and now we just have to fix the problem.

A simple fix (this time)

The reason for the error is because HTMLDoc hates extra whitespace created by the inclusion of the image. Open up lib/htmldoc.rb and change line 186 from

          case line

to

          case line.strip

Save the file. Now run the entire test suite again to make sure that nothing else broke.

Loaded suite basic_test
Started
......
Finished in 0.007855 seconds.

6 tests, 324 assertions, 0 failures, 0 errors
Loaded suite generation_test
Started
......
Finished in 3.434714 seconds.

6 tests, 43 assertions, 0 failures, 0 errors

Hurray! We fixed it! Now we just have to make a patch!

Commit your changes!

You should commit your changes to your branch so you don't lose them.

  git commit -a -m "Fixed problem when embedding images in PDFs"

Making a patch

If you're quick, you can probably just create the patch right now, but let's assume the project is a fast-moving one, like Rails. Youl want to pull down the latest version of the project and fix any conflicts.

git checkout master
git svn rebase
git checkout fix_images
git rebase master

There won't be any collisions you have to fix now, so you can just make the patch file.

git format-patch master --stdout > htmldoc_fix_images.diff

This creates the file htmldoc_fix_images.diff which is a unified diff containing both the fix and the test. You can now send the patch file to the maintainer who will happily apply it because it has tests!

Wrapping up

Working with open-source projects gets easier every day, and you can really take ownership of the tools you use if you get involved. Hopefully this article gets you started on the path to contributing to projects. Good luck, and leave comments if something needs more clarification!

“Meet Rails” presentation materials from Chippewa Valley Code Camp

Posted by Brian in Howto, Rails, tips, web (November 24th, 2008)

I gave a talk at the Chippewa Valley Code Camp earlier this month where I built a Rails pastebin application using test-first development principles. Since there were no slides for this, I’ve prepared a small PDF that will walk you through the installation instructions and the coding of the application I built.

Download Meet Rails to get started.

You can grab the source code for the finished project at Github.

Watch this site, as this project may get turned into a full-scale book.

RSpec helped me refactor my code.

Posted by Brian in Howto, Rails, Testing (June 10th, 2008)

I’ve been extremely against using RSpec. I always found it rather clunky, but it turns out that resources to really help a person learn how RSpec works are dificult to find. The examples you find out on the web are just poorly written or just contrived and impractical, or they’re so hopelessly overengineered that a newcomer would be overwhelemed.

This weekend I took it upon myself to really learn RSpec and so I started rewriting some of the tests for FeelMySkills. I started with the Account model which is used all over the app. The account_id is stored in the session and I use the Restful_authentication plugin to get access to a current_account method which returns the Account object. I want to be able to determine whether or not that account is an Admin, and I have an entry in the roles called “admin”. Nothing too special about all this, as many apps use a similar bit of functionality.

To make this easy on myself, I wrote a method called is_admin? which returns true if the admin role is associated with my account and nil if it’s not, and as we all know, nil evaluates to false, and anything other than nil or false evaluates to true.

When an account is created, I give them a role called “user”. Eventually, Pro users will have a different role, giving them access to more stuff in the system. Here’s what i have so far:

  class Account < ActiveRecord::Base
    has_and_belongs_to_many :roles
    after_create :add_user_role


    def is_admin?
      self.roles.detect{|r| r.name == "admin"}
    end

    def is_user?
       self.roles.detect{|r| r.name == "user"}
    end
    
    def add_user_role
          self.roles << Role.user

    end

  end

So, when I create a new user, I need to make sure that user gets the user role. My original Test/Unit test looked like this:

  def test_new_account_should_have_user_role
    account = Account.create(:login => "test",
                                       :password=>"test",
                                       :password_confirmation => "test",
                                       :email => "test@test.com")
    assert account.is_user?
  end

This test passes without any issues, so I know the code is right. Here's what I tried with RSpec:

  describe "when creating an account" do
    fixtures :accounts, :roles
    before(:each) do
      @account = Account.create(:login => "test",
                                           :password=>"test",
                                           :password_confirmation => "test",
                                           :email => "test@test.com")
    end

    it "should have the user role" do
      @account.is_user?.should be_true
    end

  end

Imagine my surprise when I ran this spec and it failed! The reason why makes perfect sense when you start thinking about it.

First, RSpec's matcher be_true evaluates the response of the method to be equal to true. The code for is_admin? actually returns an instance of Role, and not true like I asserted earlier. While that method evaluates to true, it does not equal true. So it's interesting that the assert method has no probelm making the evaluation, but RSpec's matchers are pickier.

A fair argument here would be "why does your is_admin? method return a Role and not just true or false?" The answer is that I'm lazy. I rely on Ruby to work for me, and until now, #detect has been a great ally. In my controller code, I can do

if current_user.is_admin...

and all is well, without the need to explicitly return true or false from the is_admin? or is_user? methods.

A better way

Looking at the spec again, I notice that I am in fact asking for the role in that specification. So I rewrite it to grab the User role from the fixtures and ensure they're equal and it passes.

    it "should have the user role" do
      @account.is_user?.should equal roles(:user)
    end

But something about that bothers me. What am I really testing? I'm testing to make sure that the account is a regular user. Maybe I really do need a method that returns true or false.

It turns out that if you have a method in your model that ends with a question mark (?) and returns true or false, then RSpec can dynamically create a matcher for it. I rewrote the spec like this:


    it "should be a user" do
      @account.should be_a_user
    end

and then added this method to my model:

   # calls is_user? and returns true if is_user? returns a result, 
   # or false if it returns nil
   def user?
      self.is_user? != nil
   end

and I ended up with something I am much more comfortable with. I think future refactorings might change this around even more, but I found this exploration to be extremely enlightening.

P.S. For those that are interested, I actually have several roles in my system and I don't manually declare these methods like is_admin? and is_user? by hand. I use this instead:

class Account < ActiveRecord::Base

  # ...
    
    # constant containing all of the role names
    # in the system
    Role::ROLE_NAMES.each do |r| 

    class_eval <<-CODE
      def is_#{r}?
        self.roles.detect{|role| role.name == "#{r}"}
      end
      
      def #{r}?
        self.is_#{r}? != nil
      end
      
    CODE
  end

  #... 

end

That way I don't need to add new methods when I implement factchecker or pro or business roles later. Just thought I'd share that.

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.

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.

ActiveMerchant and Authorize.Net

Posted by Brian in Howto, Rails, 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| %>
     

Card number: <%= f.text_field "card_number" %>

... 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 Howto, Rails, snacks (June 1st, 2007)

UPDATED 12-05-2008: The instructions in this article were written for Tiger. With the release of Rails 2.21 this article’s been getting a lot more traffic and the instructions here needed to be updated. What you see here are instructions that should work for most installations of MySQL on the Mac.

When I got the Macbook Pro in March 2007, 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

First, locate your installation of MySQL using

which mysql_config

You need three libraries:
include/mysql5
lib/mysql5
bin/mysql5_config

These should all be located within your mysql base folder.

For example, if MySQL is installed by Macports, the default location would be

/opt/local/lib/mysql5/

So you’d change the paths accordingly:

 sudo gem install mysql -- --with-mysql-include=/opt/local/include/mysql5 --with-mysql-lib=/opt/local/lib/mysql5 --with-mysql config=/opt/local/lib/mysql5/bin/mysql_config

This solution seems to get things rolling. Thanks much to crookshanks on IRC for the fix. Really, if you’re not participating in the IRC channels for Rails (#rubyonrails on irc.freenode.net) you’re missing out on some good discussions.

The rest of this article is most likely not relevant anymore.,/b>

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.

« Previous PageNext Page »