Rails 2.3 and the ability to update created_at, created_on, updated_at and updated_on timestamps
The timing of the automatically generated timestamps and attribute assignment in rails 2.3 security note 1 was interesting to me because I’d just spent a while really trying to get my head around attr_accessible and attr_protected and the best way to test their effects in an app I was working on. Although I intend to write about the testing separately, the conclusion I came to was that I wanted to test the behaviour (more specifically, I was interested in what I could/couldn’t assign) of my objects and not that I was specifically using attr_accessible or attr_protected.
Reading Alex’s post made me wonder whether the rails API ever guaranteed that the timestamps would be readonly: If that was the case then I can see arguments for not wanting to test the behaviour of your objects (because you’re essentially testing the framework itself). I couldn’t find any mention of whether they’d specifically be readonly so chose to do a bit of digging. I created a script that allowed me to, with relative ease, run tests against rails apps created with different versions of rails. Using this I was able to test the mass assignment of timestamps (created_at, created_on, updated_at and updated_on) against multiple versions of rails. The conclusion I came to (and I’d love for other people to replicate my experiment and prove/disprove my results) was that the created_at and created_on pair of attributes have always (at least as far back as rails 1.0.0) been assignable, while the updated_at and updated_on attributes have only recently become assignable. While digging I managed to find the lighthouse ticket (#1612) that requested the ability to set the updated_* timestamps and the rails commit that closed that ticket.
Replicating my experiment
Get a copy of rails
$ cd /path/to/code $ git clone git://github.com/rails/rails.git
Create a mysql database called rails_timestamps_test, and create a table within that database called people.
mysql> CREATE DATABASE rails_timestamps_test; mysql> USE rails_timestamps_test; mysql> CREATE TABLE people (id INTEGER AUTO_INCREMENT, created_on DATE, created_at DATETIME, updated_on DATE, updated_at DATETIME, PRIMARY KEY (id));
Get a copy of my test script
$ cd /path/to/code $ svn co http://chrisroos.googlecode.com/svn/trunk/scratch/rails_timestamps_test
Run my script against rails 2.3 (run setup_rails_project.rb without any arguments to see the options) and hopefully see output similar to below (which means that the tests passed as expected).
$ cd /path/to/code/rails_timestamps_test $ ruby setup_rails_project.rb 2.3.0 /path/to/code/rails *** Removing the rails app at /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0 *** Checking out the rails version tagged v2.3.0 HEAD is now at beca1f2... Template#mime_type should not use Mime::Type when Action Controller is not included *** Creating the rails app at /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0 *** Vendorising rails from /path/to/code/rails to /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0 *** Generating rails_version_test.rb in /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0/test/unit to ensure we are testing against the correct version of rails *** Copying assets to the new rails app ****** Linking /path/to/code/rails_timestamps_test/assets/person_test.rb to /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0/test/unit/person_test.rb ****** Linking /path/to/code/rails_timestamps_test/assets/person.rb to /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0/app/models/person.rb ****** Linking /path/to/code/rails_timestamps_test/assets/database.yml to /path/to/code/rails_timestamps_test/projects/rails-app-2-3-0/config/database.yml *** Running the rails version test to ensure that we're testing against the correct version of rails Loaded suite test/unit/rails_version_test Started . Finished in 0.000327 seconds. 1 tests, 1 assertions, 0 failures, 0 errors *** Running the timestamps test Loaded suite test/unit/person_test Started .... Finished in 0.078819 seconds. 4 tests, 8 assertions, 0 failures, 0 errors
Re-run the script against rails 2.2.2 (the last tag before 2.3) and you should see the same as above except for the last test which should now contain two failures.
$ cd /path/to/code/rails_timestamps_test $ ruby setup_rails_project.rb 2.2.2 /path/to/code/rails ... some lines snipped ... *** Running the timestamps test Loaded suite test/unit/person_test Started ..FF Finished in 0.069429 seconds. 1) Failure: test_should_be_able_to_set_updated_at(PersonTest) [test/unit/person_test.rb:35:in `test_should_be_able_to_set_updated_at' /path/to/code/rails_timestamps_test/projects/rails-app-2-2-2/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `__send__' /path/to/code/rails_timestamps_test/projects/rails-app-2-2-2/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `run']: FAIL: Couldn't persist the mass assigned updated_at attribute. <Thu Jan 01 00:00:00 +0000 2009> expected but was <Sun, 19 Apr 2009 09:23:46 UTC +00:00>. 2) Failure: test_should_be_able_to_set_updated_on(PersonTest) [test/unit/person_test.rb:28:in `test_should_be_able_to_set_updated_on' /path/to/code/rails_timestamps_test/projects/rails-app-2-2-2/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `__send__' /path/to/code/rails_timestamps_test/projects/rails-app-2-2-2/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `run']: FAIL: Couldn't persist the mass assigned updated_on attribute. <Thu, 01 Jan 2009> expected but was <Sun Apr 19 09:23:46 UTC 2009>. 4 tests, 8 assertions, 2 failures, 0 errors
Re-run the script against any other tagged version of rails you wish.
A couple of notes about the script
If you specify an invalid tag (i.e. a tag that doesn’t exist in the rails repository) then the rails_version_test.rb will fail.
$ cd /path/to/code/rails_timestamps_test $ ruby setup_rails_project.rb made.up.tag /path/to/code/rails ... some lines snipped ... Loaded suite test/unit/rails_version_test Started F Finished in 0.045543 seconds. 1) Failure: test_should_be_using_rails_made_up_tag(RailsVersionTest) [test/unit/rails_version_test.rb:6:in `test_should_be_using_rails_made_up_tag' /path/to/code/rails_timestamps_test/projects/rails-app-made-up-tag/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `__send__' /path/to/code/rails_timestamps_test/projects/rails-app-made-up-tag/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:94:in `run']: <"made.up.tag"> expected but was <"2.2.2">. 1 tests, 1 assertions, 1 failures, 0 errors
If you specify a tag of rails that is anything other than a 3 part version number (e.g. the four part version 220.127.116.11, or the release candidate 2.1.0_RC1) then the version test will fail because the Rails::VERSION constant only contains the MAJOR, MINOR and TINY components and that’s what we use to check the version our app uses.
The supplied database.yml file assumes a mysql database running on localhost that has a root user with no password. You can update this to reflect your actual environment.
The projects are created in /path/to/code/rails_timestamps_test/projects/ with a name like rails-app-x-x-x (major, minor, tiny).
In order to ensure we are testing against the correct version of rails, I symlink rails from /path/to/code/rails (after changing to the requested tag) into rails-app-x-x-x/vendor/rails. The problem here is that if you re-run the script with a different rails version and then attempt to run a test from your first project that it will be using the recently specified version of rails. Luckily, the rails_version_test will fail fast which means you shouldn’t have to waste time wondering why something is not working as expected.
I wonder whether it’d be useful to extract the ‘create a rails app from a specific version of rails’ functionality out of this script?