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’