1. assert_select: Rails core and handling lists and tables

    July 30th, 2006

    logo.jpg

    assert_select is up for inclusion in Rails core. It simplified my life, and I hope we can do the same for other Rails developers. So head over to the Ruby on Rails blog and voice your opinion.

    And incidentally, I just did a major new release of assert_select, adding some features I needed for my recent test cases. The most important one, it’s now easier to test lists, tables, forms and anything that has structure and repetition using nested assertions.

    For example:

    assert_select “form[action=http://test.host/login]” do
        assert_select “input[name=username]”
        assert_select “input[name=password]”
    end
    
    assert_select “ol>li” do
        # List item has an ID we can relate to.
        assert_select “li#?”, /item-\d+/
        assert_select “p”
        # And a link to that resource.
        assert_select “a[href=?]”, /item\/\d+/
    end

    Testing flash[:notice]:

    assert_select "div#notice", flash[:notice] || false

    Testing for text will now match (string or regular expression) every selected element against that text. And as you can see from the above examples, it’s now easier to work with substitution values.

    Install and use with:

    ./script/plugins install http://labnotes.org/svn/public/ruby/rails_plugins/assert_select
  2. links for 2006-07-30

    July 29th, 2006

  3. links for 2006-07-29

    July 28th, 2006

  4. links for 2006-07-28

    July 27th, 2006

    • Better semantics than XHTML 1.1, better structuring for documents, easier for UI, so many things to like about it. But the lack of compatibility.
  5. Parsing expressions with sub! and blocks

    July 26th, 2006

    To get CSS pseudo classes working for scrAPI and assert_select, I had to rewrite the CSS selector parser. I’m sure there’s a Lex and Yacc for Ruby somewhere, but I ended up with a much simpler solution. One I can actually read and fix.

    I ended up using sub! and blocks.

    Starting with the current expression, I simply sub! the token I’m looking for, testing if it exists and removing it at the same time, reducing the expression by one token.

    To test if :empty comes next:

    if statement.sub!(/^:empty, "")
      @pseudo << some code
      next
    end

    To deal with tokens that have values, I use blocks. For example:

    next if statement.sub!(/^#(w+)/) do |match|
      id = $1
      attributes << ["id", id]
      “”
    end

    Again, test for a match, do something with the token, and reduce the expression.

    In CSS selectors, identifiers, class names, attributes and pseudo classes can come in any order. So a loop repeats on the expression until it doesn’t find any token it recognizes, or there’s nothing left to parse.

    Inside the loop, I could use if and elsif, but I found it’s easier to keep the code readable (less indentation) by repeating on each match and breaking at the end. So the loop looks something like:

    while true
      next if statement.sub!(/^#(w+)/) do |match|
        # handle ID
        ""
      end
        next if statement.sub!(/^.(w+)/) do |match|
        # handle class name
        ""
      end
      # And so forth.
      break
    end

    If you’re interested, you can check the code here.

  6. New features: scrAPI toolkit for Ruby and assert_select

    July 26th, 2006

    Major update to both libraries.

    I added a full test suite, and in the process caught and fixed a few bugs, like case sensitive (where it shouldn’t), group selectors not working as expected, and a few other small gotchas.

    I also added pseudo classes from CSS 3. Pseudo classes are a bit tricky to explain, so let me show with some examples:

    table tr:nth-child(odd)

    Selects every other (odd) row in the table.

    table tr:nth-child(-n+6)

    Selects the first six rows in the table.

    table tr:nth-child(6)

    Selects the sixth row in the table.

    table tr:first-child

    Selects the first row in the table.

    div p:first-child

    Will almost work like you expect it to, but only if the paragraph is the first element in the div. Otherwise, it selects nothing.

    div p:first-of-type

    Will select the first paragraph in the div, ignoring any elements that are not a paragraph.

    div p:not(.post)

    Will select all the paragraphs in the div, except those that have the class “post”.

    p:not(:empty)

    Will select all the paragraphs except the ones that are empty.

    p a:only-child

    Will select all paragraphs that have a single link, no paragraphs that have zero, two or more links.

    You can install assert_select as a plugin with:

    ./script/plugin install http://labnotes.org/svn/public/ruby/rails_plugins/assert_select

    To download the scrAPI toolkit for Ruby:

    gem install scrapi

    Or:

    svn export http://labnotes.org/svn/public/ruby/scrapi

    And if you have cool tricks for scraping that you’d like to share, leave a comment or e-mail me. I’d like to collect them all into a tips & tricks post (with attribution, of course).

  7. links for 2006-07-25

    July 24th, 2006

  8. Test Your Migrations

    July 24th, 2006

    Judging by the lack of posts on the topic of testing and migration, I’m guessing most developers don’t. You try and hope for the best.

    I wouldn’t trust my migrations to just work, not when it comes to live data in production. In fact, I just discovered two bugs in mine.

    The first bug happened when I tried to split one table in two, to allow users to auto-login from different computers. One table has an optional field, but in the split table, that field is record that only exists if you login. The migration I wrote worked well on the test database, which happened to have login for all accounts.

    But it failed when doing a dry-run on the production database. I wouldn’t catch it in time without doing the dry-run test.

    The second bug happened when I tried to merge two tables into one. I created a redundancy in the authentication system which evolved over time. The code was growing too big to maintain, so I decided to streamline it and merge the duplicates. So I loaded objects from one table, created them in the other table, and deleted the first set.

    That worked fine, but just before deploying the migration, I decided to get rid of some unused fields in the database. And that’s when I broke the migration.

    The migration starts by requiring the User model. Then changes the database schema, and then attempts to save new User objects. Because the database schema changes after the User model is loaded, the saving fails without reporting an error. It looks like it works, but there’s no change in the database.

    The solution was easy, one line of code courtesy of script/console. After changing the database schema, I force all classes to reload by calling Dispatcher.reset_application!.

    Note to self: Always use Dispatcher.reset_application! if you’re changing the database schema and then changing the data.

    Fortunately, I discovered both bugs by doing a dry-run against the production database. The migration starts by loading objects into memory, then runs the migration against the database, and tests these objects against the database, printing discrepancies to the console.

    I backup the affected tables, run the migration, check the console. If there’s no output, I’m good to go. If I see any errors, I switch back to the old table and fix the migration code.

    This also brings me to another topic. Using migrations as the database definition (thanks to Ed Gibbs for the reminder):

    There’s a downside to migrations. Over time, your schema definition will be spread across a number of separate migration files, with many files potentially affecting the definition of each table in your schema. When this happens, it becomes difficult to see exactly what each table contains.

    I don’t. I treat migrations like patches. You write them to change the existing behavior, and apply the patch to get a new version. And you can’t always go back. If I’m splitting a table to allow multiple values, I can’t switch back without losing the new data.

    The patch is the difference, but the current state is in the latest snapshot. That’s what rake db:structure:dump is for. The migration is just code I can test in development environment, and patch the production environment with the confidence it will work.

  9. links for 2006-07-23

    July 22nd, 2006

  10. Microsoft vs Apple vs Yahoo

    July 22nd, 2006

    Details are starting to leak out about Microsoft’s new Zune device. Here’s what I learned so far.

    Microsoft is still bad at picking names. They might as well call it iPod Enterprise Edition SP2. I hope they switch to something friendly.

    Microsoft will be back-stabbing all its media player partners since they failed to deliver the expected marketshare. It will be interesting to see if the accessory market will jump in, or learning the lesson stay clear of the bait.

    Microsoft’s PlayForSure will for sure not play on Microsoft’s new device. Though I doubt customers will notice they’ve been fooled once.

    Microsoft will pay for you to transfer all your iTunes collection over. Someone will start a viral campagin to buy Zunes, transfer collections, then return the devices, just for the sake of bleeding Microsoft dry. (But they can take it)

    But really there’s one thing I’m most curious about. The one thing I really can’t wait to find out.

    Microsoft will, true to its track record, cram every imaginable feature into the new device regardless of cost to them. Apple will remain true to its spirit and only add features that matter, work well and have a profit margin. Which one will be more appealing to the consumer market? And a better play in the long run?

    So far Microsoft has only bee successful with keyboards and mice. The XBox is great, but where’s the profit margin?

    Meanwhile, Yahoo is taking a different strategy altogether. They can’t afford to make devices, so they can’t fight in the DRM market. Instead, they’re trying to get the music industry to realize we’re all willing to play for MP3s.

    At least I am.

    And I know eMusic has been doing this for years, but eMusic has a very limited collection of independent stuff. And Yahoo has a very limited collection of mainstream stuff. But if we get big brands to convience the industry MP3 is a good thing, we’re going to break out of this music collection silos.

    And buy MP3 over at Amazon.

    That’s bad for Microsoft and even worse for Apple, but it’s great for consumers.