Swombat

wombat + s 

Getting rspec before(:all) and nested contexts working together

As posted to the rspec-users mailing list:
 
> Like everyone (?), I use nested contexts to keep my specs well organised and tidy.
>
> However, I have a problem. I have various sets of specs that needs to perform very time-expensive operations to set up the fixtures that will be examined in the tests. Two specific examples: testing access control logic (requires creating a whole tree of items to verify the correct access level against each item), and project archival (which creates a project, fills it with test data, archives/zips the project contents, then unzips them for examination).
>
> I tried using before(:all) to set up those costly fixtures, however I hit upon a feature of rspec that made that less than successful:
>
> When using before(:all) along with nested contexts, rspec actually re-runs the before(:all) before each sub-context. So if, like me, you have your specs neatly organised in sub-contexts, the before(:all) is actually re-run many times.
>
> Interestingly, when a before(:all) is run in the root context, rspec does not actually remove the data from the database when re-running the before(:all). "Great," I then thought, "I can just detect whether the data is created and decide whether or not to create the objects on that basis". Not so fast, though: Rspec may not clobber the database, but it does clobber instance variables. In the case of the access control test, there's about 40 different instance variables, so keeping track of them all manually in some global variable outside of rspec would be messy to say the least...
 
Turns out, I couldn't be bothered to wait for a reply, so I wrote a workaround. It's not very pretty, and a bit hackish, but here it goes.
What I've done is written a "Hoover" class that will hoover up instance variables from the root context and save them for later use.
 

 
class InstanceVariableHoover 
 
 @@variables = false 
 
 def self.empty? 
  !@@variables 
 end 
 
 def self.save_instance_variables(object, &block) 
  puts @@variables.inspect 
  if @@variables 
   @@variables.each do |var, val| 
    object.instance_variable_set(var, val) 
   end 
  else 
   yield 
   @@variables = {} 
   object.instance_variables.each do |var| 
    @@variables[var] = object.instance_variable_get(var) unless 
var.starts_with?("@_") || var == "@method_name" 
   end 
  end 
 end 
 
end 

 
This is all great, unless, like most normal people, you like your specs to run more than once, because the database doesn't get clobbered between requests, so you'll fail various uniqueness constraints if, for example, you're creating users and the like.
 
The solution is, unfortunately, to manually clobber the objects that you know might be lying around, and to make sure the objects you create will not interfere with your other specs. Moreover, there's another subtle problem - each set of specs that needs a different before(:all) block will need its own hoover. Here's a full length example of this at work:
 
 
require File.dirname(__FILE__) + '/../spec_helper' 
 
class BarSpecHoover 
 @@variables = false 
 
 def self.empty? 
  !@@variables 
 end 
 
 def self.save_instance_variables(object, &block) 
  if @@variables 
   @@variables.each do |var, val| 
    object.instance_variable_set(var, val) 
   end 
  else 
   yield 
   @@variables = {} 
   object.instance_variables.each do |var| 
    @@variables[var] = object.instance_variable_get(var) unless 
var.starts_with?("@_") || var == "@method_name" 
   end 
  end 
 end 
end 
 
class FooSpecHoover 
 @@variables = false 
 
 def self.empty? 
  !@@variables 
 end 
 
 def self.save_instance_variables(object, &block) 
  if @@variables 
   @@variables.each do |var, val| 
    object.instance_variable_set(var, val) 
   end 
  else 
   yield 
   @@variables = {} 
   object.instance_variables.each do |var| 
    @@variables[var] = object.instance_variable_get(var) unless 
var.starts_with?("@_") || var == "@method_name" 
   end 
  end 
 end 
end 
 
describe "Bar" do 
 include GenericSpecHelper 
 
 before(:all) do 
  if BarSpecHoover.empty? 
   User.delete_all 
   Project.delete_all 
  end 
  BarSpecHoover.save_instance_variables(self) do 
   @user = confirmed_user_with_details("bar-test-01") 
   puts "Running Before-All 1" 
  end 
 end 
 
 describe "seriously" do 
  it "should behave correctly" do 
   @user.should_not be_nil 
   @project.should be_nil 
  end 
 end 
 
 it "should behave correctly" do 
  @user.should_not be_nil 
  @project.should be_nil 
 end 
end 
 
describe "Foo" do 
 include GenericSpecHelper 
 
 before(:all) do 
  if FooSpecHoover.empty? 
   User.delete_all 
   Project.delete_all 
  end 
  FooSpecHoover.save_instance_variables(self) do 
   @user = confirmed_user_with_details("foo-test-01") 
   @project = create_project 
   @user = nil 
   puts "Running Before-All 2" 
  end 
 end 
 
 describe "seriously" do 
  it "should behave correctly" do 
   @user.should be_nil 
   @project.should_not be_nil 
  end 
 end 
 
 it "should behave correctly" do 
  @user.should be_nil 
  @project.should_not be_nil 
 end 
end 

 
I'll grant you that it's not the prettiest thing in the world, but at least it works, and can save oodles of time on your specs. I hope it helps someone!

Comments [0]

Rails: Rendering templates outside of a controller

There are times when you need to render a view template outside of the context of a controller.

Yes, most of the time, if you think "I want to render this in my mode" you're Doing It Wrong. So, before you do this, think very carefully whether this rendering really belongs outside of a controller. Most of the time, it doesn't.

Here's an example from Woobius. I've been working on a Project Archive functionality. For the project archives to be useful, they should not only include files from the project, but also issue sheets, audit logs, and other meta-information. The result of the archival process is a zip file containing all the files in the project, as well as all that meta-information - ideally in an easily accessible format like, say, HTML.

Archiving a project that might contain several gigabytes of architectural drawings is obviously not something that should be happening in a controller. It's a fire, forget, and receive a DVD in the post a few days later kind of process. Rendering HTML files in a Rails application with anything other than ERB (or HAML, etc if you swing that way) would be silly. So, this is a perfectly legitimate case where we need to render ERB templates outside of the context of a controller.

A long time ago, I worked on an application where I had to rush to figure out how to do this, so I ended up spawning a test-controller from within the console context and using that to render the page. That had a number of issues which I won't get into in this article, but the main one was probably the fact that it was really ugly and nasty - a hack of the wrong kind. 

That was then, and this is now, and I thought I'd figure out the correct way of doing this. Perhaps my Google-fu is lacking, but I didn't find all that much help on this topic, until I stumbled on this page. In short, it gives examples of how to render an ERB in a controller-free context. I thought it'd be quite simple to use lifo's code, but it turned out to have a big problem.

You see, when I render views, I like to use my carefully constructed helpers, not juggle plain ERB and HTML. So I included my helpers:

require 'erb'
 require 'ostruct'
 
 def render_template(template, params)
  vars = OpenStruct.new params
  class << vars
   include CustomHelper, ApplicationHelper, ActionView::Helpers::TextHelper
  end
  template = File.read("#{TEMPLATE_ROOT}/#{template_path}.html.erb")
  puts ERB.new(template).result(vars.send(:binding))
 end

