DataMapper 2 - an object mapping toolkit

E864e5088627498df8f9b911a9bc3219?s=47 Piotr Solnica
September 29, 2012

DataMapper 2 - an object mapping toolkit

An introduction to the DataMapper 2 stack.

E864e5088627498df8f9b911a9bc3219?s=128

Piotr Solnica

September 29, 2012
Tweet

Transcript

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

    Monday, October 1, 12
  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
  3. Active Record vs Data Mapper Monday, October 1, 12

  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
  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
  6. Active Record Data Mapper vs Monday, October 1, 12

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

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

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

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

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

    391 new public class methods... Monday, October 1, 12
  12. ActiveRecord in Rails Does TOO MUCH Monday, October 1, 12

  13. • Persistence • Validations • Life cycle hooks • Typecasting

    • Mass-assignment security • ...and much much more! ActiveRecord in Rails Monday, October 1, 12
  14. DataMapper 2 Monday, October 1, 12

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

    1, 12
  16. Domain Model Monday, October 1, 12

  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
  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
  19. Mapper Monday, October 1, 12

  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
  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
  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
  23. Mapper DataMapper[User].all DataMapper[User].order(:age) DataMapper[User].one(name: 'Jane') DataMapper[User].find(age: 18) Monday, October 1,

    12
  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
  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
  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
  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
  28. Mapper user = DataMapper[User].include(:address).first user.address.city user.address.street user.address.zipcode Monday, October 1,

    12
  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
  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
  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
  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
  33. Session Work In Progress... Monday, October 1, 12

  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  46. Repository Monday, October 1, 12

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

  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
  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
  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
  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
  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
  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
  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
  55. Thank You! Time for questions? Monday, October 1, 12