Author Archive

Timecop 0.2.0 Released: Freeze and Rebase Time in Ruby

Wednesday, December 24th, 2008 by John Trupiano

I just released version 0.2.0 of Timecop this evening (morning).

The primary feature added was the distinction between “freezing” and “rebasing” time. In 0.1.0, Timecop.travel would actually freeze time. This is no longer the case. Rather, a time offset will be calculated, and a running clock is simulated by always offsetting the time returned by Time.now (and friends) by the original offset.

(Note that time can still be frozen with Timecop.freeze.)

(more…)

Timecop: Freeze Time in Ruby for Better Testing

Wednesday, November 19th, 2008 by John Trupiano
The API mentioned in this blog post is specific to v0.1.0. The latest version is v0.2.0. See the blog post announcement, the documentation, or the github home page.

This past weekend I released v0.1.0 of the Timecop gem. Timecop makes it dead simple to travel through or freeze time for the sake of creating a predictable and ultimately testable scenario.

The gem is derived from a plugin I wrote a while back to achieve more or less the same functionality for an extremely time-sensitive application. My goals for the gem included:

  1. Drop-in-ability: The primary goal is to allow your app to continue to use Time.now, Date.today and DateTime.now as normal within your application. No overloading of functions with optional arguments (a la today=Date.today) just so you can write test cases.
  2. Environment independence: I wanted the gem to work (a) w/ rails (ActiveSupport actually), (b) w/ plain ruby when the ‘date’ library has been loaded, and (c) w/ plain ruby when the ‘date’ library had not been loaded.
  3. Library independence: I could have utilized mocha to achieve the mocking functionality found under the hood, but because I wanted this to work with plain vanilla ruby, libraries like mocha are out.
  4. Short-term time travel: I wanted to expose the ability to temporarily change the concept of “now.” This is particularly helpful when writing tests where time needs to pass.
  5. Long-range time travel: I wanted to expose the ability to change the concept of “now” for an indeterminate period of time. This is particularly helpful when setting up a rails test environment along with the test data.
  6. Nested time travel: I wanted to provide the ability to nest traveling, allowing the state to be kept within each block (we’ll see an example later).

The gem is hosted on RubyForge and can be installed by simply running:

sudo gem install timecop

(more…)

Benchmark Ruby Code with R, rsruby and better-benchmark

Wednesday, October 8th, 2008 by John Trupiano

I’ve found myself on a benchmarking kick these last couple of weeks. Sometime last week, I dug up the better-benchmark library written by Pistos. Pistos’ library is basically just a wrapper for the rsruby gem, which is more or less an interface to R (similar to what rmagick is to ImageMagick).

Combining these tools together, we can do some pretty nifty code performance analysis in very few lines of code, e.g.

require 'rubygems'
require 'better-benchmark'
 
result = Benchmark.compare_realtime(:iterations => 10) { |iteration|
  save_the_world()
}.with { |iteration|
  save_the_world_and_save_the_girl()
}
Benchmark.report_on result

I have forked better-benchmark and wrapped the library up into a RubyGem.

(more…)

