$30 off During Our Annual Pro Sale. View Details »

DataMapper 2 - an object mapping toolkit

Piotr Solnica
September 29, 2012

DataMapper 2 - an object mapping toolkit

An introduction to the DataMapper 2 stack.

Piotr Solnica

September 29, 2012
Tweet

More Decks by Piotr Solnica

Other Decks in Programming

Transcript

  1. DataMapper 2
    an object mapping toolkit
    RubyShift Conference, Kiev 2012
    Monday, October 1, 12

    View Slide

  2. Hi I’m solnic!
    • Software Consultant
    • DataMapper Core Team
    • Blog at solnic.eu
    • Hack at github.com/solnic
    • Tweet @_solnic_
    Monday, October 1, 12

    View Slide

  3. Active Record
    vs
    Data Mapper
    Monday, October 1, 12

    View Slide

  4. Active Record
    • Simple pattern
    • Designed for simple use cases when
    business logic is not complex (ie CRUD)
    • No separation between persistence and
    domain objects
    • 1:1 mapping between db schema and
    domain objects
    Monday, October 1, 12

    View Slide

  5. Data Mapper
    • Advanced system implementing
    various patterns (Mapper, Unit of Work,
    Repository)
    • Strict separation between persistence
    and domain logic
    • Designed for cases where mixing
    persistence concerns with domain logic
    becomes a problem
    Monday, October 1, 12

    View Slide

  6. Active Record
    Data Mapper
    vs
    Monday, October 1, 12

    View Slide

  7. Active Record
    Data Mapper
    vs
    Monday, October 1, 12

    View Slide

  8. Active Record
    Data Mapper
    &
    Monday, October 1, 12

    View Slide

  9. What went wrong
    with
    ActiveRecord
    in Rails
    Monday, October 1, 12

    View Slide

  10. class User < ActiveRecord::Base
    end
    Active Record in Rails
    Monday, October 1, 12

    View Slide

  11. Active Record in Rails
    (User.methods.sort - Object.methods.sort).count
    # => 391
    391
    new public class methods...
    Monday, October 1, 12

    View Slide

  12. ActiveRecord
    in Rails
    Does TOO MUCH
    Monday, October 1, 12

    View Slide

  13. • Persistence
    • Validations
    • Life cycle hooks
    • Typecasting
    • Mass-assignment security
    • ...and much much more!
    ActiveRecord in Rails
    Monday, October 1, 12

    View Slide

  14. DataMapper 2
    Monday, October 1, 12

    View Slide

  15. DataMapper 2
    Repository
    Mapper & Session
    Domain Model
    Monday, October 1, 12

    View Slide

  16. Domain Model
    Monday, October 1, 12

    View Slide

  17. Domain Model
    class User
    attr_reader :id, :name, :age
    def initialize(attributes)
    @id, @name, @age = attributes.values_at(:id, :name, :age)
    end
    # business logic goes here :)
    end
    Monday, October 1, 12

    View Slide

  18. Domain Model
    require './user'
    require 'rspec'
    describe User do
    subject { User.new(id: 1, name: 'Jane', age: 21) }
    describe '#name' do
    its(:name) { should eql('Jane') }
    end
    describe '#age' do
    its(:age) { should eql(21) }
    end
    end
    Monday, October 1, 12

    View Slide

  19. Mapper
    Monday, October 1, 12

    View Slide

  20. Mapper
    class User
    attr_reader :id, :name, :age
    def initialize(attributes)
    @id, @name, @age = attributes.values_at(:id, :name, :age)
    end
    # business logic goes here :)
    end
    Monday, October 1, 12

    View Slide

  21. Mapper
    class UserMapper < DataMapper::Mapper::Relation::Base
    model User
    relation_name :users
    repository :postgres
    map :id, Integer, :key => true
    map :name, String, :to => :username
    map :age, Integer
    end
    Monday, October 1, 12

    View Slide

  22. Mapper
    class User
    include DataMapper::Model
    attribute :id, Integer
    attribute :name, String
    attribute :age, Integer
    end
    DataMapper.generate_mapper_for(User) do
    key :id
    map :name, :to => :username
    end
    Monday, October 1, 12

    View Slide

  23. Mapper
    DataMapper[User].all
    DataMapper[User].order(:age)
    DataMapper[User].one(name: 'Jane')
    DataMapper[User].find(age: 18)
    Monday, October 1, 12

    View Slide

  24. Mapper
    class Address
    include DataMapper::Model
    attribute :id, Integer
    attribute :street, String
    attribute :city, String
    attribute :zipcode, String
    end
    class User
    include DataMapper::Model
    attribute :id, Integer
    attribute :name, String
    attribute :age, Integer
    attribute :address, Address
    end
    Monday, October 1, 12

    View Slide

  25. Mapper
    class Address
    include DataMapper::Model
    attribute :id, Integer
    attribute :street, String
    attribute :city, String
    attribute :zipcode, String
    end
    class User
    include DataMapper::Model
    attribute :id, Integer
    attribute :name, String
    attribute :age, Integer
    attribute :address, Address
    end
    a user has
    an address
    Monday, October 1, 12

    View Slide

  26. Mapper
    DataMapper.generate_mapper_for(Address) do
    key :id
    end
    DataMapper.generate_mapper_for(User) do
    key :id
    map :name, :to => :username
    has 1, :address, Address
    end
    Monday, October 1, 12

    View Slide

  27. Mapper
    DataMapper.generate_mapper_for(Address) do
    key :id
    end
    DataMapper.generate_mapper_for(User) do
    key :id
    map :name, :to => :username
    has 1, :address, Address
    end
    OneToOne
    Relationship
    Monday, October 1, 12

    View Slide

  28. Mapper
    user = DataMapper[User].include(:address).first
    user.address.city
    user.address.street
    user.address.zipcode
    Monday, October 1, 12

    View Slide

  29. Mapper
    class Order
    include DataMapper::Model
    attribute :id, Integer
    attribute :product, String
    end
    class User
    include DataMapper::Model
    attribute :id, Integer
    attribute :name, String
    attribute :age, Integer
    attribute :orders, Array[Order]
    end
    Monday, October 1, 12

    View Slide

  30. Mapper
    class Order
    include DataMapper::Model
    attribute :id, Integer
    attribute :product, String
    end
    class User
    include DataMapper::Model
    attribute :id, Integer
    attribute :name, String
    attribute :age, Integer
    attribute :orders, Array[Order]
    end
    a user has
    orders
    Monday, October 1, 12

    View Slide

  31. Mapper
    DataMapper.generate_mapper_for(Order) do
    key :id
    end
    DataMapper.generate_mapper_for(User) do
    key :id
    map :name, :to => :username
    has 0..n, :orders, Order
    end
    DataMapper[User].include(:orders).all
    Monday, October 1, 12

    View Slide

  32. Mapper
    DataMapper.generate_mapper_for(Order) do
    key :id
    end
    DataMapper.generate_mapper_for(User) do
    key :id
    map :name, :to => :username
    has 0..n, :orders, Order
    end
    DataMapper[User].include(:orders).all
    OneToMany
    Relationship
    Monday, October 1, 12

    View Slide

  33. Session
    Work In Progress...
    Monday, October 1, 12

    View Slide

  34. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    session.persisted?(user) # => false
    session.insert(user)
    session.commit
    end
    Monday, October 1, 12

    View Slide

  35. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    session.persisted?(user) # => false
    session.insert(user)
    session.commit
    end
    is the user
    persisted?
    Monday, October 1, 12

    View Slide

  36. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    session.persisted?(user) # => false
    session.insert(user)
    session.commit
    end
    queue the user
    to be persisted
    Monday, October 1, 12

    View Slide

  37. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    session.persisted?(user) # => false
    session.insert(user)
    session.commit
    end
    write changes
    to the database
    Monday, October 1, 12

    View Slide

  38. Session
    DataMapper.session do |session|
    user = session[User].get(1)
    user.age = 29
    session.dirty?(user) # => true
    session.update(user)
    session.commit
    end
    Monday, October 1, 12

    View Slide

  39. Session
    DataMapper.session do |session|
    user = session[User].get(1)
    user.age = 29
    session.dirty?(user) # => true
    session.update(user)
    session.commit
    end
    fetch the user
    and track it
    Monday, October 1, 12

    View Slide

  40. Session
    DataMapper.session do |session|
    user = session[User].get(1)
    user.age = 29
    session.dirty?(user) # => true
    session.update(user)
    session.commit
    end
    was the user
    changed?
    Monday, October 1, 12

    View Slide

  41. Session
    DataMapper.session do |session|
    user = session[User].get(1)
    user.age = 29
    session.dirty?(user) # => true
    session.update(user)
    session.commit
    end
    update the user
    in the database
    Monday, October 1, 12

    View Slide

  42. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    address = Address.new(
    street: 'Street 1', city: 'Krakow', zipcode: '12345'
    )
    user.address = address
    session.insert(user)
    session.commit
    end
    Monday, October 1, 12

    View Slide

  43. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    address = Address.new(
    street: 'Street 1', city: 'Krakow', zipcode: '12345'
    )
    user.address = address
    session.insert(user)
    session.commit
    end
    assign the address
    to the user
    Monday, October 1, 12

    View Slide

  44. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    address = Address.new(
    street: 'Street 1', city: 'Krakow', zipcode: '12345'
    )
    user.address = address
    session.insert(user)
    session.commit
    end
    queue the user
    to be persisted
    Monday, October 1, 12

    View Slide

  45. Session
    DataMapper.session do |session|
    user = User.new(name: 'Piotr', age: 28)
    address = Address.new(
    street: 'Street 1', city: 'Krakow', zipcode: '12345'
    )
    user.address = address
    session.insert(user)
    session.commit
    end
    write both objects
    to the database
    Monday, October 1, 12

    View Slide

  46. Repository
    Monday, October 1, 12

    View Slide

  47. Veritas
    Powerful Relational Algebra Library
    Monday, October 1, 12

    View Slide

  48. Veritas
    • Created by Dan Kubb in 2010
    • Complete support for all relational
    algebra operations
    • All operations can be run in-memory
    • Can be extended to support any kind of a
    datastore
    • Can be used with multiple different
    databases
    Monday, October 1, 12

    View Slide

  49. Repository
    class User
    include DataMapper::Model
    attribute :id, Integer
    attribute :name, String
    attribute :age, Integer
    end
    DataMapper.generate_mapper_for(User) do
    key :id
    map :name, :to => :username
    end
    Monday, October 1, 12

    View Slide

  50. Repository
    mapper = DataMapper[User]
    mapper.relation.class # => Veritas::Relation::Gateway
    mapper.relation.header.to_set.map { |a|
    [ a.name, a.type ]
    }
    # => [
    # [ :id, Veritas::Attribute::Integer ],
    # [ :username, Veritas::Attribute::String ],
    # [ :age, Veritas::Attribute::Integer ]
    # ]
    mappers wrap veritas gateway relations
    Monday, October 1, 12

    View Slide

  51. Repository
    mapper = DataMapper[User]
    relation = Veritas::Relation.new(
    mapper.relation.header,
    [ [ 1, 'John', 18 ], [ 2, 'Jane', 21 ] ]
    )
    relation.restrict { |r| r.age.gt(18) }.each do |tuple|
    puts tuple[:username] # => 'Jane'
    end
    building an in-memory relation
    Monday, October 1, 12

    View Slide

  52. Repository
    user_mapper = DataMapper[User]
    address_mapper = DataMapper[Address]
    user_relation = Veritas::Relation.new(
    user_mapper.relation.header, [
    [ 1, 'John', 18 ], [ 2, 'Jane', 21 ]
    ])
    address_relation = Veritas::Relation.new(
    address_mapper.relation.header, [
    [ 1, 1, 'Street 1', '12345' ], [ 2, 2, 'Street 2', '54321' ]
    ])
    joining two relations in-memory
    Monday, October 1, 12

    View Slide

  53. Repository
    user_address = user_relation.rename(:id => :user_id).
    join(address_relation)
    user_address.restrict { |r| r.street.eq('Street 1') }.
    each do |tuple|
    puts tuple[:username] # => "John"
    end
    joining two relations in-memory
    Monday, October 1, 12

    View Slide

  54. Projects on Github
    • github.com/dkubb/veritas
    • github.com/solnic/virtus
    • github.com/solnic/dm-mapper
    • github.com/solnic/dm-session
    Monday, October 1, 12

    View Slide

  55. Thank You!
    Time for questions?
    Monday, October 1, 12

    View Slide