Ruby for Newbies: Working with DataMapper
videos

Ruby for Newbies: Working with DataMapper

Tutorial Details
  • Topic: Ruby + DataMapper
  • Difficulty: Easy
  • Estimated Completion Time: 30 minutes
This entry is part 9 of 13 in the Ruby for Newbies Session
« PreviousNext »

Ruby is a one of the most popular languages used on the web. We’ve started a new Session here on Nettuts+ that will introduce you to Ruby, as well as the great frameworks and tools that go along with Ruby development. Today, we’ll look at the DataMapper gems to get up and running with a database in Ruby.


Prefer a Video Tutorial?

Subscribe to our YouTube and Blip.tv channels to watch more screencasts.

Step 0: Introducing DataMapper

DataMapper is an ORM: an Object-Relational Mapping. Basically, it’s a library that lets you work with your database from object-oriented code. There’s absolutely no SQL in this tutorial at all. However, an ORM uses a regular database under the covers; we’ll be using sqlite3 today, but you could just use a different adapter to work with a mysql, postgresql or other database.

In Singing with Sinatra – The Recall App, Dan Harper introduced you to DataMapper. In this tutorial, we’re going to take a deeper dive in to working with the library.


Step 1: Installing the Right Gems

The first step is installing the required gems. The DataMapper functionality is broken into many different gems, so you’ll have to install several different parts. Of course, we’re not going to work with it all; but these are the gems you’ll have to install.

  • sqlite3: This is the database gem.
  • dm-core: This is the core functionality of DataMapper.
  • dm-migrations: This gem does the database migration.
  • dm-validations: As you’ll guess, this offers data validation functionality.
  • dm-timestamps: Helps with timestamping database records.
  • dm-sqlite-adapter: This is the adapter that connects DataMapper to your database; we’ll be using sqlite here, but you can use the dm-postgres-adapter, dm-mysql-adapter, or whatever suits your fancy.

Once you’ve got all those gems installed (see the last chapter if you need to know how to install gems), we’re ready to go.


Step 2: Creating a Basic Model

Let’s start by creating a basic model. Models are defined in classes. However, we first have to connect to our database.

Actually, the very first thing is requiring our libraries at the top of our file.

require 'dm-core'
require 'dm-timestamps'
require 'dm-validations'
require 'dm-migration'

So now that we have DataMapper in the environment, let’s connect to the database.

DataMapper.setup :default, "sqlite://#{Dir.pwd}/database.db"

The first parameter tells DataMapper to use the default adapter for the database type. The second is the link / URL for the database. Since we’re using sqlite, we’re just linking to a database file. Note that we don’t have to create this file; DataMapper will create it for us.

Now we’re ready to create the model. As you know, this is a class.

class User
    include DataMapper::Resource
    
    property :id       , Serial
    property :username , String
    property :email    , String
end

The first step is to include the DataMapper::Resource module. This gives you the custom methods you’ll use in your class. The most important method here is property. Here, we’re using it to create three different properties: an id, a username, and an email. As you see, the first parameter in property is a symbol that’s the name of the property. The second is the type. You understand String, of course, but what’s serial. Actually, property :id, Serial is DataMapper’s shorthand for the primary key; ‘serial’ is an auto-incrementing integer. That’s your primary key!


Step 3: Migrating the Database

Now that we’ve created our model, we need to migrate the database. If you’re not familiar with migrating a database, it’s the process of changing the schema of the database. This could be adding a column, renaming a column, or changing properties of a column. DataMapper offers two ways to do this:

DataMapper.auto_migrate!
DataMapper.auto_upgrade!

The difference here is that auto_migrate! will clear all the data from the database; the auto_upgrade! methods tries to reconcile what’s in the database already with the changes you want to make. The way this works is that after your model class, you’ll call one of these methods. You don’t want to be running auto_migrate! every time you load the model, of course, but you might want to run auto_upgrade! on every reload in development. I’ve done it this way in Sinatra:

configure :development do
    DataMapper.auto_upgrade!
end

You’ll notice that so far, we haven’t had to touch a single SQL query; that’s the point of using on ORM is that you can write normal code and have that work with relational databases.


Step 4: Adding some Advanced Attributes

Now that we have our feet wet with DataMapper, let’s take our model to another level. Let’s start with timestamps.

Timestamps

We’re requiring the dm-timestamps gem, so why not use it? If we add ‘created_at’ and ‘updated_at’ properties to the model, this gem will automatically update those fields.

property :created_at, DateTime
property :updated_at, DateTime

Of course, you don’t need to add both, if you don’t want them.

Options

There are several options that you can add to each field. For example, if you want a field to be required, or unique, or have a default value, you can do that there. Let’s create a post model to showcase some of this:

class Post
    include DataMapper::Resource

    property :slug       , String   , key: true, unique_index: true, default: lambda { |resource,prop| resource.title.downcase.gsub " ", "-" }
    property :title      , String   , required: true
    property :body       , Text     , required: true
    property :created_at , DateTime
    property :updated_at , DateTime
end

We’re mixing things up a bit here; our ‘title’ and ‘body’ are required fields. We’re defining the ‘slug’ property as the primary key, and saying that it must be a unique index. Don’t get scared off by the default value of ‘slug.’ Of course, you can just use a raw value of whatever type your property is, but we’re doing something more. Ruby (and other languages) has lambdas, which you could think of as a small function. It’s something that can take “parameters” and return a value, just like a function. If we use a lambda as the value of the ‘default’ property, DataMapper will pass it the resource (or database record you’re working with) and the property itself (in this case, ‘slug’). So here, what we’re doing is taking the value in resource.title (the title property), putting it in lowercase, and using gsub method (think global substitution) to switch every space to a dash. This way, something like this:

"This is a Title"

Will become this:

"this-is-a-title"

Note: Don’t get confused with how we’re using options here. First of all, remember that when a hash is the last parameter of a method, we don’t need to add the curly braces. Also, with Ruby 1.9, there’s a new hash syntax. Previously, hashes looked like this:

{ :key => "value" }

You can still do this in 1.9, and you have to if you you’re not using symbols as your keys. But, if you are using symbols as keys, you can do this instead:

{ key: "value" }

Basically, you just move the colon to the end of the symbol (no space!) and remove the rocket.

Validations

There’s a lot you can do with validation in DataMapper, and you can read all about it here. However, let’s take a look at the basics.

There are two ways to do validations; we’re going to use the method that adds your validations to the options hash. For the email property in the User model, we’ll set the format validation:

property :email, String, format: :email_address

In this case, we’re using a built-in regex that DataMapper offers; we could put a custom regex there if we wanted something else.

Let’s require a certain length on the password:

property :password, String, length: 10..255

If you’re not familiar with the 10..255 notation, that’s a Ruby range. We’re saying that the password must be between 10 and 255 characters long.

Associations

How about foreign keys? DataMapper makes this real easy. Let’s associate our User and Post models. We want a user to be able to have many posts, and a post to belong to a user.

In the User model, add this line

has n, :posts

Then, in the Post model, do this:

belongs_to :user

In the database, this adds a user_id property to a post table. In practice, it’s really easy; we’ll see this soon.

Custom Property Accessors

If you want to customize the input for a given property, you can add custom property accessors. For example, let’s say we want to make sure a user’s username is always stored in lowercase. We can add property accessor methods similar to the way you would in a normal class. This way, we take the value the user is trying to store and fix it up. Let’s do this:

def username= new_username
    super new_username.downcase
end

We’re defining the username=, so when the username is assigned, it will be lowercased. The super part just passes our value to this method’s super method, which is the one we are overriding.

Note: According to the documentation (see both here and here), we should be able to do @username = new_username.downcase in the method above. This is what I did in the screencast, and as you know, it didn’t work as expected. Since recording the screencast I’ve discovered that the documentation is wrong, and that super is the way to do this.


