Slide 1

Slide 1 text

rom-rb 4.0 Piotr Solnica >> Moscow >> RailsClub 2017

Slide 2

Slide 2 text

Piotr Solnica → ! Cracow, Poland → github.com/solnic → @_solnic_ → solnic.eu

Slide 3

Slide 3 text

Tech Lead at Icelab

Slide 4

Slide 4 text

Tech Lead at Icelab

Slide 5

Slide 5 text

rom-rb 4.0 is coming rom-rb 4.0 is here

Slide 6

Slide 6 text

sort of

Slide 7

Slide 7 text

4.0.0.rc1 was released a week ago

Slide 8

Slide 8 text

What's rom-rb?

Slide 9

Slide 9 text

an open-source persistence and mapping toolkit for Ruby built for speed and simplicity

Slide 10

Slide 10 text

Database-agnostic

Slide 11

Slide 11 text

Flexible

Slide 12

Slide 12 text

Extendible

Slide 13

Slide 13 text

Fast

Slide 14

Slide 14 text

Simple

Slide 15

Slide 15 text

Sounds like some crazy object relational mapper, eh?

Slide 16

Slide 16 text

rom-rb is not an ORM

Slide 17

Slide 17 text

Typical ORM ! that we DO NOT HAVE → Relying on mutable objects → Complex association model → Leaky abstractions → [ActiveRecord] tight coupling with database schema → [ActiveRecord] n+1 query problem → [ActiveRecord] callbacks → [ActiveRecord] huge API

Slide 18

Slide 18 text

Why does it even exist?

Slide 19

Slide 19 text

A TRUE ALTERNATIVE to Active Record

Slide 20

Slide 20 text

rom-rb provides a way to separate persistence concerns from application domain

Slide 21

Slide 21 text

What's the deal with rom-rb 4.0? (aka: serious business)

Slide 22

Slide 22 text

Big, BIG, challenge

Slide 23

Slide 23 text

Make rom-rb simple to use (comparable with Active Record)

Slide 24

Slide 24 text

What's so awesome about Active Record?

Slide 25

Slide 25 text

No boilerplate class User < ActiveRecord::Base end

Slide 26

Slide 26 text

Persistence, so simple user = User.create(name: "Jane")

Slide 27

Slide 27 text

Queries, so simple User.where(name: "Jane")

Slide 28

Slide 28 text

Making changes, so simple User.where(name: "Jane").update_all(name: "Jane Doe")

Slide 29

Slide 29 text

Ease of use → Little code to write to get started → A lot of functionality OOTB → No boilerplate

Slide 30

Slide 30 text

This was a real challenge for rom-rb!

Slide 31

Slide 31 text

Dynamic query interface not tied to the database schema

Slide 32

Slide 32 text

Explicit representation of data structures returned by relations

Slide 33

Slide 33 text

Mapping to struct objects decoupled from the database

Slide 34

Slide 34 text

No concept of lazy-loadable associations

Slide 35

Slide 35 text

This is fine

Slide 36

Slide 36 text

We made it (⊃ꙏ•́‿•̀ ꙏ)⊃━☆ꚍ.*ꙓꙏꚍ

Slide 37

Slide 37 text

Relations and Structs

Slide 38

Slide 38 text

class Users < ROM::Relation[:sql] schema(infer: true) end

Slide 39

Slide 39 text

✅ No boilerplate

Slide 40

Slide 40 text

users.changeset(:create, name: "Jane").commit # => #

Slide 41

Slide 41 text

✅ Persistence, so simple

Slide 42

Slide 42 text

users.where(name: "Jane").one # => #

Slide 43

Slide 43 text

✅ Queries, so simple

Slide 44

Slide 44 text

users.where(name: "Jane").changeset(:update, name: "Jane Doe").commit

Slide 45

Slide 45 text

✅ Making changes, so simple

Slide 46

Slide 46 text

BUT...

Slide 47

Slide 47 text

users.first # => # users.select(:name).first # => #

Slide 48

Slide 48 text

users.first.class.schema.keys # [:id, :name] users.select(:name).first.class.schema.keys # [:name]

Slide 49

Slide 49 text

People want their own methods, obviously

Slide 50

Slide 50 text

class User < ActiveRecord::Base def first_name name.split(' ').first end def last_name name.split(' ').last end end

Slide 51

Slide 51 text

rom-rb: custom struct namespace

Slide 52

Slide 52 text

class Users < ROM::Relation[:sql] struct_namespace Entities schema(infer: true) end

Slide 53

Slide 53 text

class Users < ROM::Relation[:sql] struct_namespace Entities schema(infer: true) end

Slide 54

Slide 54 text

module Entities class User < ROM::Struct def first_name name.split(' ').first end def last_name name.split(' ').last end end end

Slide 55

Slide 55 text

module Entities class User < ROM::Struct def first_name name.split(' ').first end def last_name name.split(' ').last end end end

Slide 56

Slide 56 text

user = users.first => # user.first_name # "Jane" user.last_name # "Doe"

Slide 57

