Archive for the rubyonrails Category

Rails Migrations and AR Models — be careful my friend

Posted in rubyonrails on October 23, 2010 by Kelly Felkins

Be careful when using AR models in migrations. Many of us know this and some know the workaround of defining your model in the migration, as documented in the aging Rails Recipes book by Chad Fowler in the recipe “Safely Use Models in Migrations.”

As the recipe specifies, you may need to refresh the model’s column data, with the “reset_column_information” class method.

I was caught by surprise when I did this on models that extended a base class using single table inheritance. My first try was to reset the column information of the base class.

Nothing complained. The data was simply not what I expected. After a while I decided to reset the column information for every sub class — voilà, success.

Easy migration between Databases revisited

Posted in rubyonrails on February 20, 2009 by Kelly Felkins

Tobias Lütke described a technique for moving data from one database to another using rake, active record, and yaml. Unfortunately his rake task is out of date and comments are closed on his article. An updated version of the rake task is available on github.

Happy Path Testing With Selenium RC Fu

Posted in rubyonrails, selenium, testing on February 5, 2008 by Kelly Felkins

Note: a Pivotal Blabs version of this article is available here.

Selenium RC Fu is a fantastic system for testing Ruby On Rails applications. It is the blending of xUnit testing with selenium. Selenium is an amazing system that operates your browser as if a human were sitting there moving the mouse, pressing buttons and keys.

Selenium RC Fu is also a remarkable example of the power of open source. It’s Selenium remotely controlled by rails and ruby. You can learn more about it by viewing the slides for Full-stack webapp testing with Selenium and Rails presented by my colleagues Alex Chaffee and Brian Takita at the SDForum Silicon Valley Ruby Conference.

Now that you are excited about Selenium RC Fu, by law I must inform you that this wonderful testing tool comes with some costs. First, this is the daisy cutter of testing — problems will be detected, but it won’t be too specific about those problems. A failed selenium test will likely only tell you some expected text was not present on the page — you have to do some digging to discover the real problem.

It’s also slow. To be fair, a lot of software is running to do this testing.

So use selenium testing sparingly. A good strategy is to restrict selenium testing to “happy path” testing. These happy path tests become a compliment to other more focused and faster unit and integration tests.

Getting Started With Selenium RC Fu

Selenium RC Fu is hosted at rubyforge.org in the “pivotal.rb” project. It’s not well documented and a little hard to find. The primary documentation is the README File.

Step 1 is to add it to your project:


    script/plugin install svn://rubyforge.org/var/svn/pivotalrb/seleniumrc_fu/trunk

Building Your Tests

Create your tests in app/tests/selenium. The basic structure mirrors the other test types:



    require File.dirname(__FILE__) + "/selenium_helper"
    class HappyPathsTest < MyProject::SeleniumTestCase
    
      def test_nav_bar
      	  ...
      end
    
      def test_about
      	  ...
      end
    
      # more tests
    end

Note that HappyPathsTest extends something called MyProject::SeleniumTestCase. Selenium RC Fu provides a sample selenium helper file that suggests this convention for name spacing your tests.

For the happy paths tests I simply wanted to go to a page and know that the page loads. A pattern I learned from my colleague Shifra is:

  1. Pick something you know is on the target page, but not on the current page. Let’s call it ‘evidence’. Assert that evidence is not present on the current page.
  2. go to the target page
  3. assert the evidence is present on the target page.

This process insures that you are moving about as you expect. Here’s an example from my project:



      def test_tasks
        ...

        assert_element_not_present "xpath=//h1&91;text()='The Daily Planet']"
        click_and_wait "link=The Daily Planet"
        assert_element_present "xpath=//h1[text()='The Daily Planet']"

        ...    
      end

I repeated this simple pattern for every path.

Selenium Functions/Assertions/Commands

Here are the functions and assertions I used in my tests:

  • click_and_wait locator
  • go_back
  • wait_for_page_to_load
  • type locator, text
  • assert_text_present text
  • assert_text_not_present text
  • assert_equal value1, value2

