Posts Tagged ‘datamapper’

DataMapper is Viewable

Friday, December 5th, 2008

Even when I’m panicking I’m a fan of datamapper.  I like how clean it generally makes my code, but I do notice that I tend to have the same queries cropping up from time to time either in the same application or in other apps using my model library.  This just wasn’t dry enough for me, so I made dm-is-viewable.  It gives sql-like view functionality to DataMapper Resources.  It’s a pretty simple plugin, but lets take a look, um kay?

Let’s start with a really simple User class…


class User
  include DataMapper::Resource
  belongs_to :location

  property :id, Serial
  property :name, String
  property :username, String
  property :password, String

  property :gender, String
  property :age, Integer

  property :favorite_color, String
  property :favorite_number, Integer
end

class Location
  include DataMapper::Resource

  has n, :users

  property :id, Serial
  property :name, String
  property :desc, String
end

Making your resource viewable is pretty easy, first grab the dm-is-viewable gem, then say that your resource is viewable. Views take the exact same parameters that you could pass to Resource.all. The only difference is that dm-is-viewable stores them until they are called.


class User
  include DataMapper::Resource
  is :viewable  

  #lets create some views
  create_view :legal_women, :gender => 'female', :age.gt => 18
  create_view :serial_killers, :favorite_number => 666, :favorite_color => 'black'

  #... Resource code *SNIP SNIP*
end

See the befores and afters below:


# Legal Women Before
User.all(:gender => 'female', :age.gt => 18)

# Legal Women After
User.view :legal_women

# Serial killers before
User.all(:favorite_number => 666, :favorite_color => 'black')

# Serial killers after
User.view :serial_killers

# You can also pass further limiting query parameters to #view.
# Legal Local Women Before
User.all(:gender => 'female', :age.gt => 18, User.location.name => 'Los Angeles')

# Legel Local Women After
User.view :legal_women, User.location.name => 'Los Angeles'

# Passing additional parameters FURTHERS the limitation of records, so..
User.view :legal_women, :gender => 'male'
# => Would return nil, the query would essentially be generated as:
#  SELECT * from users where gender = 'male' and gender = 'female'

Simply, clean, useful. Woot.

Massive Flaw in DataMapper 0.9.6 - Write Once Read Many Many Many Many Many, Im outta space here…

Thursday, October 30th, 2008

So I found a pretty massive flaw in DataMapper (ticket @ lighthouse).  Whenever using the :fields attribute when doing Resource#first or Resource#all a crap load (techincally speaking) of queries are fired at MySQL.  I put a ticket in and a lot of information and links to some pasties with examples (see lighthouse).  I don’t have the time to look into this now, being that I have a deadline Saturday, but it’s a pretty serious problem and I’m sure a lot of people are experiencing it and don’t realize it.

To try it out for yourself lets use Merb (and Ive tried it without merb, and without any dm plugins and the problem still persists).

Steps to reproduce:


merb-gen app my_test_app

cd ./my_test_app

merb -i

DataMapper.auto_migrate!

#turn on dm loggering (thats what I call it)

DataObjects::Mysql.logger = DataObjects::Logger.new('log/dm.log', 0)

# create a few users, then, get their IDs, I dare you...

User.all(:fields=>[:id])

So, the problem is if you look at your mysql log or DataMapper Query Log you’ll see a crapload of selects trying to get what the first select in the list retrieved.  The funnier part is, the subsequent selects are selecting the fields that aren’t included in the :fields list.

Why is this a huge problem?  That query above for my 3 user test table generated about 90 trips to MySQL.  The ‘friend button’ on Vokle.com just generated about 240 trips to MySQL in one click, that’s how I found the issue in the first place.

Someone fix this please!?  If its not fixed by Saturday (my day ‘off’), I’m going to look into it.

Happy (bug) hunting.

Modular Tables? DataMapper is Remixable.

Friday, August 22nd, 2008

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’