Ruby debugging with puts, tap and Hirb
I use puts heaps when debugging. Combined with tap, it’s pretty handy. You can jump right in the middle of a method chain without having to move things around into variables.
1 |
x = long.chain.of.methods.tap {|x| puts x }.to.do.something.with
|
I thought hey why don’t I merge the two? And for bonus points, add in Hirb’s table display to format my models nicely. These are fairly personal customizations, and aren’t specific to a project, so I put them in my own ~/.railsrc file rather than each project.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# config/initializers/developer_specific_customizations.rb if %w(development test).include?(Rails.env) railsrc = "#{ENV['HOME']}/.railsrc" load(railsrc) if File.exist?(railsrc) end # ~/.railsrc require 'hirb' Hirb.enable :pager => false class Object def tapp(prefix = nil, &block) block ||= lambda {|x| x } tap do |x| value = block[x] value = Hirb::View.formatter.format_output(value) || value.inspect if prefix print prefix if value.lines.count > 1 print ":\n" else print ": " end end puts value end end end # Usage (in your spec files, perhaps?) "hello".tapp # => hello "hello".tapp('a') # => a - "hello "hello".tapp(&:length) # => 5 MyModel.first.tapp # => # +----+-------------------------+ # | id | created_at | # +----+-------------------------+ # | 7 | 2009-12-29 00:15:56 UTC | # +----+-------------------------+ # 1 row in set |
Insect Marketing
Steve recently posted a link to a company promoting their brand by attaching business cards to flies and have them fly around the conference room. If you haven’t seen it, go and watch a short snippet of the video now, so you know what I’m talking about, when I say:
Using flies for marketing purposes is not morally acceptable.

