Getting started with DataMapper
So lets imagine we're setting up some models for a blogging app. We'll keep it nice and simple. The first thing to decide on is what models we want. Post is a given. So is Comment. But let's mix it up and do Category too.
Install an Adapter
First, you will need to install an Adapter, which allows DataMapper to communicate to the Database:
# Debian / Ubuntu sudo apt-get install libsqlite3-dev # RedHat / Fedora sudo yum install sqlite-devel # MacPorts sudo port install sqlite3 # HomeBrew sudo brew install sqlite gem install dm-sqlite-adapter
# Debian / Ubuntu sudo apt-get install libmysqlclient-dev # RedHat / Fedora sudo yum install mysql-devel # MacPorts sudo port install mysql5 # HomeBrew sudo brew install mysql gem install dm-mysql-adapter
# Debian / Ubuntu sudo apt-get install libpq-dev # RedHat / Fedora sudo yum install postgresql-devel # MacPorts sudo port install postgresql91 # HomeBrew sudo brew install postgresql gem install dm-postgres-adapter
If you have RubyGems installed, open a Terminal and install a few things.
gem install data_mapper
This will install the following, most commonly used DataMapper gems.
Require it in your application
require 'rubygems' require 'data_mapper' # requires all the gems listed above
Specify your database connection
You need to make sure to do this before you use your models, i.e. before you actually start accessing the database.
# If you want the logs displayed you have to do this before the call to setup DataMapper::Logger.new($stdout, :debug) # An in-memory Sqlite3 connection: DataMapper.setup(:default, 'sqlite::memory:') # A Sqlite3 connection to a persistent database DataMapper.setup(:default, 'sqlite:///path/to/project.db') # A MySQL connection: DataMapper.setup(:default, 'mysql://user:password@hostname/database') # A Postgres connection: DataMapper.setup(:default, 'postgres://user:password@hostname/database')
Note: that currently you must setup a
:default repository to work
with DataMapper (and to be able to use additional differently named
repositories). This might change in the future.
Define your models
The Post model is going to need to be persistent, so we'll include DataMapper::Resource. The convention with model names is to use the singular, not plural version...but that's just the convention, you can do whatever you want.
class Post include DataMapper::Resource property :id, Serial # An auto-increment integer key property :title, String # A varchar type string, for short strings property :body, Text # A text block, for longer string data. property :created_at, DateTime # A DateTime, for any date you might like. end class Comment include DataMapper::Resource property :id, Serial property :posted_by, String property :email, String property :url, String property :body, Text end class Category include DataMapper::Resource property :id, Serial property :name, String end
The above example is simplified, but you can also specify more options such as constraints for your properties. DataMapper supports a lot of different property types natively, and more through dm-types.
An important thing to note is that every model must have a key in
order to be valid. If a model has no key, there's no way to identify a
resource and thus no way to update its persistent state within the
backend datastore. DataMapper will raise a
when trying to
auto_migrate! a model that has no key declared.
Have a look at property to learn about the different ways of declaring keys for your models.
Ideally, these declarations should be done inside your class definition with the properties and things, but for demonstration purposes, we will just re-open the classes.
One To Many
Posts can have comments, so we’ll need to setup a simple one-to-many association between then:
class Post has n, :comments end class Comment belongs_to :post end
Has and belongs to many
Categories can have many Posts and Posts can have many Categories, so we’ll need a many to many relationship commonly referred to “has and belongs to many”. We’ll setup a quick model to wrap our join table between the two so that we can record a little bit of meta-data about when the post was categorized into a category.
class Categorization include DataMapper::Resource property :id, Serial property :created_at, DateTime belongs_to :category belongs_to :post end # Now we re-open our Post and Categories classes to define associations class Post has n, :categorizations has n, :categories, :through => :categorizations end class Category has n, :categorizations has n, :posts, :through => :categorizations end
After declaring all of the models, you should finalize them:
This checks the models for validity and initializes all properties associated with relationships. It is likely if you use a web-framework such as merb or rails, this will already be done for you. In case you do not, be sure to call it at an appropriate time.
DataMapper allows the use of natural primary keys, composite primary keys and
other complexities. Because of this, when a model is declared with a
belongs_to relationship the property to hold the foreign key cannot be
initialized immediately. It can only be initialized when the parent model has
also been declared. This is hard for DataMapper to determine, due to the
dynamic nature of ruby, so it is left up to developers to determine the
In general, you want to call
finalize before your application starts
accessing the models.
Set up your database tables
Relational Databases work with pre-defined tables. To be able to create the tables in the
underlying storage, you need to have
Note: If you've been following this instructions and did
you can safely skip the following require statement as it has already
been done for you.
dm-migrations is loaded, you can create the tables by issuing the following command:
This will issue the necessary
CREATE statements (
DROPing the table first, if
it exists) to define each storage according to their properties. After
auto_migrate! has been run, the database should be in a pristine state. All
the tables will be empty and match the model definitions.
This wipes out existing data, so you could also do:
This tries to make the schema match the model. It will
CREATE new tables, and
add columns to existing tables. It won't change any existing columns though
(say, to add a
NOT NULL constraint) and it doesn't drop any columns. Both these commands
also can be used on an individual model (e.g.
Create your first resource
Using DataMapper to create a resource (A resource is an instance of a model) is simple
# create makes the resource immediately @post = Post.create( :title => "My first DataMapper post", :body => "A lot of text ...", :created_at => Time.now ) # Or new gives you it back unsaved, for more operations @post = Post.new(:title => ..., ...) @post.save # persist the resource
Both are equivalent. The first thing to notice is we didn't specify the
auto-increment key. This is because the data-store will provide that value for
us, and should make sure it's unique, too. Also, note that while the property
DateTime, we can pass it a
Time instance, and it will convert (or
typecast) the value for us, before it saves it to the data-store. Any
properties which are not specified in the hash will take their default values in