All about Struct

All about Struct

This guest post is by Steve Klabnik. Steve is a Rubyist, writer, and teaches Ruby and Rails classes with Jumpstart Lab. He maintains Draper, Hackety Hack, and Shoes, and
contributes to Rails from time to time.

Steve Klabnik One of my favorite classes in Ruby is Struct, but I feel like many Rubyists don’t know when to take advantage of it. The standard library has a lot of junk in it, but Struct and OStruct are super awesome.

Struct

If you haven’t used Struct before, here’s the documentation of Struct from the Ruby standard library.

Structs are used to create super simple classes with some instance variables and a simple constructor. Check it:

Struct.new("Point", :x, :y) #=> Struct::Point
origin = Struct::Point.new(0,0) #=> #

Nobody uses it this way, though. Here’s the way I first saw it used:

class Point < Struct.new(:x, :y)
end
origin = Point.new(0,0)

Wait, what? Inherit…from an instance of something? Yep!

1.9.3p194 :001 > Struct.new(:x,:y) => #<Class:0x007f8fc38da2e8>

Struct.new gives us a Class. We can inherit from this just like any other Class. Neat!

However, if you’re gonna make an empty class like this, I prefer this way:

Point = Struct.new(:x, :y)
origin = Point.new(0,0)

Yep. Classes are just constants, so we assign a constant to that particular Class.

If you’d like, you can pass a block to the Struct:

Point = Struct.new(:x, :y) do
  def translate(x,y)
    self.x += x
    self.y += y
  end
end

Now we can do this:

origin = Point.new(0,0)
origin.translate(1,2)
puts origin #=> <struct Point x=1, y=2>

OStruct

OStructs are like Struct on steroids. Check it:

require 'ostruct'
origin = OpenStruct.new
origin.x = 0
origin.y = 0
origin = OpenStruct.new(:x => 0, :y => 0)

OStructs are particularly good for configuration objects. Since any method works to set data in an OStruct, you don’t have to worry about enumerating every single option that you need:

require 'ostruct'

def set_options 
  opts = OpenStruct.new
  yield opts
  opts
end

options = set_options do |o|
  o.set_foo = true
  o.load_path = "whatever:something"
end

options #=> #<OpenStruct set_foo=true, load_path="whatever:something">

Neat, eh?

Structs for domain concepts

You can use Structs to help reify domain concepts into simple little classes. For example, say we have this code, which uses a date:

class Person
  attr_accessor :name, :day, :month, :year

  def initialize(opts = {})
    @name = opts[:name]
    @day = opts[:day]
    @month = opts[:month]
    @year = opts[:year]
  end

  def birthday
    "#@day/#@month/#@year"
  end
end

and we have this spec

$:.unshift("lib")
require 'person'

describe Person do
  it "compares birthdays" do
    joe = Person.new(:name => "Joe", :day => 5, :month => 6, :year => 1986)
    jon = Person.new(:name => "Jon", :day => 7, :month => 6, :year => 1986)

    joe.birthday.should == jon.birthday
  end
end

It fails, of course. Like this:

$ rspec
F

Failures:

1) Person compares birthdays
Failure/Error: joe.birthday.should == jon.birthday
expected: "7/6/1986"
got: "5/6/1986" (using ==)
# ./spec/person_spec.rb:9:in `block (2 levels) in'

Finished in 0.00053 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/person_spec.rb:5 # Person compares birthdays

Now. We have these two birthdays. In this case, we know about why the test was failing, but imagine this failure in a real codebase. Are these month/day/year or day/month/year? You can’t tell, it could be either. If we switched our code to this:

class Person
  attr_accessor :name, :birthday

  Birthday = Struct.new(:day, :month, :year)

  def initialize(opts = {})
    @name = opts[:name]
    @birthday = Birthday.new(opts[:day], opts[:month], opts[:year])
  end
end

We get this failure instead:

$ rspec
F

Failures:

1) Person compares birthdays
Failure/Error: joe.birthday.should == jon.birthday
expected: #
got: # (using ==)
Diff:
@@ -1,2 +1,2 @@
-#
+#
# ./spec/person_spec.rb:9:in `block (2 levels) in'

Finished in 0.00092 seconds
1 example, 1 failure

Failed examples:

rspec ./spec/person_spec.rb:5 # Person compares birthdays

We have a way, way more clear failure. We can clearly see that its our days that are off.

