Perspectives

Mapblog

The Google Maps API is without a doubt one of the most popular APIs available on the web today. It offers webmasters the ability to add mapping functionality, similar to maps.google.com, to their websites. All of Google Maps functionalities, including markers, directions, zoom options and satellite view are only a few lines of code away. But before we get started with the code portion, you will need a Google Maps API key. Getting a Google Maps API key takes a minute and costs nothing. Just sign in to your Google Account (I am sure you have one) and head to http://code.google.com/apis/maps. On the right hand side you will see ‚How Do I Start, where you will just simply click on Step 1 "Sign up for a Google Maps API key". Then enter your site's live domain, accept the terms and click the "Generate API Key" button. On the next page your unique API key will appear. Make sure that you copy and paste it to a safe location. Below the API key will be JavaScript examples to quickly get you started. In this tutorial, we will generate similar code with the use of some rails plug-ins. The most popular plug-in is the Yellow Maps for Ruby, also known as YM4R. Since there are other mapping APIs, this plug-in comes in a few flavors. Since we're doing Google Maps, we will use YM4R/GM.

1. Lets start by generating a new Rails application:

rails map_example -d mysql
2. Create a 'map_example_development' database and remove index.html from the public folder

3. The lets add a controller and the index action
ruby script/generate controller location index
4. Lets install the YM4R/GM plugin
ruby script/plugin install git://github.com/queso/ym4r-gm.git
5. Add frontend.html.erb in app/views/layouts with the following code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<title>Google Maps Rails Example</title>
	
	<%= GMap.header %>
	<%= @map.to_html unless @map.blank? %>
</head>
<body>
	
<%= yield %>

</body>
</html>
Note that we have included two YM4R/GM lines in the head section. The GMap.header will include needed JavaScript files while the @map.to_html line generates JavaScript based on the parameters passed into the map object. We will be creating the @map object in our location controller.

6. Don't forget to specify the layout in app/views/location_controller:
layout "frontend"
7. Inside of the index action, add the following code:
coordinates = [41.8921254,-87.6096669]

