YADM - Yet Another Data Mapper

Another attempt to implement Data Mapper in ruby.

Built with 2 goals in mind:

  • to get familiar with common pitfalls in implementing Data Mapper
  • to make a tool that can be useful now and has the potential to be able to serve as a replacement for ActiveRecord eventually


# Gemfile
gem 'yadm'
$ bundle


YADM consists of several components:

  • entities
  • repositories
  • identity map
  • data sources
  • mapper


Entity is a basic object with some attributes.

You can create an entity by defining a class that includes YADM::Entity:

class Person
  include YADM::Entity

  attributes :first_name, :last_name, :email, :password, :age

You don't need to specify the id attribute, it comes by default.


A repository is a module representing a collection of entities. It can fetch the objects from the data store and persist the changes back. Here you can define complex criteria for querying the data source.

A repository is created as a module that includes YADM::Repository and specifies it's entity:

module People
  include YADM::Repository
  entity Person

  criteria :kids do
    with { age < 12 }

  criteria :older_than do |min_age|
    with { age > min_age }

  criteria :in_alphabetical_order do
    ascending_by { last_name }.ascending_by { first_name }

  criteria :oldest do |count|
    descending_by { age }.first(count)

Identity map

The identity map is a cache for data.

Most data requests first look it up in the identity map. If it's there it is returned without accessing the data source; otherwise it is pulled from the data source, put into the map for subsequent queries and then returned.

Currently the identity map doesn't handle any complex queries - only .find calls are cached.

Data sources

Data sources encapsulate the ability to read the data and write it back. They are defined by adapters for different data storage solutions; YADM ships with the following adapters:

  • memory (useful for testing)
  • sqlite (requires sequel and sqlite3 gems)
  • mysql (requires sequel and mysql2 gems)
  • postgresql (requires sequel and pg gems)

Adapters are not required by default (because of their dependencies) so you should manually require each adapter you need manually.

You can register a data source with some unique identifier to use it later on:

require 'yadm/adapters/memory'
require 'yadm/adapters/postgresql'

YADM.setup do
  data_source :memory_store, adapter: :memory
  data_source :pg_store, adapter: :postgresql, database: 'yadm', user: 'yadm', password: 'yadm'


Mapper is the central part glueing everything together - it connects repositories to data sources.

Assuming the memory_store data source created earlier we can link the repository to it and define some attributes:

YADM.setup do
  map do
    repository People do
      data_source :memory_store
      collection  :people

      attribute :id,         Integer
      attribute :first_name, String
      attribute :last_name,  String
      attribute :email,      String
      attribute :password,   String
      attribute :age,        Integer

The data source is divided into separate collections represented by tables in a database (and by plain ruby hashes in the memory adapter).

Creating a new record

A new record can be created by building a new entity object and passing it to it's repository .persist method. Entity gets an id after being saved.

john = Person.new(
  first_name: 'John',
  last_name:  'Smith',
  email:      '[email protected]',
  password:   'secret',
  age:        28
john.id # => nil

jonh.id # => 1

Getting a record by id

Dead simple:

People.find(1) # => #<Person:0x007ffdeab7f8c8 ...>

Updating a record

The .persist method is able to distinguish between a new entity and an already saved one; in the latter case it updates the respective record in the data source.

john.password = 'f1E2m0CdP'

Deleting a record

Deleting a record is as simple as passing the respective entity to .delete method.


Using complex queries

The criteria method in the repository DSL (mentioned earlier) allows to create query criteria such as query conditions, order and limit. Criteria's name serves as a name for the repository method that applies the criteria.

People.kids # => #<People::Query:0x007f940b104db0 ...>

The query object is enumerable - you can call any Enumerable methods such as each or map on it. Data is fetched lazily: the data source will be asked for data only when it is needed:

People.kids.map(&:first_name) # => ['John']

This laziness allows to chain criteria methods together effectively merging them in one big criteria:

People.older_than(30).in_alphabetical_order # => #<People::Query:0x007f940a9abed8 ...>

When you just want to get all the records without filtering/ordering them you can call .to_a on the repository:

People.to_a # => [#<Person:0x007f940ae39360 ...>, #<Person:0x007f940acfa580 ...>]

You can call enumerable methods on the repository as well - this allows to traverse all records in the collection.


Working with a relational database requires changing it's schema often; this is what migrations are for. YADM provides a very simple interface for defining sequel migrations:

YADM.migrate :store do |db|
  db.create_table :posts do
    primary_key :id

    String  :title
    String  :author
    Integer :comments
    Time    :created_at

You must define the respective data source before trying to migrate it.


  • SQL joins
  • associations
  • more adapters


This project is heavily inspired by lotus/model and ROM projects, the famous Uncle Bob's "Architecture the Lost Years" and POODR of course.


There are a couple examples in the examples/ directory.


  1. Fork it (https://github.com/7even/yadm/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request