Of course, there are other good reasons to package related instance variables into Structs, too: it makes more conceptual sense. This code represents our intent better: a Person has a Birthday, they don’t have three unrelated numbers stored inside them somehow. If we need to add something to our concept of birthdays, we now have a place to put it.

I hope you found this article valuable. Feel free to ask questions and give feedback in the comments section of this post. Also, do check out Steve’s other articles: “How do I test my code with Minitest? and “How do I keep multiple Ruby projects separate?” on RubyLearning. Thanks!

Technorati Tags: , ,


(Powered by LaunchBit)

How do I test my code with Minitest?

How do I test my code with Minitest?

This guest post is by Steve Klabnik, who is a software craftsman, writer, and former startup CTO. Steve tries to keep his Ruby consulting hours down so that he can focus on maintaining Hackety Hack and being a core member of Team Shoes, as well as writing regularly for multiple blogs.

Steve Klabnik Programming is an interesting activity. Everyone has their favorite metaphor that really explains what programming means to them. Well, I have a few, but here’s one: Programming is all about automation. You’re really just getting the computer to automatically do work that you know how to do, but don’t want to do over and over again.

When I realized this, it made me look for other things that I do that could be automated. I don’t like repeating myself over and over and over again. That’s boring! Well, there’s one particular task that’s related to programming that’s easily made automatic, and that’s testing that your software works!

Does this story sound familiar? You run your program, try a few different inputs, check the outputs, and see that they’re right. Then, you make some changes in your code, and you’d like to see if they work or not, so you fire up Ruby and try those inputs again. That repetition should stick out. There has to be a better way.

Luckily, there is! Ruby has fantastic tools that let you set up tests for your code that you can run automatically. You can save yourself tons of time and effort by letting the computer run thousands of tests every time you make a change to your code. And it’ll never get tired and accidentally type in a 2 when you mean to type 3… Many people take this one step farther. They find testing so important and so helpful that they actually write the tests before they write the code! I won’t expound on the virtues of “test driven development” in this post, but it’s actually easier to write the tests first, once you get some practice at it. So, let’s pick a tiny bit of code to work on, and I’ll show you how to test it using Ruby’s built-in testing library, minitest.

For this exercise, let’s do something simple, so we can focus on the tests. We’ll make a Ruby class called CashRegister. It’ll have a bunch of features, but here’s the first two methods we’ll need:

  • The register will have a scan method that takes in a price, and records it.
  • The register will have a total method that shows the current total of all the prices that have been scanned so far.
  • If no prices have been scanned, the total should be zero.
  • The register will have a clear method that clears the register of all scanned items. The total should go back to zero again.

Seems simple, right? You might even know how to code this already. Sometimes, intermediate programmers practice coding problems that are easy, just to focus on how to write good tests, or to work on getting the perfect design. We call these kinds of problems ‘kata.’ It’s a martial arts thing.

Anyway, enough about all of this! Let’s dig in to minitest. It already comes with Ruby 1.9, but if you’re still using 1.8, you can install it with ‘gem install minitest.’ After doing so, open up a new file, register.rb, and put this in it:

require 'minitest/autorun'

class TestCashRegister < MiniTest::Unit::TestCase
  def setup
    @register = CashRegister.new
  end
  def test_default_is_zero
    assert_equal 0, @register.total
  end
end

Okay! There’s a lot going on here. Let’s take it line by line. On the first line, we have a ‘require.’ The autorun part of minispec includes everything you need to run your tests, automatically. All we need to do to run our tests is to type ruby register.rb, and they’ll run and check our code. But let’s look at the rest of the file before we do that. The next thing we do is set up a class that inherits from one of minitest’s base classes. That’s how minitest works, by running a series of TestCases. It also lets you group similar tests together, and split different ones up into multiple files.

Anyway, enough organizational stuff. In this class, we have two methods: the first is the setup method. This runs before each test, and allows us to prepare for the test we want to run. In this case, we want a new CashRegister each time, and we’ll store it in a variable. Now we don’t have to repeat our setup over and over again… it’s just automatic!

Finally, we get down to business, with the test_default_is_zero method. Minitest will run any method that starts with test_ as a test. In that method, we use the assert_equal method with two arguments. assert_equal is where it all happens, by comparing 0 to our register’s total, and it will complain if they’re not equal.

