Ruby meetup ROM

58

Transcript of Ruby meetup ROM

MeNikita Shilnikov

• github.com/flash-gordon

• Whatever developer

• dry-rb and rom-rb core team member

ROMdisinterring the stewardess

Ruby Object Mapper

ROM vs ORM

ROM vs AR

ROM vs AR

user = Users.create(name: 'John Doe')user.age = 30user.save

ROM vs AR builder = SqlBuilder.new <<-SQL UPDATE topic_users tu SET notification_level = CASE WHEN should_track THEN :tracking WHEN should_watch THEN :watching ELSE notification_level END, notifications_reason_id = CASE WHEN should_track THEN null WHEN should_watch THEN :auto_watch_category ELSE notifications_reason_id END FROM ( SELECT tu1.topic_id, tu1.user_id, CASE WHEN cu.user_id IS NULL AND tu1.notification_level = :watching AND tu1.notifications_reason_id = :auto_watch_category THEN true ELSE false END should_track, CASE WHEN cu.user_id IS NOT NULL AND tu1.notification_level in (:regular, :tracking) THEN true ELSE false END should_watch

FROM topic_users tu1 JOIN topics t ON t.id = tu1.topic_id LEFT JOIN category_users cu ON cu.category_id = t.category_id AND cu.user_id = tu1.user_id AND cu.notification_level = :watching /*where2*/ ) as X

/*where*/SQL

ROM vs AR --builder = SqlBuilder.new <<-SQL UPDATE topic_users tu SET notification_level = CASE WHEN should_track THEN :tracking WHEN should_watch THEN :watching ELSE notification_level END, notifications_reason_id = CASE WHEN should_track THEN null WHEN should_watch THEN :auto_watch_category ELSE notifications_reason_id END FROM ( SELECT tu1.topic_id, tu1.user_id, CASE WHEN cu.user_id IS NULL AND tu1.notification_level = :watching AND tu1.notifications_reason_id = :auto_watch_category THEN true ELSE false END should_track, CASE WHEN cu.user_id IS NOT NULL AND tu1.notification_level in (:regular, :tracking) THEN true ELSE false END should_watch

FROM topic_users tu1 JOIN topics t ON t.id = tu1.topic_id LEFT JOIN category_users cu ON cu.category_id = t.category_id AND cu.user_id = tu1.user_id AND cu.notification_level = :watching /*where2*/ ) as X

/*where*/SQL

Ac#veRecord is not your Domain Layer

Code is not data

Data is $$$

Code is !

Code is live

Data is not

Filling the gap

Use your database

ROM

Ideology

• No global state

• Immutability

• Explicitness

• Leverage your database

• CQRS

Overview

Overview

Gems

• rom – core facili-es

• rom-sql – RDBMS interac-on

• rom-repository – applica-on-level interface

Rela%on

• Reads data

• Adapter-specific

Rela%ons

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

Rela%on

class Users < ROM::Relation[:sql] schema do attribute :id, Types::Serial attribute :name, Types::String endend

Rela%on

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

def by_name(name) where(name: name) endend

Command

• Writes data

• Bound to rela0on

Command

class CreateUser < ROM::Commands::Create[:sql] relation :users register_as :create result :oneend

Container !

rela%ons + commands + DB connec%ons

Container !

rom = ROM.container(:sql, 'sqlite::memory')

Repository

• Accesses applica+on data

• Receives a container

Repository

(ROM::Repository::Root.instance_methods - Object.new.methods).size # => 12(ROM::Repository::Root.methods - Class.new.methods).size # => 9

Repository

(ROM::Repository::Root.instance_methods - Object.new.methods).size # => 12(ROM::Repository::Root.methods - Class.new.methods).size # => 9

(ApplicationRecord.instance_methods - Object.new.methods).size(ApplicationRecord.methods - Class.new.methods).size

Repository