Ruby: Patch to fix broken YAML.dump for multi-line strings (String#to_yaml)

Thursday, September 4th, 2008 by John Trupiano

I love YAML. It’s portable. The majority of languages I work with already have libraries built to read/write it. It’s more lightweight, more expressive, and easier to read than XML. I really love YAML.

Unfortunately, it turns out that Ruby’s YAML library is a little incomplete for some edge cases. I just discovered that YAML.dump fails to generate valid YAML for certain multi-line strings. The specifics are very difficult to explain, and we’ll summarize them at the end after trying out some examples. Fire up irb and give the following a try.

require 'yaml'
s = "\n  Something embedded.\nAnd on a new line."
YAML.load(YAML.dump(s))

(more…)

Introducing environmentalist: an intuitive, environment-focused config structure for your rails applications

Monday, August 4th, 2008 by John Trupiano

A couple of months ago, I wrote an article (Better setup for environments in rails) discussing the standard set of changes we make to the config structure of each of our rails apps.

The primary motivation for me to make these changes stemmed from the need to have several deployable environments. The standard set of rails environments (development, test, production) simply just don’t cut it for me. It’s important for us to be able to deploy to staging, demo and even production-test environments. When including server configurations (e.g. a Passenger config snippet, or a mongrel_cluster config snippet), I’ve often had to use unique configurations for each deployable environment. Consequently, my config/ directory quickly became polluted with files such as: apache_prod.conf, apache_staging.conf, apache_demo.conf. Furthermore, it also requires special care when deployment comes around.

(more…)

Problems with restful_authentication Plugin and Internet Explorer Cookies

Friday, July 11th, 2008 by John Trupiano

I just ran into a fairly obscure bug. Bit me pretty good and stole an hour from me on an otherwise quiet Friday afternoon.

How the Problem Manifested Itself: Using restful_authentication, I could log in fine using Firefox and Opera, but not Internet Explorer or Safari. I figured, it’s just an HTML POST, nothing special, so what could be going wrong? I started to tail my logfile, and the session#create action was working properly. It was redirecting to a protected page, signifying that the login was successful. However, there was a second redirect occurring immediately after, sending me back to the login page. Here’s a tail of the logfile:

(more…)

I can’t upgrade RubyGems from 1.1.1 to 1.2.0 on Ubuntu

Monday, July 7th, 2008 by John Trupiano

Just a quick little snippet here. RubyGems 1.2.0 dropped roughly two weeks ago….however, I’d been having trouble getting my 1.1.1 installs on Ubuntu to update properly.

john@john-ubuntu:~/passenger-recipes$ gem -v
1.1.1
john@john-ubuntu:~/passenger-recipes$ sudo gem update --system
Updating RubyGems
Nothing to update
john@john-ubuntu:~/passenger-recipes$ gem -v
1.1.1
john@john-ubuntu:~/passenger-recipes$ sudo gem update --system
Updating RubyGems
Nothing to update

The first few times I ran into this, I was too busy to figure out what was wrong and ignored it. However, there is one HUGE TIME SAVER in 1.2.0 that I just couldn’t wait for any longer. Up through 1.1.1, gem update and gem install calls both updated all locally cached gemspec’s from your gem sources. This not only led to large memory consumption (anyone on a VPS got a horror story?), but was also just a plain waste of time. If I know which gem I want to update, it should just update that gem. The onus should be on me specify that I want to fully update my cache.

That said, I finally came across Eric Hodel’s notice addressing this very problem. The solution:

john@john-ubuntu:~/passenger-recipes$ sudo gem install rubygems-update -v 1.1.1
Bulk updating Gem source index for: http://gems.rubyforge.org/
Successfully installed rubygems-update-1.1.1
1 gem installed
john@john-ubuntu:~/passenger-recipes$ sudo gem update --system
Updating RubyGems
Updating rubygems-update
Successfully installed rubygems-update-1.2.0
Updating version of RubyGems to 1.2.0
Installing RubyGems 1.2.0
...
... (success)
...
john@john-ubuntu:~/passenger-recipes$ gem -v
1.2.0

Happy upgrading.

Rails 2.1 broke my mysql foreign keys!

Tuesday, June 24th, 2008 by John Trupiano

Rails 2.1 introduced in the MySQL Adapter “smart integer columns.” The idea was to use the :limit option to determine whether a smallint, int, or bigint should be used. This is something that the Postgres adapter had already previously implemented. The relevant code in activerecord/lib/active_record/connection_adapters/mysql_adapter.rb is:

  # Maps logical Rails types to MySQL-specific data types.
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
    return super unless type.to_s == 'integer'
 
    case limit
    when 0..3
      "smallint(#{limit})"
    when 4..8
      "int(#{limit})"
    when 9..20
      "bigint(#{limit})"
    else
      'int(11)'
    end
  end

Mirko Froehlich suggests monkey patching this function. Timothy Jones blogged about it.

To monkey-patch this, just drop a file (fix_mysql_adapter.rb) into your initializers/ directory, as such:

(more…)

Don’t Abuse the Session

Monday, June 23rd, 2008 by John Trupiano

Never, ever, ever, ever, ever store an ActiveRecord model in the session. Just store the id and load it into an instance variable from the database on every request. Why? A couple reasons…

First, you’re susceptible to staleness. Consider this. User A logs in, and you store their user object in the session. Administrator X logs in and deactivates User A’s account. User A can still muck around your site because you’re reading the user data from the session, which has stale data.

Second, the default in Rails these days is to store your session data in cookies (honestly, I don’t know why…..it only clutters up your requests, forcing the session to be passed back and forth on _every_ request, and opening up the possibility that the encryption key could be brute-forced……this is a rant for another day). You just don’t want to be storing whole ActiveRecord objects in the session. They’re big and clunky. The extra database call to reload the object in a before_filter on every request is practically trivial, and you’ll keep the “tubes” less clogged.

This practice is certainly not rails-specific, and should be adopted no matter the server-side technology.

Subversion Timestamps + Capistrano finalize_update

Saturday, June 7th, 2008 by John Trupiano

Update 2008/06/13: Jamis released Capistrano 2.4.0, and it includes the :normalize_asset_timestamps patch that I submitted!

Update 2008/06/11: Here’s a link back to the Google Groups Discussion regarding this topic.

Subversion has a lesser-known feature that allows you to specify that checkouts/exports/switches/reverts should timestamp files with the last committed timestamp. By default, this setting is turned off. As such, when you checkout a repository, every file is timestamped with the current time on your local machine.

To be honest, I’m not quite sure why this is the default. The pertinent section of the Subversion manual (you have to scroll to the bottom) describes the setting as such:

———-
use-commit-times

Normally your working copy files have timestamps that reflect the last time they were touched by any process, whether that be your own editor or by some svn subcommand. This is generally convenient for people developing software, because build systems often look at timestamps as a way of deciding which files need to be recompiled.

In other situations, however, it’s sometimes nice for the working copy files to have timestamps that reflect the last time they were changed in the repository. The svn export command always places these “last-commit timestamps” on trees that it produces. By setting this config variable to yes, the svn checkout, svn update, svn switch, and svn revert commands will also set last-commit timestamps on files that they touch.

———-

It’s not clear to me why the default aids in Makefiles. What is clear to me though is that Jamis Buck has taken this default behavior into account in Capistrano, the wonderful deployment tool we use at SLS.

The following code snippets will require a bit of understanding of the built-in Capistrano deployment recipes. Let’s take a look at the code for the :finalize_update task. This task is invoked after the code has been updated on the server (for Subversion, either by an export or update).

  task :finalize_update, :except => { :no_release => true } do
    # ... other details omitted

    stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
    asset_paths = %w(images stylesheets javascripts).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
    run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
  end

What this snippet of code does is compute a timestamp, and then touch each asset file on the server with that timestamp (-t #{stamp}). The intention for doing this was to handle the scenario where you have multiple asset servers. Since an export/checkout updates the timestamp with the local machine’s current time, it’s possible for the same asset to have different timestamps on separate asset servers.

So what’s the big deal? First, rails serves up images (when using the image_tag helper) with a querystring appended to it. This querystring is simply a timestamp. The reason for this is to support client-side caching (you’re doing this, right?). This basically allows you to set the “expires” attribute of that file several years (decades or millenniums, in fact) into the future. The reason this is so is because if that file ever changes, it’s last modified attribute will also change, effectively changing the querystring rails appends automatically, and causing your browser to download a ‘new asset.’ So, when the finalize_update task is invoked (which happens every time you re-deploy), all of these last-modified timestamps are reset, causing any repeat visitors to re-download these very same assets again.

I have submitted a patch to Jamis (which I hope he’ll apply soon!) that exposes an extra Capistrano parameter (:normalize_asset_timestamps), which would be set to true by default, leaving the original behavior in tact. The new :finalize_update task looks like:

  task :finalize_update, :except => { :no_release => true } do
    # ... other details omitted

    if fetch(:normalize_asset_timestamps, true)
      stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
      asset_paths = %w(images stylesheets javascripts).map { |p| "#{latest_release}/public/#{p}" }.join(" ")
      run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
    end
  end

I’ll follow up when/if Jamis accepts the patch. Hopefully it can make it into version 2.4!