attr_bucket, A Gem for Your Lolrus Model

Comments

Good evening, Internet! I hope you had a wonderful and productive workday. Now, with those pleasantries out of the way, I'd like to point you in the direction of a little gem called attr_bucket that I am regretful for having written even as I type this blog post suggesting you go try it. That is because this gem, while tiny and unassuming, has tremendous potential for evil.

Evil? Sign me up for some of that!

OK, then. So here's the situation.

So you have a bunch of things you need to attach to different types of other things. "Hah!" you say, "this looks like a job for a polymorphic association!" And indeed it is. So you polymorphic-ize that stuff up before it knows what's coming. We'll call the first things attachments, and the other things attachables, because everyone knows when you are polymorphic-izing stuff up, words start ending in -able.

So then you run into a problem. The types of things you want to attach are different. Your attachments are all pretty much the same, except for one or two things. These one or two things aren't particularly interesting except when it comes time to display the attachments -- a bit of text here, a number or boolean over there, but they need a place to live. You really don't want to have several different models, with several different tables, all with polymorphic belongs_to associations, mostly because you can't think of that many "-able" words.

So you want to use STI, but now you're looking at doing some kind of filthy hack, just to handle those one-off attributes. Do you put extra columns, properly named, for each of the extra attributes in the different types of attachments? Maybe you create a couple of generic string columns, a couple of booleans, and so on, and give them the proper "names" via virtual attributes?

Sure, that would work, but something about it would feel wrong. And you just know the client is going to want to add more types of stuff, with lots more wrongness down the road. So, what's a hacker to do? I'll tell you what: Embrace the wrongness, and go all out with it.

Enter attr_bucket

So, it occurs to you that you don't really need to search by any of these extra bits of data. They just need to be there when you ask for them once you've loaded up the attachment. It'd be great if you could just throw them all in some sort of... bucket... and they would come along for the ride when you loaded the attachment. Well, you could normalize the crap out of things and have an extra_attributes table with attribute names and extra joins and pivots and... and... argh. No. How about some creative (ab)use of serialization?

So you start serializing a bucket text attribute, and throwing stuff in a hash in it, but then you need to be sure to make these "bucket" attributes act enough like "real" attributes to do validations, which means you need to create accessors for them, and whatnot. Then you write a gem, and you call it attr_bucket.

    class Attachment < ActiveRecord::Base
      belongs_to :attachable, :polymorphic => true
      # t.integer :attachable_id
      # t.string :attachable_type
      # t.string :type
      # t.string :subtype
      # t.string :title
      # t.text :bucket
      # t.string :data_file_name
      # t.string :data_content_type
      # t.integer :data_file_size
      # t.datetime :data_updated_at
    end

    class Link < Attachment
      attr_bucket :bucket => [:url, :short_title]
      validates_presence_of :title, :url # :title is a "real" attribute, :url isn't
    end

    class PurchaseableThingWithImage < Attachment
      attr_bucket :bucket => [:details, :purchase_url]

      validates_presence_of :title, :purchase_url

      has_attached_file :data
    end

You get the idea. There's a hash syntax as well, for specifying typecasts. Anyway, go bask in the evil on its GitHub page.

One last note: of course, attr_bucket is aliased as i_has_a_bucket. You're welcome, Internet.

comments powered by Disqus