2. О чем буду рассказывать?
1. Преимущества и недостатки ActiveRecord
2. Что такое ROM
3. Философия ROM
4. Основные компоненты
5. Больше чем gem
6. Необычные кейсы применения
2
4. ActiveRecord
• Хороший выбор для простой логики предметной области
• Независимость от СУБД
• Встроенная валидация
• Связи (relations)
4
5. ActiveRecord
...и капля дегтя
• Хороший выбор для простой логики предметной области CRUD
• Независимость от СУБД sql-only?!
• Встроенная валидация Напрямую завязана на модель
• Связи (relations) Магия :)
5
6. Недостатки ActiveRecord
• Нарушает SRP (Single responsibility principle)
• Нет PORO (Plain Old Ruby Object)
• 1-в-1 маппинг на колонки таблицы
• Вы всегда работаете со строкой таблицы
• Тесты тоже завязаны на базу данных
6
7. ROM
Ruby Object Mapper
ROM is an open-source persistence and mapping toolkit for Ruby built for
speed and simplicity
“
7
8. ROM: Концепции и решения
• Минимальная инфраструктура для маппинга и сохранности
• Высокоуровневые абстракции над низкоуровневыми компонентами
• Частные реализации запросов к хранилищу
• Разграничение чтения и изменения данных
• Упрощение базового хранилища (при желании)
• Слабые связи между компонентами
8
11. ROM: Adapter
• свой интефейс для каждой реализации (нет общего или базового)
• внутри себя использует Шлюзы, Наборы данных и Связи
• более высокая абстракция для relations и commands
11
12. ROM: Repositories
• Отдельный gem: gem install rom-repository
• Инкапсулирует доступ к объектам предметной области
• Автоматически связывает кортежи данных с объектами ROM::Struct
12
13. ROM: Repositories. Repository Class
class UserRepository < ROM::Repository::Base
relations :users
end
rom = ROM.finalize.env
user_repo = UserRepository.new(rom)
user_repo.users
13
14. ROM: Repositories. Repository Class
class UserRepository < ROM::Repository::Base
relations :users
def [](id)
users.where(id: id).one!
end
end
user_repo.users[1] # => <ROM::Struct[User] id=1 name="Jane">
14
15. ROM: Repositories
• ROM::Struct
• доступ к аттрибутам like Hash (по ключам)
• ... или с помощью методов
user = user_repo[1] #=> <ROM::Struct[User] id=1 name="Jane">
user[:id] # 1
user.id # 1
user.to_hash # {:id=>1, :name=>"Jane"}
15
16. ROM: Relations
• запросы к базам данных для использования в repository
• Представления (view)
• Поддержка составных связей
class UserRepository < ROM::Repository::Base
relations :users, :tasks
def with_tasks(id)
users.by_id(id).combine_children(many: tasks)
end
end
16
17. ROM: Relations. Auto-Combine
#combine_parents , #combine_children
class TaskRepository < ROM::Repository::Base
relations :users, :tasks, :tags
def with_owner_and_tags(id)
tasks
.by_id(id)
.combine_parents(one: { owner: users })
.combine_children(tags: tags)
end
end 17
18. ROM: Relations. Data Pipeline (>>)
class Users < ROM::Relation[:memory]
def by_name(name)
restrict(name: name)
end
end
name_list = -> users do
users.map { |user| user[:name] }
end
user_names = rom.relation(:users) >> name_list
rom.relation(:users).to_a # [{ id: 1, name: 'Joe'}]
user_names.to_a # ['Joe'] 18
19. ROM: Relations. Auto-curry
users_by_name = rom.relation(:users).by_name
# call later on using short `[]` syntax
users_by_name['Jane']
# or
users_by_name.('Jane')
# or more explicit and longer form
users_by_name.call('Jane')
19
20. ROM: Commands
• Изменение состояния
• ROM::Commands::Create
• ROM::Commands::Update
• ROM::Commands::Delete
• или специфичные для вашего адаптера
20
25. ROM: Mappers
• Приведение объектов бизнес-логики к схебе базы данных
• ... и обратно
• Может использоваться с другими библиотеками
• Основные задачи:
• Переименование и фильтрация, группировка атрибутов
• Приведение типов атрибутов
• Создание агрегатов, иммутабельных объектов и др.
25
26. ROM: Mappers. Definition
users = ROM.env.relation(:users)
class UserAsEntity < ROM::Mapper
register_as :entity # name of the mapper
relation :users # the name of the relation
model User # the domain model to map tuples to
end
users.as(:entity).to_a
# [ <User @id=1, @name='jane', @email='jane@doo.org'>
# <User @id=2, @name='alex', @email='alex@doo.org'> ]
26
27. ROM: Mappers. Strategies
• Тонкий интерфейс
• нет знаний о модели предметной области
• самостоятельное инстанцирование
• Обычно - просто Hash
• Толстый интерфейс
• Глубокие знания модели
• Преинициализированные объекты
• Сложный доменный объект со связями и логикой
27
28. ROM: Mappers. Data Transformation
• Фильтрация атрибутов ( exclude, reject_keys )
• Переименование ( from )
• Оборачивание ( wrap, unwrap )
• Группировка ( group, ungroup )
• Сложение/вычитание fold, unfold
• Комбинирование из разных связей
• Встраивание ( embeded )
28
30. ROM: Forms
class TaskForm < ROM::Model::Form
input do
set_model_name 'Task'
attribute :title, String
end
validations do
relation :tasks
validates :title, presence: true
end
end
30
31. Что уже есть?
• Релиз 1.0 в конце сентября
• Большое количество адаптеров
• SQL: ado, amalgalite, cubrid, db2, dbi, do, fdbsql, firebird, ibmdb,
informix, jdbc, mysql, mysql2, odbc, openbase, oracle, postgres,
sqlanywhere, sqlite, swift, tinytds
• Graphs: neo4j
• NoSQL: mongo, couchdb; KV: redis
• FileStorage: json, yml, csv; Network: http, git
31