@map = GMap.new("map")
@map.control_init(:large_map => true, :map_type => true)
@map.center_zoom_init(coordinates,14)
@map.overlay_init(GMarker.new(coordinates,:title => "Navy Pier", :info_window => "Navy Pier"))
First we set our map coordinates into an array. The first number is the latitude and second is the longitude. These coordinates point to Navy Pier in Chicago, Illinois. If you would like to try a different area for this example, this easy tool (http://stevemorse.org/jcal/latlon.php) provides you the latitude and longitude for any address you want to use.. Next, we set the @map object to pass into our view. The @map object contains a new instance of the GMap class; the "map" string is the id of the div that will contain the map. The next line activates controls for our map. The large map activates the zoom option while the map type activates various views such as a regular map, satellite or terrain. Then we set the default map center and zoom. We first pass in the coordinates array so our location will be centered, then we pass in a integer to determine zoom level. This integer can be between 0 and 22, the higher the number the more the zoomed in the map will be. Finally, we add a marker overlay. The first argument is the coordinates and it is the only required argument need to add a marker. The title and info_window are both optional, title is the text that will display when your mouse is idle over the marker, while info_window is the text that will appear in the pop up box that appears after clicking the marker.

8. Next open our view file located at app/views/location/index.html.erb and add the following:
<h1>Google Maps Rails Example</h1>

<%= @map.div(:width => 800, :height => 500) %>

9. Map the homepage to the index action of location by adding the following to config/routes.rb.
map.root :controller => "location", :action => "index"
10. Fire up your rails application and a map should appear. Currently our map is static and the location can't change. It would be nice to have a option to change the location through a text field. Since it is a text field, the user will be able to type in any possible location and expect our application to map it. We're going to have to convert user-inputted text into map coordinates. Sounds complicated, luckily there is another rails plug-in that can be of help here and that plug-in is Google-Geocoder. Google Geocoder will find 11. Install the Google-Geocoder plugin:
ruby script/plugin install git://github.com/tobstarr/google-geocoder.git
12. Next add the form to the bottom of the view:
<br />

<% form_for "new_location", :url => location_index_path, :html => { :method => :post } do %>
	<label>Enter a new Location to map:</label><br />
	<%= text_field_tag :new_location %>
	
	<%= submit_tag "Map It" %>
<% end %>
13. We will then add the create action to the location controller:
def create
	new_location = params[:new_location]
	
	gg = GoogleGeocode.new("Your API Key Here")
	gg_locate = gg.locate(new_location)

	coordinates = [gg_locate.latitude, gg_locate.longitude]

	@map = GMap.new("map")
	@map.control_init(:large_map => true, :map_type => true)
	@map.center_zoom_init(coordinates,14)
	@map.overlay_init(GMarker.new(coordinates, :title => new_location, :info_window => new_location))

	render :action => "index"
end
A few lines of the create action is similar to the index action. First we set the new_location variable to the value of the new location parameter. Next we pass in the Google Maps API key to a new instance of the GoogleGeocode class, this will allow us to run methods such as "locate" which will return coordinate information. Then we set the coordinate array and the rest is familiar territory. The only difference is we are setting the title and info window text to the user input and finally we reuse the index template. That's it for my Google Maps on rails tutorial, we were able to get a functioning map by using the YM4R plug-in. Then we were able to map user-inputted locations by using the Google Geocoder plug-in. You could easily expand this example by giving the users the ability to map multiple locations at once or dry up similar code used in our index and create methods.



Gems

If you have built a Rails application, there is a good chance the power of Ruby gems were utilized to quickly add new functionality. In this article, we're going to go over some best practices to follow when using gems within a Rails application. I will demonstrate how needed gems can be specified in your environment, unpack a copy into your application and then we will do a little gem hacking without breaking them.

The application we are about to build won't really be an application, we won't be creating any views, controller or even touch the database. We will be using the Gruff gem by Geoffrey Grosenbach to generate some simple graphs and then we will enhance Gruff with some new options.

Installing and Freezing

Let's get started by creating a new Rails application and installing the Gruff gem.

rails gem_hacks
sudo gem install gruff

Add the following in the Rails::Initializer.run block in your config/environment.rb:

config.gem "gruff"

By adding config.gem "gruff" in our environment the application will look for the gruff gem on start up. If it doesn't exist it will throw an exception. This is much better than having the application running while the portions that depend on the gem error out.

Lets make sure Gruff appears in the application's gem list and then copy it to the vendor/gems directory of the application with the unpack task.

rake gems
rake gems:unpack:dependencies gruff

By including Gruff within the application, we won't have to install the gem on every box the application finds itself on. Also, every developer working on the project will not have to worry about having the same gem version installed.

Making It Our Own

Next let's create a BarGraph class that will require 'gruff'. The BarGraph class will serve as a wrapper method for generating common bar graphs found on the site.

require 'gruff'
class BarGraph
  
end

Every ruby class needs an initialize method in order to create new instances. Add an initialize method inside the BarGraph class filled with Gruff code that will generate a static bar graph for now. Then create the bar_graphs directory inside of public/images.

def initialize
  g = Gruff::Bar.new("400x300")
  g.data :years, [185, 155, 110, 90, 135]
  g.labels = { 0 => "2004", 1 => "2005", 2 => "2006", 3 => "2007", 4 => "2008" }
  g.write('public/images/bar_graphs/test.jpg')
end

Next fire up your console and try BarGraph.new, then go into your public/images/bar_graphs directory and open up test.jpg, it should look like the following:

Making Improvements

Notice how the bar of the lowest value (90) is barely visible? By default, the minimum value will be the lowest value and the maximum will be the highest value. We can customize this by setting more attribute values of the instance. Add the following before the g.write line.

g.minimum_value = 0
g.maximum_value = 200

Let's once again regenerate our graph through the console; now the bar with the value of 110 should be visible.

Great, our graph starts at 0 and we've added some potential by specifying a higher maximum value. Lets make our BarGraph class more dynamic by adding some arguments that can be passed through the initialize method and refactor the code within.

def initialize(values, labels, save_path, options={})
  g = Gruff::Bar.new("400x300")
  g.data :years, values
  g.labels = labels
  g.minimum_value = 0
  g.maximum_value = 150
  g.write(save_path)
end

Now whenever we call BarGraph.new, arguments for values, labels and save_path will be needed. Reload your console and give the following a try:

BarGraph.new( [185, 155, 110, 90, 135], { 0 => "2004", 1 => "2005", 2 => "2006", 3 => "2007", 4 => "2008" }, "public/images/bar_graphs/test_with_arguments.jpg" )

This will generate the same graph as before since we are passing in the same data. If we passed in an array with values higher then 200, however, it would get cut off due to the static maximum value in our initialize method. We can remove the maximum value portion from our initialize and Gruff will default to the highest value in our array. Another option is to add code that will take that maximum value and round it up.

Hacking the Gruff Gem

The auto-rounding of the maximum value would be a good feature to have in other Gruff graphs as well. Since we have the gem source in the vendor/gems of our projects, adding it there might sound like a good idea at first. Later in the project lifecycle, however, the gem might be updated and those custom additions would be lost.

The ideal solution would be to create a new module that would contain our hacks which will be included wherever needed. Whenever the gem is updated and our hacks break, we can easily fix them since they are all located in one place. With that being said let's create our GruffHacks module and save it as gruff_hacks.rb in lib.

module GruffHacks
  class Gruff::Base
  
  end
end

Our GruffHacks module contains the Gruff::Base class which is found in vendor/gems/gruff/lib/gruff/base.rb. Take a few minutes to browse through the Base class as we will be overriding methods from it next. Near the top all the attribute accessors for Gruff are being set. These attributes are shared between all graphs since they are part of base. Lets add a attribute accessor for rounding maximum values called round_maximum_value into Gruff::Base of our GruffHacks module.

Remember we are not editing any code from the gem source, we are only going to use it for reference and copy methods that we will be overriding from it. Next lets copy the entire initialize_ivars method from the gem source into Gruff::Base of our GruffHacks module. At the bottom, after @norm_data = nil, let's add @round_maximum_value = false. The initialize_ivars method is responsible for setting default values for Gruff attributes, here we are setting @round_max_value to false to cancel out the rounding functionality we will add soon. The goal with this module is to enhance Gruff, but to have it function normally if our attribute hacks are not being set.

With the round_maximum_value attribute accessor along with its default value added, now it is time to set the new maximum value when round_maximum_value is true. Add the following method into Gruff::Base of our GruffHacks module

def round_maximum_value=(value)
  if value == true
    rounded_maximum_values = []
    
    # loop through each bar group, grab the highest value, round it up and add
                # to rounded_maximum_values array
    highest_val = @data.each do |row|
      row_highest = row[1].sort.last
      round_to = 10 ** (row_highest.to_s.length - 1)
      rounded_maximum_values << row_highest.roundup(round_to)
    end
    # Set maximum value based on highest rounded value
    @maximum_value = rounded_maximum_values.sort.last
  end
end

Add this after the very last method of GruffHacks, these two helpful methods are created by Charlie from PullMonkey for rounding numbers up or down. Our round_maximum_value method uses the roundup method.

class Numeric
  def roundup(nearest=10)
    self % nearest == 0 ? self : self + nearest - (self % nearest)
  end
  def rounddown(nearest=10)
    self % nearest == 0 ? self : self - (self % nearest)
  end
end

Before testing our new hack, lets include the GruffHacks module into our BarGraph class by adding include GruffHacks after the Gruff require statement. In our initialize method remove the g.maximum_value line and add in g.round_maximum_value = true. If you try regenerating the same graph as before, the maximum value will now be 200 since 185 was rounded up.

Worth the Effort

By overriding the initialize_ivars and adding the round_maximum_value setter into Gruff::Base we are able to automatically add some space between the highest value and the top of the graph by simply setting a single attribute to true. If there is a need to change this functionality later on, we won't have to dig through the gem source and hunt for modified lines. Instead we open up our GruffHacks module and easily find our overridden methods. It is also much easier to add new methods, maybe more style options are needed for the bars or the labels need to be positioned in a different way. Whatever it is, by keeping these enhancements in a separate module makes sharing and updating hacks between applications easier.

As projects grow, it becomes harder to keep up with all the details. Even if you are the only developer on the project, there is a good chance the inner workings will be forgotten months later. By breaking simple hacks into new modules we are minimizing the potential of possible bugs, and any bugs that do exist will be easier to find. Spending a few extra minutes to better organize your code and using the object oriented nature of a language such as Ruby will save time for you and your fellow colleagues.



Controls

In my previous article we took a Behavior Driven Development approach to testing our data layer, in which our models were tested using RSpec. In this article I will showcase how RSpec can be used for controller testing. If you are new with RSpec, I will not go into detail with basic RSpec syntax such as should and it, please read my previous article, TDD, BDD and Using RSpec which goes over the basics to get you started.

Before we dive into controller testing, let's quickly create our app that will help birdkeepers find information on birds. Run the following commands:

rails birdkeeper -d mysql
cd birdkeeper
script/generate scaffold Bird title:string species_id:integer notes:text

Create birdkeeper_development and birdkeeper_test databases, add your database credentials to config/database.yml and migrate:

rake db:migrate
rake db:migrate RAILS_ENV=test

Then add the RSpec plugins (through git), gem (if not installed) and generate the spec directories:

sudo gem install rspec
script/plugin install git://github.com/dchelimsky/rspec.git
script/plugin install git://github.com/dchelimsky/rspec-rails.git
script/generate rspec

Open up app/controllers/birds_controller.rb, it should contain the 7 CRUD actions created from the Rails scaffold generator. If there are no actions, your version of Rails may need to be updated. Create birds_controller_spec.rb inside of spec/controllers. Add the following code:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe BirdsController do

end

The first line loads in the spec_helper file which will contain common code that can be shared between specs. Next we have a describe block which helps keep our tests organized. This one will contain tests relating to the BirdsController, which will hold all our tests. It will also contain inner describe blocks for further organization. Let's test the update method, since in addition to performing a find call like most CRUD actions, it also updates the object. The update method we will be using is as follows:

# PUT /birds/1
# PUT /birds/1.xml
def update
  @bird = Bird.find(params[:id])

  respond_to do |format|
    if @bird.update_attributes(params[:bird])
      flash[:notice] = 'Bird was successfully updated.'
      format.html { redirect_to(@bird) }
      format.xml  { head :ok }
    else
      format.html { render :action => "edit" }
      format.xml  { render :xml => @bird.errors, :status => :unprocessable_entity }
    end
  end
end

Go back to the BirdsController spec and add the following inside of the BirdsController describe block:

# UPDATE
describe "PUT birds/:id" do
    describe "with valid params" do
    
    end
    
    describe "with invalid params" do
    
    end
end

Above we have added 3 describe blocks. One wrapper describe block will contain all tests relating to the update method. Inside there are two describe blocks, one with tests if valid params are given and the other if invalid params are given. Let's start with valid parameters. Before writing the actual tests, we need to set expectations. This is done through mocking and stubbing within a before block. Add the following inside the "with valid params" describe block:

before(:each) do
    @bird = mock_model(Bird)
    Bird.stub!(:find).with("1").and_return(@bird)
end

The before block will run before each test. This will DRY up our tests since we won't have to rewrite the same mocks and stubs for each test. Mocks and stubs allow us to test the controller functionality without relying on ActiveRecord. With mock_model we are imitating a Bird object. Stubs are used to fake method calls, we don't need to know the details of the actual method. We just know that the Bird class will receive a find call with a argument of "1" and it should successfully return a @bird object, which will be our mock. Then later in the same method, the @bird mock will receive an update_attributes call, so we also need to stub this call out. We can stub it out as follows:

@bird.stub!(:update_attributes).and_return(true)

But there is another way to accomplish this much more DRYly. Our mock model accepts a optional hash of method calls and their return value. We can modify our @bird mock object into:

@bird = mock_model(Bird, :update_attributes => true)

With the before block set up we can start writing tests. Tests are contained in it blocks and takes a string argument explaining its contents. Let's first test the find call, which is the first action to happen after update is called.

it "should find bird and return object" do
    Bird.should_receive(:find).with("1").and_return(@bird)
    put :update, :id => "1", :bird => {}
end

Here we are testing if the Bird class received a find call, with the should_receive syntax. The rest is very similar to the stub method since we are checking if it received "1" as an argument and returned a @bird object.

After a Bird object is found, its attributes are updated. Testing this call is very similar to the find call:

it "should update the bird object's attributes" do
    @bird.should_receive(:update_attributes).and_return(true)
    put :update, :id => "1", :bird => {}
end

Next we make sure a flash notice is set:

it "should have a flash notice" do
    put :update, :id => "1", :bird => {}
    flash[:notice].should_not be_blank
end

If the controller can have one of many flash notices, we can also be more specific:

it "should have a successful flash notice" do
    put :update, :id => "1", :bird => {}
    flash[:notice].should eql 'Bird was successfully updated.'
end

After the flash notice is set, the user should get redirected to the bird's show page. We can test the redirect by accessing the response object as so:

it "should redirect to the bird's show page" do
    put :update, :id => "1", :bird => {}
    response.should redirect_to(bird_url(@bird))
end

As for testing if there was invalid data, we would do this:

before(:each) do
    @bird = mock_model(Bird, :update_attributes => false)
    Bird.stub!(:find).with("1").and_return(@bird)
end

it "should find bird and return object" do
    Bird.should_receive(:find).with("1").and_return(@bird)
    put :update, :id => "1", :bird => {}
end

it "should update the bird object's attributes" do
    @bird.should_receive(:update_attributes).and_return(false)
    put :update, :id => "1", :bird => {}
end

it "should render the edit form" do
    put :update, :id => "1", :bird => {}
    response.should render_template('edit')
end

Most is similar to the valid data version, but there are a few differences. We are stubbing the object's update_attributes call to return false, this will cause the conditional to take the else route. Since there are errors, it needs to render the edit page. We test this by doing a response.should render_template('edit').

This is how the complete Bird Controller spec looks:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe BirdsController do

  # UPDATE
  describe "PUT birds/:id" do

    describe "with valid params" do
      before(:each) do
        @bird = mock_model(Bird, :update_attributes => true)
        Bird.stub!(:find).with("1").and_return(@bird)
      end
      
      it "should find bird and return object" do
        Bird.should_receive(:find).with("1").return(@bird)
      end
      
      it "should update the bird object's attributes" do
        @bird.should_receive(:update_attributes).and_return(true)
      end
      
      it "should redirect to the bird's show page" do
        response.should redirect_to(bird_url(@bird))
      end
    end

    describe "with invalid params" do
      before(:each) do
        @bird = mock_model(Bird, :update_attributes => false)
        Bird.stub!(:find).with("1").and_return(@bird)
      end

      it "should find bird and return object" do
        Bird.should_receive(:find).with("1").return(@bird)
      end

      it "should update the bird object's attributes" do
        @bird.should_receive(:update_attributes).and_return(false)
      end

      it "should render the edit form" do
        response.should render_template('edit')
      end
      
      it "should have a flash notice" do
        flash[:notice].should_not be_blank
      end
    end
    
  end
end
Run this spec from the root of your application with the following command, all specs will pass:
ruby spec/controllers/birds_controller_spec.rb

Using the information learned here can be applied to writing tests for the other 6 CRUD actions. Since these actions were generated through the Rails scaffold generator, they should work and tests may not be needed. On the other hand, changes in functionality may cause new bugs to pop up. It is recommended to write tests and cover as much possible. It may become tedious to write tests for basic CRUD actions for each generated controller, luckily RSpec has a scaffold generator that will generate the same files complete with RSpec tests. The RSpec version of the scaffold can be used as follows:

script/generate rspec_scaffold Bird title:string species_id:integer notes:text

As a bonus, let's say we created a Species scaffold. A bird will belong to Species and we have the Birds controller nested within the Species controller. On the Species show page, we would run the following find methods for a list of birds that belong to that Species. Just how should the following be mock and stubbed?

@species = Species.find("1")
@birds_in_species = @species.birds.find(:all)

Take a few minutes to think about it. It is a bit more complicated, but like any complicated matter, can be simplified by breaking it down. I start off by mocking all objects involved, @species and @birds_in_species are a given. But we also can't forget the birds that are going to be returned from @species.birds, that needs to be mocked as well.

@birds_in_species = mock_model(Bird)
@species = mock_model(Species)
@birds = mock_model(Bird)

As for stubbing out the method calls. There are three in total, @species.birds.find counts as two.

Species.stub!(:find).with("1").and_return(@species)
@species.stub!(:birds).and_return(@birds)
@birds.stub!(:find).and_return(@birds_in_species)

Then in our tests, we would do the following:

Species.should_receive(:find).with("1").and_return(@species)
@species.birds.should_receive(:find).and_return(@birds_in_species)

I hope you have found this article helpful on testing controllers with RSpec. If you have checked out the generated specs through the rspec_scaffold generator, there are less tests for the update method. I prefer to have many smaller tests with each testing a small portion of the controller, that way when a single line is changed, the error from the test will be more helpful since it is more specific. I have found out about this approach from Mike Mangino, though ultimately how your specs are organized is a matter of personal preference.




RSS Feed


CATEGORIES


ARCHIVES


BOOKMARKED


Add to Technorati Favorites