Slide 57 text

user = users.select(:name).first => # user.first_name # "Jane" user.last_name # "Doe"

Slide 58

Slide 58 text

What about associations?

Slide 59

Slide 59 text

class Users < ROM::Relation[:sql] schema(infer: true) do associations do has_many :tasks end end end

Slide 60

Slide 60 text

users.combine(:tasks).first # => # # ]>

Slide 61

Slide 61 text

module Entities class User < ROM::Struct def has_tasks? !tasks.empty? end end end user = users.combine(:tasks).first user.has_tasks? # true

Slide 62

Slide 62 text

user = users.first user.has_tasks? # ROM::Struct::MissingAttribute: # undefined method `tasks' for # # (attribute not loaded?)

Slide 63

Slide 63 text

What does this mean, really?

Slide 64

Slide 64 text

→ Dynamic query interface is maintained → Custom methods are maintained → Objects decoupled from the database are maintained too

Slide 65

Slide 65 text

So, this is awesome

Slide 66

Slide 66 text

Ability to start simple without even defining any struct classes

Slide 67

Slide 67 text

Ability to provide your own methods that rely on various attributes in various contexts

Slide 68

Slide 68 text

AR-like convenience without tight coupling with the database

Slide 69

Slide 69 text

BUT...

Slide 70

Slide 70 text

This is not enforced by the library!

Slide 71

Slide 71 text

At any point in time, you can define structs with explicit attributes and ask rom-rb to load them

Slide 72

Slide 72 text

You can establish different models based on various domain concepts

Slide 73

Slide 73 text

However...this requires time and good understanding of the application domain

Slide 74

Slide 74 text

It's a process and rom-rb fully supports it

Slide 75

Slide 75 text

show-off mode: on

Slide 76

Slide 76 text

10 things you can do with rom-rb

Slide 77

Slide 77 text

…that Active Record can't do

Slide 78

Slide 78 text

1. Use it with non-SQL databases

Slide 79

Slide 79 text

class Users < ROM::Relation[:mongo] schema do attribute :_id, Types::ObjectID attribute :name, Types::String end end

Slide 80

Slide 80 text

2. Define cross-database associations

Slide 81

Slide 81 text

class Users < ROM::Relation[:sql] schema(infer: true) do associations do has_many :tasks, override: true, view: :for_users end end end class Tasks < ROM::Relation[:yaml] gateway :external schema(infer: true) def for_users(users) tasks.restrict(UserId: users.pluck(:id)) end end

Slide 82

Slide 82 text

class Users < ROM::Relation[:sql] schema(infer: true) do associations do has_many :tasks, override: true, view: :for_users end end end class Tasks < ROM::Relation[:yaml] gateway :external schema(infer: true) def for_users(users) tasks.restrict(UserId: users.pluck(:id)) end end

Slide 83

Slide 83 text

3. Map to any model

Slide 84

Slide 84 text

class MyUser < MySuperModelLibrary end users.map_to(MyUser)

Slide 85

Slide 85 text

4. Project data easily and get back convenient structs

Slide 86

Slide 86 text

posts. select(:title). select_append { meta.get_text(:comment_count, :integer).as(:comments) }. first # #

Slide 87

Slide 87 text

5. Easily construct complex conditions

Slide 88

Slide 88 text

users.where { admin.is(true).or(moderator.is(true)) } users.where { !admin.is(true) } posts.where { meta.get_text("comments_count", :integer) > 1 }

Slide 89

Slide 89 text

6. Easily use SQL functions

Slide 90

Slide 90 text

users.select { str::first_name.concat(last_name).as(:full_name) }.first # #

Slide 91

Slide 91 text

7. Persist nested data without special model configuration

Slide 92

Slide 92 text

users. combine(:tasks). changeset(:create, name: "Joe", tasks: [{ title: "Task 1" }]). commit # => # # ]>

Slide 93

Slide 93 text

8. Use it with legacy schemas without yelling at your screen too much

Slide 94

Slide 94 text

class Users < ROM::Relation[:sql] schema(:SomehorriblyNamedUseRtable, as: :users) do attribute :UserIdentifier, Serial.meta(alias: :id) attribute :UserName, String.meta(alias: :name) end end users.first # #

Slide 95

Slide 95 text

9. Define custom data mappers

Slide 96

Slide 96 text

class EncryptionMapper < ROM::Mapper register_as :encryption def call(relation) relation.map { |tuple| # do whatever you want } end end users.map_with(:encryption)

Slide 97

Slide 97 text

10. Use changesets to transform data before passing it to the database

Slide 98

Slide 98 text

class NewUser < ROM::Changeset::Create map do rename_keys user_name: :name end end users.changeset(NewUser, user_name: "Jane").commit # #

Slide 99

Slide 99 text

there’s more

Slide 100

Slide 100 text

rom 4.0.0.rc1 is out

Slide 101

Slide 101 text

rom-rb.org @rom_rb

Slide 102

Slide 102 text

Questions?

Slide 103

Slide 103 text

Thank you