Step 5: Creating and Finding Records

Well, now that our models are created, let’s add a few records to test them out. We can do this a few ways. First, we can create a record with the new method, passing a hash of attributes, or assigning them individually.

user = User.new username: "JoeSchmo", firstname: "Joe", lastname: "Schmo", email: "joe@schmo.com", password: "password_12345"
user.save

user = User.new
user.username = "Andrew"
# etc.
user.save

When using User#new, you have to call the save method to actually put the record in the database. If there’s an error (remember those validations?), the save method will return false. Then, you can go to the errors property to see the errors; it’s a DataMapper::Validations::ValidationsErrors object, but you can iterate over the errors with the each method.

user.errors.each do |error|
    puts error
end

If you want to make and save a record in one fell swoop, use the create method, of course passing it an attributes hash.

User.create username: "joeschmo", firstname: "Joe", lastname: "Schmo", email: "joe@schmo.com", password: "password_!@#$%"

Boom: created and saved!

How about finding a record in the database? If you know the key of the record you’re looking for, just use the get method:

User.get(1) 

Post.get("this-is-a-title")

Yes, you’re seeing that this works with both normal integer keys and other types of keys. Since we said the slug was the key in the Post model, we can get by slug.

What about those fields that could be the same for multiple records? You’ve got three options for that: first, last, and all. Just pass them a hash, and they get the records for you

User.first firstname: "Andrew"

User.last( :lastname => "Schmo")

User.all  #gets all the post

Conclusion: Learning More

