abangratz - blag

Stuff that matters - at least for me.

Datamapper: Customizing Associations

Datamapper is an alternative ORM for various ruby projects, and can work as an ORM replacement for ActiveRecord in Ruby on Rails. The most pervasive arguments for using Datamapper over AR is the easier handling of legacy databases and the intelligent deconstruction of N+1 queries, and the fact that functionality is splitted into small plugins, so for simple projects it does not weigh down the framework codebase with unused code.

One other strength for me is the auto-migration part: You do not have (but you are able) to add separate migrations that pretty much reflect what you have configured in the models already. It takes the configuration of the model properties and creates the tables from that (destructive, non-destructive: your choice). This means that the control of the properties of a data model is no longer handled by a separate script, but by the definitions of the structure of the models itself. This can optionally include referential integrity in the underlying database, too.

Also, the documentation is really good. There are whole chapters handling understanding of HABTM (has n, through: …, self-referential HABTM relationships et al). But one chapter left me stumped a bit:

How to customize a relationship?

Maybe it was me skimming over the surface of the documentation, maybe my current needs were not exactly covered, but reading the fine manual was not quite enough for me. I still managed to create classes that wouldn’t play nice.

The problem:

I had a simple model, User:

User Model - user.rb
1
2
3
4
5
6
7
8
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

end

And another simple model, Message:

Message Model - message.rb
1
2
3
4
5
6
7
8
9
class Message

  include DataMapper::Resource

  property id, Serial

  property subject, required: true, length: 3..255
  property text, lazy: false
end

Now, I wanted to add a relation from User to Message as follows:

  • a User has many sent messages
  • a Message belongs to one sender

Doing it as a normal relation would be simple, as follows:

First, I change the User model:

User Model - user.rb
1
2
3
4
5
6
7
8
9
10
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

  has n, :messages

end

Then the Message model:

Message Model - message.rb
1
2
3
4
5
6
7
8
9
10
11
class Message

  include DataMapper::Resource

  property id, Serial

  property subject, required: true, length: 3..255
  property text, lazy: false

  belongs_to :user
end

So far, so easy. Now the more complex part: I want to have another, more complex relation:

  • a Message can have many recipients
  • a User can have many received messages

Now, as you can see immediately, this is going to be a real HABTM-relationship. But when I started to add the relation to the User model, I saw oddities immediately:

User Model - user.rb
1
2
3
4
5
6
7
8
9
10
11
12
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

  has n, :messages

  has n, :messages, through: Resource  # That will not do.

end

At this point I decided to just rename the first relation to what I already suggested: sender and sent_messages:

User Model - user.rb
1
2
3
4
5
6
7
8
9
10
11
12
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

  has n, :sent_messages, 'Message', child_key: ['sender_id']

  has n, :messages, through: Resource

end
Message Model - message.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class Message

  include DataMapper::Resource

  property id, Serial

  property subject, required: true, length: 3..255
  property text, lazy: false

  belongs_to :sender, 'User', child_key: ['sender_id']

  has n, :users, through: Resource
end

As an explanation: If I rename a relation, but there is no model that corrensponds to the singularized name (e.g. Sender), Datamapper will complain when migrating or instantiating the relationship. So, I have to tell datamapper to use the ‘Message’ model for the relation ‘sent_messages’, and the ‘User’ model for the ‘sender’ relation. The constraint ‘child_key’ helps with selecting the correct foreign key field in the child model - the side that has the ‘belongs_to’ association added.

And the second association will add a table messages_users with the fields user_id and message_id as primary key. This should be sufficient …

But no, I wanted a bit more: first, I wanted to store the time the message was read by each recipient, and then I wanted to have nice names for the associations without using (ugly) proxy methods.

The first part is easy: I will add another model, Notification, that keeps the recipient/message relation and has a ‘read’ DateTime field as payload:

Notification Model - notification.rb
1
2
3
4
5
6
7
8
9
class Notification

  include DataMapper::Resource

  property :read, DateTime, required: false #if it is null, the associated message has not been read yet

  belongs_to :message, key: true
  belongs_to :user, key: true
end

The key: true constraint on the property means that both fields are part of the primary key. Now let’s adapt the other two classes. User first:

User Model - user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

  has n, :sent_messages, 'Message', child_key: ['sender_id']

  has n, :notifications # this is a simple has many/has one relationship

  has n, :messages, through: :notifications

end

Message second:

Message Model - message.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Message

  include DataMapper::Resource

  property id, Serial

  property subject, required: true, length: 3..255
  property text, lazy: false

  belongs_to :sender, 'User', child_key: ['sender_id']

  has n, :notifications # this is a simple has many/has one relationship

  has n, :users, through: :notifications
end

The through: constraint tells Datamapper to use the notifications table as underlying n:m relationship table.

But now I wanted to make it really neat: from Clean Code we know that having methods that are named for their purpose create more value that cryptically named ones. So, I decided to rename those associations to reflect theirs:

User Model - user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

  has n, :sent_messages, 'Message', child_key: ['sender_id']

  has n, :notifications # this is a simple has many/has one relationship

  has n, :received_messages, 'Message', through: :notifications

end
Message Model - message.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Message

  include DataMapper::Resource

  property id, Serial

  property subject, required: true, length: 3..255
  property text, lazy: false

  belongs_to :sender, 'User', child_key: ['sender_id']

  has n, :notifications # this is a simple has many/has one relationship

  has n, :recipients, 'User', through: :notifications
end

… to find out that this does not work.

First, the notification tables looked like this:

1
2
3
4
5
Notifications:
  user_id             | Integer
  recipient_id        | Integer
  message_id          | Integer
  received_message_id | Integer

Trying to use the models on the console lead to messages like:

property or constraint recipient not found on model User

Well. Erm. No good. But there is a solution already built in, but the documentation all but skips it:

[…]we will use :via to be able to provide “better” names[…]

Solution:

But the rest of the example took me a while to figure out. I will let the finished example speak for itself:

User Model - user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User
  include DataMapper::Resource

  property id, Serial

  property name, required: true, length: 3..255

  has n, :sent_messages, 'Message', child_key: ['sender_id']

  has n, :notifications # this is a simple has many/has one relationship

  has n, :received_messages, 'Message', through: :notifications, via: :message

end
Message Model - message.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Message

  include DataMapper::Resource

  property id, Serial

  property subject, required: true, length: 3..255
  property text, lazy: false

  belongs_to :sender, 'User', child_key: ['sender_id']

  has n, :notifications # this is a simple has many/has one relationship

  has n, :recipients, 'User', through: :notifications, via: :user
end
Notification Model - notification.rb
1
2
3
4
5
6
7
8
9
class Notification

  include DataMapper::Resource

  property :read, DateTime, required: false #if it is null, the associated message has not been read yet

  belongs_to :message, key: true
  belongs_to :user, key: true
end

So, what does :via tell Datamapper? Fairly simple: it says: “ The reverse relation of this relationship is called “:message” in the “Notification” model”.

Reading the code at the documentation example first, I tried the following:

  1. Let :via point to the reverse relation in the other model (User or Message, not Notification)
  2. Let the :notification relationship know that the child key is :message_id for :received_messages in Notification (and :user_id for :recipients in Message)
  3. Both of the above

It took me the better part of two hours to read through the code and realize where I made the wrong assumption. But now everything works.

Conclusio

If you want to customize a HABTM-Association in Datamapper, make sure that:

  1. The link model (here: Notification) uses the derived name for the association as is standard: “User” is related by “belongs_to :user”, “Message” by “belongs_to :message” and so on.

  2. The other models’ relations know what the relationship’s reverse is named: In the “User” model, “received_messages” is defined as

    has n, :received_messages, 'Message', through: :notifications, via: :message

    where

    • “:received_messages” is the relationship name
    • “‘Message’” the name of the model that is linked via this relationship, and
    • “via: :message” denotes the reverse of the association in the “link” model (here: “Notification”).

I hope that this helps someone to save some time and helps understanding.

Rbenv vs Rvm (Again)

There has been a multitude of posts about this topic. In the light of this, I will try to keep this post neutral and objective. And then I will include personal impressions and experiences. I do not want to add to any spin or angry discussions.

Disclaimer: I am a Debian user. I like apt/dpgk, I try to adhere to the FHS, I do not exactly love system-intrusive libraries or third-party package managers. I am biased. You have been warned.

What’s it all about?

Yesterday, I decided to give rbenv a chance, after hearing friends talking about it. Together with all the changes covered in my First Post, I also decided to install it and try it.

Up to now, I have been a very happy rvm user, and I am still using it at work and on most of my computers. Just my laptop received the upgrade (yet).

An old friend: rvm

I want to stress the point: I am using rvm in a very complex environment, using a multitude of ruby versions as well as gemsets (especially when upgrading rails, you do not want to have half of the projects failing because of a changed gem version). What can I say? It’s working.

There is just a small but: updating rubies makes this even worse. Having rvm use 1.9.3@somegemset in an .rvmrc file in each project directory can pretty brutally hose your installation (which is no big thing, drop the gemsets and reinstall all of it). But it can confuse for a moment.

Also, having an indicator is nice. I use rvm-prompt to change part of my zsh prompt to show the current rvm version and gemset and I love that, too.

And anyways, if push comes to shove, I use rvm implode and reinstall the whole thing.

The new contender: rbenv

With rbenv, I do like the idea of shims and the whole thing only “polluting” your path instead of installing really huge functions in your environment. It’s actually neat: you have wrapper scripts that pick up environment and location information, and do the right thing. No cd function, no rvm function, very little preloading, no extra hooks. It’s not better, just from a sysadmin standpoint, it’s neater (YMMV, of course).

Now, with rbenv, I first installed rbenv-build and rbenv-gemsets. Obviously, I need this functionality – at least, I felt like it. And then I used it immediately, and was already a bit surprised that there is no mapping from ‘1.9.3’ to the most current patchlevel known (as in rvm). Still, not a problem.

Anyways, the build recipes I tried worked out of the box and left me with a system I can just use - and also integration of Command-T is even easier than with rvm. So far, so good …

Conclusion

I have no real need to switch from rvm to rbenv in my development environments. Sure, it’s neat - but rvm has matured and has a lot of nifty little commands and helpers that make my life easier. Including upgrading of ruby versions et al.

Still, for servers I would definitely consider using rbenv. The integration is less tight with shell functions and can work on a very easily understandable low level.

But which is the right tool for you? Just try and decide for yourself. Maybe this short post or some of the linked ones help.

New Blog!

Looks like I got rid of the lazyness for a beautiful sunday afternoon, and refreshed the old stack of stuff I hardly maintained at all lately anyways.

And following my own good advice and the good example of others (hellooo citizen428) and also use static html pages.

Given my track record of ugly pages and my nerd factor, I ended up customizing github:pages and Octopress (see footer!). I am OK with the result - and integrating all that evil social media stuff wasn’t too hard (alright, alright, it’s ridiculously easy. Writing this post was harder.).

So, yeah, now this is my new blog. Fancy, huh?