Okay, so we have our first test. Rock! You might be tempted to start implementing our CashRegister class, but wait! Let’s try running the tests first. We know they’ll fail, because we don’t even have a CashRegister yet! But if we run the tests before writing code, the error messages will tell us what we need to do next. The tests will guide us through the implementation of our class. So, as I mentioned earlier, we can run the tests by doing this:

$ ruby register.rb

We get this as output:

Loaded suite register
Started
E
Finished in 0.000853 seconds.

1) Error:
test_default_is_zero(TestRegister):
NameError: uninitialized constant TestRegister::CashRegister
register.rb:5:in `setup'

1 tests, 0 assertions, 0 failures, 1 errors, 0 skips

Test run options: --seed 36463

Whoah! Okay, so you can see that we had one test, one error. Since we know classes are constants in Ruby, we know that the uninitialized constant error means we haven’t defined our class yet! So let’s do that. Go ahead and stick in an empty class at the bottom:

class CashRegister
end

And run the tests again. You should see this:

1) Error:
test_default_is_zero(TestRegister):
NoMethodError: undefined method `total' for #<CashRegister:0x00000101032a80>
register.rb:9:in `test_default_is_zero'

Progress! Now it says we don’t have a total method. So let’s define an empty one. Modify the class like this:

class CashRegister
  def total
  end
end

And run the tests again. Another failure:

1) Failure:
test_default_is_zero(TestRegister) [register.rb:9]:
Expected 0, not nil.

Okay! No more syntax errors, just the wrong result. Let’s keep it as simple as possible, and fill out a nice and easy total method:

def total
  0
end

Now, you may be saying, “Steve, that doesn’t calculate a total!” Well, you’re right. It doesn’t. But our tests aren’t yet asking to calculate a total, they’re just asking for a default. If we want a total, we should write a test that actually demonstrates adding it up. But we have fulfilled objective #3, so we’re doing good! Now, let’s work on objective #2, since we sorta feel like the total method is lying about what it’s supposed to do. In order to add up the items that were scanned, we need to scan them in the first place! Objective #1 says that this method should be called scan, so let’s write a test. Put it in your test class with the test_default_is_zero method:

def test_total_calculation
  @register.scan 1
  @register.scan 2
  assert_equal 3, @register.total
end

Make sense? We want to scan two things in, and then check that the total is correct. Let’s run our tests!

Loaded suite register
Started
.E
Finished in 0.000921 seconds.

1) Error:
test_total_calculation(TestRegister):
NoMethodError: undefined method `scan' for #<CashRegister:0x00000101031838>
register.rb:13:in `test_total_calculation'

2 tests, 1 assertions, 0 failures, 1 errors, 0 skips

Test run options: --seed 54501

Okay! See that ‘.E’ up there? That graphically shows that we had one test passing, and one test with an error. Our first test still works, but our second is failing because we don’t have a scan method. Add an empty one to our CashRegister class, and run again:

1) Error:
test_total_calculation(TestRegister):
ArgumentError: wrong number of arguments (1 for 0)
register.rb:24:in `scan'
register.rb:13:in `test_total_calculation'

Whoops! It takes an argument. Let’s add that: def scan(price). Run the tests!

1) Failure:
test_total_calculation(TestRegister) [register.rb:15]:
Expected 3, not 0.

Okay! This sounds more like what we expected. Our total method just returns zero all the time! Let’s think about this for a minute. We need to have scan add the price to a list of scanned prices. So we’d better have it do that:

def scan(item)
  @items << item
end

But if you run the tests, you’ll see this:

1) Error:
test_total_calculation(TestRegister):
NoMethodError: undefined method `<<' for nil:NilClass
register.rb:25:in `scan'
register.rb:13:in `test_total_calculation'

Oops! @items is undefined. Let’s make it be an empty array, when we create our register:

def initialize
  @items = []
end

And run the tests:

1) Failure:
test_total_calculation(TestRegister) [register.rb:15]:
Expected 3, not 0.

Okay! We’re back to our original failure. But we’ve made some progress: now that we have an actual list of items, we’re in a position to make our total method work. Also, at each step, even though one test was failing, the other was still passing, so we know that we didn’t break our default functionality while we were working on getting a real total going.

Now, we’re in a better place to calculate the total:

def total
  @items.inject(0) {|sum, item| sum += item }
end

Or, if you want to make it even shorter:

def total
  @items.inject(0, &:+)
end