(ROM::Repository::Root.instance_methods - Object.new.methods).size # => 12(ROM::Repository::Root.methods - Class.new.methods).size # => 9

(ApplicationRecord.instance_methods - Object.new.methods).size # => 244(ApplicationRecord.methods - Class.new.methods).size # => 467

TOTAL # => 711

Repository

class UserRepo < ROM::Repository[:users]

end

Repository

class UserRepo < ROM::Repository[:users] def list users.order(:name).to_a endend

Repository

repo = UserRepo.new(rom)repo.list # => [#<ROM::Struct[User] id=1 name="Jane">]

Changeset

changeset = repo.changeset(name: 'John')changeset.map(:add_timestamps).to_h# => {:name=>"John",# :created_at=>2017-03-18 16:33:19 +0300,# :updated_at=>2017-03-18 16:33:19 +0300}

repo.create(changeset.map(:add_timestamps))

Changeset

changeset = repo.changeset(user.id, name: 'John Doe')

changeset.diff? # => truechangeset.diff # => {:name=>'John Doe'}

To the cool stuff! !

No global state

Many containers

Many containers

rom = ROM.container(:sql, DB_URL)rom_replica = ROM.container(:sql, DB_REPLICA_URL)

repo = UserRepo.new(rom)read_only_repo = UserRepo.new(rom_replica)

Many adapters

rom = ROM.container( default: [:sql, 'postgres://localhost/rom_repository'], cache: [:sql, 'sqlite::memory']) do |c| c.gateways[:cache].create_table :counters do primary_key :id column :user_id, Integer column :type, String column :value, Integer end

end

Many adapters

rom = ROM.container( default: [:sql, 'postgres://localhost/rom_repository'], cache: [:sql, 'sqlite::memory']) do |c| c.gateways[:cache].create_table :counters do primary_key :id column :user_id, Integer column :type, String column :value, Integer end

c.relation(:users) do schema(infer: true) end

end

Many adapters

rom = ROM.container( default: [:sql, 'postgres://localhost/rom_repository'], cache: [:sql, 'sqlite::memory']) do |c| c.gateways[:cache].create_table :counters do primary_key :id column :user_id, Integer column :type, String column :value, Integer end

c.relation(:users) do schema(infer: true) end

c.relation(:counters) do gateway :cache

schema(infer: true)

def for_users(users) where(user_id: users.pluck(:id)) end endend

Many adapters

class UserRepo < ROM::Repository[:users] relations :countersend

repo = UserRepo.new(rom)

Many adapters

repo. users. combine(many: { counters: [repo.counters.for_users, id: :user_id] }). where { id <= 2 }. to_a

=begin

[ #<ROM::Struct[User] id=1 name="Jane" counters=[ #<ROM::Struct[Counter] id=1 user_id=1 type="posts" value=3>, #<ROM::Struct[Counter] id=2 user_id=1 type="comments" value=512> ]>, #<ROM::Struct[User] id=2 name="Joe" counters=[]>]

=end

No N+1!

I, [2017-03-18T19:37:14 #6638] (0.000765s) SELECT "id", "name" FROM "users" WHERE ("id" <= 2) ORDER BY "users"."id"I, [2017-03-18T19:37:14 #6638] (0.000172s) SELECT `id`, `user_id`, `type`, `value` FROM `counters` WHERE (`user_id` IN (1, 2)) ORDER BY `counters`.`id`

One database

repo.aggregate(:counters).where { id <= 2 }.to_a

Moar backends

• rom-yaml

• rom-h*p

• rom-mongo

• rom-couchdb

• rom-ka3a

• rom-redis

• rom-git

PlansROM 4.0 roadmap

• More powerful mapping facili2es

• Cross-adapter associa2ons

• Move a bunch of features from rom-repository to rom-core

• Auto-migra2ons in rom-sql ✨

Help

• Give it a try

• Use different adapters

• Docs

• Contribute

Spasibo