Enabling/disabling observers for testing 1

Posted by Ryan Kinderman Thu, 10 Apr 2008 02:53:50 GMT

If you use ActiveRecord observers in your application and are concerned about the isolation of your model unit tests, you probably want some way to disable/enable observers. Unfortunately, Rails doesn't provide an easy way to do this. So, here's some code I threw together a while ago to do just that.

module ObserverTestHelperMethods
  def observer_instances
    ActiveRecord::Base.observers.collect do |observer|
      observer_klass = \
        if observer.respond_to?(:to_sym)
          observer.to_s.camelize.constantize
        elsif observer.respond_to?(:instance)
          observer
        end
      observer_klass.instance
    end
  end

  def observed_classes(observer=nil)
    observed = Set.new
    (observer.nil? ? observer_instances : [observer]).each do |observer|
      observed += (observer.send(:observed_classes) + observer.send(:observed_subclasses))
    end
    observed
  end

  def observed_classes_and_their_observers
    observers_by_observed_class = {}
    observer_instances.each do |observer|
      observed_classes(observer).each do |observed_class|
        observers_by_observed_class[observed_class] ||= Set.new
        observers_by_observed_class[observed_class] << observer
      end
    end
    observers_by_observed_class
  end

  def disable_observers(options={})
    except = options[:except]
    observed_classes_and_their_observers.each do |observed_class, observers|
      observers.each do |observer|
        unless observer.class == except
          observed_class.delete_observer(observer)
        end
      end
    end
  end

  def enable_observers(options={})
    except = options[:except]
    observer_instances.each do |observer|
      unless observer.class == except
        observed_classes(observer).each do |observed_class|
          observer.send :add_observer!, observed_class
        end
      end
    end
  end
end

Include this in a Test::Unit::TestCase or 'include' in your RSpec configuration, whatever rocks your boat. Here's a stupid example:

class SomethingCoolTest < Test::Unit::TestCase
  include ObserverTestHelperMethods

  def setup
    disable_observers
  end

  def teardown
    enable_observers
  end

  def test_without_observers
    # ...
  end

end

When you go to test the behavior of the observer itself, simply disable/enable like the following to disable/enable all observers except the one you're testing:

class DispassionateObserverTest < Test::Unit::TestCase
  include ObserverTestHelperMethods

  def setup
    disable_observers :except => DispassionateObserver
  end

  def teardown
    enable_observers :except => DispassionateObserver
  end

  def test_without_observers_except_dispassionate_observer
    # ...
  end

end

Plugin to Support composed_of Aggregations in ActiveRecord Finder Methods 1

Posted by Ryan Kinderman Thu, 03 Jan 2008 23:11:17 GMT

In Rails, hooking up an ActiveRecord model to use a value object to aggregate over a set of database fields is a piece of cake. With the accessor methods that are created for a composed_of association, you can now deal exclusively with the composed_of field on your model, instead of directly manipulating or querying the individual database fields that it aggregates. Or can you? As long as all you're doing with the aggregate field is getting and setting its value, your aggregated database fields remain encapsulated. However, if you want to retrieve instances of your model from the database through a call to a finder method, you must do so on the individual database fields.

Consider the following ActiveRecord model definition:

class Customer < ActiveRecord::Base
  composed_of :balance, :class_name => "Money", :mapping => %w(balance_amount amount)
end

Given such a model, we can do something like this with no problem:

customer = Customer.new
customer.balance = Money.new(512.08)
customer.balance                      # returns #<Money:abc @amount=512.08>
customer.save!

However, now that we've saved the record, we might want to get that record back from the database at some point with code that looks something like:

customer = Customer.find(:all, :conditions => { :balance => Money.new(512.08) })

or like:

customer = Customer.find_all_by_balance( Money.new(512) )

This would provide full encapsulation of the aggregated database fields for the purposes of both record creation and retrieval. The problem is, at the time of my posting this article, it doesn't work. Instead, you have to do this:

customer = Customer.find(:all, :conditions => { :balance_amount => 512.08 })

To deal with this problem, I've submitted a ticket, which is currently scheduled to be available in Rails 2.1.

If you need this functionality, but your project is using a pre-2.1 release of Rails, I've also created a plugin version of the changes I submitted in the aforementioned ticket. To install:

script/plugin install http://svn.ryankinderman.net/find_conditions_with_aggregation

Addendum: The patch has been committed to changeset 8671. Yay!

Testing on High: Bottom-up versus Top-down Test-driven Development 10

