SlideShare a Scribd company logo
Ruby meetup ROM
Me
Nikita Shilnikov
• github.com/flash-gordon
• Whatever developer
• dry-rb and rom-rb core team member
ROM
disinterring the stewardess
Ruby meetup ROM
Ruby Object Mapper
ROM vs ORM
ROM vs AR
ROM vs AR
user = Users.create(name: 'John Doe')
user.age = 30
user.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
Ruby meetup ROM
Ruby meetup ROM
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
Ruby meetup ROM
Ruby meetup 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
end
end
Rela%on
class Users < ROM::Relation[:sql]
schema(infer: true)
def by_name(name)
where(name: name)
end
end
Command
• Writes data
• Bound to rela0on
Command
class CreateUser < ROM::Commands::Create[:sql]
relation :users
register_as :create
result :one
end
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
end
end
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? # => true
changeset.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
end
end
Many adapters
class UserRepo < ROM::Repository[:users]
relations :counters
end
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
Plans
ROM 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

More Related Content

PDF
Database schema management in Ruby apps
PDF
Lumberjack XPath 101
PDF
1 year with ROM on production
PDF
"Meet rom_rb & dry_rb" by Piotr Solnica
PDF
Umleitung: a tiny mochiweb/CouchDB app
KEY
Wider than rails
PPT
Sql php-vibrant course-mumbai(1)
PDF
Php 5.6 From the Inside Out
Database schema management in Ruby apps
Lumberjack XPath 101
1 year with ROM on production
"Meet rom_rb & dry_rb" by Piotr Solnica
Umleitung: a tiny mochiweb/CouchDB app
Wider than rails
Sql php-vibrant course-mumbai(1)
Php 5.6 From the Inside Out

What's hot (20)

PDF
Config Management with MicroOperators
PDF
Ruby HTTP clients
PDF
Using Ruby on Rails with legacy Oracle databases
PPTX
Laravel5 Introduction and essentials
PPT
Php classes in mumbai
PPT
Rush, a shell that will yield to you
PDF
Workshop 4: NodeJS. Express Framework & MongoDB.
PPTX
Unix - Shell Scripts
PDF
PDF
Painless Data Storage with MongoDB & Go
PDF
Mini Rails Framework
PPT
Basic command ppt
ODP
2. writing MySql plugins general
PDF
Vidoop CouchDB Talk
DOCX
Basic linux commands
PDF
React for Beginners
PDF
The new features of PHP 7
KEY
DSLs Internas e Ruby
PDF
Example Stream Setup
TXT
Logrotate sh
Config Management with MicroOperators
Ruby HTTP clients
Using Ruby on Rails with legacy Oracle databases
Laravel5 Introduction and essentials
Php classes in mumbai
Rush, a shell that will yield to you
Workshop 4: NodeJS. Express Framework & MongoDB.
Unix - Shell Scripts
Painless Data Storage with MongoDB & Go
Mini Rails Framework
Basic command ppt
2. writing MySql plugins general
Vidoop CouchDB Talk
Basic linux commands
React for Beginners
The new features of PHP 7
DSLs Internas e Ruby
Example Stream Setup
Logrotate sh
Ad

Similar to Ruby meetup ROM (20)

PDF
HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective
PPTX
Intro To Node.js
KEY
Rails web api 开发
PDF
Building web framework with Rack
PDF
Rack
PPTX
Learning to code for startup mvp session 3
PDF
Some tips to improve developer experience with Symfony
PDF
Tools and Tips for Moodle Developers - #mootus16
PDF
Ruby meetup-dry
PDF
Rails 4.0
PDF
How to Make Android's Bootable Recovery Work For You by Drew Suarez
PDF
Docker
PPTX
Intro to node and mongodb 1
PPTX
REST APIs in Laravel 101
PPTX
C++ in kernel mode
PPT
Linux Device Driver for Writing a real world driver for embedded Linux
PPTX
Introduction to Laravel Framework (5.2)
KEY
Rack is Spectacular
PDF
Ruby on Rails Presentation
PDF
Intro To JavaScript Unit Testing - Ran Mizrahi
HES2011 - joernchen - Ruby on Rails from a Code Auditor Perspective
Intro To Node.js
Rails web api 开发
Building web framework with Rack
Rack
Learning to code for startup mvp session 3
Some tips to improve developer experience with Symfony
Tools and Tips for Moodle Developers - #mootus16
Ruby meetup-dry
Rails 4.0
How to Make Android's Bootable Recovery Work For You by Drew Suarez
Docker
Intro to node and mongodb 1
REST APIs in Laravel 101
C++ in kernel mode
Linux Device Driver for Writing a real world driver for embedded Linux
Introduction to Laravel Framework (5.2)
Rack is Spectacular
Ruby on Rails Presentation
Intro To JavaScript Unit Testing - Ran Mizrahi
Ad