If you’re not familiar with Enumerable#inject, it takes a list of somethings and turns it into a single something by means of a function, in a block. So in this case, we can keep a running sum of all items, and then add the price of each one to the sum. Done! Run your tests:

Started
..
Finished in 0.000762 seconds.

2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

Woo hoo! We’re done! Our total can now be calculated. Great job!

Now, here’s a challenge, to see if you’ve really learned this stuff: write a test for a new method, clear, that clears the total. That’s objective #4 we talked about above.

Other parts of minitest

This has been a mini intro to minitest and using it to test your code. There are other methods in the assert family, too, like assert_match, which takes a regular expression and tries to match it against something. There’s the refute family of tests, which are the opposite of assert:

assert true #=> pass
refute true #=> fail

There are also other tools that make minitest useful, like mocks, benchmark tests, and the RSpec-style ‘spec’ syntax. Those will have to wait until later! If you’d like to learn about them now, check out the source code on GitHub.

Happy testing!

I hope you found this article valuable. Feel free to ask questions and give feedback in the comments section of this post. Also, do check out Steve’s other article: “How do I keep multiple Ruby projects separate?” on RubyLearning. Thanks!

Technorati Tags: , ,

How do I keep multiple Ruby projects separate?

How do I keep multiple Ruby projects separate?

This guest post is by Steve Klabnik, who is a software craftsman, writer, and former startup CTO. Steve tries to keep his Ruby consulting hours down so that he can focus on maintaining Hackety Hack and being a core member of Team Shoes, as well as writing regularly for multiple blogs.

Steve Klabnik If you’re anything like me, you’re already starting a new project immediately after wrapping up the last one. There just aren’t enough hours in the day to code up all the crazy ideas I have floating around in my head. Often, these ideas are the result of checking out some fun new gem, GitHub project, or even a different Ruby. Real quickly, a problem develops: what happens when these projects interfere with one another? What if I want to use Ruby 1.8.7 for an older project, Ruby 1.8.5 for a legacy application, Ruby 1.9.2 for the latest and greatest, and JRuby to use an interesting Ruby library? Luckily, there are a few things that you can do to isolate your different projects from one another, and some settings for that will make them quite painless to use. There are three main things that can go wrong when you try to use different sets of tools on a per-project basis: conflicts between Ruby versions, conflicts between gems, and forgetting which tools you use on which project.

Ruby Version Conflicts

This is the biggest and most painful kind of problem. If you want to use Ruby 1.8 for one project and Ruby 1.9 for another, you have a problem. If you’re using Linux, for example, your package manager may see that both ruby18 and ruby19 fulfill a ‘ruby’ dependency, and so it won’t let you have them both installed side by side. The solution isn’t pretty: install different Rubies from source. This gets ugly really quickly, because it’s easy to forget where you’ve compiled different Rubies, and having software outside of your package manager isn’t a great answer. If you’re on OS X or Windows, you skip right past the package manager problem and straight to the source ‘solution.’ This is no good!

Luckily, there’s an awesome project by Wayne E. Seguin named rvm. rvm is sort of like a package manager for Ruby. If you’d like to install both Ruby 1.8.7 and 1.9.2, just type this in:

$ rvm install 1.8.7
$ rvm install 1.9.2

It’ll go fetch the Ruby source code, compile it, and get you all set up. To use a specific Ruby, you can type ‘use’:

$ rvm use 1.8.7
$ ruby -v
ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]
$ rvm use 1.9.2
$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]

Neat! You can even get other Ruby versions:

$ rvm install jruby
$ rvm install rbx
$ rvm install macruby

You can see a full list of these with ‘rvm list known’. For a full list of everything that rvm can do, as well as installation instructions, visit the rvm website.

Gem Conflicts

Once you’ve gotten your Rubies straight, you can still have conflicts between different gems that your project needs. One project uses Rails 2.3.8, another uses Rails 3… It gets worse when you have certain gems installed only as a dependency, and you don’t know exactly which one is correct:

$ gem list | grep net-ssh
net-ssh (2.0.23, 2.0.4, 1.1.4)

rvm has a neat feature called ‘gemsets.’ They let you create separate sets of gems per Ruby you have installed. This allows you to isolate each application, giving it its own set of gems. Check it out:

$ gem list

*** LOCAL GEMS ***

aasm (2.1.5)
abstract (1.0.0)
acl9 (0.12.0)
*snip*

$ rvm gemset create new-gemset
$ rvm use 1.9.2@new-gemset
$ gem list