Posted by Ryan Kinderman Mon, 19 Nov 2007 02:13:21 GMT

I recently talked to a number of Rails developers about their general approach to testing some new functionality they're about to code. I asked these developers if they found it to be more useful to start testing from the bottom-up or top-down. I suggested to them that, since Rails uses the MVC pattern, it's easy to think of the view, or user interface, as the "top", and the model as the "bottom". Surprisingly, nearly every developer that I asked this question of answered that they prefer to start from the bottom, or model, and test upwards. Nearly every one! I expected that I'd get a much more mixed response than I have. In fact, I think that the correct place to start testing is precisely at the highest level possible, to reduce the risk of building software based on incorrect assumptions of how best to solve a user requirement.

Bottom-up Testing

Bottom-up testing implies bottom-up design in TDD. In bottom-up design, a developer would probably consider the high-level objectives and break them up into manageable components that interact with each other to provide the desired functionality. The developer thinks about how each component will be used by its client components, and tests accordingly.

The problem with the bottom-up approach is that it's difficult to really know how a component needs to be used by its clients until the clients are implemented. To consider how the clients will be implemented, the developer must also think about how those clients will be used by their clients. This thought process continues until we reach the summit of our mighty design! Hopefully, when the developer is done pondering, they can write a suite of tests for a component which directly solves the needs of its client components. In my experience, however, this is rarely the case. What really happens is that the lower-level components tend either to do too much, too little, or the right amount in a way that is awkward or complicated to make use of.

The advantage of bottom-up testing is that, since we're starting with the most basic, fundamental components, we guarantee that we'll have some working software fairly quickly. However, since the software being written may not be closely associated with the high-level user requirements, it may not produce results that are necessarily valuable to the user. A simple client could quickly be written which demonstrates how the components work to the user, but that's besides the point unless the application being developed is a simple application. In such a case, the bottom-level of components are probably close enough to the top-level ones that there is little risk involved in choosing either the bottom-up or top-down approach.

Unless you're writing a small application, the code is probably going to have to support unforeseen use cases. When this comes as a result of ungrounded assumptions about the software that's already been written, this can mean a lot of rework. I can tell you from experience, once you realize that your lower-level components don't fit the bill for the higher levels in the system, it can be quite a chore to go back and fix, remove, or replace all of that unnecessary or incorrect code.

Top-down Testing

Top-down testing implies top-down design in TDD. Following the top-down approach, the developer will pick the highest level of the system to be tested; that is to say, the part of the system that has the closest correlation to the user requirements. This approach is sometimes referred to as Behavior Driven Development. Whatever it's called, the point is that you test the most critical parts of the application first.

Since software is often written for human users, the most critical parts usually involve the front-end as it relates to the value being provided by the system being developed. When testing from the top-down, the effort is the inverse of bottom-up testing: Instead of spending a lot of time thinking about how the components to be developed will be used by other components to be developed, the focus is on how the user needs to interact with the system. Testing involves proving that the system supports the required usability. For an application with a graphical front-end, this might involve testing for a minimal version of that front-end.

The disadvantage of top-down testing is that you can end up with a lot of stubbed or mocked code that you then have to go back and implement. This means it might take longer before you have software that actually does something besides pass tests. However, there are ways that you can minimize this sort of recursive development problem.

One way to minimize the time between starting development of a feature and demonstrating functionality that is valuable to the user is to focus on a thin slice of the overall architectural pie of the application. For example, there may be a number of views that need to be implemented before the system provides some major piece of functionality. However, the developer can focus on one view at a time, or one part of the view. That way, the number of components that need to be implemented before the system does something useful is small; ideally, one component in each architectural layer that I need build out, and often times only a part of the overall functionality of each component.

Another way to minimize the amount of time before the system does something useful is to code a small bit of functionality without worrying about breaking the problem up into classes until you have some tested, working code to analyze. You can then use established methods for refactoring to bring the code to an acceptable level of quality.

The advantage of top-down testing is that you write functionality that solves the most critical functionality first. This generally means starting development at a high level. When the system eventually does something besides pass tests, what it does will provide value to its users. Additionally, because development starts at a high level, the code that is written is based on the current understanding of the problem, and not on assumptions. This guarantees that the tests and code that are written are not superfluous.

Conclusion

