After living in a type-safe world for the last year, it’s strange to come back to languages like Ruby that are duck typed and then try to be productive. So many safety nets that you can take for granted in a type-strong language just aren’t there – and it’s considered a feature of the language. While I will admit that the immediate benefit to my battery life I enjoy by not running the Scala compiler constantly is nice, Ruby – in all it’s duck-like goodness – is still going to take a good bit of getting used to. It’s going to take a lot of hammering against brick walls of the language to get to where my preferred design patterns are first nature. Today was an exercise in that.
We had some code that was responsible for encrypting secure data in our database on the fly. Naturally, since we’re using Rails, we’re taking advantage of some gems to make our lives easier, and these gems generally expect your model objects to behave like typical ActiveRecord::Base instances or they get fussy. Of course, these instances aren’t exactly normal ActiveRecord instances because of our encryption.
So, what’s an engineer to do?
Abstraction, Module Style
Our particular scenario has a few unique requirements. First, we’re encrypting data in fields that aren’t actually real fields in the database. We want external accessors (like simple_form) to treat them as such, but because we’re putting a lot of different things in the same table – they aren’t actually distinct fields. Thankfully, ActiveRecord’s subclass instantiation behavior allows us to instantiate classes of the appropriate type that have the correct behavior – so even if the database design isn’t perfect, at least our system design can be a little more so.
We already had a working implementation up and running, but it wasn’t perfect (what implementation is?) and I was certain I could do better, so I decided to take a stab at it. The design that I chose for this exercise was to abstract all of the stuff related to encrypting this secure data into a Module, and then mix that Module into the parent class. Then, I wanted to implement some method of declaring these “virtual secure attributes” that we were using in the form of a macro statement.
So,if we’re talking code – this was the end goal I had in mind in terms of structure.
It took awhile, but I’m happy to report that I succeeded. The code isn’t perfect yet, and I suspect it will be revised during code review, but it’s working pretty nicely in my manual testing and all of our unit tests are passing. What more could I ask for? ?
One of the benefits of Ruby’s duck-typing is that it makes it really simple to add additional methods to a class using one of these macro methods. Following the convention in ActiveRecord, I defined my attr_secure function (that’s not actually what we called it, but this is an example) to do two things:
- Record the names of the attributes that are secure virtual attributes.
- Add class instance methods for “attributename” and “attributename=”. These methods do the encryption and decryption on the fly. For our implementation we have a column in a table that ActiveRecord serializes and deserializes to a YAML (in the DB) and a Ruby hash (in memory). These methods just interact with this hash directly.
Simple enough, right? Yes and no.
Blowing ActiveRecord to Pieces
You’ve got to remember that these aren’t normal classes I’m working with. They are ActiveRecord classes. That makes them special in how they handle attributes that are sent in their constructor, and a whole host of other behavior. So, we’ve defined two methods for reading and writing secure virtual attributes that don’t really exist in the database. So, we can use them like so:
But what about if we wanted to use them like this?
This would break our code if all we had done is what I outlined above, and indeed it broke mine. ActiveRecord appropriately includes some code to protect against mass assignment. Since these haven’t been declared as attr_accessible (and in fact can’t because they don’t really exist in the DB) we’re in trouble.
When I was faced with this challenge, I decided to try to take the road less traveled. Instead of turning back and forgoing my dream of a nice modularized syntax – I decided to go spelunking in the ActiveRecord source code to see what I could find. Sure enough, the Rails guys were clever enough to pump all of the mass assignment code through one place: assign_attributes. So, in theory, if I could override assign_attributes to cherry pick out of the incoming data the things I had declared as “attr_secure” – I should be golden.
Turns out that was a little be harder than it sounds (see point 1 above about recording the names of those attributes). My module I was mixing in needed to create some class variables on the modules it was mixed into to keep track of what attributes needed to be cherry picked during mass assignment. That turned out to be a little bit difficult because I’m still getting accustomed to the object model in Ruby. In fact, some syntactical changes may happen in that code soon now that I know what I wanted to do is actually possible.
So, Ruby’s object model is certainly intriguing. The duck-typing caused me a lot of headaches today, but I lived. In all, Ruby is perfectly capable of anything I could do in Scala (even functional fun with lambdas), but the mechanics of how those idioms relate to their parent/sibling/target classes can be very different at times. TL;DR: I have more to learn.
I won’t be posting the code I wrote for public consumption just yet. I don’t really have the time right now to package it up into a gem. But if you want to create secure attributes on an ActiveRecord model that don’t actually exist in the database, it can be done beautifully and elegantly. So, why not?
Although I’m not posting the code right now, here’s some recommended reading if you’re looking to do the same yourself:
- “Include vs Extend”
- The ActiveRecord source.for assign_attributes and attr_accessible
- Ruby documentation for define_method
I’m sure I’m going to have plenty of thoughts over the coming weeks regarding Ruby, so be looking forward to it. As always, I’d love for you to leave me some comment love. Cheers.