However, getting this to work involved all sorts of 'fun' because of the output_buffer is not provided to the template, by ERB, and so the concat TextHelper method doesn't work. Without concat, most interesting helpers fall on their nose. There follows a lengthy session of working my way through ERB's internals to find out how to expose that output_buffer. It turns out ERB is pure evil stuff (though I'm sure it all made sense at the time). It renders the template into a string, which it then evals. You can set the output_buffer variable name, but try as I might I couldn't get the template to have access to it.

Luckily, though, while I was desperately tracing through the Rails code and doing a lot of global searches, I found a file, compiled_templates_test.rb, which involved rendering some templates. And, lo and behold, in the tests, a completely different rendering method was used:

 assert_equal "Hello world!", render(:file => "test/hello_world.erb")
 def render(*args)
  view_paths = @explicit_view_paths || ActionController::Base.view_paths
  ActionView::Base.new(view_paths, {}).render(*args)
 end
 

Aha!

It turns out, this is by far the easier way to render templates, in a rails application, outside of a controller. So here's the final code, for anyone to reuse as they see fit. Hopefully it will save some other hapless soul a couple of hours of poking around:

 def render_erb(template_path, params)
  view = ActionView::Base.new(ActionController::Base.view_paths, {})
 
  class << view
   include EmailHelper, ApplicationHelper
  end
 
  view.render(:file => "#{template_path}.html.erb", :locals => params)
 end
 

Comments [4]

Getting SecureTrading's XPay running locally on the Mac (Leopard)

So, you've decided to use SecureTrading's XPay gateway as your gateway (as I recently have, having had problems with my previous gateway, Protx, and their awful customer support). Your next step is to want to write some code and connect it up to the XPay server.

Here's the code you need to write to get XPay started (funnily enough, you don't need any of the -Dsecurity.manager things that they mention in the docs):

java -cp ./XPay.jar XPay

That's nice and neat, but when you run the test code, you'll get the following result:

danieltenner@daniel-tenners-macbook-pro:~/Downloads/xpay/xpay/example$ java Test /somepath/xpay-certs/sometest1234certs.pem
** Note: When using live certificates, they should be kept in secure place on your server **
Reading from file: /somepath/xpay-certs/sometest1234certs.pem 
Reading from file: test.xml
Please wait while the transaction is authorised...
HTTP/1.1 200 OK
<ResponseBlock>
<Response Type="Not Given">
<OperationResponse>
<Result>0</Result>
<Message>(1000) Failed to connect to a payment gatewayjava.io.IOException: Connection to gateway failed</Message>
</OperationResponse>
<Order>Omitted</Order>
<Optional>Omitted</Optional>
</Response>
</ResponseBlock>

Oops. It turns out that when SecureTrading say they want Java 1.4.2, they really mean it. So how do you get that going? Well, it's easier than it sounds, actually. Just open up "Java preferences" using QuickSilver or Spotlight, and you can just drag the java 1.4.2 to the top spot on the bottom list, like so:

Great. So then you run your test, and you get... another error, perhaps something like:

<ResponseBlock Live="" Version="3.51">
  <Response Type="AUTH">
    <OperationResponse>
      <Message>(3100) Invalid SiteReference for certificate</Message>
      <TransactionReference>0-0-0</TransactionReference>
      <Result>0</Result>
    </OperationResponse>
  </Response>
</ResponseBlock>