Recently uploaded (20)

PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPT
Teaching material agriculture food technology
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Electronic commerce courselecture one. Pdf
PDF
Review of recent advances in non-invasive hemoglobin estimation
PPTX
MYSQL Presentation for SQL database connectivity
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
KodekX | Application Modernization Development
Reach Out and Touch Someone: Haptics and Empathic Computing
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Advanced methodologies resolving dimensionality complications for autism neur...
“AI and Expert System Decision Support & Business Intelligence Systems”
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Digital-Transformation-Roadmap-for-Companies.pptx
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
Chapter 3 Spatial Domain Image Processing.pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Teaching material agriculture food technology
Understanding_Digital_Forensics_Presentation.pptx
Electronic commerce courselecture one. Pdf
Review of recent advances in non-invasive hemoglobin estimation
MYSQL Presentation for SQL database connectivity
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Diabetes mellitus diagnosis method based random forest with bat algorithm
Network Security Unit 5.pdf for BCA BBA.
KodekX | Application Modernization Development

Ruby meetup ROM

  • 2. Me Nikita Shilnikov • github.com/flash-gordon • Whatever developer • dry-rb and rom-rb core team member
  • 8. ROM vs AR user = Users.create(name: 'John Doe') user.age = 30 user.save
  • 9. 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
  • 10. 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
  • 13. Ac#veRecord is not your Domain Layer
  • 14. Code is not data
  • 20. ROM
  • 23. Ideology • No global state • Immutability • Explicitness • Leverage your database • CQRS
  • 26. Gems • rom – core facili-es • rom-sql – RDBMS interac-on • rom-repository – applica-on-level interface
  • 27. Rela%on • Reads data • Adapter-specific
  • 28. Rela%ons class Users < ROM::Relation[:sql] schema(infer: true) end
  • 29. Rela%on class Users < ROM::Relation[:sql] schema do attribute :id, Types::Serial attribute :name, Types::String end end
  • 30. Rela%on class Users < ROM::Relation[:sql] schema(infer: true) def by_name(name) where(name: name) end end
  • 31. Command • Writes data • Bound to rela0on
  • 32. Command class CreateUser < ROM::Commands::Create[:sql] relation :users register_as :create result :one end
  • 33. Container ! rela%ons + commands + DB connec%ons
  • 34. Container ! rom = ROM.container(:sql, 'sqlite::memory')
  • 35. Repository • Accesses applica+on data • Receives a container
  • 36. Repository (ROM::Repository::Root.instance_methods - Object.new.methods).size # => 12 (ROM::Repository::Root.methods - Class.new.methods).size # => 9
  • 37. 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
  • 38. 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
  • 39. Repository class UserRepo < ROM::Repository[:users] end
  • 40. Repository class UserRepo < ROM::Repository[:users] def list users.order(:name).to_a end end
  • 41. Repository repo = UserRepo.new(rom) repo.list # => [#<ROM::Struct[User] id=1 name="Jane">]
  • 42. 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))
  • 43. Changeset changeset = repo.changeset(user.id, name: 'John Doe') changeset.diff? # => true changeset.diff # => {:name=>'John Doe'}
  • 44. To the cool stuff! !
  • 47. 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)
  • 48. 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
  • 49. 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
  • 50. 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 end end
  • 51. Many adapters class UserRepo < ROM::Repository[:users] relations :counters end repo = UserRepo.new(rom)
  • 52. 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
  • 53. 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`
  • 55. Moar backends • rom-yaml • rom-h*p • rom-mongo • rom-couchdb • rom-ka3a • rom-redis • rom-git
  • 56. Plans ROM 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 ✨
  • 57. Help • Give it a try • Use different adapters • Docs • Contribute