Run. Bike. Code.

Living the active life while maintaining some nerdiness.

DataMapper DateTime Fields With Default

| Comments

Today’s post should be in the “doh-I’m-stupid” category, but I’ve put it into the Coding category nonetheless.

So, I’m happily hacking away on a new and really small Sinatra app that uses DataMapper as the ORM. The app receives POST requests of a certain form and creates entries in the database for each of these requests. Since I don’t want to have the user pass a timestamp for every entry, I’m making use of the :default key for the timestamp property.

1
2
3
4
5
6
class Entry
  include DataMapper::Resource
  property :id, Serial
  property :when, DateTime, :default => DateTime.now
  property :data, Text
end

So, did you spot the error already? Good on you, because it took me a while to figure out what was going on. See, entries where created alright, but somehow after a while weren’t displayed anymore, when filtered for “entries of the last 24 hours”. Even if you just created an entry, it wouldn’t show up on that page. Let’s take a look at the database, shall we?

db=> select id,"when" from entries order by id;
 id  |        when         
-----+---------------------
   1 | 2014-01-17 17:38:44
   2 | 2014-01-17 17:38:44
   3 | 2014-01-17 17:38:44
   4 | 2014-01-17 17:38:44
   5 | 2014-01-17 17:38:44
   6 | 2014-01-17 17:38:44
   7 | 2014-01-20 13:12:58
   8 | 2014-01-21 15:03:13
   9 | 2014-01-21 15:03:13
  10 | 2014-01-21 15:03:13
  11 | 2014-01-21 15:03:13
  12 | 2014-01-21 15:03:13
  13 | 2014-01-21 15:03:13
  14 | 2014-01-21 15:03:13
  15 | 2014-01-21 15:03:13
  16 | 2014-01-21 15:03:13
  17 | 2014-01-21 15:03:13
  18 | 2014-01-22 11:33:20
  19 | 2014-01-22 11:33:20
  20 | 2014-01-22 11:33:20
  21 | 2014-01-22 11:33:20
  22 | 2014-01-23 15:46:35
  23 | 2014-01-27 11:48:21
  24 | 2014-01-27 11:48:21
  25 | 2014-01-27 13:18:45
  26 | 2014-01-27 13:18:45
  27 | 2014-01-27 13:18:45
  28 | 2014-01-27 13:18:45
  29 | 2014-01-27 13:18:45
  30 | 2014-01-27 13:18:45
  31 | 2014-01-27 13:18:45
  32 | 2014-01-27 16:20:26
  33 | 2014-01-27 16:20:26
  34 | 2014-01-28 10:34:55
  35 | 2014-01-28 11:57:46
  36 | 2014-01-28 11:57:46
  37 | 2014-01-28 11:57:46
  38 | 2014-01-28 11:57:46
  39 | 2014-01-30 17:34:03
  40 | 2014-01-30 17:48:11

NB: Don’t name your database fields when. Just don’t.

All entries are created correctly, but somehow they end up with the same timestamp, even if created minutes and hours apart. And then, the timestamp sometimes changes and again groups a couple of entries to the same date. So after a bit of head-scratching I finally figured it all out: The timestamps correlated to the times when I deployed the code to production, which in turn calls DataMapper.finalize and DataMapper.auto_upgrade!. So whenever I deployed the code, the default value for the timestamp was evaluated (and only then), setting the timestamp to DateTime.now.

Here’s the fix, commit message first and then followed by the correct and updated code.

commit 5cfa94f0c080c9b1a2c80583ff42718446b6588b
Date:   Thu Jan 30 15:31:00 2014 +0100

Default for 'when' is now evaluated with a lambda

Otherwise all entries inherit the 'time of deploy'

To correctly default to the current time when creating an entry in the database, use a lambda for the default value, like so:

1
2
3
4
5
6
7
class Entry
  include DataMapper::Resource
  property :id, Serial
  property :when, DateTime, :default => lambda{ |p,s| DateTime.now}
  property :data, Text
  property :ticket, Integer, :required => true
end

With this fix deployed to production, the timestamps of entries with IDs greater than 39 all have their correct, up-to-date timestamp. Happy campers all around.

Isn’t that embarrassing?

Well, yes, I guess this is a real rookie mistake and I don’t want to blame anyone else than me. Then why did I share this embarrassing story, you ask?

Because Ben Orenstein said so. No, not directly to me, but he often mentions in the excellent Giant Robots smashing into other giant Robots podcast that his number one advice to new programmers is to start a blog and share their findings, no matter how small and irrelevant they seem. Because somewhere, some time, someone will have a similar problem, which you just solved, and by writing about it you share the knowledge.

So, if you wonder why your DataMapper timestamps are all the same, I solved it for you the hard way.

An idiot is someone who doesn’t know something you just learned yesterday.

Comments