There’s a lot more you can do with DataMapper; check out the documentation for more! Hit the question box if you have any questions.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://twitter.com/satlavida Satlavida

    Nice tutorial. But i yet did not understand how to get things : Example
    SELECT `password` FROM table_name WHERE `username` = “admin”
    Or something similar.
    Thank you for the tutorial. And Thanks in advance for the help.

    • http://andrewburgess.ca Andrew Burgess
      Author

      I’m not too well read in SQL (that’s what using ORMs with Ruby will do to you :) ), so this might not be exactly what you’re looking for, but I’ll take a stab at it . . .

      If you want to get specific fields for a set of records, add a :fields key to your options hash when getting the records (Docs on this). For example, if I only wanted to get the title of a post, I could do this:

      Post.all fields: [:title] # get only the titles of all the posts
      Post.all :user_id => 1, :fields => [:title] # get only the titles of all posts from user 1

      This doesn’t return an array of the titles; it returns an array of all the appropriate post records, with only their title attributes loaded. The IRB output makes this clear (notice that this is an array, even though there is only one item in it; the all method always returns an array):

      ruby> Post.all :user_id => 1, :fields => [:title]
      => [#<Post @slug= @title="This Is A Title" @body= @created_at= @updated_at= @user_id=>]

      This brings to mind another DataMapper feature that is kinda-sorta-maybe related: lazy loading of properties. You can create properties that will only load from the database when they are requested for the first time. You can read about that in the property docs. (Note that the other properties above are not lazy-loading; trying to get them will result in an error.)

      • http://andrewburgess.ca Andrew Burgess
        Author

        Okay, so that example didn’t show up the way I expected: I guess I should escape <s.

        Here’s what IRB shows:

        [#<Post @slug=<not loaded&gl; @title="This Is A Title" @body=<not loaded&gl; @created_at=<not loaded&gl; @updated_at=<not loaded&gl; @user_id=<not loaded&gl;&gl;]

      • http://andrewburgess.ca Andrew Burgess
        Author

        Arg, now I’m copying and pasting typos; obviously, all those &gl;s should be >s.

      • http://webprogramming360.com/ Web Programming 360

        By the way it’s >’s not &gl;’s. Great article though!

  • http://itcutives.com Jatin

    I am following this Ruby series since part 1, and every tutorial is a gem in this series (or I should say, every tutorial is a different kind of Ruby). Jokes apart, this article, is very well written.

    Many of my colleague were asking me where they can find nice Ruby tutorials, and I have suggest them this series. Every Ruby beginner should once (at-least) go through these tutorials.

    • http://www.jeffrey-way.com Jeffrey Way

      Thanks, Jatin! We appreciate that. Andrew has been doing a great job with these.

    • http://andrewburgess.ca Andrew Burgess
      Author

      Thanks so much, Jatin! It’s great to hear that these tutorial are helping people. If there’s anything specific you’d like to see, just let me know!

  • http://www.panasofts.com Website Design and Development

    Thanks for This aricles but not satisfactory. You need to expand this to next level. Please check this link for more informations. Instructions for building Ruby, Rails, LightTPD and MySQL on Mac OS X Tiger http://hivelogic.com/articles/2005/12/01/ruby_rails_lighttpd_mysql_tiger

  • http://github.com/snusnu snusnu

    Thx a lot for the article! There are two small errors you might want to correct tho.

    First, there’s no need to gem install sqlite3, doing gem install dm-sqlite-adapter will be enough. Of course you must have the sqlite3 database installed for that to work.

    Second, require ‘dm-migration’ should be require ‘dm-migrations’.

    Apart from those minor errors, the article is a nice introduction to DataMapper. Users who want to know more about the project should go to visit http://datamapper.org which contains more information on the topic.

    I also want to add a couple things to Andrew Burgess’ answer to Satlavida’s question:

    1) Andrew is right, passing the :fields option will do the trick, however there’s one thing to be aware of. If you don’t include the :id – or whatever is/are your key(attributes) – you will not be able to persist the resource back to the datastore. This is because without a key, DataMapper has no way to know exactly which resource it is supposed to persist back.

    2) Technically, result sets returned by DataMapper’s all method are no arrays. They are instances of DataMapper::Collection. However, for the scope of this article, it’s probably fine to speak of them as arrays, as they are designed to behave like such whenever that is possible.

    cheers
    snusnu

    • http://andrewburgess.ca Andrew Burgess
      Author

      Thanks for chiming in on this!

  • Imran

    Thanks Andrew another great screencast! please keep them coming ;-)

  • http://dazsnow.com Darryl Snow

    Hi Andrew. Loving the Ruby lessons.

    A couple of episodes back you asked for feedback on wich direction we think the course should go in. My personal opinion is that most people following may be web developers who have tinkered with lots of JavaScript and PHP and have heard about this “Ruby” and “Rails” business and want to get in on the action. I think from what we’ve seen so far in your series, Ruby is very readable and intuitive and so quite easy for people like me to pick up. I know it’s a good idea to take it slow, one chunk at a time, but what people like me are thinking is “right… so how do i use Ruby for what i usually do in PHP?” or “how can I start building web apps with this?”.

    So that’s the direction I think we should be going… sample actions / apps, web frameworks… Rails… Sinatra etc.

    Cheers.

  • Raj Rathore

    Great Article!

    I have a query: You have changed field name username to user_name and you have defined a method username= and doesn’t changed its name to user_name=

    Is that error occurred because you declared the method name and field name same.

    I hope you understood my question.

    I have seen some talks of David Heinemeier Hansson, and I want to learn rails.
    I think learning a language is more important than a framework, and your tutorials are really great.
    I Think there should be one more tutorial “Rails for newbies”

    Thank You :)

  • Roee Aizman

    Hey.
    You have great tutorials :)
    the username didn;t work die to the function that didn’t mention to the column itself.
    You should have written self.username.
    here:
    def username= new_username
    self.user_name = new_username.downcase
    end

  • Etay Luz

    I posted a question on StackOverflow regarding this tutorial. I would be happy if you could answer it. I’ll be sure to give you a point up if you do.
    http://stackoverflow.com/questions/11608870/racklintlinterror-status-must-be-100-seen-as-integer

    Thanks!

  • Alejandro

    thank you for this tutorial! That was great!