The challenge with top-down testing is that you must be highly disciplined to ensure that the code you write is being refactored and is properly evolving into a cohesive domain model for the application. This is compared with bottom-up testing, where you start with the domain model and build your system around it. Either way, you're going to be refactoring code. The difference is in where the time in refactoring is spent. In my experience, when doing bottom-up testing, more time is spent correcting incorrect assumptions about how the domain model will be used than on actually improving code that already works to solve the user requirements. In order to avoid making assumptions about the code being written, it must be written at the level that is closest to providing actual value to the end-user. In so doing, the developer focuses on continuous refinement of code that already provides value, as opposed to speculative design and development.

Bug: composite_primary_keys and belongs_to with :class_name option

Posted by Ryan Kinderman Sat, 17 Nov 2007 02:39:44 GMT

For those of you using the composite_primary_keys gem as of version 0.9.0, you may encounter an issue if you try to do something like:

class Reading < ActiveRecord::Base
  belongs_to :reader, :class_name => "User"
end

When a User is loaded up from the database via the reader association, the CPK modification to ActiveRecord::Reflection::AssociationReflection#primary_key_name incorrectly returns "user_id" as the primary key name. If you encounter this issue, I've submitted a patch against revision 124 that can be obtained here.

Hopefully this will get fixed in the next release. More hopefully, I won't need to care by then.

AppleScript: Reverse screen colors on a Mac and keep a black background in Terminal

Posted by Ryan Kinderman Mon, 12 Nov 2007 06:21:32 GMT

Sometimes, it's such a hassle to get up and turn on a light when I'm in the middle of coding. Other times, I don't have control over the lighting, like in a dim pub or cafe. In dim lighting, it's a strain on my eyes to be looking at black text on a white background. I've tried dimming the brightness, but that decreases the contrast, which is also a strain on my eyes. Then, I discovered Control+Option+Command+8, which is a keyboard shortcut to the "Reverse black and white" Universal Access feature. Contrary to it's name, it does more than reverse black and white, it reverses all of the colors on the screen. This, however, is acceptable and requires only a slight mental adjustment for me. Now I use a nice black-on-white color scheme in TextMate for coding, and when I'm in dim lighting, I simply reverse the colors and it's white-on-black, still full-contrast. I'm happy, except for one thing.

I often have a number of Terminal windows open when I'm working, and I open and close them frequently. Although I'm happy with a black-on-white color scheme for TextMate, I can't tolerate anything but white-on-black for my normal Terminal windows. So, what happens when I throw the screen colors in reverse is that all of my lovely white-on-black Terminal windows go to black-on-white. Unacceptable! Thus began my first foray into AppleScript.

After referencing here, here, and here, along with the Terminal "dictionary", I came up with this:

tell application "System Events"
  tell application processes
    key code 28 using {command down, option down, control down}
  end tell
end tell

tell application "Terminal"
  if default settings is equal to settings set "White on Black" then
    set settingsSet to "Black on White"
  else if default settings is equal to settings set "Black on White" then
    set settingsSet to "White on Black"
  end if
  set default settings to settings set settingsSet
  repeat with w in every window
    set current settings of w to settings set settingsSet
  end repeat
end tell

This script first invokes the Control+Option+Command+8 keyboard shortcut to reverse the screen colors. It then toggles the default "settings set" for Terminal to one of two pre-defined sets I've created: "White on Black" and "Black on White". Hopefully the names are self-explanatory. Then, the script iterates over all currently-open Terminal windows and changes their current "settings set" to whatever the new default is.

Pretty simple. I named the script "Reverse Screen Colors" and dropped it in /Users/[user account]/Library/Scripts. I invoke it via the keyboard with QuickSilver.

Selenium Core Bug and TinyMCE Anchor Tags

Posted by Ryan Kinderman Fri, 12 Oct 2007 05:01:18 GMT

Today, I was trying to get Selenium to click an anchor tag that was created with the "link" plugin in a TinyMCE editor. I was able to verify that the link was present with something as simple as verifyElementPresent('link=Link Text'). However, when I tried calling clickAndWait('link=Link Text'), it gave a "Window does not exist" error. A quick Google search yielded the answer: a bug in Selenium Core.

When the TinyMCE "link" plugin creates a link that doesn't open in a new window, it sets the "target" attribute on the anchor tag to "_self". Selenium Core versions prior to 0.8.4 (which hasn't been released yet) don't respond to links with "target" set to "_self".

If you're doing Rails development and using the selenium_on_rails plugin, it uses an old version of Selenium Core (0.7.something) as of this posting. To fix the anchor tag problem, I replaced the contents of the selenium-core directory under vendor/plugins/selenium_on_rails with that of the core directory of the Selenium Core 0.8.3 release, then applied the patch described in the bug spec linked to above. This seems to have fixed the problem.