Many of the functions require a ‘locator’ argument. This needs to identify a single element on the page. Often the name or id of an element is sufficient, but you may need to use an XPath. Check the docs for more information on element locators.

These functions were sufficient for me. Look for more in vendor/plugins/seleniumrc_fu/lib/seleniumrc_fu/selenium_dsl.rb.

Selenium RC Fu comes with some rake commands too (from rake -T selenium):



    rake selenium:restart_servant           # Stop and start the selenium servant (the server that launches browsers) on localhost
    rake selenium:run_server                # Run the selenium servant in the foreground
    rake selenium:server                    # Run the selenium remote-control server
    rake selenium:start_servant             # Start the selenium servant (the server that launches browsers) on localhost
    rake selenium:stop_servant              # Stop the selenium servant (the server that launches browsers) on localhost
    rake selenium:test                      # Run a selenium test file (default test/selenium/selenium_suite)
    rake selenium:test_with_server_started  # Run the selenium tests in Firefox

Running Your Tests

You can run your selenium tests individually or as part of a suite. Selenium RC Fu comes with a suite script in the vendor/plugins/seleniumnrc_fu/sample directory. Drop that in your test/selenium directory.

The selenium server must be running before running individual tests. You can start the server with the rake command rake selenium:server. Once the server is running you run your tests from the command line or via the suite.

You can also run the suite with the command rake selenium:test. This command is smart enough to start the server if it’s not already running. However, when the server is started by this mechanism its a little more work to stop it. You can stop the server with a simple ^c if it was started via the rake selenium:server command.

Make It So

That’s really all you need to build your tests but here are few more things that may help.

Site Diagram

A site diagram is handy for building your happy paths tests. There will be a lot of paths through the site and marking the paths on the diagram as you visit them is an easy way to track your progress. On the diagram you need to indicate both pages and the paths to those pages.

The later is really important. It’s the paths that you will be testing with your happy path selenium testing. Often there are buttons, links, or other controls such as check boxes that may touch the server and reload the current page, or just update the current page via ajax. Be sure to add these to your navigation diagram.

Here’s the diagram I used for my site.

site diagram

Selenium IDE

You can code your tests by hand using Selenium RC Fu functions but it’s easier if you use Selenium IDE. This is a Firefox plugin that records your activity. That activity is available in various selenium dialects and can be pasted into your tests. Unfortunately, it does not have output for Selenium RC Fu. Nonetheless I still found it useful. I simply clicked through my application while Selenium IDE was recording, pasted the resulting selenium commands into my tests, then converted those commands to equivalent Selenium RC Fu commands. For example:

Output from Selenium IDE:



    @selenium.click "link=My Account"
    @selenium.wait_for_page_to_load "30000"
    assert @selenium.is_text_present("Update Account")

Becomes:



    click_and_wait "link=My Account"
    assert_text_present "Update Account"

Test Data

Your selenium tests will be exercising your application just like standard unit or integration tests. These tests require data and there are a variety of techniques to make data available to your tests. Building your happy path tests is easier if you have fixture data and that fixture data is loaded in your development environment via rake db:fixtures:load. If you use fixture scenarious, it is handy to have a scenario for selenium testing, which you would load with something like rake db:scenario:load SCENARIO=selenium.

With your fixtures loaded in your development environment you can see exactly what is present during testing and the selenium IDE will record exactly what is played back by selenium.

XPath Checker

Usually Selenium IDE can assign a locator that simply works but there are times when it can’t or won’t. Using XPath Checker you can right click on an element and it will display an XPath to that element. You can also experiment with varitions and XPath Checker will list all of the elements on the page that can be identified by that XPath.

That’s it. Let me know how it goes.

Teaching Your Tests To Report Unused Parameters

Posted in rubyonrails, testing on January 23, 2008 by Kelly Felkins

Note: a Pivotal Blabs version of this article is available here.

Recently I was about to check in some changes and did a last minute click through of the application. All of a sudden I’m staring at a stack trace. My tests were green and I had functional tests for the failing
controller/action.