If you get that, it just means you forgot to update the SiteReference in the xml file. If you received a certificate file that looked like company1234testcerts.pem, that means your site reference is company1234. Replace it in test.xml, and you may (if they haven't fixed that by the time you read this article), get the next error!

<ResponseBlock Live="FALSE" Version="3.51">
  <Response Type="AUTH">
    <OperationResponse>
      <Message>(3100) Invalid ExpiryDate</Message>
      <TransactionReference>17-9-1155953</TransactionReference>
      <Result>0</Result>
    </OperationResponse>
  </Response>
</ResponseBlock>

This one's more obvious... when I last downloaded the XPay package (in 2009), the credit card expiry date in the test.xml file was set to 02/08! Change that to a date in the future, and you should finally get the expected result:

<ResponseBlock Live="FALSE" Version="3.51">
  <Response Type="AUTH">
    <OperationResponse>
      <TransactionReference>12-3-4567890</TransactionReference>
      <TransactionCompletedTimestamp>2009-04-14 15:22:20</TransactionCompletedTimestamp>
      <AuthCode>AUTH CODE:TEST</AuthCode>
      <TransactionVerifier>Aw6BrpfpS+3o2t7h4/c7daA9NShF42ZYBtRVK3kVRW8F4/LGUzP5my2FawzJDULJaKXTwUE2CjHupzYDKWddlPP0nlndJKBGwQIt0NuK8QFfLS/w+TFxRuesFvcWBf2yd+nYmjVSudb3i4PurVQpkwOjvbE3uVsQnzaSzHF7Z6yY=</TransactionVerifier>
      <Result>1</Result>
      <SettleStatus>0</SettleStatus>
      <SecurityResponseSecurityCode>2</SecurityResponseSecurityCode>
      <SecurityResponsePostCode>2</SecurityResponsePostCode>
      <SecurityResponseAddress>2</SecurityResponseAddress>
    </OperationResponse>
    <Order>
      <OrderInformation>This is a test order</OrderInformation>
      <OrderReference>Order0001</OrderReference>
    </Order>
  </Response>
</ResponseBlock>

Comments [2]

Setting up Terminal.app with transparent background colours via AppleScript

So, you want to have transparent background colours, specific to each server, on your terminal, so that you won't confuse your production server with your local test VM?

Easy.

Of course, you could follow this tutorial or this tutorial to set your background colour directly via AppleScript. Unfortunately, there's a problem with that: in their infinite wisdom, Apple decided to remove the transparency argument from the "set background color" hook, so that whenever you set the colour programmatically, you'll lose the terminal transparency.

And what's the point of having a shiny new glossy macbook pro if you can't have transparent terminal windows, eh? What would be the point of that? We demand our transparency! 

Some heretics might suggest doing something like this, which basically involves getting AppleScript to actually open up the window settings and click on the sliders to enable the transparency. Oh, and each step is separated from other steps by a 1 second delay. Insane! We demand not only transparency, we demand instantaneous transparency. We programmers are a busy lot, and have no time to wait several seconds for a dumb terminal to adjust its sliders.

Well, here it is.

Step 1: Create a Terminal.app profile that has all the transparency you want, and all the other tidbits set correctly, in the "Settings" tab of the Terminal.app settings. Call this profile 'Black'.

Step 2: Duplicate that profile, and adjust the colour so it's a nice dark red. Call this one 'Red'.

Step 3: Duplicate 'Red' a bunch of times and change the colour each time, until you have all the essential colours... Red, blue, cyan, green, purple, and yellow.

Step 3: Create a bunch of aliases in your .bashrc or your .bash_aliases, that do something like this:

alias t.green='echo "tell application \"Terminal\" to set current settings of first window to settings set 4" | osascript'

Since there's no way to tell "setting set"s by name, you'll unfortunately have to play around a little bit until you match up the alias names with the profiles.

Done!

You can now prefix aliases that connect to your favourite servers with "t.red" or "t.blue" or whatever strikes your fancy. For example:

alias srv_production="t.red; ssh production"

Ain't that a beauty?

Daniel

Comments [0]

Sprout: undefined method `register_service' for Net::SSH:Module

Worth noting:

When running a sprout compilation on a fresh machine, you might get the error:

undefined method `register_service' for Net::SSH:Module

The solution to this is to have both net-ssh and net-sftp updated to the latest version, via a simple gem install.

Hope this helps someone!

Daniel

Comments [1]

Sending mail On Behalf Of with Rails

This is probably helpful to non-rails users too.
 
Today, I looked into reproducing a feature of some document management systems which was pointed out to me by a colleague.

Sending mail "On behalf of" is good for sending invitations, files, etc... it means that when the user clicks "Reply" the reply will go to the right email, and it means that they are more likely to pay attention to that email since it looks like it's coming from someone they probably know.
 
Anyway, so how to do that? There are many resources out there that tell you how to do this, but none of them are particularly clear and they don't give an example of how to test that it worked, where to test it, etc. All the advice I found was summarised as "Add a Sender header", without even much guidance as to what you're supposed to put in there. I guessed, wrongly, that the user's email was supposed to go into "Sender".
 
So, anyway, here's what it's supposed to be:
 
[code lang="ruby"]
@from = "#{sent_by.name} <#{sent_by.email}>"
@headers["Sender"] = "Your App <yourapp@yourapp.com>"
[code]
 
So, the "From" header needs to be the email of the user on behalf of whom the email is being sent, while the Sender email needs to be your application's email.
 
What will happen then?
 
Well, as far as Mail.app, gmail, and Outlook Express are concerned, not much. They don't display the "On Behalf Of" / "Sent By", so testing on those clients won't help you:

However, if you test on Entourage or Outlook, you'll get the desired behaviour (though Entourage words it differently):

Hope this helps others - I would have liked to read this post before I got started on this task!
 
Daniel

Comments [0]

Rails Migration for a list of countries with ISO codes

It seems every time I write a new web application I eventually hit upon the dreaded "I need a list of countries!" issue. And each time I end up spending 20 minutes on the web looking for an easily downloadable list.

Well, this post is as much for my reference as for yours.

There are two main formats I might be interested in for this list of countries: raw SQL, and a Rails migration.

The raw SQL is usually easy to find. Here's the link to it: Country name and ISO code SQL import.

As for the Rails migration, it took a little bit more digging, but here it is, on DZone: Rails countries migration.

But.. but.. but.. What if you don't want to create a database table for this? What if it's something that you don't access that incredibly often and which really doesn't need to be in your db. Or what if you're writing a ruby script that doesn't have access to a db?

Enter: the rails migration, un-migrationed.

class Country
  COUNTRIES_LIST = [{:iso => 'AF', :name => 'AFGHANISTAN', :printable_name => 'Afghanistan', :iso3 => 'AFG', :numcode => '004'},
    {:iso => 'AL', :name => 'ALBANIA', :printable_name => 'Albania', :iso3 => 'ALB', :numcode => '008'},
    {:iso => 'DZ', :name => 'ALGERIA', :printable_name => 'Algeria', :iso3 => 'DZA', :numcode => '012'},
    {:iso => 'AS', :name => 'AMERICAN SAMOA', :printable_name => 'American Samoa', :iso3 => 'ASM', :numcode => '016'},
    {:iso => 'AD', :name => 'ANDORRA', :printable_name => 'Andorra', :iso3 => 'AND', :numcode => '020'},
    {:iso => 'AO', :name => 'ANGOLA', :printable_name => 'Angola', :iso3 => 'AGO', :numcode => '024'},
    {:iso => 'AI', :name => 'ANGUILLA', :printable_name => 'Anguilla', :iso3 => 'AIA', :numcode => '660'},
    {:iso => 'AG', :name => 'ANTIGUA AND BARBUDA', :printable_name => 'Antigua and Barbuda', :iso3 => 'ATG', :numcode => '028'},
    {:iso => 'AR', :name => 'ARGENTINA', :printable_name => 'Argentina', :iso3 => 'ARG', :numcode => '032'},
    {:iso => 'AM', :name => 'ARMENIA', :printable_name => 'Armenia', :iso3 => 'ARM', :numcode => '051'},
    {:iso => 'AW', :name => 'ARUBA', :printable_name => 'Aruba', :iso3 => 'ABW', :numcode => '533'},
    {:iso => 'AU', :name => 'AUSTRALIA', :printable_name => 'Australia', :iso3 => 'AUS', :numcode => '036'},
    {:iso => 'AT', :name => 'AUSTRIA', :printable_name => 'Austria', :iso3 => 'AUT', :numcode => '040'},
    {:iso => 'AZ', :name => 'AZERBAIJAN', :printable_name => 'Azerbaijan', :iso3 => 'AZE', :numcode => '031'},
    {:iso => 'BS', :name => 'BAHAMAS', :printable_name => 'Bahamas', :iso3 => 'BHS', :numcode => '044'},
    {:iso => 'BH', :name => 'BAHRAIN', :printable_name => 'Bahrain', :iso3 => 'BHR', :numcode => '048'},
    {:iso => 'BD', :name => 'BANGLADESH', :printable_name => 'Bangladesh', :iso3 => 'BGD', :numcode => '050'},
    {:iso => 'BB', :name => 'BARBADOS', :printable_name => 'Barbados', :iso3 => 'BRB', :numcode => '052'},
    {:iso => 'BY', :name => 'BELARUS', :printable_name => 'Belarus', :iso3 => 'BLR', :numcode => '112'},
    {:iso => 'BE', :name => 'BELGIUM', :printable_name => 'Belgium', :iso3 => 'BEL', :numcode => '056'},
    {:iso => 'BZ', :name => 'BELIZE', :printable_name => 'Belize', :iso3 => 'BLZ', :numcode => '084'},
    {:iso => 'BJ', :name => 'BENIN', :printable_name => 'Benin', :iso3 => 'BEN', :numcode => '204'},
    {:iso => 'BM', :name => 'BERMUDA', :printable_name => 'Bermuda', :iso3 => 'BMU', :numcode => '060'},
    {:iso => 'BT', :name => 'BHUTAN', :printable_name => 'Bhutan', :iso3 => 'BTN', :numcode => '064'},
    {:iso => 'BO', :name => 'BOLIVIA', :printable_name => 'Bolivia', :iso3 => 'BOL', :numcode => '068'},
    {:iso => 'BA', :name => 'BOSNIA AND HERZEGOVINA', :printable_name => 'Bosnia and Herzegovina', :iso3 => 'BIH', :numcode => '070'},
    {:iso => 'BW', :name => 'BOTSWANA', :printable_name => 'Botswana', :iso3 => 'BWA', :numcode => '072'},
    {:iso => 'BR', :name => 'BRAZIL', :printable_name => 'Brazil', :iso3 => 'BRA', :numcode => '076'},
    {:iso => 'BN', :name => 'BRUNEI DARUSSALAM', :printable_name => 'Brunei Darussalam', :iso3 => 'BRN', :numcode => '096'},
    {:iso => 'BG', :name => 'BULGARIA', :printable_name => 'Bulgaria', :iso3 => 'BGR', :numcode => '100'},
    {:iso => 'BF', :name => 'BURKINA FASO', :printable_name => 'Burkina Faso', :iso3 => 'BFA', :numcode => '854'},
    {:iso => 'BI', :name => 'BURUNDI', :printable_name => 'Burundi', :iso3 => 'BDI', :numcode => '108'},
    {:iso => 'KH', :name => 'CAMBODIA', :printable_name => 'Cambodia', :iso3 => 'KHM', :numcode => '116'},
    {:iso => 'CM', :name => 'CAMEROON', :printable_name => 'Cameroon', :iso3 => 'CMR', :numcode => '120'},
    {:iso => 'CA', :name => 'CANADA', :printable_name => 'Canada', :iso3 => 'CAN', :numcode => '124'},
    {:iso => 'CV', :name => 'CAPE VERDE', :printable_name => 'Cape Verde', :iso3 => 'CPV', :numcode => '132'},
    {:iso => 'KY', :name => 'CAYMAN ISLANDS', :printable_name => 'Cayman Islands', :iso3 => 'CYM', :numcode => '136'},
    {:iso => 'CF', :name => 'CENTRAL AFRICAN REPUBLIC', :printable_name => 'Central African Republic', :iso3 => 'CAF', :numcode => '140'},
    {:iso => 'TD', :name => 'CHAD', :printable_name => 'Chad', :iso3 => 'TCD', :numcode => '148'},
    {:iso => 'CL', :name => 'CHILE', :printable_name => 'Chile', :iso3 => 'CHL', :numcode => '152'},
    {:iso => 'CN', :name => 'CHINA', :printable_name => 'China', :iso3 => 'CHN', :numcode => '156'},
    {:iso => 'CO', :name => 'COLOMBIA', :printable_name => 'Colombia', :iso3 => 'COL', :numcode => '170'},
    {:iso => 'KM', :name => 'COMOROS', :printable_name => 'Comoros', :iso3 => 'COM', :numcode => '174'},
    {:iso => 'CG', :name => 'CONGO', :printable_name => 'Congo', :iso3 => 'COG', :numcode => '178'},
    {:iso => 'CD', :name => 'CONGO, THE DEMOCRATIC REPUBLIC OF THE', :printable_name => 'Congo, the Democratic Republic of the', :iso3 => 'COD', :numcode => '180'},
    {:iso => 'CK', :name => 'COOK ISLANDS', :printable_name => 'Cook Islands', :iso3 => 'COK', :numcode => '184'},
    {:iso => 'CR', :name => 'COSTA RICA', :printable_name => 'Costa Rica', :iso3 => 'CRI', :numcode => '188'},
    {:iso => 'CI', :name => 'COTE D\'IVOIRE', :printable_name => 'Cote D\'Ivoire', :iso3 => 'CIV', :numcode => '384'},
    {:iso => 'HR', :name => 'CROATIA', :printable_name => 'Croatia', :iso3 => 'HRV', :numcode => '191'},
    {:iso => 'CU', :name => 'CUBA', :printable_name => 'Cuba', :iso3 => 'CUB', :numcode => '192'},
    {:iso => 'CY', :name => 'CYPRUS', :printable_name => 'Cyprus', :iso3 => 'CYP', :numcode => '196'},
    {:iso => 'CZ', :name => 'CZECH REPUBLIC', :printable_name => 'Czech Republic', :iso3 => 'CZE', :numcode => '203'},
    {:iso => 'DK', :name => 'DENMARK', :printable_name => 'Denmark', :iso3 => 'DNK', :numcode => '208'},
    {:iso => 'DJ', :name => 'DJIBOUTI', :printable_name => 'Djibouti', :iso3 => 'DJI', :numcode => '262'},
    {:iso => 'DM', :name => 'DOMINICA', :printable_name => 'Dominica', :iso3 => 'DMA', :numcode => '212'},
    {:iso => 'DO', :name => 'DOMINICAN REPUBLIC', :printable_name => 'Dominican Republic', :iso3 => 'DOM', :numcode => '214'},
    {:iso => 'EC', :name => 'ECUADOR', :printable_name => 'Ecuador', :iso3 => 'ECU', :numcode => '218'},
    {:iso => 'EG', :name => 'EGYPT', :printable_name => 'Egypt', :iso3 => 'EGY', :numcode => '818'},
    {:iso => 'SV', :name => 'EL SALVADOR', :printable_name => 'El Salvador', :iso3 => 'SLV', :numcode => '222'},
    {:iso => 'GQ', :name => 'EQUATORIAL GUINEA', :printable_name => 'Equatorial Guinea', :iso3 => 'GNQ', :numcode => '226'},
    {:iso => 'ER', :name => 'ERITREA', :printable_name => 'Eritrea', :iso3 => 'ERI', :numcode => '232'},
    {:iso => 'EE', :name => 'ESTONIA', :printable_name => 'Estonia', :iso3 => 'EST', :numcode => '233'},
    {:iso => 'ET', :name => 'ETHIOPIA', :printable_name => 'Ethiopia', :iso3 => 'ETH', :numcode => '231'},
    {:iso => 'FK', :name => 'FALKLAND ISLANDS (MALVINAS)', :printable_name => 'Falkland Islands (Malvinas)', :iso3 => 'FLK', :numcode => '238'},
    {:iso => 'FO', :name => 'FAROE ISLANDS', :printable_name => 'Faroe Islands', :iso3 => 'FRO', :numcode => '234'},
    {:iso => 'FJ', :name => 'FIJI', :printable_name => 'Fiji', :iso3 => 'FJI', :numcode => '242'},
    {:iso => 'FI', :name => 'FINLAND', :printable_name => 'Finland', :iso3 => 'FIN', :numcode => '246'},
    {:iso => 'FR', :name => 'FRANCE', :printable_name => 'France', :iso3 => 'FRA', :numcode => '250'},
    {:iso => 'GF', :name => 'FRENCH GUIANA', :printable_name => 'French Guiana', :iso3 => 'GUF', :numcode => '254'},
    {:iso => 'PF', :name => 'FRENCH POLYNESIA', :printable_name => 'French Polynesia', :iso3 => 'PYF', :numcode => '258'},
    {:iso => 'GA', :name => 'GABON', :printable_name => 'Gabon', :iso3 => 'GAB', :numcode => '266'},
    {:iso => 'GM', :name => 'GAMBIA', :printable_name => 'Gambia', :iso3 => 'GMB', :numcode => '270'},
    {:iso => 'GE', :name => 'GEORGIA', :printable_name => 'Georgia', :iso3 => 'GEO', :numcode => '268'},
    {:iso => 'DE', :name => 'GERMANY', :printable_name => 'Germany', :iso3 => 'DEU', :numcode => '276'},
    {:iso => 'GH', :name => 'GHANA', :printable_name => 'Ghana', :iso3 => 'GHA', :numcode => '288'},
    {:iso => 'GI', :name => 'GIBRALTAR', :printable_name => 'Gibraltar', :iso3 => 'GIB', :numcode => '292'},
    {:iso => 'GR', :name => 'GREECE', :printable_name => 'Greece', :iso3 => 'GRC', :numcode => '300'},
    {:iso => 'GL', :name => 'GREENLAND', :printable_name => 'Greenland', :iso3 => 'GRL', :numcode => '304'},
    {:iso => 'GD', :name => 'GRENADA', :printable_name => 'Grenada', :iso3 => 'GRD', :numcode => '308'},
    {:iso => 'GP', :name => 'GUADELOUPE', :printable_name => 'Guadeloupe', :iso3 => 'GLP', :numcode => '312'},
    {:iso => 'GU', :name => 'GUAM', :printable_name => 'Guam', :iso3 => 'GUM', :numcode => '316'},
    {:iso => 'GT', :name => 'GUATEMALA', :printable_name => 'Guatemala', :iso3 => 'GTM', :numcode => '320'},
    {:iso => 'GN', :name => 'GUINEA', :printable_name => 'Guinea', :iso3 => 'GIN', :numcode => '324'},
    {:iso => 'GW', :name => 'GUINEA-BISSAU', :printable_name => 'Guinea-Bissau', :iso3 => 'GNB', :numcode => '624'},
    {:iso => 'GY', :name => 'GUYANA', :printable_name => 'Guyana', :iso3 => 'GUY', :numcode => '328'},
    {:iso => 'HT', :name => 'HAITI', :printable_name => 'Haiti', :iso3 => 'HTI', :numcode => '332'},
    {:iso => 'VA', :name => 'HOLY SEE (VATICAN CITY STATE)', :printable_name => 'Holy See (Vatican City State)', :iso3 => 'VAT', :numcode => '336'},
    {:iso => 'HN', :name => 'HONDURAS', :printable_name => 'Honduras', :iso3 => 'HND', :numcode => '340'},
    {:iso => 'HK', :name => 'HONG KONG', :printable_name => 'Hong Kong', :iso3 => 'HKG', :numcode => '344'},
    {:iso => 'HU', :name => 'HUNGARY', :printable_name => 'Hungary', :iso3 => 'HUN', :numcode => '348'},
    {:iso => 'IS', :name => 'ICELAND', :printable_name => 'Iceland', :iso3 => 'ISL', :numcode => '352'},
    {:iso => 'IN', :name => 'INDIA', :printable_name => 'India', :iso3 => 'IND', :numcode => '356'},
    {:iso => 'ID', :name => 'INDONESIA', :printable_name => 'Indonesia', :iso3 => 'IDN', :numcode => '360'},
    {:iso => 'IR', :name => 'IRAN, ISLAMIC REPUBLIC OF', :printable_name => 'Iran, Islamic Republic of', :iso3 => 'IRN', :numcode => '364'},
    {:iso => 'IQ', :name => 'IRAQ', :printable_name => 'Iraq', :iso3 => 'IRQ', :numcode => '368'},
    {:iso => 'IE', :name => 'IRELAND', :printable_name => 'Ireland', :iso3 => 'IRL', :numcode => '372'},
    {:iso => 'IL', :name => 'ISRAEL', :printable_name => 'Israel', :iso3 => 'ISR', :numcode => '376'},
    {:iso => 'IT', :name => 'ITALY', :printable_name => 'Italy', :iso3 => 'ITA', :numcode => '380'},
    {:iso => 'JM', :name => 'JAMAICA', :printable_name => 'Jamaica', :iso3 => 'JAM', :numcode => '388'},
    {:iso => 'JP', :name => 'JAPAN', :printable_name => 'Japan', :iso3 => 'JPN', :numcode => '392'},
    {:iso => 'JO', :name => 'JORDAN', :printable_name => 'Jordan', :iso3 => 'JOR', :numcode => '400'},
    {:iso => 'KZ', :name => 'KAZAKHSTAN', :printable_name => 'Kazakhstan', :iso3 => 'KAZ', :numcode => '398'},
    {:iso => 'KE', :name => 'KENYA', :printable_name => 'Kenya', :iso3 => 'KEN', :numcode => '404'},
    {:iso => 'KI', :name => 'KIRIBATI', :printable_name => 'Kiribati', :iso3 => 'KIR', :numcode => '296'},
    {:iso => 'KP', :name => 'KOREA, DEMOCRATIC PEOPLE\'S REPUBLIC OF', :printable_name => 'Korea, Democratic People\'s Republic of', :iso3 => 'PRK', :numcode => '408'},
    {:iso => 'KR', :name => 'KOREA, REPUBLIC OF', :printable_name => 'Korea, Republic of', :iso3 => 'KOR', :numcode => '410'},
    {:iso => 'KW', :name => 'KUWAIT', :printable_name => 'Kuwait', :iso3 => 'KWT', :numcode => '414'},
    {:iso => 'KG', :name => 'KYRGYZSTAN', :printable_name => 'Kyrgyzstan', :iso3 => 'KGZ', :numcode => '417'},
    {:iso => 'LA', :name => 'LAO PEOPLE\'S DEMOCRATIC REPUBLIC', :printable_name => 'Lao People\'s Democratic Republic', :iso3 => 'LAO', :numcode => '418'},
    {:iso => 'LV', :name => 'LATVIA', :printable_name => 'Latvia', :iso3 => 'LVA', :numcode => '428'},
    {:iso => 'LB', :name => 'LEBANON', :printable_name => 'Lebanon', :iso3 => 'LBN', :numcode => '422'},
    {:iso => 'LS', :name => 'LESOTHO', :printable_name => 'Lesotho', :iso3 => 'LSO', :numcode => '426'},
    {:iso => 'LR', :name => 'LIBERIA', :printable_name => 'Liberia', :iso3 => 'LBR', :numcode => '430'},
    {:iso => 'LY', :name => 'LIBYAN ARAB JAMAHIRIYA', :printable_name => 'Libyan Arab Jamahiriya', :iso3 => 'LBY', :numcode => '434'},
    {:iso => 'LI', :name => 'LIECHTENSTEIN', :printable_name => 'Liechtenstein', :iso3 => 'LIE', :numcode => '438'},
    {:iso => 'LT', :name => 'LITHUANIA', :printable_name => 'Lithuania', :iso3 => 'LTU', :numcode => '440'},
    {:iso => 'LU', :name => 'LUXEMBOURG', :printable_name => 'Luxembourg', :iso3 => 'LUX', :numcode => '442'},
    {:iso => 'MO', :name => 'MACAO', :printable_name => 'Macao', :iso3 => 'MAC', :numcode => '446'},
    {:iso => 'MK', :name => 'MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF', :printable_name => 'Macedonia, the Former Yugoslav Republic of', :iso3 => 'MKD', :numcode => '807'},
    {:iso => 'MG', :name => 'MADAGASCAR', :printable_name => 'Madagascar', :iso3 => 'MDG', :numcode => '450'},
    {:iso => 'MW', :name => 'MALAWI', :printable_name => 'Malawi', :iso3 => 'MWI', :numcode => '454'},
    {:iso => 'MY', :name => 'MALAYSIA', :printable_name => 'Malaysia', :iso3 => 'MYS', :numcode => '458'},
    {:iso => 'MV', :name => 'MALDIVES', :printable_name => 'Maldives', :iso3 => 'MDV', :numcode => '462'},
    {:iso => 'ML', :name => 'MALI', :printable_name => 'Mali', :iso3 => 'MLI', :numcode => '466'},
    {:iso => 'MT', :name => 'MALTA', :printable_name => 'Malta', :iso3 => 'MLT', :numcode => '470'},
    {:iso => 'MH', :name => 'MARSHALL ISLANDS', :printable_name => 'Marshall Islands', :iso3 => 'MHL', :numcode => '584'},
    {:iso => 'MQ', :name => 'MARTINIQUE', :printable_name => 'Martinique', :iso3 => 'MTQ', :numcode => '474'},
    {:iso => 'MR', :name => 'MAURITANIA', :printable_name => 'Mauritania', :iso3 => 'MRT', :numcode => '478'},
    {:iso => 'MU', :name => 'MAURITIUS', :printable_name => 'Mauritius', :iso3 => 'MUS', :numcode => '480'},
    {:iso => 'MX', :name => 'MEXICO', :printable_name => 'Mexico', :iso3 => 'MEX', :numcode => '484'},
    {:iso => 'FM', :name => 'MICRONESIA, FEDERATED STATES OF', :printable_name => 'Micronesia, Federated States of', :iso3 => 'FSM', :numcode => '583'},
    {:iso => 'MD', :name => 'MOLDOVA, REPUBLIC OF', :printable_name => 'Moldova, Republic of', :iso3 => 'MDA', :numcode => '498'},
    {:iso => 'MC', :name => 'MONACO', :printable_name => 'Monaco', :iso3 => 'MCO', :numcode => '492'},
    {:iso => 'MN', :name => 'MONGOLIA', :printable_name => 'Mongolia', :iso3 => 'MNG', :numcode => '496'},
    {:iso => 'MS', :name => 'MONTSERRAT', :printable_name => 'Montserrat', :iso3 => 'MSR', :numcode => '500'},
    {:iso => 'MA', :name => 'MOROCCO', :printable_name => 'Morocco', :iso3 => 'MAR', :numcode => '504'},
    {:iso => 'MZ', :name => 'MOZAMBIQUE', :printable_name => 'Mozambique', :iso3 => 'MOZ', :numcode => '508'},
    {:iso => 'MM', :name => 'MYANMAR', :printable_name => 'Myanmar', :iso3 => 'MMR', :numcode => '104'},
    {:iso => 'NA', :name => 'NAMIBIA', :printable_name => 'Namibia', :iso3 => 'NAM', :numcode => '516'},
    {:iso => 'NR', :name => 'NAURU', :printable_name => 'Nauru', :iso3 => 'NRU', :numcode => '520'},
    {:iso => 'NP', :name => 'NEPAL', :printable_name => 'Nepal', :iso3 => 'NPL', :numcode => '524'},
    {:iso => 'NL', :name => 'NETHERLANDS', :printable_name => 'Netherlands', :iso3 => 'NLD', :numcode => '528'},
    {:iso => 'AN', :name => 'NETHERLANDS ANTILLES', :printable_name => 'Netherlands Antilles', :iso3 => 'ANT', :numcode => '530'},
    {:iso => 'NC', :name => 'NEW CALEDONIA', :printable_name => 'New Caledonia', :iso3 => 'NCL', :numcode => '540'},
    {:iso => 'NZ', :name => 'NEW ZEALAND', :printable_name => 'New Zealand', :iso3 => 'NZL', :numcode => '554'},
    {:iso => 'NI', :name => 'NICARAGUA', :printable_name => 'Nicaragua', :iso3 => 'NIC', :numcode => '558'},
    {:iso => 'NE', :name => 'NIGER', :printable_name => 'Niger', :iso3 => 'NER', :numcode => '562'},
    {:iso => 'NG', :name => 'NIGERIA', :printable_name => 'Nigeria', :iso3 => 'NGA', :numcode => '566'},
    {:iso => 'NU', :name => 'NIUE', :printable_name => 'Niue', :iso3 => 'NIU', :numcode => '570'},
    {:iso => 'NF', :name => 'NORFOLK ISLAND', :printable_name => 'Norfolk Island', :iso3 => 'NFK', :numcode => '574'},
    {:iso => 'MP', :name => 'NORTHERN MARIANA ISLANDS', :printable_name => 'Northern Mariana Islands', :iso3 => 'MNP', :numcode => '580'},
    {:iso => 'NO', :name => 'NORWAY', :printable_name => 'Norway', :iso3 => 'NOR', :numcode => '578'},
    {:iso => 'OM', :name => 'OMAN', :printable_name => 'Oman', :iso3 => 'OMN', :numcode => '512'},
    {:iso => 'PK', :name => 'PAKISTAN', :printable_name => 'Pakistan', :iso3 => 'PAK', :numcode => '586'},
    {:iso => 'PW', :name => 'PALAU', :printable_name => 'Palau', :iso3 => 'PLW', :numcode => '585'},
    {:iso => 'PA', :name => 'PANAMA', :printable_name => 'Panama', :iso3 => 'PAN', :numcode => '591'},
    {:iso => 'PG', :name => 'PAPUA NEW GUINEA', :printable_name => 'Papua New Guinea', :iso3 => 'PNG', :numcode => '598'},
    {:iso => 'PY', :name => 'PARAGUAY', :printable_name => 'Paraguay', :iso3 => 'PRY', :numcode => '600'},
    {:iso => 'PE', :name => 'PERU', :printable_name => 'Peru', :iso3 => 'PER', :numcode => '604'},
    {:iso => 'PH', :name => 'PHILIPPINES', :printable_name => 'Philippines', :iso3 => 'PHL', :numcode => '608'},
    {:iso => 'PN', :name => 'PITCAIRN', :printable_name => 'Pitcairn', :iso3 => 'PCN', :numcode => '612'},
    {:iso => 'PL', :name => 'POLAND', :printable_name => 'Poland', :iso3 => 'POL', :numcode => '616'},
    {:iso => 'PT', :name => 'PORTUGAL', :printable_name => 'Portugal', :iso3 => 'PRT', :numcode => '620'},
    {:iso => 'PR', :name => 'PUERTO RICO', :printable_name => 'Puerto Rico', :iso3 => 'PRI', :numcode => '630'},
    {:iso => 'QA', :name => 'QATAR', :printable_name => 'Qatar', :iso3 => 'QAT', :numcode => '634'},
    {:iso => 'RE', :name => 'REUNION', :printable_name => 'Reunion', :iso3 => 'REU', :numcode => '638'},
    {:iso => 'RO', :name => 'ROMANIA', :printable_name => 'Romania', :iso3 => 'ROM', :numcode => '642'},
    {:iso => 'RU', :name => 'RUSSIAN FEDERATION', :printable_name => 'Russian Federation', :iso3 => 'RUS', :numcode => '643'},
    {:iso => 'RW', :name => 'RWANDA', :printable_name => 'Rwanda', :iso3 => 'RWA', :numcode => '646'},
    {:iso => 'SH', :name => 'SAINT HELENA', :printable_name => 'Saint Helena', :iso3 => 'SHN', :numcode => '654'},
    {:iso => 'KN', :name => 'SAINT KITTS AND NEVIS', :printable_name => 'Saint Kitts and Nevis', :iso3 => 'KNA', :numcode => '659'},
    {:iso => 'LC', :name => 'SAINT LUCIA', :printable_name => 'Saint Lucia', :iso3 => 'LCA', :numcode => '662'},
    {:iso => 'PM', :name => 'SAINT PIERRE AND MIQUELON', :printable_name => 'Saint Pierre and Miquelon', :iso3 => 'SPM', :numcode => '666'},
    {:iso => 'VC', :name => 'SAINT VINCENT AND THE GRENADINES', :printable_name => 'Saint Vincent and the Grenadines', :iso3 => 'VCT', :numcode => '670'},
    {:iso => 'WS', :name => 'SAMOA', :printable_name => 'Samoa', :iso3 => 'WSM', :numcode => '882'},
    {:iso => 'SM', :name => 'SAN MARINO', :printable_name => 'San Marino', :iso3 => 'SMR', :numcode => '674'},
    {:iso => 'ST', :name => 'SAO TOME AND PRINCIPE', :printable_name => 'Sao Tome and Principe', :iso3 => 'STP', :numcode => '678'},
    {:iso => 'SA', :name => 'SAUDI ARABIA', :printable_name => 'Saudi Arabia', :iso3 => 'SAU', :numcode => '682'},
    {:iso => 'SN', :name => 'SENEGAL', :printable_name => 'Senegal', :iso3 => 'SEN', :numcode => '686'},
    {:iso => 'SC', :name => 'SEYCHELLES', :printable_name => 'Seychelles', :iso3 => 'SYC', :numcode => '690'},
    {:iso => 'SL', :name => 'SIERRA LEONE', :printable_name => 'Sierra Leone', :iso3 => 'SLE', :numcode => '694'},
    {:iso => 'SG', :name => 'SINGAPORE', :printable_name => 'Singapore', :iso3 => 'SGP', :numcode => '702'},
    {:iso => 'SK', :name => 'SLOVAKIA', :printable_name => 'Slovakia', :iso3 => 'SVK', :numcode => '703'},
    {:iso => 'SI', :name => 'SLOVENIA', :printable_name => 'Slovenia', :iso3 => 'SVN', :numcode => '705'},
    {:iso => 'SB', :name => 'SOLOMON ISLANDS', :printable_name => 'Solomon Islands', :iso3 => 'SLB', :numcode => '090'},
    {:iso => 'SO', :name => 'SOMALIA', :printable_name => 'Somalia', :iso3 => 'SOM', :numcode => '706'},
    {:iso => 'ZA', :name => 'SOUTH AFRICA', :printable_name => 'South Africa', :iso3 => 'ZAF', :numcode => '710'},
    {:iso => 'ES', :name => 'SPAIN', :printable_name => 'Spain', :iso3 => 'ESP', :numcode => '724'},
    {:iso => 'LK', :name => 'SRI LANKA', :printable_name => 'Sri Lanka', :iso3 => 'LKA', :numcode => '144'},
    {:iso => 'SD', :name => 'SUDAN', :printable_name => 'Sudan', :iso3 => 'SDN', :numcode => '736'},
    {:iso => 'SR', :name => 'SURINAME', :printable_name => 'Suriname', :iso3 => 'SUR', :numcode => '740'},
    {:iso => 'SJ', :name => 'SVALBARD AND JAN MAYEN', :printable_name => 'Svalbard and Jan Mayen', :iso3 => 'SJM', :numcode => '744'},
    {:iso => 'SZ', :name => 'SWAZILAND', :printable_name => 'Swaziland', :iso3 => 'SWZ', :numcode => '748'},
    {:iso => 'SE', :name => 'SWEDEN', :printable_name => 'Sweden', :iso3 => 'SWE', :numcode => '752'},
    {:iso => 'CH', :name => 'SWITZERLAND', :printable_name => 'Switzerland', :iso3 => 'CHE', :numcode => '756'},
    {:iso => 'SY', :name => 'SYRIAN ARAB REPUBLIC', :printable_name => 'Syrian Arab Republic', :iso3 => 'SYR', :numcode => '760'},
    {:iso => 'TW', :name => 'TAIWAN, PROVINCE OF CHINA', :printable_name => 'Taiwan, Province of China', :iso3 => 'TWN', :numcode => '158'},
    {:iso => 'TJ', :name => 'TAJIKISTAN', :printable_name => 'Tajikistan', :iso3 => 'TJK', :numcode => '762'},
    {:iso => 'TZ', :name => 'TANZANIA, UNITED REPUBLIC OF', :printable_name => 'Tanzania, United Republic of', :iso3 => 'TZA', :numcode => '834'},
    {:iso => 'TH', :name => 'THAILAND', :printable_name => 'Thailand', :iso3 => 'THA', :numcode => '764'},
    {:iso => 'TG', :name => 'TOGO', :printable_name => 'Togo', :iso3 => 'TGO', :numcode => '768'},
    {:iso => 'TK', :name => 'TOKELAU', :printable_name => 'Tokelau', :iso3 => 'TKL', :numcode => '772'},
    {:iso => 'TO', :name => 'TONGA', :printable_name => 'Tonga', :iso3 => 'TON', :numcode => '776'},
    {:iso => 'TT', :name => 'TRINIDAD AND TOBAGO', :printable_name => 'Trinidad and Tobago', :iso3 => 'TTO', :numcode => '780'},
    {:iso => 'TN', :name => 'TUNISIA', :printable_name => 'Tunisia', :iso3 => 'TUN', :numcode => '788'},
    {:iso => 'TR', :name => 'TURKEY', :printable_name => 'Turkey', :iso3 => 'TUR', :numcode => '792'},
    {:iso => 'TM', :name => 'TURKMENISTAN', :printable_name => 'Turkmenistan', :iso3 => 'TKM', :numcode => '795'},
    {:iso => 'TC', :name => 'TURKS AND CAICOS ISLANDS', :printable_name => 'Turks and Caicos Islands', :iso3 => 'TCA', :numcode => '796'},
    {:iso => 'TV', :name => 'TUVALU', :printable_name => 'Tuvalu', :iso3 => 'TUV', :numcode => '798'},
    {:iso => 'UG', :name => 'UGANDA', :printable_name => 'Uganda', :iso3 => 'UGA', :numcode => '800'},
    {:iso => 'UA', :name => 'UKRAINE', :printable_name => 'Ukraine', :iso3 => 'UKR', :numcode => '804'},
    {:iso => 'AE', :name => 'UNITED ARAB EMIRATES', :printable_name => 'United Arab Emirates', :iso3 => 'ARE', :numcode => '784'},
    {:iso => 'GB', :name => 'UNITED KINGDOM', :printable_name => 'United Kingdom', :iso3 => 'GBR', :numcode => '826'},
    {:iso => 'US', :name => 'UNITED STATES', :printable_name => 'United States', :iso3 => 'USA', :numcode => '840'},
    {:iso => 'UY', :name => 'URUGUAY', :printable_name => 'Uruguay', :iso3 => 'URY', :numcode => '858'},
    {:iso => 'UZ', :name => 'UZBEKISTAN', :printable_name => 'Uzbekistan', :iso3 => 'UZB', :numcode => '860'},
    {:iso => 'VU', :name => 'VANUATU', :printable_name => 'Vanuatu', :iso3 => 'VUT', :numcode => '548'},
    {:iso => 'VE', :name => 'VENEZUELA', :printable_name => 'Venezuela', :iso3 => 'VEN', :numcode => '862'},
    {:iso => 'VN', :name => 'VIET NAM', :printable_name => 'Viet Nam', :iso3 => 'VNM', :numcode => '704'},
    {:iso => 'VG', :name => 'VIRGIN ISLANDS, BRITISH', :printable_name => 'Virgin Islands, British', :iso3 => 'VGB', :numcode => '092'},
    {:iso => 'VI', :name => 'VIRGIN ISLANDS, U.S.', :printable_name => 'Virgin Islands, U.s.', :iso3 => 'VIR', :numcode => '850'},
    {:iso => 'WF', :name => 'WALLIS AND FUTUNA', :printable_name => 'Wallis and Futuna', :iso3 => 'WLF', :numcode => '876'},
    {:iso => 'EH', :name => 'WESTERN SAHARA', :printable_name => 'Western Sahara', :iso3 => 'ESH', :numcode => '732'},
    {:iso => 'YE', :name => 'YEMEN', :printable_name => 'Yemen', :iso3 => 'YEM', :numcode => '887'},
    {:iso => 'ZM', :name => 'ZAMBIA', :printable_name => 'Zambia', :iso3 => 'ZMB', :numcode => '894'},
    {:iso => 'ZW', :name => 'ZIMBABWE', :printable_name => 'Zimbabwe', :iso3 => 'ZWE', :numcode => '716'}]

  def self.find_by(property, value)
    COUNTRIES_LIST.each do |country|
      return country if country[property.to_sym] == value
    end
  end
end

Usage:

Country.find_by(:iso3, "GBR")  #=> {:iso => 'GB', :name => 'UNITED KINGDOM', :printable_name => 'United Kingdom', :iso3 => 'GBR', :numcode => '826'}
Thanks, me.

Comments [0]

Recognising and learning good design

Right now I am implementing something for Peeves that is similar to a subset of ActiveMerchant. Every once in a while I do a refactoring that ends up making the code look a little more like ActiveMerchant, and I think "Wow, that was a good bit of design there". This is not very surprising, and doesn't invalidate the work I'm doing on Peeves. Peeves solves a subtly different problem that's not covered by ActiveMerchant, with different design forces. However, it does point to an interesting bit of insight.

"I can give you the foolproof formula to write a best-seller," goes the joke, "just pick any best-seller and copy it out word for word." Often, beginning coders are given the advice, when they ask how they can improve their programming skills, that they should look at some quality open-source code. I would now add an extra insight to that advice:

To improve your programming skills, pick a popular, well designed piece of open-source code (ActiveMerchant is certainly a good one) and not only read it, but try to re-implement it. Start from zero, and grow it from the smallest, simplest useful unit, progressively, into the whole thing. In the process, you'll learn to not only recognise what good design looks like, but why the choices that the original implementers made were good, and you'll also be exposed to the processes that lead one from an inferior design to a superior design.

Comments [0]

Peeves - Protx VSP Server rails plugin

It's worth pointing out that I'm in the process of throwing together a simple plugin, loosely based on ActiveMerchant (meaning, I stole a fair bit of code from there), to process Protx VSP Server payments.
 
I've called it Peeves (Protx Vsp Server => PVS => Peeves), and it's available on Github at http://github.com/swombat/peeves/tree/master . Feel free to make use of it for your own ends (and/or copy, adapt, upgrade the code, whatever).
 
It's not functional yet, but it will be within the next week.
 
It doesn't have any tests... I think it's too simple in scope to be worth the overhead of testing.
 
One last note - why not use ActiveMerchant? Well, because ActiveMerchant relies on the assumption that we'll be capturing credit card details ourselves and sending them over to the payment gateway for processing. Within the Protx world, this is known as "VSP Direct". This is great for many websites, and certainly provides a good user experience (well, so long as they trust you), but it does submit you to the PCI DSS compliance standards, which are a requirement for any website that handles credit card details (even if you don't store them in your own database). On top of that, it also means you need to implement 3D Secure yourself - so you can't even guarantee that you'll be able to keep the user on your site and avoid messy redirects.
 
I don't want to have to deal with PCI DSS at this point in time, nor do I want to write any 3D Secure code - I have enough other things to worry about. Therefore, VSP Server makes more sense. Since ActiveMerchant doesn't directly support this (and it doesn't really fit within the API of ActiveMerchant), it seemed easiest to just extract a simple plugin to deal with this.

Update: I couldn't do it, I just couldn't. Writing code without specs just feels wrong. Adding in RSpec now.

Comments [0]

Testing your plugin code without running rails

Scenario: you need to write a test script that should run independently from Rails, to test that your plugin hangs together.

 Of course, you can install RSpec or another test framework, create a test directory, and start gathering tests. And you should do that. But at first, you may want to just write a teeny little .rb script that will call some method in your plugin to give you a reality check that the thing is working. Yes, yes, that's not true TDD/BDD, but hey, we're not all perfect.

 So how to do that? Simple. If the script you want to run is in your plugin's root directory, just put this at the top:

 

 
require 'rubygems' 
require 'activesupport' 
 
$LOAD_PATH << "lib" 
 
ActiveSupport::Dependencies.load_paths = $LOAD_PATH 

 And bingo... you'll have all the railsy auto-loading goodness that you need to write the rest of the script.

Comments [0]