Hopefully this saves you all some time and muddling.

Learning Ruby Meta-programming with MetaKoans

Posted by Ryan Kinderman Sun, 23 Sep 2007 03:29:59 GMT

As I mentioned previously, the MetaKoans Ruby Quiz (#67) is a great way to learn meta-programming. However, it had some shortcomings. I've used MetaKoans as a training tool, and something I hear a lot is that it's unclear why certain things make a koan, or set of koans, pass. One reason for this confusion is that, often times while puzzling through the solution, a student will do something that causes multiple koans to pass at once. Due to the way that the koans are structured, I wasn't able to find a way to make a single koan pass at a time.

I've addressed this shortcoming by restructuring the koans so that the problem can be solved incrementally, one koan at a time. While restructuring the koans, I wrote a solution to each in turn, and saved that solution to its own knowledge file. Each file is a small refactoring from the one before it, ultimately building up to the final solution.

For the purposes of future training sessions, I've also started adding documentation to each refactoring, explaining how it changed from the previous one, and why it changed the way it did. The documentation isn't complete yet, but it's a start.

The restructured MetaKoans, along with the individual refactorings of my solution, can be found at http://svn.ryankinderman.net/metakoans_training. Feel free to check it out.

A little explanation

You'll notice that the knowledge files in my solution follow the pattern: knowledge_for_koan_XX_Y.rb. The XX number is the koan that the knowledge is a solution for. The Y number is the ordered refactoring index, with 1 being the first, most straight-forward solution, and subsequent indices being refinements of the original.

The reason for this structuring is that, often times, the straight-forward, brute-force solution to a koan isn't always the optimal solution. So, I'd make refactorings to show how the code could, IMHO, be improved.

Finally

Thanks go to ara.t.howard for coming up with the original MetaKoans quiz. It's been an extremely informative tool for myself and many others.

Tag permalink bug and bad Content state values in Typo Rev 1528 1

Posted by Ryan Kinderman Sat, 22 Sep 2007 18:13:27 GMT

For those Typo users out there, this might be helpful.

As usually happens once every 3 or 4 blog posts, a few days ago I discovered a bug in Typo that cripples my blog and causes me to have to bring it down for a while. I'm not sure exactly which revision it was; one from version 4.0.2 I believe. Finding bugs so frequently probably has something to do with the fact that my idea of "upgrading" is doing an svn up from trunk and running db:migrate RAILS_ENV=production. Anyways, each time this happens, I end up spending at least 2 hours getting into "Typo debug mode", once getting passed "oh crap my blog is screwed mode", and fixing the problem. I don't mind debugging Typo. Having to do it now and then wouldn't be a problem if the types of problems I found were edge cases; unfortunately, they're usually not. These are things that, most definitely, should be covered by unit tests somewhere and caught when they get broken. This time around, I found three issues of this type.

The first issue was as simple as forgetting to update the values in the state column of the contents table in or before database migration 058. These values apparently were valid at some point with values such as ContentState::Ham, ContentState::Spam, ContentState::Published, and so on. However, they are now supposed to be simply ham, spam, and published, respectively. Not updating this data causes migration 058 to fail with Content complaining about an invalid state value. I had to go into mysql and update the values manually.

The second problem is also a simple oversight. After running migration 058, all comments are now stored in a table called feedback. Fine. However, whereas before the comments were displayed in the blog, after running the migration, you'll notice that they're not. This is because there is a published field in the feedback table that indicates whether a comment should be shown or not. After running migration 058, the value of this field for all comments on the blog is 0 (zero), which causes a comment to not be shown. Simply set this value to 1, and the comments are back again.

The third issue, which I discovered by accident from having a tail -f log/production.log running whilst fixing the first bug. I noticed that, every once in a while, a request would come in for Parameters: {"action"=>"show", "id"=>"some_category", "controller"=>"categories"}, which would result in an ActiveRecord::RecordNotFound error to be raised from Category.find_by_permalink. I noticed that the value of params[:id] was actually the name of a tag, not a category. So, I traced the error to Tag.permalink_url, which looked like this:

File: typo/app/models/tag.rb

def permalink_url(anchor=nil, only_path=true)
  blog = Blog.find(1) # remove me...

  blog.url_for(
    :controller => 'categories',
    :action => 'show',
    :id => permalink
  )
end

Woops! I guess we don't want to construct a URL for CategoriesController as the permalink for a Tag. I just changed :controller => 'categories' to :controller => 'tags' and the problem was fixed.

Addendum: Updated this entry to include a description of and resolution to the issue with the feedback.published database field.

Task Dependencies in Capistrano 2.0

Posted by Ryan Kinderman Fri, 21 Sep 2007 04:33:21 GMT

I've been tooling around with Capistrano 2.0 for the past couple of days. I've decided that the more mature Capistrano gets, the more it seems to be, at its core, a remote rake system with a really good suite of predefined tasks specific to Rails deployment issues. Some things Rake has that Cap 2 seems to be lacking are the ability to define dependent tasks, as well as tasks that are executed only once (the first time), with subsequent invocations being skipped.

So, here it is: http://svn.ryankinderman.net/cap_task_dependencies/trunk

Just svn checkout or svn export that wherever you want (for Rails, vendor or vendor/plugins seems to make sense) and then require cap_task_dependencies/trunk/capistrano in your deploy.rb file or wherever else it might make sense for you.

Here's an example deploy.rb that uses the two new bits of functionality with the URL above exported to /vendor/cap_task_dependencies:

File: config/deploy.rb

require File.expand_path(File.dirname(__DIR__) + "/../vendor/cap_task_dependencies/capistrano")

namespace :prerequisites do
  task :some_task1, :once => true do
    # this task will only be invoked once
  end
end

task :some_task2, :once => true do
  # this task will only be invoked once
end

task :dependent_task, 
  :depends => ["prerequisites:some_task1", :some_task2] do
  # this task will be invoked as many times as it's called,
  # and it will call some_task1 and some_task2 each time, but
  # they will only be invoked once each
end

task :combo_task do
  # combo_task combines two tasks but still, prerequisites:some_task1
  # and some_task2 will be invoked only once each. 
  # prerequisites:some_task1 will be invoked from the first line in 
  # combo_task and some_task2 as a dependency of dependent_task
  prerequisites.some_task1
  dependent_task
end

I think that just about covers it. Look at the RSpec examples if you want more info.

As always, I'd love your feedback.

HOWTO: Clean the distribution files for all installed MacPorts packages 4

Posted by Ryan Kinderman Mon, 03 Sep 2007 19:41:00 GMT

I've been using a MacBook Pro for a while now. I got it when I started getting deep into the Ruby/Rails stuff. Being a developer, I use a lot of unix-y command-line tools, server software, and so on. The best way to install these types of things is to use some sort of package manager, such as Fink or MacPorts, the latter being the one I've chosen to use. With MacPorts, I can install, upgrade, or uninstall most of the open source apps that I love in Linux. However, there's a catch to MacPorts' ease of use: it doesn't clean up the packages that it downloads when you install/upgrade/uninstall an app unless you tell it to.

An example of an app that I installed recently that would be a pain to compile/install using a traditional build script is Tomcat 5. Tomcat 5 has 20+ library dependencies, some of which have their dependencies of their own. When I ran sudo port install tomcat5, it took about 20 minutes or so to download and compile everything. However, I never had to do anything else. I literally walked away and checked back periodically until it was done.

Today I decided that, since I was only using Tomcat for occasional development, I'd rather have it, and its plethora of dependencies, installed on a Linux VM instead of my real machine, so it didn't sit there eating up resources all the time. So, I went through and uninstalled everything. But I noticed a while ago that there was a 'clean' command for MacPorts that removed the downloaded packages from an install of an app, and I was curious about whether or not my uninstalling of Tomcat and it's dependencies resulted in these packages being auto-cleaned. So, I took a look in /opt/local/var/macports/distfiles and was surprised to find that the install packages for most of the apps I've ever installed/upgraded/uninstalled were still there!

Now, since these packages mainly consist of compressed source code, it's not going to eat up a lot of hard drive space. But, I had quite a few built up, and would rather not have them sitting there for no reason. Rather than go through and 'clean' each one individually, I created a bash script to do this for me for all packages represented in the distfiles directory. Since I know a few people who use MacPorts that aren't exactly savvy with *nix command-line tools, I thought I'd share it:

File: clean_ports

#!/bin/bash
ls -1 /opt/local/var/macports/distfiles | \
awk '{
   if ($1 !~ /^./) {
      system("port clean --all " $1)
   }
}'

Just throw this into a file and then run chmod +x clean_ports to make it executable. Now you can just periodically run clean_ports to avoid unnecessary build-up of the remnants of MacPorts installs.

Addendum: Aaron Schaefer mentioned in the comments that this same thing can be done using MacPorts pseudo-portnames. So, the simpler solution is to do: sudo port clean --all installed

Older posts: 1 2 3