Tests are like pants — they cover your backside while you focus on other things like adding features to your application. Suddenly I felt a breeze on my cheeks. Something was amiss.

I soon discovered the action and its associated tests had diverged over time. Some of the parameters were renamed in the action but not in the functional test. Since some of the work of the action was
conditional on the presence of certain parameters, that work was no longer being tested.

This exposed weaknesses in the tests and code, such as expected side effects in the tests that are never checked. If they had been checked the tests would have failed and the parameter name mismatch
would have been discovered.

Most functional tests provide specific parameters that should at least be examined during the processing of the action. Reporting unread parameters would strengthen those tests. It was conceivable to me that
some of the other functional tests had similar unused parameters. I wanted all of my functional tests to report all unused parameters.

The first step was to instrument the params hash. I wanted to track access to the params hash and report parameters that were not read during the processing of the action. I don’t know what all is done to params during the life cycle of a test. I’m only interested in access
from the time the action starts till it returns so I need to be able
to turn the tracking on and off at specific times.

It turns out that Rails uses a subclass of Hash called HashWithIndifferentAccess. I added my changes to HashWithIndifferentAccess in test/test_helper.rb:


    class HashWithIndifferentAccess
      def [](key)
        @accessed_keys ||= {}
        @accessed_keys[key] = true
        super
      end
    
      def start_logging
        @accessed_keys = {}
      end
    
      def end_logging
        @accessed_keys['action'] = true
        @accessed_keys['controller'] = true
        never_accessed = []
        self.each_key do |key|
          never_accessed << key unless @accessed_keys.include?(key)
        end
        raise "Some keys never accessed: #{never_accessed.join(', ')}" unless never_accessed.empty?
      end
    end

With these changes an exception will be raised if any first level keys are not read between start_logging and end_logging.

In each of my functional tests I added code similar to this (from account_controller_test.rb):


    class AccountController
      around_filter :check_params
      private
      def check_params
        params.start_logging
        yield
        params.end_logging
      end
    end

The around filter starts and ends the logging in the context of the action.

With these changes in place my tests no longer passed and my backside was warm and protected again.

ruby and gems on os-x leopard

Posted in leopard, os-x, ruby, rubyonrails on November 1, 2007 by Kelly Felkins

I continue the quest to use the leopard provided ruby and gems. Unfortunately, I’ve made some mistakes. Perhaps I can save you from doing the same. Want to benefit from my experience without investing by reading the story? Skip to the recommended process at the end.

The Story

Part I: Anticipation

I upgraded to leopard recently. Before doing so I read about leopard’s new and improved ruby and rails support and some of the issues around this support. Since I had previously installed ruby and rails using Dan Benjamin’s excellent “Building Ruby, Rails, Subversion, Mongrel, and MySQL on Mac OS X” I anticipated that, at the least, I would need to make some adjustments when it was time to install or update gems so I wasn’t surprised when I got an error while running geminstaller on one of my projects. I’m not blaming geminstaller. I suspect the problem would have occurred if I had attempted my installation with a standard “sudo gem install” command.

Part II: Just Like I Had Good Sense

I decided to move forward by removing my installations of ruby and rubygems then using the leopard installation for future gem installs. The first step was to remove the installations of rubygems and ruby. Fortunately, I had saved the source directories that I used to install these features originally. To remove rubygems, I did the following:

$ cd /usr/local/src/rubygems-0.9.2 
$ sudo rm `cat InstalledFiles`

This will remove the gem excutable, but not the gems themselves. I expected to remove the gem library when I removed my installation of ruby.To remove ruby I thought I could do something similar:

$ cd /usr/local/src/ruby-1.8.6 
$ cat .installed.list | xargs -L 1 sudo rm -dfv

I ran that last command over and over again until it no longer deleted anything. I could have changed the rm command to ‘rm -rf’ but I wanted to see what got deleted. At some point I realized I was only deleting ‘ri’ stuff so it was time to do some more research.

Part III: The Awakening