*** LOCAL GEMS ***

$

Cool stuff! As you can see, use an ‘@’ symbol to tell rvm which gemset you’d like to use. Now we’ve isolated each project’s gems from each other. There is, however, a much more complicated kind of conflicts that can occur between gems. This happens when two gems have interlocking dependencies.

Here’s an example of this from the past: ActionPack 2.3.5 requires Rack =1.0.0, which is the newest version. Unicorn requires Rack >1.0.0. Rack releases a new version, 1.1.0. Now, when starting up a Rails application, the unicorn gem is loaded first, so it loads the newest version of the gem that works, which is rack-1.1.0. Then rails loads, and it loads actionpack, which tries to load rack. It needs =1.0.1, but sees that 1.1.0 has already been loaded, and throws this ugly, ugly error:

Gem::LoadError: can't activate rack (~> 1.0.0, runtime)
for ["actionpack-2.3.5"], already activated rack-1.1.0
for ["unicorn"]

There’s a set of versions here that works, but the way that the gems are loaded means that it doesn’t. The problem is that at the time that unicorn loads, it can’t possibly know that you’re planning on loading a different version of rack somewhere down the line. What we really need is a tool that knows about all of our dependencies, and can calculate the graph of all of our requirements, and figure out which versions of everything we need, and then only place those versions on the $LOAD_PATH. Luckily, such a project exists: bundler.

To use bundler, you first need to make a file named ‘Gemfile’ in the root of your project directory. This file looks something like this:

source "http://rubygems.org"

gem "rails", "~>3.0.0"

group :development do
  gem 'sqlite3-ruby', :require => 'sqlite3'
end

group :production do
  gem "pg"
end

The first line tells Bundler where to look for gems. The second line says that we want to use the ‘rails’ gem, and we want any version that’s at least 3.0.0 but less than 3.1.0. Finally, the other lines show ‘groups’ of gems: in development, we want to use sqlite3-ruby, and we need to require it via the name ‘sqlite3′, but we want to use Postgres in production. To install these gems, just:

$ bundle install

Bundler gets all the information that it needs on all the gems, figures out what versions of everything work together, and then installs the right versions. It then creates a Gemfile.lock file that holds all of this information. It’s just a simple YAML file, you can open it up and see the specifics. You’ll want to add the Gemfile and Gemfile.lock into your version control, so that anyone else that’s developing with you can also get the same gem versions.

To use the gems in your bundle, just use these two lines:

require "rubygems"
require "bundler/setup"

From there, whenever you require a gem, it’ll be the version from the bundle. If you want Bundler to automatically require all of your gems for you, just ‘Bundler.require‘ and it’ll require the default group of gems.

Rails 3 automatically comes with a Gemfile and bundler support right out of the box. If you want to use Bundler with Rails 2.3, check out the Bundler site for setup instructions.

The combination of gemsets and Bundler will make sure that you don’t have any nasty gem conflicts. Gemsets keep your projects isolated from each other, and Bundler keeps your gems’ versions from interfering with each other. The two work really well together.

I can’t remember which tool I used!

All of these rubies and gemsets can get confusing. Luckily, rvm has an awesome feature to take care of this, too: .rvmrc files. If you put a file named ‘.rvmrc’ in your project’s root directory, when you enter the project, it’ll switch your Ruby version (and gemset) automatically. It’s really easy to use, too. Just put the command you’d use to switch in the file. For example, in the Hackety Hack website project, I have the following .rvmrc:

rvm 1.8.7@hackety-hack.com

Astute readers will notice that I left off the ‘use,’ rvm defaults to ‘use’ if you don’t give it a different command. Check it out:

$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
$ cd hackety-hack.com
$ ruby -v
ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]

Super cool. Now you’ll never forget which Ruby you were using, and you don’t even need to switch manually. This is one of the first things that I do when I start a new project in Ruby: Pick a Ruby version, make a gemset with the same name as the project, and set up an .rvmrc. It’s saved me hours of time and headaches.

Multiple projects: super simple

rvm is a fantastic tool to help solve your multiple-ruby woes. It really does make using multiple kinds of Ruby really, really easy. And Bundler makes sure that your gems play nice togther. It’s a great time to be a Rubyist.

I hope you found this article valuable. Feel free to ask questions and give feedback in the comments section of this post. Thanks!

Technorati Tags: , , ,