I wish a preface wasn’t necessary, but I’ve seen one too many logical fallacy to avoid one. This article is about a relatively unimportant moral issue, and many non-vegans may even find it ludicrous. That’s OK. What’s not OK, is making the leap to “since you have to care about flies, veganism is ludicrous”. That’s a logical fail, and totally missing the point. There is interesting ethical material here, no matter which side of the line you fall.
Replace flies with a familiar mammal – say pigs, or dogs – and the above statement is not particularly controversial. What makes mammals different from flies?
Unsurprisingly, there is little animal rights literature afforded to the use of insects for marketing purposes. Instead we can turn to an analogous problem, one of the most heated areas of debate within the vegan camp – can vegans eat honey? Zealots will flat out reject this question – bees are animals too, they say, and as such we have no right to exploit them. Unfortunately, “animal” is a tricky definition. The ocean sponge, despite having no brain nor nervous system and for nearly all purposes resembling a plant, is classified by science as an animal. Intuitively we see bees as living things, but we need to be more rigourous in our thinking in order to afford them rights.
Bees certainly are exploited in industrial farming – in that their natural behaviour and environment is subverted – both for honey and for pollination of other crops. If they can not be found deserving of rights however, this exploitation is by the by. It is no more morally relevant than the farming of vegetables. Replace the flies with a tiny mechanical ornithopter.
The classic “test” for determining animalhood is whether a being can suffer from pain. Intelligence, though handy, is not required. Insects are quite far removed from humans physically and mentally, so unlike mammals where we can apply familiar criteria for pain (distress, avoidance, etc…), far more extrapolation is required for flies. It is only in the past decade that research studies have started to find that insects do exhibit responses best classified as pain. Capability for physical pain though doesn’t necessarily present a problem in this scenario however. An electric shock is a very different type of stimulus than the distress of having your natural movement inhibited. This distress is easily observed in mammals, however it is less clear in the case of flies, whose movements are erratic at the best of times. It seems the jury is still out, though given our woeful track record of reasoning about other living beings, even within our own species (racism, sexism, homophobia), all other concerns aside it is prudent to give flies the benefit of the doubt.
Perhaps we can learn something from our everyday behaviour towards insects. A child pulling the legs off a spider is generally considered negative, not for the pain it causes the spider, but from the callous disregard and use of another living being that was causing the child no harm. Indeed, it is the same judgment that mistreatment of a dog would attract. It is seen as abnormal behaviour – we associate disregard for animals with disregard for other humans. Instinctively we apply Tolstoy’s observation that “as long as there are slaughterhouses, there will be battlefields.” The welfare of the spider may be disregarded, but perhaps we need to consider our own? On the other hand, many of us would swat a fly without a second thought. This is often justified by the inconvenience the fly is causing us – its buzzing wings, its tickling feet. Overall this is an interesting consideration, but not relevant to the topic at hand where there is no need for compromise. Indeed, we would first need to address the “compromise” of using higher-order creatures for food and labour, a topic outside the particular scope of this essay. Using flies for marketing is actively seeking out and exploiting them, a very different circumstance.
It’s impossible to go through life never hurting a fly, as it were. As mentioned above, bees are used commercially to pollinate a large proportion of our edible plants. The classic definition of vegan as “not using animal products” is more usefully replaced with “reasonably avoiding products that cause suffering to non-humans. ” In the case of honey, it is neither an essential nutrient, nor the only sweetener of its kind. It is trivial to avoid, and as such should be. Which brings us in a rather large circle back to the case of insect marketing.
It sounds absurd when stated so directly, but flies are clearly not an essential component of any marketing strategy. In this case it may appear unique and novel, but this bears no significance on the moral acceptability of the act. Steve’s followup question “How else could your startup be promoted by living things other than humans?” is not worth dwelling on – exploitation of living things, even if you assign a low likelihood to their ability to feel pain, is trivially avoided in your marketing strategy, and as such should be.
Photo is Fly by Reini68
The CPRS will not save us
When Kevin Rudd says “it is now time to act”, he is not himself acting. When he says “failing to act today is to roll the dice on our children’s future”, he is throwing them down the chute and calling for lucky 7. This is PR. This is spin. This is not to be believed.
5% by 2020 is not “ambitious”. It’s not even close. We’re too late for 450ppm. This isn’t a problem we can softly softly approach. We’re already past the 350ppm limit our planet can maintain. We’re fat and about to have a heart attack. This isn’t the time ease into a healthy lifestyle. Give up the burgers, get to the gym – your only other choice is death.
I’m a big believer in markets, but a market can’t work if you’re monkeying about with it. There’s a time and place for subsidies, but propping up a dying industry is not one of them. Giving the most compensation to the biggest polluters is bullshit. It undermines the very goal the CPRS is supposed to achieve. There is no economic incentive to change if the government is offsetting your costs. The market doesn’t even cover one of the biggest polluters in the country – agriculture. Yes, there are complications in measuring emissions from farms, but the idea that it’s not possible is frankly an outright lie. A market so obstructed and distorted cannot function effectively.
Taking into account voluntary action for households is a token gesture. The direct measurable impact households can make is important psychologically, but in the scheme of things neglible. The real gains to be made – primarily dietary changes and reduced consumption – will not and should not be covered by an emissions trading scheme1. Don’t think for a second GreenPower absolves your responsibility to our planet.
It all comes down to risk.
If science is wrong and we do something, the precious “economy” slows a little as we move to renewables we’re going to need anyway2.
If science is right (current consensus) and we do nothing: Big capital-T Trouble.
Skepticism is important, but I’m not a betting man. And I’m not stupid. I’m not going to gamble on something this important. Keep asking questions, keep demanding proof, but the evidence overwhelmingly suggests that we must act now. Real action, not KRudd-jumping-up-and-down-not-going-anywhere action.
What can you do? Start making some noise. Tweet about it, blog about it, write your local paper. Tell your friends, tell your family, tell the person you meet on the train. Get angry. The government’s job is to look after us. It’s time to demand they stop posturing and start doing their job. The Walk Against Warming is coming up on the 12th December. Get off your couch and come show them – and those complicit on the sideline – that we’ve had enough. The future is not to be gambled with.
Supplementary
- Government media release on CPRS compromises
- Greens call CPRS actively harmful
- AYCC also unhappy
- 350ppm science
- Walk Against Warming
1 Though it’d be neat to get some credits for being vegan…
2 Even if all the climate change research is bollocks, “renewables” will rear it’s head again in a few years anyways. Exponential growth does not play well with finite resources.
Full stack testing rack applications
Herein is described a method for full stack testing CloudKit apps. The same techniques could easily be applied to other rack web application or framework, which is pretty much all the ruby ones these days (rails, sinatra, pancake, etc…) This method is ideal for non-html services. For HTML you’re probably better off just using webrat/selenium.
There are two external services that make up our stack:- CloudKit application
- OpenID server
Both of these are rack applications, so we can start them up using the same method in our spec helper.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
require 'spec' require 'pathname' require Pathname(__FILE__).dirname + 'support/application_server' require Pathname(__FILE__).dirname + 'support/tcp_socket' TEST_PORTS = { :app => 9293, :openid => 9294 } $servers = nil Spec::Runner.configure do |config| config.before(:all) do $servers ||= Support::ApplicationServer.multi_boot( { :config => File.expand_path(Dir.pwd + '/config.ru'), :port => TEST_PORTS[:app], :daemonize => true }, { :config => File.expand_path(Dir.pwd + '/spec/support/rack_my_id.rb'), :port => TEST_PORTS[:openid], :daemonize => true } ) end end |
A global variable is required here, since before(:all) in rspec runs once per describe block, rather than once per test run. An at_exit hook is used to shutdown the services after the test run.
You need a way of resetting your data between test runs. The default CloudKit::MemoryTable does not provide a mechanism for this – any deleted resource will exist in the version history of that resource (and will respond with a 410 rather than 404). By subclassing MemoryTable, we can provide a purge method that does what we need:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# A custom storage adapter that allows a total purge of a collection # This is handy in test mode to clear out data between specs class PurgeableTable < CloudKit::MemoryTable # Remove all resources in a collection. # Unlike a normal delete, which versions the resource (and sets up a 410 response), # this method removes all trace of the resource (it will 404). # # Example: # CloudKit.setup_storage_adapter(adapter = PurgeableTable.new) # adapter.purge('/items') def purge(collection) query {|q| q.add_condition('collection_reference', :eql, collection) }.each do |item| @hash.delete(@keys.delete(item[:pk])) end end end |
Since we’ll be testing the CloudKit app from a separate process, we also need a way of triggering a purge. An easy way is some custom rack middleware that provides a URL we can hit to reset the app. Clearly, we only want to enable this in test mode.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class ResetApp def initialize(app, options = {}) @app = app @options = options end def call(env) request = Rack::Request.new(env) if request.path == '/test_reset' && request.request_method == 'POST' @options[:adapter].purge('/items') return Rack::Response.new([], 200).finish else @app.call(env) end end end |
1 2 3 4 5 6 |
# config.ru CloudKit.setup_storage_adapter(adapter = PurgeableTable.new) if ENV["RACK_ENV"] == 'test' use ResetApp, :adapter => adapter end |
Now all the infrastructure is set up, we can test the CloudKit app using familiar ruby HTTP libraries:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
require 'httparty' require 'mechanize' require 'json' require 'oauth' describe 'OAuth + OpendID' do include HTTParty base_uri "localhost:#{TEST_PORTS[:app]}" before(:each) do HTTParty.post("/test_reset").code.should == 200 end specify 'Registering for an oauth token' do @consumer = OAuth::Consumer.new('cloudkitconsumer','', :site => "http://localhost:#{TEST_PORTS[:app]}", :authorize_path => "/oauth/authorization", :access_token_path => "/oauth/access_tokens", :request_token_path => "/oauth/request_tokens" ) @request_token = @consumer.get_request_token agent = WWW::Mechanize.new page = agent.get(@request_token.authorize_url) login_form = page.forms.first login_form.field_with(:name => "openid_url").value = "localhost:#{TEST_PORTS[:openid]}" page = agent.submit(login_form) oauth_form = page.forms.first page = agent.submit(oauth_form, oauth_form.button_with(:value => "Approve")) # Get access token @access_token = @request_token.get_access_token # Update an item result = @access_token.put("/items/12345", {:name => "Hello"}.to_json) result.code.should == "201" end end |
There’s a lot of code and not much supporting text here. I’m hoping it all just clicks together pretty easy. Hit me up with any questions.
Pain free cows are not awesome
Check it out, science reckons it can make a cow that doesn’t feel physical pain. Celebrate progress! We can all eat meat guilt free now, right?
Not quite.
How do you think scientists develop a pain-free cow?
“When the team injected a noxious, painful chemical into their paws, the mice licked them only briefly. In contrast, normal mice continued to do so for hours afterwards”
By inflicting pain. After mice, you work your way up the animal heirachy (each animal is different, you know) until eventually you arrive at livestock. There is a high moral cost, but also a high economic and opportunity cost – these scientists are smart guys, imagine what they could come up with if they weren’t busy torturing? We’re not even close to getting this working on larger mammals – behaviour in mice is not a good indicator for behaviour in more developed animals, not to mention all the regulatory barriers to actually marketing such an animal. At this point an actual robot cow is still a long way off.
Assuming we do eventually develop the perfect pain-free cow though, it really doesn’t solve the core problems. No matter what your views on animal rights, industrial farms cannot continue to exist. They cause tremendous amounts of pollution, both on a global the-seas-are-rising scale, and on a local my-whole-city-has-lung-problems scale. Current rates of meat consumption place an enormous burden on the health system – we produce far more meat than a healthy societly can consume. Pain-free research is a blatant misuse of resources.
Even if we allow that battery farms should continue to operate, a pain-free cow still isn’t necessarily a worthwhile goal. As evidenced by the neurotic behaviours developed by all battery farmed animals kept in confined spaces, the mental suffering of these animals is at least, if not greater, equal to the physical suffering endured. Confinement drives people crazy, it’s drives animals crazy too. A pain-free cow will still suffer. Strictly less than a normal cow perhaps, but the magnitude of this reduction would likely be less than many commentators are assuming.
We also encounter a problem of moral cushioning. As abolitionists are all too familiar with, in many cases the proliferation of “free range”, “organic” and other pretty labels are simply providing a convenient excuse for people to ignore (or even feel good about!) the consequences of their food choices. Is eating pain-free beef morally acceptable? Of course not. You are still eating an animal that suffered, you are still killing the earth. We need to be wary of providing excuses that will further delay any real moral progress.
We ought to reduce suffering, but as with any potential improvement the costs need to be weighed against the benefits. Pain-free free beef does not pass the grade.
Photo is Two Cows by kwerfeldein
Participation in the open source gift economy
Programmers should not use open source “for free”. If you are using open source code, you need to be thinking about your contribution back to the economy. Open source is a gift economy, and can only flourish with participation. While there is no legal or expected obligation for using open source code, there is an ethical duty to contribute to the health of the economy which you are benefiting from. The type of contribution you can make varies depending on what skills you can offer. Patches, documentation, advocacy, feedback, money – at least one of these is open to anyone (at a basic level) no matter what your level of competency, even to non-programmers. The most computer illiterate can still talk something up to his friends!
Programmers, at the opposite end of the spectrum, have an even greater ethical duty to contribute due to the dramatically lower cost options available to us. It is easy for us to give quick, specific, helpful feedback to many projects due to our understanding of the technical domain. Indeed, the cost can even be negative – I know many programmers (myself included) who have not only had their projects improved by releasing them open source, but have benefited dramatically from the social network and standing that this creates. In this case, we not only have an ethical motivation to contribute, but a selfish one as well!
Mandating a contribution back to every project you use may seem onerous, and I’d agree. The beauty of a gift economy is that you only need to contribute back to the system as a whole, rather than specific projects. A gift economy allows (encourages!) you to create value for needy projects that you only tangentially use, or to add value by releasing your own code. As opposed to a barter system, which is a direct exchange of value, the open source gift economy works without this reciprocity. You scratch my back, I’ll scratch someone else’s. This allowance makes it almost trivial to meet your ethical responsibility. It certainly isn’t a chore. Write some documentation, publish some code, talk about your latest find down at the pub. You’ll be creating a richer experience for everyone, including yourself.
What have you shared lately?
BacktraceCleaner and gems in rails
UPDATE: Fixed the monkey-patch to match the latest version of the patch, and to explicitly require Rails::BacktraceCleaner before patching it to make sure it has been loaded
If there’s one thing my mother taught me, if you’re going to clean something up you may as well do it properly. Be thorough, cover every surface.
Rails::BacktraceCleaner is a bit sloppy when it comes to gem directories. It misses all sorts of dust – hyphens, underscores, upper case letters, numbers. That’s not going to earn any pocket money. Let’s teach it a lesson.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# config/initializers/this_is_what_a_gem_looks_like.rb require 'rails/backtrace_cleaner' module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner private GEM_REGEX = "([A-Za-z0-9_-]+)-([0-9.]+)" def add_gem_filters Gem.path.each do |path| # http://gist.github.com/30430 add_filter { |line| line.sub(/(#{path})\/gems\/#{GEM_REGEX}\/(.*)/, '\2 (\3) \4')} end vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{RAILS_ROOT}/",'') add_filter { |line| line.sub(/(#{vendor_gems_path})\/#{GEM_REGEX}\/(.*)/, '\2 (\3) [v] \4')} end end end |
I’ve submitted a patch to rails, please review if you like.
Kudos to Matthew Todd for pairing with me on this.
Gnocchi with pumpkin
Ingredients
- Potatoes
- Flour (works with gluten free)
- Pumpkin
- Onion
- Garlic
- Spinach
- Pine nuts
- Non-dairy milk (soy, rice, whatever)
Method
Roast pumpkin, onion and garlic. Meanwhile, peel and chop the potatoes (one per person), boil, then mash. Add flour while kneading until not sticky (i.e. dough). When roast is just about done, heat some oil in a pan and add a heaped tablespoon of flour. Stir constantly, gradually adding milk, to make a sauce that isn’t lumpy. Add spinach, pinenuts, and the roasted produce. Roll out the dough into long worms, then chop into small pieces. This is the gnocchi! Bring a pot of water to the boil and throw in the gnocchi. When it floats to the top, fish it out with a slotted spoon and throw in the sauce.
Serve!
For a delicious alternative, use broccoli and mushrooms in the white sauce.
Benchmarks for creating a new array
1 2 3 4 5 6 7 8 9 10 11 |
require 'benchmark' n = 1000 m = 50000 blank = [0] * m Benchmark.bm(7) do |x| x.report(".new with block:") { (0..n).collect { Array.new(m) { 0 } }} x.report(" .new no block:") { (0..n).collect { Array.new(m, 0) }} x.report(" [0] * x:") { (0..n).collect { [0] * m }} x.report(" #dup:") { (0..n).collect { blank.dup }} end |
1 2 3 4 5 6 |
$ ruby19 benchmark.rb
user system total real
.new with block: 10.180000 0.210000 10.390000 ( 10.459538)
.new no block: 3.690000 0.210000 3.900000 ( 3.915348)
[0] * x: 4.280000 0.210000 4.490000 ( 4.505334)
#dup: 0.000000 0.000000 0.000000 ( 0.000491)
|
Know your constructors! What is #dup doing? I think it’s cheating.
Acts_as_state_machine locking
consider the following!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Door < ActiveRecord::Base acts_as_state_machine :initial => :closed state :closed state :open, :enter => :say_hello event :open do transitions :from => :closed, :to => :open end def say_hello puts "hello" end end door = Door.create! fork do transaction do door.open! end end door.open! # >> hello # >> hello |
It’s broken, you can only open a door once. This is a classic double-update problem. One way to solve is with pessimistic locking. I made some codes that automatically lock any object when you call an event on it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class ActiveRecord::Base # Forces all state transition events to obtain a DB lock def self.obtain_lock_before_all_state_transitions event_table.keys.each do |transition| define_method("#{transition}_with_lock!") do self.class.transaction do lock! send("#{transition}_without_lock!") end end alias_method_chain "#{transition}!", :lock end end end class Door < ActiveRecord::Base # ... as before obtain_lock_before_all_state_transitions end |
beware! Your state transitions can now throw ActiveRecord::RecordNotFound errors (from lock!), since the object may have been deleted before you got a chance to play with it.
If you’re not using any locking in your web app, you’re probably doing it wrong. Just sayin’.
Range#include? in ruby 1.9
Range#include? behaviour has changed in ruby 1.9 for non-numeric ranges. Rather than a greater-than/less-than check against the min and max values, the range is iterated over from min until the test value is found (or max). This is necessary to cover some edge cases of ranges which are incorrect in 1.8.7, as demonstrated by the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class EvenNumber < Struct.new(:value) def <=>(other) puts "#{value} <=> #{other.value}" value <=> other.value end def succ puts "succ: #{value}" EvenNumber.new(value + 2) end end puts (EvenNumber.new(2)..EvenNumber.new(6)).include?(EvenNumber.new(5)) # 1.8.7 # 2 <=> 6 # 2 <=> 5 # 5 <=> 6 # true # buggy! # 1.9.1 # 2 <=> 6 # 2 <=> 6 # succ: 2 # 4 <=> 6 # succ: 4 # 6 <=> 6 # false # correct! |
This makes sense for the conceptual range, but has a performance impact especially on large ranges. #include? has gone from O(1) to O(N). This is most likely to crop up when checking time ranges – Time#succ returns a time one second in the future.
1 2 3 4 5 6 |
(Time.utc(1999)..Time.utc(2001)).include?(2000) # 1.8.7 # true # 1.9.1 # Don't wait for this to finish... |
Workarounds
Ruby 1.9 introduces a new method Range#cover? that implements the old include? behaviour, however this method isn’t available in 1.8.7.
1 2 3 4 5 6 7 8 9 |
puts (EvenNumber.new(2)..EvenNumber.new(6)).cover?(EvenNumber.new(5)) # 1.8.7 # undefined method `cover?' for #<struct EvenNumber value=2>..#<struct EvenNumber value=6> (NoMethodError) # 1.9.1 # 2 <=> 6 # 2 <=> 5 # 5 <=> 6 # true |
Another alternative, if it makes sense for your range, is to define the to_int method, which ruby will use to do a straight comparison against your min/max values.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class EvenNumber < Struct.new(:value) # ... as before def to_int value end end puts (EvenNumber.new(2)..EvenNumber.new(6)).include?(EvenNumber.new(5)) # 1.8.6 and 1.9.1 # 2 <=> 6 # 2 <=> 5 # 5 <=> 6 # true |
Personally, I’ve monkey-patched range in 1.8.* to alias cover? to include?. That’s it. May your test suites not appear to hang.
Selenium, webrat and the firefox beta
*UPDATE: * Latest version of webrat (0.5.3, maybe earlier) includes a fixed version of selenium, so you shouldn’t need this hack.
I needed a few hacks to get selenium running with webrat.
First, make sure you are running at least 0.4.4 of webrat. Don’t make the same mistake I did and upgrade your gem version, but not the plugin installed in vendor/plugins.
1 2 3 |
gem install webrat gem install selenium-client gem install bmabey-database_cleaner --source=http://gems.github.com |
There is a trick to get Firefox 3.5 beta working. The selenium server package with webrat 0.4.4 only supports FF 3.0.*. Follow these instructions, patching the jar that is packaged with webrat (vendor/selenium-server.jar) so that the extensions that selenium uses will be valid for the new FF.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
cd vendor/ # In webrat dir
jar xf selenium-server.jar \
customProfileDirCUSTFFCHROME/extensions/readystate@openqa.org/install.rdf
jar xf selenium-server.jar \
customProfileDirCUSTFFCHROME/extensions/{538F0036-F358-4f84-A764-89FB437166B4}/install.rdf
jar xf selenium-server.jar \
customProfileDirCUSTFFCHROME/extensions/\{503A0CD4-EDC8-489b-853B-19E0BAA8F0A4\}/install.rdf
jar xf selenium-server.jar \
customProfileDirCUSTFF/extensions/readystate\@openqa.org/install.rdf
jar xf selenium-server.jar \
customProfileDirCUSTFF/extensions/\{538F0036-F358-4f84-A764-89FB437166B4\}/install.rdf
replace "3.0.*" "3.*" -- `find . | grep rdf`
jar uf selenium-server.jar \
customProfileDirCUSTFFCHROME/extensions/readystate@openqa.org/install.rdf
jar uf selenium-server.jar \
customProfileDirCUSTFFCHROME/extensions/{538F0036-F358-4f84-A764-89FB437166B4}/install.rdf
jar uf selenium-server.jar \
customProfileDirCUSTFFCHROME/extensions/\{503A0CD4-EDC8-489b-853B-19E0BAA8F0A4\}/install.rdf
jar uf selenium-server.jar \
customProfileDirCUSTFF/extensions/readystate\@openqa.org/install.rdf
jar uf selenium-server.jar \
customProfileDirCUSTFF/extensions/\{538F0036-F358-4f84-A764-89FB437166B4\}/install.rdf
|
(hat tip to space vatican)
I haven’t been able to get Safari working yet.
I want to run selenium tests besides normal webrat tests, so I created a new environment “acceptance” that I can run tests under. Modify your test helper file:
1 2 3 4 5 6 7 8 |
# test/test_helper.rb ENV["RAILS_ENV"] ||= "test" raise "Can't run tests in #{ENV['RAILS_ENV']} environment" unless %w(test acceptance).include?(ENV["RAILS_ENV"]) require 'webrat' require "test/env/#{ENV["RAILS_ENV"]}" # ... |
1 2 3 4 5 6 7 |
# test/env/test.rb require 'webrat/rails' Webrat.configure do |config| config.mode = :rails config.open_error_files = false end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# test/env/acceptance.rb require 'webrat/selenium/silence_stream' require 'webrat/selenium' require 'test/selenium_helpers' require 'test/element_helpers' # Required because we aren't isolating tests inside a transaction require 'database_cleaner' DatabaseCleaner.strategy = :truncation Webrat.configure do |config| config.mode = :selenium end class ActionController::IntegrationTest self.use_transactional_fixtures = false # Necessary, otherwise selenium will never see any changes! setup do |session| session.host! "localhost:3001" end teardown do DatabaseCleaner.clean end end # Hack: webrat requires this, even though we're not using rspec module Spec module Expectations class ExpectationNotMetError < Exception end end end |
1 2 3 4 5 6 7 8 9 10 11 |
# lib/tasks/test.rake namespace :test do task :force_acceptance do ENV["RAILS_ENV"] = 'acceptance' end Rake::TestTask.new(:acceptance => :force_acceptance) do |t| t.test_files = FileList['test/acceptance/*_test.rb'] t.verbose = true end end |
Notes
- selenium and javascript helpers are from pivotallabs pat, they’re really handy for testing visibilty of DOM elements
- there’s some magic in webrat to conditionally require
silence_streambased on something in active_support. I don’t understand it quite enough, but requiring it explicitly was necessary to get things running for me - webrat/selenium assumes some classes are loaded that only happens if you’re using rspec. I’m not, so stubbed out the ExpectationNotMetError (it is only referred to in a rescue block).
rake test:acceptanceruns the selenium tests. Running acceptance tests directly as a ruby script runs them using normal webrat – this is actually handy when writing tests because you get a quicker turnaround.- to pause selenium mid test run (to see wtf is going on), just add
getsat the appropriate line in your test
Faster rails testing with ruby_fork
A long running test suite isn’t the problem. Your build server can take care of that. A second or two here or there, no one notices.
The killer wait is in the red/green/refactor loop. You’re only running one or two tests, and an extra second can mean the difference between getting into flow or switching to twitter. And you know what kills you in rails?
1 2 3 4 5 |
$ time ruby -e '' -r config/environment.rb real 0m3.784s user 0m2.707s sys 0m0.687s |
Yep, the environment. That’s a lot of overhead to be waiting for everytime you run a test, especially since it’s the same code every time! You fix this with a clever script called ruby_fork that’s included in the ZenTest package. It loads up your environment, then just chills out, waiting. You send a ruby file to it, and it forks itself (the process containing the environment) to execute that file. The beauty of this is that forking is really quick, and it leaves a pristine copy of the environment around for the next test run.
‘Environment’ doesn’t just have be environment.rb, for bonus points you can load up test_helper.rb, which will also load your testing framework into memory. In fact, you can preload any ruby code at all – ruby_fork isn’t rails specific.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ ruby_fork -r test/test_helper.rb & /opt/local/bin/ruby_fork Running as PID 526 on 9084 $ time ruby_fork_client -r test/unit/your_test.rb Started ... Finished in 0.565636 seconds. # Aside: this time is bollocks 3 tests, 4 assertions, 0 failures, 0 errors real 0m0.972s # This is the time you're interested in user 0m0.225s sys 0m0.035s |
That’s fantastic, though you’ll notice in newer versions of rails your application code is not reloaded. By default your test environment caches classes – which normally isn’t a problem except that newer rails versions also eager load those classes (so they’re loaded when you load enviornment.rb). You can fix this by clearing out the eager load paths in your test environment file:
1 2 |
# config/environments/test.rb
config.eager_load_paths = []
|
On my machine this gets individual test runs down from about 4 seconds to less than 1 second. You can sell that to your boss as a four-fold productivity increase.
Testing Glue Code
db2s3 combines together 3 external dependencies – your database, the filesystem, and Amazon’s S3 service. It has 1 conditional in the main code path (and it’s not even an important one). The classic unit testing approach of “stub everything” provides little benefit.
Unit testing is good for ensuring complex code paths execute properly, that edge cases are properly explored, and for answering the question “what broke?”. For trivial glue code, none of these are of particular benefit. There are no complex code paths or edge cases, and it will be quickly obvious what broke. In fact, the most likely thing to “break” (or change) over time isn’t your code, but the external services it is sticking together, which stubs cannot protect you from. Considering the high relative cost of stubbing out all your dependencies, unit testing becomes an expensive way of testing something quite simple.
For glue code, integration tests are the best solution. Glue code needs to stick, and integration tests ensures that it does. Here is the only test that matters from db2s3:
1 2 3 4 5 6 7 8 9 |
it 'can save and restore a backup to S3' do db2s3 = DB2S3.new load_schema Person.create!(:name => "Baxter") db2s3.full_backup drop_schema db2s3.restore Person.find_by_name("Baxter").should_not be_nil end |
This test costs money to run since it hits the live S3 service, but only in the academic sense. The question you need to ask is “would I pay one cent to have confidence my backup solution works?”
Always remember why your are testing. Unit tests are a focussed tool, and not always necessary.
Backup MySQL to S3 with Rails
Here is some code I wrote over the weekend – db2s3. It’s a rails plugin that provides rake tasks for backing up your database and storing it on Amazon’s S3 cloud storage. S3 is a trivially cheap offsite backup solution – for small databases it costs about 4 cents per month, even if you’re sending full backups every hour.
There are many scripts around that do this already, but they fail to address the biggest actual problem. The aws-s3 gem provides a really nice ruby interface to S3, and dumping a backup then storing it really isn’t that hard. The real problem is that I really hate system administration. I want to spend as little time as possible and I want things to Just Work.
A script is great but there’s still too many things for me to do. Where does it go in my project? How do I set my credentials? How do I call it?
That’s why a plugin was needed. It’s as little work as possible for a rails developer to backup their database, so they can get back to making their app awesome.
db2s3. Check it out.