Apparently you can specify an option when installing ruby from source to keep a list of all files installed. I didn’t know about this and didn’t do it. It would have been helpful.Instead I attempted to decipher the makefile to determine what gets done when you say ‘make install’. It turns out most of the work is done in a script called instruby.rb. I sifted through that script and attempted to devine where and what it moves into place when performing an install. I listed every directory and file, then removed the duplicates and came up with this set of commands for removing ruby:

sudo rm -rf /usr/local/lib/ruby 
sudo rm /usr/local/bin/ruby 
sudo rm /usr/local/bin/erb 
sudo rm /usr/local/bin/irb 
sudo rm /usr/local/bin/rdoc 
sudo rm /usr/local/bin/ri 
sudo rm /usr/local/bin/testrb 
sudo rm /usr/local/share/man/man1/ruby.1

I expected that typing $ ruby -v would show the leopard version of ruby. Surprisingly, I saw something like “/usr/local/bin/ruby not found” (I don’t remember the exact message). Of course the issue was that the previous ruby had been hashed by the shell. Fixing it was simple: $ hash -r. Now I see:

$ ruby -vruby 1.8.6 (2007-06-07 patchlevel 36) [universal-darwin9.0]

Then I tried a few commands installed as gems. Here again there were errors. This time the solution was to remove the associated bin file from /usr/local/bin. At this point I realized that all gems with executables that I had previously installed had entries in /usr/local/bin that would likely generate errors.

Part IV: Drastic Measures

It was at this point I elected to remove the entire /usr/local and /opt/local directories. Keep in mind that I have a relatively new macbook and I can still fairly easily reinstall anything that I had previously removed from there.Now my gem installs are working correctly when I apply some of the new darwin solutions outlined below.

What would I do now, with the benefit of hindsight?

  1. First, why change your existing ruby and gems installation? If it works, you’re done. Wait till the kinks are worked out before investing your time.
  2. If you really want to use the leopard versions of ruby and rails, don’t install leopard… Not Yet!
  3. Remove all of your existing gems, using “sudo gem uninstall [gem name here]. You don’t need them in their current location. Remove them first while you can use the gem utility. This will remove them both from the gem install directory and also from the /usr/local/bin directories (for gems that have bins).
  4. Uninstall ruby using the commands listed above.
  5. Now install leopard.
  6. Update xcode from the leopard install disk.
  7. Do not gem update --system (see this thread on apple’a ruby support discussion)

At this point you should be able to use rubygems to install and update gems in the new normal way. What’s that mean? I’ve encountered these issues:

  1. The well documented architecture issue for some gems. Hint: try something like “sudo env ARCHFLAGS="-arch i386" gem install [gem name here]“.
  2. The ‘gems’ command apparently attempts to update gems even when they don’t need updating. Why update a gem that doesn’t need updating? I don’t know, but if you try it will often fail with
  3. Directory /Library/Ruby/Gems/1.8/doc/[some gem]/ri already exists, but it looks like it isn't an RDoc directory. Because RDoc doesn't want to risk destroying any of your existing files, you'll need to specify a different output directory name (using the --op option).

    Try adding the “–no-rdoc” option.

If you see “missing ruby header files” when installing a gem then you probably missed the “install xcode” recommendation above.

bread and butter capistrano

Posted in capistrano, rubyonrails on February 20, 2007 by Kelly Felkins

29.pngThe East Bay Ruby Meetup for February was all about Capistrano.

Capistrano’s creator, Jamis Buck, says capistrano

…is a utility for executing commands in parallel on multiple machines, such as for automating the deployment of applications

Three of us talked about capistrano, deployment and scaling. My talk was titled “Bread and Butter Capistrano” because I wanted to emphasize that I use it as a tool to get my work done, find it very helpful, and use it all the time, but I haven’t spent a lot of time studying the tool itself.

Here’s the talk. Enjoy!

Bread and Butter Capistrano

If you’ve read this far, please consider rating me at Working With Rails.

Follow

Get every new post delivered to your Inbox.