Cory O'Daniel – These are just words Software development, thoughts, and randomness

22Aug/083

Modular Tables? DataMapper is Remixable.

One of my favorite things about Ruby is mixins.  I love the pseudo multiple inheritance it provides.  One thing that is frustrating to me though when working on developing back-ends is the amount of un-DRY code I am forced to create when working with the database.

For example, comments.  Look at sites like MySpace, Facebook, and SocialGiant3.0 (sarcasm), everything is commentable.  A user can comment on pictures, profiles, videos, and just about any other resource you can think of.

In this scenario you generally have one of two choices:

A.) You create a comments table for each of the resources: user_comments, profile_comments, etc.

B.) You create a giant comments table and horizontally partition it based on the comment_type.

I prefer the choice 'A', but the downfall, a very wet, anti-DRY set of models.  This is were dm-is-remixable comes in.  It gives you the ability to remix datamapper models into other models.

Whats that mean?

You write one model describing what a comment looks like and you mix it into User, Profile, etc; like magic, you've got a dry 'comment' model and a nice normalized table structure to store it in.

Here are some examples:

Create a remixable database model named Commentable

module Commentable
  include DataMapper::Resource

  is :remixable,
    :suffix => "comment"

  property :id,         Integer, :key => true, :serial => true
  property :comment,    String
  property :created_at, DateTime
end

Create a remixable image model and mix it into User

module Image
  include DataMapper::Resource

  is :remixable

  property :id,           Integer, :key => true, :serial => true
  property :description,  String
  property :path,         String
end

class User
  include DataMapper::Resource

  property :id,             Integer,
    :key          => true,
    :serial       => true

  property :first_name,     String,
    :nullable     => false,
    :length       => 2..50

  property :last_name,      String,
    :nullable     => false,
    :length       => 2..50

  remix n, :images, :as => "pics" #:as is optional and gives you an additional accessor
end

user = User.new
user.user_images
=>[] #Array of images
user.pics
=>[] #Array of images

Enhance the functionality of UserImages

class User
  enhance :images do
    def dimensions
      # ... get the dimensions of the image
    end

    def resize(x,y)
      # ... resize the image
    end
  end
end

user = User.first
user.pics.first.dimensions
=> "300x500"
user.pics.first.resize(100,300)
=> "100x300"

Remix commentables into Videos

class Video
  include DataMapper::Resource
  is :remixable

  property :id,           Integer, :key => true, :serial => true
  property :description,  String
  property :path,         String

  remix n, :commentables, :as => "comments" # as is optional, provides add'l accessor name
end

video = Video.new
video.comments
=>[] # Array of comments
video.video_comments
=>[] # same array of comments

Remixing commentables into User;

class User
  # ... from declaration above ...

  # This is some of the more complex syntax
  # here we are remixing a module between two of the same class, so we need some sort of alias
  # :as => is the accessor method
  # :for => is the class that this class remixes the module with
  # :via => what we want to call the other user object

  remix n, :commentables, :as => "comments", :for => "User", :via => "commentor"

end

commentee = User.first
commentor = User.get(2)
comment = UserComment.new
comment.comment = "Zomg!  I talk non-stop, you are awesome!"
comment.commentor = commentor
commentee.comments << comment
commentee.save

How do you get it?  Well its in dm-more.  If you dont know what DataMapper is, then git with the program.

From your favorite command line:

git clone git://github.com/sam/dm-more.git

Then from your source:

require 'dm-is-remixable'

Post to Twitter Post to Digg Post to Facebook Post to Reddit Post to StumbleUpon

Comments (3) Trackbacks (0)
  1. Nice write up, thanks.

    Quick question, can we define relationships in the remixable modules?

  2. Yeah, you can define relationships, but its kinda wonky. Since the remixable is actually a module, you have to ’send’ the relationship to the class that is remixing the module via the ‘extended’ method.

    module MyRemixable
    def self.extended(klass)
    klass.send :has, n, :yada_yadas
    end
    end

  3. And these new notes were not just plain bank notes. ,


Leave a comment


No trackbacks yet.