Chad Woolley's blog
Many people (including me) have complained about the lack of a good GUI debugger for Ruby. Now that some are finally getting usable, I've found I actually prefer IRB-style ruby-debug to a GUI.
There's good tutorial links on the ruby-debug homepage, and a very good Cheat sheet, but I wanted to give a bare-bones HOWTO to help you get immediately productive with ruby-debug.
Install the latest gem
$ gem install ruby-debug
Install the cheatsheet
$ gem install cheat
$ cheat rdebug
Set autolist, autoeval, and autoreload as defaults
$ vi ~/.rdebugrc
set autolist
set autoeval
set autoreload
Run Rails (or other app) via rdebug
$ rdebug script/server
Breakpoint from rdebug
(rdb:1) b app/controllers/my_controller.rb:10
Breakpoint in source
require 'ruby-debug'
debugger
my_buggy_method('foo')
Catchpoint
(rdb:1) cat RuntimeError
Continue to breakpoint
(rdb:1) c
Next Line (Step Over)
(rdb:1) n
Step Into
(rdb:1) s
Continue
(rdb:1) c
Where (Display Frame / Call Stack)
(rdb:1) where
List current line
(rdb:1) l=
Evaluate any var or expression
(rdb:1) myvar.class
Modify a var
(rdb:1) @myvar = 'foo'
Help
(rdb:1) h
There are many other commands, but these are the basics you need to poke around. Check the Cheat sheet for details.
This can also be used directly from any IDE that supports input into a running console (such as Intellij Idea).
That should get you started. So, before you stick in another 'p' to debug, try out ruby-debug instead!
We all love to Monkey Patch Rails and other Ruby apps. However, we sometimes want to target these patches to the specific versions where they are needed.
Here's the easiest way to do this, via RubyGem's built-in version requirement support. The version 0.11.0 should indeed be greater than version 0.9.0:
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> Gem::Version::Requirement.new(['> 0.9.0']).satisfied_by?(Gem::Version.new('0.11.0'))
=> true
Notice that you can't do this with string comparison, because with a per-character comparison,1 is not greater than 9:
irb(main):001:0> '0.11.0' > '0.9.0'
=> false
Here's a little class which puts some helper and example methods around this approach (these methods are all in real use for some of our multipart mailer hacks):
module Pivotal
class VersionChecker
def self.current_rails_version_matches?(version_requirement)
version_matches?(Rails::VERSION::STRING, version_requirement)
end
def self.version_matches?(version, version_requirement)
Gem::Version::Requirement.new([version_requirement]).satisfied_by?(Gem::Version.new(version))
end
def self.rails_version_is_below_2?
result = Pivotal::VersionChecker.current_rails_version_matches?('<1.99.0')
result
end
def self.rails_version_is_below_rc2?
Pivotal::VersionChecker.current_rails_version_matches?('<1.99.1')
end
def self.rails_version_is_1991?
Pivotal::VersionChecker.current_rails_version_matches?('=1.99.1')
end
end
end
(note: some angle brackets changed due to code formatting bug)
Here's an example of how you'd use this:
if Pivotal::VersionChecker.rails_version_is_below_2?
# do some backward compatibility stuff
# or handle bugs that have been fixed in Rails > 2
end
Note that this is only possible now that Rails has started using a more sensible strategy for versioning edge gems and improved support for using advanced versioning with RAILS_GEM_VERSION.
For many projects, this may be overkill. It is useful at Pivotal, though, where many various projects may be on different rails versions, but still want to use the latest common core libraries (and monkey patches) without having to upgrade Rails for their app.
This isn't only useful for monkey patching. It can be handy for any library that wants to be backward- or forward-compatible with its dependencies. I've used this approach at Pivotal and on my personal projects to have Continuous Integration automatically run my tests against multiple dependency versions, without having to change anything other than the CI project name:
There are numerous other related topics for discussion in this area, such as the power of versions or the wisdom of freezing, but I'll save those for future posts. Even if you do freeze the trunk of Rails/plugins/gems, since the version is included in the source, this approach should work barring any conflicts with trunk changes since the last release.
Happy Versioning!
Interesting Things
- You can do single key presses in selenium with key-press. This is very useful in some scenarios - for example, predictive typing in search fields.
Interesting Things
- Don't forget about the 'net' tab in the FireBug firefox extension. This can take the place of other separate plugins such as Live HTTP Headers.
Interesting Things
RSpec global before/after: In addition to Behaviour-scoped before and after method forms, Rspec also has global
prepend_before, prepend_after, append_before, append_aftermethods.RSpec Custom Expectation Matchers: Remember that you can write your own Custom Expectation Matchers to make your specs as expressive as desired. For example:
foo.should contain_text.Self-Signed SSL Cert Gotcha: Firefox and IE hate it when your Certificate Authority and Server Certificate have the same Common Name.
RailMail: RailMail "gives you a persisted view of any mail your application has sent... With railmail there is no need to set up testing email accounts while developing your application; just send out mail to any address and Railmail will capture it." Nice...
validates_presence_of in ActiveRecord calls
blank?. This means you can use it check for dependent objects if you defineblank?.
I was just converting some Test::Unit tests to Rspec, and these regexps were handy. In one file, they handled 51 out of 53 lines, saving my fingers a lot of work. Tests can take an infinite variety of formats, so these obviously won't apply to everything, but they do illustrate how to use regexp substitution. This is using TextMate, your regexp implementation may vary...
from -> to
search string
replace string
def test_foo -> it "test_foo" do
def (test_[a-z_]*)
it "$1" do
assert !foo -> foo.should_not be_true
assert !(.*)$
$1.should_not be_true
assert foo -> foo.should be_true
assert (.*)$
$1.should be_true
assert_equal foo, bar -> bar.should == foo
assert_equal (.*), (.*)$
$2.should == $1
In his post "Redefining Constants" ( http://www.pivotalblabs.com/articles/2007/04/14/redefining-constants ), Brian Takita describes how to redefine Rails constants at test time. He points out that "it's all dirty", and that "...maybe the storage service can be an attribute that can be changed for individual tests.".
In a comment, I suggested that a global configuration object would be a better approach, and here's an example. It still uses a constant (as opposed to a global singleton object), but the constant is an object (a hash) which contain other values and objects. This avoids the need to redefine constants to use different values at test-time.
Create a sample Rails app
$ rails railsdi $ cd railsdi/ $ ls $ script/generate controller Sample $ # create development/test databases
Declare the configuration hash
First, add a constant in boot.rb. Just ignore the warning to not modify boot.rb - it's not talking about you. Put this at the beginning, right after the section that defines RAILS_ENV
boot.rb
REGISTRY = {}
Set per-environment defaults
Set any values or objects you want in the registry:
development.rb
REGISTRY[:key] = "development_value"
test.rb
REGISTRY[:key] = "test_value"
production.rb
REGISTRY[:key] = "production_value"
Verify that the correct values are used in each environment
Make a simple controller and view to verify the values are set per-environment:
sample_controller.rb
class SampleController < ApplicationController
def index
@registry_value = REGISTRY[:key]
end
end
sample/index.rhtml
Rails Environment: <%= RAILS_ENV %> Registry Value: <%= @registry_value %>
Start up the app in development and production environments, and hit http://localhost:3000/sample
Verify that registry values can be overridden at test time
sample_controller_test.rb
def test__can_redefine_registry_value
REGISTRY[:key] = 'overridden_value'
get :index
assert_equal 'overridden_value', assigns['registry_value']
end
Summary
I think this is a pretty good approach, and it feels a lot like testing in an app that uses a Dependency Injection/Registry architecture (in other words, simple to override anything you want). I'd be interested to hear if there are any situations that could not use this approach, and would have to fall back to defining constants in the environment files.
It would also be interesting to hear if anyone has had success integrating a Rails application with a Dependency Injection approach (using Needle or a home-grown solution).
-- Chad
Dave asked about this, in response to my "Best Remote Pairing Settings" post (http://www.pivotalblabs.com/articles/2007/04/13/best-remote-pairing-setting), so here's my take on it.
Audio Software:
For audio, Skype is the best. It takes a lot of CPU though. Sometimes the audio gets flaky, but restarting it usually fixes it. Also, Skype on the mac seems to crash sometimes. Audio over Yahoo and MSN messenger is pretty bad compared to skype, I think this is because Skype's peer-to-peer technology gives a much superior audio quality.
Video Software:
For video, Skype is very good, but only does 1-on-1 currently, not conference video. If you want cross-platform conference video, Yahoo video is the currently the best (only?) free option. Unfortunately, yahoo doesn't let you resize the video window like skype does.
iChat is really nice and does video conferencing, but requires a Mac on both ends. Also, iChat seems to need a lot of open ports (see portforward.com). I still haven't gotten around to playing with our firewall to open the necessary iChat ports. I tried ssh-tunneling all the ports listed for iChat on portforward.com, but that didn't work.
Audio Hardware:
For group conversation, the PolyCom Communicator, Model C100s, is the best (http://www.polycom.com). It works on Macs and PCs. It also has built-in echo cancellation which usally works pretty well with Skype. It also takes a headphone jack, so you can hear the room, but still let your pair wear headphones. The only downside is that the speaker is kind of underpowered, and you can't be heard in a loud room, but unless you are talking to the entire room your pair can just wear headphones. Also, it's hard to hear really large rooms over the polycom. For that, you can use a sudio mic (see below)
For headsets, the Plantronics GameCom Pro1 (Digital Signal Processing) is an awesome headset. It's really comfortable, which is a big deal if you wear it all day long. The only downside is that it is USB only, which means you can't use a splitter to
If you want to listen to a large room, and the Polycom isn't cutting it, you can invest in a studio quality microphone, and a tube preamp. We use the Behringer Tube Ultragain MIC100 Preamp, and a nice sudio directional mic - not sure of the brand now, because I'm remote :). Just plug these into an input jack via an adapter. The main downside of this is you don't get the echo-cancellation like you get with the Polycom, so you always hear yourself talk. You can mitigate this by putting the speakers far away from the mic, but it's still difficult.
One other note - on MacBooks, the audio input doesn't seem to work with a normal jack mic (non-USB). It is a dual-purpose jack or something. I've only had success with USB mics on my MacBook.
Video Hardware:
The QuickCam Orbit MP is a very nice webcam, and you can even move it around through software - it has little motors on it that let you point it different directions, if you have remote VNC access to the box it is connected to.
The QuickCam Orbit works with Skype and Yahoo on the Mac, but has problems with other software. The remote motor control doesn't seem to work, and iMovie and some other Mac apps don't recognize it.
The iSight is also a decent Mac webcam, but the resolution isn't as good as the QuickCam Orbit, and it's not remotely controllable.
I am a remote employee at Pivotal, so I do a lot of remote pairing, and I'm always trying new options. Here's a quick writeup on what I've found to work best.
This is specifically for working over a WAN. If you are on a LAN, other options will be better (and you should just get on the same machine as your pair anyway!). Remote pairing is pretty usable unless bandwidth is causing problems. CPU also makes a big difference - performance on an iMac is a lot better than on a Mac mini, especially the 1.6Mhz mini. I'm using the term "server" to mean the machine running the VNC server, and "client" to mean the machine running the VNC client.
Mac Server:
OSXVNC (Vine Server) with default settings. Turn on shared VNC connections if you want.
Windows server:
UltraVNC with the Video Driver Hook seems to work best; it's almost as fast as Windows Remote Desktop, but it requires that you use the UltraVNC client, which is only available for Windows. However, sometimes you get screen redraw issues with the video driver hook. This seems to be due to network or CPU issues, because it works great most of the time on most machines. If this happens, you can fall back to the Tight protocol on the client.
The "WinVNC Current User Properties" I use for the UltraVNC server are:
- Poll Full Screen (Ultra Fast)
- Poll Foreground Window
- Poll Windows Under Cursor
- System Hook DLL
- Video Hook Driver
- Low Accuracy (Turbo Speed)
- DON'T CHECK Poll Console Windows Only
- DON'T CHECK Poll on Event Only
Windows Client for server with a single monitor:
If you are using the UltraVNC windows server with the video driver hook, then you should use the UltraVNC client, with the "Ultra" encoding and 256 colors, with CopyRect and Cache encoding enabled.
If you are not using the UltraVNC video hook on your server, then UltraVNC is still a good client, with these settings:
- Tight Encoding
- 256 colors (less colors don't seem to help that much)
- Use CopyRect Encoding
- Use Cache Encoding
- Zip/Tight Compression enabled, set to 9
- Jpeg (Tight) Quality, set to 1
- Track Remote Cursor Locally
- Viewer Scale (whatever works for you)
Windows Client for a server with a dual monitor and a client with a dual monitor:
UltraVNC client has a bug where it scales, but will still not show any more width than one of the client's monitors, even though you make the window bigger. RealVNC does not have this problem, so it's probably a better client in this situation, even though it doesn't allow configuration of all the above options like UltraVNC does (at least not from the GUI).
Mac Client:
- Chicken of the VNC is OK, but it doesn't do scaling, and doesn't allow you to specify ports via the double-colon syntax (only displays, which means you have to do math if you are ssh tunneling)
- Free RealVNC is OK, but it doesn't do scaling
- Enterprise RealVNC does scaling
- There are several other mac clients such as VNCThing and VNCDimension, but they don't work on my mac for some reason, and don't seem to have any more features than Chicken of the VNC or RealVNC.
Linux server and client:
I've not been too impressed with the VNC linux servers or clients. They seem to be slow and crash a lot (both RealVNC and TightVNC). Your mileage may vary.
Alternatives I've tried and found to be inferior:
- The built in VNC server in OSX (slow and unconfigurable)
- Apple Remote Desktop (which is really just VNC too, and just as slow and unconfigurable)
- Timbuktu Pro (no faster than VNC, and had refresh issues)
- NoMachine NX (server only runs on Linux. It's a great option if you are connecting to Linux)
- Windows Remote Desktop/rdesktop (logs off server GUI, so unusable for remote pairing)
Other Notes:
I've read that running VNC over a compressed SSH tunnel will help performance. However, I think with the latest VNC protocols, which already do compression, this doens't make much of a difference.
Summary:
Most of these observations are from running different clients side-by-side. They are very subjective, because bandwidth and CPU are always affecting the performance. Let me know what your experiences are, and if you have any different ideas.
Say you have two tags you want to diff, and one has a deleted directory. If you do an 'svn diff', you won't see the deleted directory UNLESS you give the '--summarize' option:
svn diff --summarize http://host/project/tags/old_version http://host/project/tags/old_version
