http://www.developer.com/lang/rubyrails/querying-a-database-with-datamapper.html

Back to article

Meet DataMapper ORM, the Unified Ruby Interface for Data Stores


October 18, 2011

The ability to stay "in the zone" is a crucial characteristic of any productive developer, with the tiniest distraction capable of making mincemeat out of an otherwise successful workday. The oft-discussed distraction is external, the result of an unusually talkative colleague or passing fire engine. However, internal distractions are equally plentiful. One of the most notable distractions occurs when the developer is forced to interweave multiple libraries together, with each offering its own syntactical quirks.

Consider for instance an application which communicated with multiple data sources, including a MySQL database, Facebook, and an IMAP store. Typically the developer will identify the best third-party libraries which interact with these data sources, and then spend a good deal of time becoming familiar with their respective interfaces.

Many object-relational mapping (ORM) solutions effectively remove the need to familiarize oneself with multiple database-specific libraries, thanks to the ability to interact with most mainstream databases via a unified interface. However, the developer is still left with sorting out how to most effectively interact with non-relational database data stores. A great Ruby library called DataMapper is removing this delineation by offering a unified interface for a wide variety of data stores, including not only MySQL, PostgreSQL and SQLite, but also Facebook (via FQL), the Salesforce API, and Amazon S3. Once installed, Ruby developers can effortlessly plug into these data sources and more without having to tediously brush up on incongruent library interfaces.

In this article I'll introduce DataMapper by showing you how to integrate it into a Sinatra application. Even if you're not familiar with Sinatra, the Ruby code demonstrated throughout will be simple enough for even a beginning Ruby programmer to follow along just fine.

Installing DataMapper

DataMapper is available as a Ruby gem, meaning you can install it using RubyGems:

$ gem install data_mapper
Fetching: data_mapper-1.1.0.gem (100%)
Successfully installed data_mapper-1.1.0
1 gem installed
Installing ri documentation for data_mapper-1.1.0...
Installing RDoc documentation for data_mapper-1.1.0...

After installing DataMapper, you should install a database adapter. More than 30 adapters are available; see the DataMapper wiki for a complete list. For the purposes of this article I'll install the MySQL adapter:

$ gem install dm-mysql-adapter
Fetching: dm-mysql-adapter-1.1.0.gem (100%)
Successfully installed dm-mysql-adapter-1.1.0
1 gem installed
Installing ri documentation for dm-mysql-adapter-1.1.0...
Installing RDoc documentation for dm-mysql-adapter-1.1.0...

Of course, you'll additionally need access to a MySQL server in order to follow along with these examples.

Creating a DataMapper Model

DataMapper interacts with a resource's underlying data via a model, which allows you to neatly separate data-specific functionality from the rest of the application. Suppose we wanted to use the fantastic Sinatra framework in conjunction with DataMapper to create a Web application which managed our video game library. The games table schema which will contain the games looks like this:

mysql>CREATE TABLE games (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  platform VARCHAR(10) NOT NULL,
  created_at DATETIME NOT NULL
);

But rather than separately create and manage the table schemas, you can rely on DataMapper's built-in schema migration features to perform the manual labor for you (you will however need to create the database used to store the tables). After creating your project directory, create a directory named models which resides in the project directory, and within the models directory create a file named Game.rb:

class Game
  include DataMapper::Resource

  property :id, Serial
  property :name, String
  property :platform, String

end

This simple model defines a primary key named id, and two additional string-based properties named name and platform. Although it looks like any other class, DataMapper will know to treat it differently because of the reference to DataMapper::Resource.

With the model defined, create an app.rb file in your Sinatra project directory, and add the following contents to it:

require 'rubygems'
require 'sinatra'
require 'data_mapper'
require 'erb'

# Include the models
require 'models/Game'

# Connect to MySQL
DataMapper.setup(:default, "mysql://user:password@localhost/dev_gamelibrary")

# Make sure the database tables match the models
DataMapper.auto_migrate!

Next, fire up the Sinatra app per usual:

$ ruby app.rb
== Sinatra/1.2.6 has taken the stage on 4567 for development with backup from Mongrel

The Game model will be parsed and a corresponding table named games created within the database, or modified to match the latest version of the Game model:

mysql> describe games;
+----------+------------------+------+-----+---------+----------------+
| Field    | Type             | Null | Key | Default | Extra          |
+----------+------------------+------+-----+---------+----------------+
| id       | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name     | varchar(50)      | YES  |     | NULL    |                |
| platform | varchar(50)      | YES  |     | NULL    |                |
+----------+------------------+------+-----+---------+----------------+

Try making changes to the model and restarting your Sinatra application; you'll see the changes transparently propagate to the games table!

Incidentally, integers and varchar-based strings are only a few of many data types supported by DataMapper. Additionally, it's easy to tweak properties to override defaults such as the 50-character VARCHAR limit used in the games table. See the DataMapper documentation for all of the details.

Querying the Database

It's incredibly easy to query the database using DataMapper. To view a list of all of the games found in the games table, ordering the results according to the name, create the following route:

get '/games' do
	@games = Game.all(:order => [:name.asc]) 
  erb :games
end

The @games variable can then be passed into a simple ERB template named games.erb:

<% @games.each do |game| %>
 <%= game.name %><br />
<% end %>

Numerous finder variations are available; for instance, to retrieve a row associated with the game name Super Mario Kart, use the following statement:

@game = Game.first(:name => 'Super Mario Kart') 

See the DataMapper documentation for much more information about finding data.

Creating Table Relations in DataMapper

Those developers who are keen to properly normalize their databases probably balked at the games table schema, because the platform column should actually be a foreign key which references a separate platforms table. In doing so, developers can avoid the possibility of data corruption occurring due to misspelled platform names, among other inconveniences.

DataMapper makes it easy to relate models using associations. In this case, we want to create a hasMany/belongsTo association because it could be useful to know not only what platform is associated with a particular game, but also which games are associated with a particular platform. To do so, you'll need to create a new Platform model and then update the Game model in order to define the associations. The Platform model looks like this:

class Platform
  include DataMapper::Resource

  property :id, Serial
  property :name, String

  has n, :games

end

The updated Game model looks like this:

class Game
  include DataMapper::Resource

  property :id, Serial
  property :name, String

  belongs_to :platform

end

With the updated models in place and the tables populated with the appropriate associations, retrieving a platform name associated with a particular game is as easy as this:

<%= @game.platform.name %>

This is but a simple example of DataMapper's incredibly powerful associative capabilities. See the documentation for all of the details.

Conclusion

Are you doing anything cool with DataMapper? Share your experiences in the comments!

About the Author

Jason Gilmore -- Contributing Editor, PHP -- is the founder of EasyPHPWebsites.com, and author of the popular book, "Easy PHP Websites with the Zend Framework". Jason is a cofounder and speaker chair of CodeMash, a nonprofit organization tasked with hosting an annual namesake developer's conference, and was a member of the 2008 MySQL Conference speaker selection board.

Sitemap | Contact Us