SlideShare a Scribd company logo
1 of 60
Download to read offline
From rails-way to
modular architecture
Ivan Nemytchenko,
independent consultant
DevConf 2014
@inem, @inemation
Icon made by <a href="http://www.freepik.com" alt="Freepik.com">Freepik</a>
from <a href="http://www.flaticon.com/free-icon/graduate-cap_46045">flaticon.com</a>
Icon made by Icons8 from <a href="http://www.flaticon.com">flaticon.com</a>
4 bit.ly/rails-community
!"---→----→----→----→----#
Rails-way
Модульная архитектура -
это что?
4 изменения в коде делать легко
4 код легко переиспользовать
4 код легко тестировать
Чем плох rails-
way?
Single Responsibility
Principle
- Не, не слышали
Skinny controllers,
fat models.
ORLY?
Conventions over
configuration
DB ⇆ Forms
Проект
4 Монолитное приложение на Grails
4 База данных на 70 таблиц
4 Мы упоролись
Решение заказчика
4 Фронтенд на AngularJS
4 Бэкэнд на рельсе для отдачи API
Ок, rails, значит
rails
Точнее, rails-api
Модели
class ImageSettings < ActiveRecord::Base
end
class Profile < ActiveRecord::Base
self.table_name = 'profile'
belongs_to :image, foreign_key: :picture_id
end
Модели
class Image < ActiveRecord::Base
self.table_name = 'image'
has_and_belongs_to_many :image_variants,
join_table: "image_image",
class_name: "Image",
association_foreign_key: :image_images_id
belongs_to :settings,
foreign_key: :settings_id,
class_name: 'ImageSettings'
belongs_to :asset
end
RABL - github.com/nesquena/
rabl
collection @object
attribute :id, :deleted, :username, :age
node :gender do |object|
object.gender.to_s
end
node :thumbnail_image_url do |obj|
obj.thumbnail_image.asset.url
end
node :standard_image_url do |obj|
obj.standard_image.asset.url
end
!-"--→----→----→----→----#
def redeem
unless bonuscode = Bonuscode.find_by_hash(params[:code])
render json: {error: 'Bonuscode not found'}, status: 404 and return
end
if bonuscode.used?
render json: {error: 'Bonuscode is already used'}, status: 404 and return
end
unless recipient = User.find_by_id(params[:receptor_id])
render json: {error: 'Recipient not found'}, status: 404 and return
end
unless ['regular', 'paying'].include?(recipient.type)
render json: {error: 'Incorrect user type'}, status: 404 and return
end
ActiveRecord::Base.transaction do
amount = bonuscode.mark_as_used!(params[:receptor_id])
recipient.increase_balance!(amount)
if recipient.save && bonuscode.save
render json: {balance: recipient.balance}, status: 200 and return
else
render json: {error: 'Error during transaction'}, status: 500 and return
end
end
end
def redeem
begin
recipient_balance = ??????????
rescue BonuscodeNotFound, BonuscodeIsAlreadyUsed, RecipientNotFound => ex
render json: {error: ex.message}, status: 404 and return
rescue IncorrectRecipientType => ex
render json: {error: ex.message}, status: 403 and return
rescue TransactionError => ex
render json: {error: ex.message}, status: 500 and return
end
render json: {balance: recipient_balance}
end
bonuscode.redeem(user)
или
user.redeem_bonuscode(code)
?
Service/Use case:
def redeem
use_case = RedeemBonuscode.new
begin
recipient_balance = use_case.run!(params[:code], params[:receptor_id])
rescue BonuscodeNotFound, BonuscodeIsAlreadyUsed, RecipientNotFound => ex
render json: {error: ex.message}, status: 404 and return
rescue IncorrectRecipientType => ex
render json: {error: ex.message}, status: 403 and return
rescue TransactionError => ex
render json: {error: ex.message}, status: 500 and return
end
render json: {balance: recipient_balance}
end
class RedeemBonuscode
def run!(hashcode, recipient_id)
raise BonuscodeNotFound.new unless bonuscode = find_bonuscode(hashcode)
raise RecipientNotFound.new unless recipient = find_recipient(recipient_id)
raise BonuscodeIsAlreadyUsed.new if bonuscode.used?
raise IncorrectRecipientType.new unless correct_user_type?(recipient.type)
ActiveRecord::Base.transaction do
amount = bonuscode.redeem!(recipient_id)
recipient.increase_balance!(amount)
recipient.save! && bonuscode.save!
end
recipient.balance
end
private
...
end
!---→-"---→----→----→----#
Incoming!!!
4 В таблице "users" по
факту хранятся разные
типы пользователей и
для них нужны разные
правила валидации
4 Это как минимум
Themis - modular and switchable
validations for ActiveRecord models
class User < ActiveRecord::Base
has_validation :admin, AdminValidation
has_validation :enduser, EndUserValidation
...
user.use_validation(:enduser)
user.valid? # => true
4 github.com/TMXCredit/themis
module EndUserValidation
extend Themis::Validation
validates_presence_of :username, :password, :email
validates :password, length: { minimum: 6 }
validates :email, email: true
end
!---→---"-→----→----→----#
Form objects (Inputs)
4 Plain old ruby objects*
class BonuscodeRedeemInput < Input
attribute :hash_code, String
attribute :receptor_id, Integer
validates_presence_of :hash_code, :receptor_id
validates_numericality_of :receptor_id
end
Form objects (Inputs)
class Input
include Virtus.model
include ActiveModel::Validations
class ValidationError < StandardError; end
def validate!
raise ValidationError.new, errors unless valid?
end
end
!---→----→---"-→----→----#
Incoming!
Заказчику нужен небольшой
сервис сбоку
mkdir sinatra-app
cd sinatra-app
git init .
class SenderApp < Sinatra::Base
get '/sms/create/:message/:number' do
input = MessageInput.new(params)
sender = TwilioMessageSender.new('xxxxxx', 'xxxxxx', '+15005550006')
use_case = SendSms.new(input, sender)
begin
use_case.run!
rescue ValidationError => ex
status 403 and return ex.message
rescue SendingError => ex
status 500 and return ex.message
end
status 201
end
end
Sinatra
4 встраивается в рельсовые роуты или
запускается в связке с другими rack-
приложениями
4 рельсовые роуты оказались ненужной
абстракцией
4 отчего бы не применить такой подход для
всего приложения?!
!---→----→----→--"--→----#
А как же разделение
бизнес-логики и
хранения данных?
4 Очень хочется но непонятно как
4 Репозитории?
Entities
Entities - зачем?
statuses profiles
-------- --------
id id
name status_id
profile.status.name
Entities
4 Plain old ruby objects*
class User < Entity
attribute :id, Integer
attribute :username, String
attribute :max_profiles, Integer, default: 3
attribute :profiles, Array[Profile]
attribute :roles, Array
end
Entities
4 Plain old ruby objects*
class Entity
include Virtus.model
def new_record?
!id
end
end
Repositories
Repositories - чем
занимаются?
4 найди мне объект с таким-то id
4 дай мне все объекты по такому-то
условию
4 сохрани вот эти данные в виде объекта
AR-flavored repositories
4 под капотом все те же ActiveRecord
модели
4 репозиторий отдает инстансы Entities
Первая попытка:
AR-flavored repositories
NEVER AGAIN!
Sequel FTW!
sequel.jeremyevans.net
4 DSL для построения SQL-запросов
4 реализация паттерна ActiveRecord
Repositories
def find(id)
dataset = table.select(:id, :username, :enabled,
:date_created, :last_updated).where(id: id)
user = User.new(dataset.first)
user.roles = get_roles(id)
user
end
Repositories
def find(id)
to_add = [:gender__name___gender, :profile_status__name___status,
:picture_id___image_id]
dataset = table.join(:profile_status, id: :profile_status_id)
.join(:gender, id: :profile__gender_id)
.select_all(:profile).select_append(*to_add)
.where(profile__id: id)
all = dataset.all.map do |record|
Profile.new(record)
end
all.size > 1 ? all : all.first
end
Repositories
def persist(profile)
status_id = DB[:online_profile_status].select(:id).where(name: profile.status).get(:id)
gender_id = DB[:gender].select(:id).where(name: profile.gender).get(:id)
hash = { username: profile.username,
profile_status_id: status_id,
gender_id: gender_id,
picture_id: profile.image_id }
if profile.new_record?
dates = { date_created: Time.now.utc, last_updated: Time.now.utc }
profile.id = table.insert(hash.merge! dates)
else
table.where(id: profile.id).update(hash)
end
end
Repositories
class ImageRepo
class ImageData < Sequel::Model
set_dataset DB[:image].join(:asset, asset__id: :image__asset_id)
many_to_many :images,
join_table: :image_image,
left_key: :image_id,
right_key: :image_images_id,
class: self
end
...
Benefits?
!---→----→----→----→---"-#
Presenters
class ProfilePresenter
def initialize(p, viewer)
...
def wrap!
hash = { id: p.id, deleted: p.deleted,
username: p.username, is_online: p.is_online }
hash[:user_id] = p.user_id if viewer.admin?
hash[:image] = p.image.to_hash if add_image?
hash
end
def self.wrap!(profiles, viewer)
profiles.map do |profile|
new(profile, viewer).wrap!
end
end
...
end
Presenters
post '/profile' do
begin
use_case = CreateProfile.new(current_user)
profile = use_case.run!(params)
rescue Input::ValidationError => ex
halt 403
end
wrapped_profiles = ProfilePresenter.wrap!([profile], current_user)
json(data: wrapped_profiles)
end
!---→----→----→----→----"#
Hexagonal/Clean
Architecture
From rails-way to
modular architecture
Ivan Nemytchenko,
independent consultant
DevConf 2014
@inem, @inemation

More Related Content

What's hot

Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子Yasuko Ohba
 
Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...
Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...
Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...King Foo
 
Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13Rafael Dohms
 
Apostrophe (improved Paris edition)
Apostrophe (improved Paris edition)Apostrophe (improved Paris edition)
Apostrophe (improved Paris edition)tompunk
 
WordPress Queries - the right way
WordPress Queries - the right wayWordPress Queries - the right way
WordPress Queries - the right wayAnthony Hortin
 
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)James Titcumb
 
Let's play a game with blackfire player
Let's play a game with blackfire playerLet's play a game with blackfire player
Let's play a game with blackfire playerMarcin Czarnecki
 
OroPlatform and OroCRM from a developer's perspective
OroPlatform and OroCRM from a developer's perspectiveOroPlatform and OroCRM from a developer's perspective
OroPlatform and OroCRM from a developer's perspectiveYevhen Shyshkin
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersKacper Gunia
 
Mastering solr
Mastering solrMastering solr
Mastering solrjurcello
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsMichelangelo van Dam
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowKacper Gunia
 
Data Localization and Translation
Data Localization and TranslationData Localization and Translation
Data Localization and TranslationYevhen Shyshkin
 
深入淺出 MVC
深入淺出 MVC深入淺出 MVC
深入淺出 MVCJace Ju
 

What's hot (20)

Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子Ruby on Rails ステップアップ講座 - 大場寧子
Ruby on Rails ステップアップ講座 - 大場寧子
 
Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...
Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...
Building Web Services with Zend Framework (PHP Benelux meeting 20100713 Vliss...
 
Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13Your code sucks, let's fix it! - php|tek13
Your code sucks, let's fix it! - php|tek13
 
Zero to SOLID
Zero to SOLIDZero to SOLID
Zero to SOLID
 
Apostrophe (improved Paris edition)
Apostrophe (improved Paris edition)Apostrophe (improved Paris edition)
Apostrophe (improved Paris edition)
 
QA for PHP projects
QA for PHP projectsQA for PHP projects
QA for PHP projects
 
WordPress Queries - the right way
WordPress Queries - the right wayWordPress Queries - the right way
WordPress Queries - the right way
 
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
 
Django Heresies
Django HeresiesDjango Heresies
Django Heresies
 
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
 
Let's play a game with blackfire player
Let's play a game with blackfire playerLet's play a game with blackfire player
Let's play a game with blackfire player
 
Data Validation models
Data Validation modelsData Validation models
Data Validation models
 
OroPlatform and OroCRM from a developer's perspective
OroPlatform and OroCRM from a developer's perspectiveOroPlatform and OroCRM from a developer's perspective
OroPlatform and OroCRM from a developer's perspective
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
Mastering solr
Mastering solrMastering solr
Mastering solr
 
PHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the testsPHPUnit Episode iv.iii: Return of the tests
PHPUnit Episode iv.iii: Return of the tests
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers Cracow
 
Data Localization and Translation
Data Localization and TranslationData Localization and Translation
Data Localization and Translation
 
深入淺出 MVC
深入淺出 MVC深入淺出 MVC
深入淺出 MVC
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
 

Similar to От Rails-way к модульной архитектуре

Symfony2 Introduction Presentation
Symfony2 Introduction PresentationSymfony2 Introduction Presentation
Symfony2 Introduction PresentationNerd Tzanetopoulos
 
RESTful API development in Laravel 4 - Christopher Pecoraro
RESTful API development in Laravel 4 - Christopher PecoraroRESTful API development in Laravel 4 - Christopher Pecoraro
RESTful API development in Laravel 4 - Christopher PecoraroChristopher Pecoraro
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 
Simplify your professional web development with symfony
Simplify your professional web development with symfonySimplify your professional web development with symfony
Simplify your professional web development with symfonyFrancois Zaninotto
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...Fabio Franzini
 
Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23
Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23
Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23Ruby Meditation
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Coupa Software
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 
Connecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOPConnecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOPraimonesteve
 
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)Mike Schinkel
 
Working With The Symfony Admin Generator
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin GeneratorJohn Cleveley
 
Champaign-Urbana Javascript Meetup Talk (Jan 2020)
Champaign-Urbana Javascript Meetup Talk (Jan 2020)Champaign-Urbana Javascript Meetup Talk (Jan 2020)
Champaign-Urbana Javascript Meetup Talk (Jan 2020)Susan Potter
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Michelangelo van Dam
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatternsChul Ju Hong
 
Rails for Beginners - Le Wagon
Rails for Beginners - Le WagonRails for Beginners - Le Wagon
Rails for Beginners - Le WagonAlex Benoit
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-publicChul Ju Hong
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responsesdarrelmiller71
 

Similar to От Rails-way к модульной архитектуре (20)

Symfony2 Introduction Presentation
Symfony2 Introduction PresentationSymfony2 Introduction Presentation
Symfony2 Introduction Presentation
 
RESTful API development in Laravel 4 - Christopher Pecoraro
RESTful API development in Laravel 4 - Christopher PecoraroRESTful API development in Laravel 4 - Christopher Pecoraro
RESTful API development in Laravel 4 - Christopher Pecoraro
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Simplify your professional web development with symfony
Simplify your professional web development with symfonySimplify your professional web development with symfony
Simplify your professional web development with symfony
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
 
Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23
Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23
Growing Rails Apps - Dmitry Zhlobo | Ruby Meditation #23
 
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
Rails 4.0
Rails 4.0Rails 4.0
Rails 4.0
 
Connecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOPConnecting your Python App to OpenERP through OOOP
Connecting your Python App to OpenERP through OOOP
 
Supa fast Ruby + Rails
Supa fast Ruby + RailsSupa fast Ruby + Rails
Supa fast Ruby + Rails
 
SOLID Ruby, SOLID Rails
SOLID Ruby, SOLID RailsSOLID Ruby, SOLID Rails
SOLID Ruby, SOLID Rails
 
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014 (PPT)
 
Working With The Symfony Admin Generator
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin Generator
 
Champaign-Urbana Javascript Meetup Talk (Jan 2020)
Champaign-Urbana Javascript Meetup Talk (Jan 2020)Champaign-Urbana Javascript Meetup Talk (Jan 2020)
Champaign-Urbana Javascript Meetup Talk (Jan 2020)
 
Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12Workshop quality assurance for php projects tek12
Workshop quality assurance for php projects tek12
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatterns
 
Rails for Beginners - Le Wagon
Rails for Beginners - Le WagonRails for Beginners - Le Wagon
Rails for Beginners - Le Wagon
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-public
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responses
 

More from Ivan Nemytchenko

Breaking Bad Habits with GitLab CI
Breaking Bad Habits with GitLab CIBreaking Bad Habits with GitLab CI
Breaking Bad Habits with GitLab CIIvan Nemytchenko
 
How to stop being Rails Developer
How to stop being Rails DeveloperHow to stop being Rails Developer
How to stop being Rails DeveloperIvan Nemytchenko
 
What I Have Learned from Organizing Remote Internship for Ruby developers
What I Have Learned from Organizing Remote Internship for Ruby developersWhat I Have Learned from Organizing Remote Internship for Ruby developers
What I Have Learned from Organizing Remote Internship for Ruby developersIvan Nemytchenko
 
Breaking bad habits with GitLab CI
Breaking bad habits with GitLab CIBreaking bad habits with GitLab CI
Breaking bad habits with GitLab CIIvan Nemytchenko
 
Lean Poker in Lviv announce
Lean Poker in Lviv announceLean Poker in Lviv announce
Lean Poker in Lviv announceIvan Nemytchenko
 
How to use any static site generator with GitLab Pages.
How to use any static site generator with GitLab Pages. How to use any static site generator with GitLab Pages.
How to use any static site generator with GitLab Pages. Ivan Nemytchenko
 
Опыт организации удаленной стажировки для рубистов
Опыт организации удаленной стажировки для рубистовОпыт организации удаленной стажировки для рубистов
Опыт организации удаленной стажировки для рубистовIvan Nemytchenko
 
Principles. Misunderstood. Applied
Principles. Misunderstood. AppliedPrinciples. Misunderstood. Applied
Principles. Misunderstood. AppliedIvan Nemytchenko
 
From Rails-way to modular architecture
From Rails-way to modular architectureFrom Rails-way to modular architecture
From Rails-way to modular architectureIvan Nemytchenko
 
Рассказ про RedDotRubyConf 2014
Рассказ про RedDotRubyConf 2014Рассказ про RedDotRubyConf 2014
Рассказ про RedDotRubyConf 2014Ivan Nemytchenko
 
Рефакторинг rails-приложения. С чего начать?
Рефакторинг rails-приложения. С чего начать?Рефакторинг rails-приложения. С чего начать?
Рефакторинг rails-приложения. С чего начать?Ivan Nemytchenko
 
Different approaches to ruby web applications architecture
Different approaches to ruby web applications architectureDifferent approaches to ruby web applications architecture
Different approaches to ruby web applications architectureIvan Nemytchenko
 
Coffescript - счастье для javascript-разработчика
Coffescript - счастье для javascript-разработчикаCoffescript - счастье для javascript-разработчика
Coffescript - счастье для javascript-разработчикаIvan Nemytchenko
 
Tequila - язык для продвинутой генерации JSON
Tequila - язык для продвинутой генерации JSONTequila - язык для продвинутой генерации JSON
Tequila - язык для продвинутой генерации JSONIvan Nemytchenko
 

More from Ivan Nemytchenko (15)

Breaking Bad Habits with GitLab CI
Breaking Bad Habits with GitLab CIBreaking Bad Habits with GitLab CI
Breaking Bad Habits with GitLab CI
 
How to stop being Rails Developer
How to stop being Rails DeveloperHow to stop being Rails Developer
How to stop being Rails Developer
 
What I Have Learned from Organizing Remote Internship for Ruby developers
What I Have Learned from Organizing Remote Internship for Ruby developersWhat I Have Learned from Organizing Remote Internship for Ruby developers
What I Have Learned from Organizing Remote Internship for Ruby developers
 
Breaking bad habits with GitLab CI
Breaking bad habits with GitLab CIBreaking bad habits with GitLab CI
Breaking bad habits with GitLab CI
 
Lean Poker in Lviv announce
Lean Poker in Lviv announceLean Poker in Lviv announce
Lean Poker in Lviv announce
 
How to use any static site generator with GitLab Pages.
How to use any static site generator with GitLab Pages. How to use any static site generator with GitLab Pages.
How to use any static site generator with GitLab Pages.
 
Опыт организации удаленной стажировки для рубистов
Опыт организации удаленной стажировки для рубистовОпыт организации удаленной стажировки для рубистов
Опыт организации удаленной стажировки для рубистов
 
Principles. Misunderstood. Applied
Principles. Misunderstood. AppliedPrinciples. Misunderstood. Applied
Principles. Misunderstood. Applied
 
From Rails-way to modular architecture
From Rails-way to modular architectureFrom Rails-way to modular architecture
From Rails-way to modular architecture
 
Рассказ про RedDotRubyConf 2014
Рассказ про RedDotRubyConf 2014Рассказ про RedDotRubyConf 2014
Рассказ про RedDotRubyConf 2014
 
Рефакторинг rails-приложения. С чего начать?
Рефакторинг rails-приложения. С чего начать?Рефакторинг rails-приложения. С чего начать?
Рефакторинг rails-приложения. С чего начать?
 
Different approaches to ruby web applications architecture
Different approaches to ruby web applications architectureDifferent approaches to ruby web applications architecture
Different approaches to ruby web applications architecture
 
ActiveRecord vs Mongoid
ActiveRecord vs MongoidActiveRecord vs Mongoid
ActiveRecord vs Mongoid
 
Coffescript - счастье для javascript-разработчика
Coffescript - счастье для javascript-разработчикаCoffescript - счастье для javascript-разработчика
Coffescript - счастье для javascript-разработчика
 
Tequila - язык для продвинутой генерации JSON
Tequila - язык для продвинутой генерации JSONTequila - язык для продвинутой генерации JSON
Tequila - язык для продвинутой генерации JSON
 

Recently uploaded

VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...aditipandeya
 
Radiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girlsRadiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girlsstephieert
 
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...SofiyaSharma5
 
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls KolkataLow Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
Moving Beyond Twitter/X and Facebook - Social Media for local news providers
Moving Beyond Twitter/X and Facebook - Social Media for local news providersMoving Beyond Twitter/X and Facebook - Social Media for local news providers
Moving Beyond Twitter/X and Facebook - Social Media for local news providersDamian Radcliffe
 
Networking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGNetworking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGAPNIC
 
Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130  Available With RoomVIP Kolkata Call Girl Kestopur 👉 8250192130  Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Roomdivyansh0kumar0
 
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxAWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxellan12
 
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
VIP Kolkata Call Girl Alambazar 👉 8250192130 Available With Room
VIP Kolkata Call Girl Alambazar 👉 8250192130  Available With RoomVIP Kolkata Call Girl Alambazar 👉 8250192130  Available With Room
VIP Kolkata Call Girl Alambazar 👉 8250192130 Available With Roomdivyansh0kumar0
 
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With RoomVIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Roomdivyansh0kumar0
 
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024APNIC
 
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls KolkataVIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
Chennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts serviceChennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts servicevipmodelshub1
 
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝soniya singh
 

Recently uploaded (20)

VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
VIP 7001035870 Find & Meet Hyderabad Call Girls Dilsukhnagar high-profile Cal...
 
Call Girls In South Ex 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
Call Girls In South Ex 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICECall Girls In South Ex 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
Call Girls In South Ex 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SERVICE
 
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No AdvanceRohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 6 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
 
Radiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girlsRadiant Call girls in Dubai O56338O268 Dubai Call girls
Radiant Call girls in Dubai O56338O268 Dubai Call girls
 
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
Low Rate Young Call Girls in Sector 63 Mamura Noida ✔️☆9289244007✔️☆ Female E...
 
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls KolkataLow Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
Moving Beyond Twitter/X and Facebook - Social Media for local news providers
Moving Beyond Twitter/X and Facebook - Social Media for local news providersMoving Beyond Twitter/X and Facebook - Social Media for local news providers
Moving Beyond Twitter/X and Facebook - Social Media for local news providers
 
Networking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOGNetworking in the Penumbra presented by Geoff Huston at NZNOG
Networking in the Penumbra presented by Geoff Huston at NZNOG
 
Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Defence Colony Delhi 💯Call Us 🔝8264348440🔝
 
Rohini Sector 26 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 26 Call Girls Delhi 9999965857 @Sabina Saikh No AdvanceRohini Sector 26 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
Rohini Sector 26 Call Girls Delhi 9999965857 @Sabina Saikh No Advance
 
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130  Available With RoomVIP Kolkata Call Girl Kestopur 👉 8250192130  Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Room
 
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptxAWS Community DAY Albertini-Ellan Cloud Security (1).pptx
AWS Community DAY Albertini-Ellan Cloud Security (1).pptx
 
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Ishita 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Ishita 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
 
VIP Kolkata Call Girl Alambazar 👉 8250192130 Available With Room
VIP Kolkata Call Girl Alambazar 👉 8250192130  Available With RoomVIP Kolkata Call Girl Alambazar 👉 8250192130  Available With Room
VIP Kolkata Call Girl Alambazar 👉 8250192130 Available With Room
 
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With RoomVIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Room
 
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
DDoS In Oceania and the Pacific, presented by Dave Phelan at NZNOG 2024
 
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls KolkataVIP Call Girls Kolkata Ananya 🤌  8250192130 🚀 Vip Call Girls Kolkata
VIP Call Girls Kolkata Ananya 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
Chennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts serviceChennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Alwarpet Phone 🍆 8250192130 👅 celebrity escorts service
 
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
Call Girls In Model Towh Delhi 💯Call Us 🔝8264348440🔝
 

От Rails-way к модульной архитектуре

  • 1. From rails-way to modular architecture Ivan Nemytchenko, independent consultant DevConf 2014 @inem, @inemation
  • 2. Icon made by <a href="http://www.freepik.com" alt="Freepik.com">Freepik</a> from <a href="http://www.flaticon.com/free-icon/graduate-cap_46045">flaticon.com</a> Icon made by Icons8 from <a href="http://www.flaticon.com">flaticon.com</a>
  • 3.
  • 5.
  • 8. Модульная архитектура - это что? 4 изменения в коде делать легко 4 код легко переиспользовать 4 код легко тестировать
  • 13. Проект 4 Монолитное приложение на Grails 4 База данных на 70 таблиц 4 Мы упоролись
  • 14. Решение заказчика 4 Фронтенд на AngularJS 4 Бэкэнд на рельсе для отдачи API
  • 16. Модели class ImageSettings < ActiveRecord::Base end class Profile < ActiveRecord::Base self.table_name = 'profile' belongs_to :image, foreign_key: :picture_id end
  • 17. Модели class Image < ActiveRecord::Base self.table_name = 'image' has_and_belongs_to_many :image_variants, join_table: "image_image", class_name: "Image", association_foreign_key: :image_images_id belongs_to :settings, foreign_key: :settings_id, class_name: 'ImageSettings' belongs_to :asset end
  • 18. RABL - github.com/nesquena/ rabl collection @object attribute :id, :deleted, :username, :age node :gender do |object| object.gender.to_s end node :thumbnail_image_url do |obj| obj.thumbnail_image.asset.url end node :standard_image_url do |obj| obj.standard_image.asset.url end
  • 20. def redeem unless bonuscode = Bonuscode.find_by_hash(params[:code]) render json: {error: 'Bonuscode not found'}, status: 404 and return end if bonuscode.used? render json: {error: 'Bonuscode is already used'}, status: 404 and return end unless recipient = User.find_by_id(params[:receptor_id]) render json: {error: 'Recipient not found'}, status: 404 and return end unless ['regular', 'paying'].include?(recipient.type) render json: {error: 'Incorrect user type'}, status: 404 and return end ActiveRecord::Base.transaction do amount = bonuscode.mark_as_used!(params[:receptor_id]) recipient.increase_balance!(amount) if recipient.save && bonuscode.save render json: {balance: recipient.balance}, status: 200 and return else render json: {error: 'Error during transaction'}, status: 500 and return end end end
  • 21.
  • 22. def redeem begin recipient_balance = ?????????? rescue BonuscodeNotFound, BonuscodeIsAlreadyUsed, RecipientNotFound => ex render json: {error: ex.message}, status: 404 and return rescue IncorrectRecipientType => ex render json: {error: ex.message}, status: 403 and return rescue TransactionError => ex render json: {error: ex.message}, status: 500 and return end render json: {balance: recipient_balance} end
  • 24. Service/Use case: def redeem use_case = RedeemBonuscode.new begin recipient_balance = use_case.run!(params[:code], params[:receptor_id]) rescue BonuscodeNotFound, BonuscodeIsAlreadyUsed, RecipientNotFound => ex render json: {error: ex.message}, status: 404 and return rescue IncorrectRecipientType => ex render json: {error: ex.message}, status: 403 and return rescue TransactionError => ex render json: {error: ex.message}, status: 500 and return end render json: {balance: recipient_balance} end
  • 25. class RedeemBonuscode def run!(hashcode, recipient_id) raise BonuscodeNotFound.new unless bonuscode = find_bonuscode(hashcode) raise RecipientNotFound.new unless recipient = find_recipient(recipient_id) raise BonuscodeIsAlreadyUsed.new if bonuscode.used? raise IncorrectRecipientType.new unless correct_user_type?(recipient.type) ActiveRecord::Base.transaction do amount = bonuscode.redeem!(recipient_id) recipient.increase_balance!(amount) recipient.save! && bonuscode.save! end recipient.balance end private ... end
  • 27. Incoming!!! 4 В таблице "users" по факту хранятся разные типы пользователей и для них нужны разные правила валидации 4 Это как минимум
  • 28. Themis - modular and switchable validations for ActiveRecord models class User < ActiveRecord::Base has_validation :admin, AdminValidation has_validation :enduser, EndUserValidation ... user.use_validation(:enduser) user.valid? # => true 4 github.com/TMXCredit/themis
  • 29. module EndUserValidation extend Themis::Validation validates_presence_of :username, :password, :email validates :password, length: { minimum: 6 } validates :email, email: true end
  • 31. Form objects (Inputs) 4 Plain old ruby objects* class BonuscodeRedeemInput < Input attribute :hash_code, String attribute :receptor_id, Integer validates_presence_of :hash_code, :receptor_id validates_numericality_of :receptor_id end
  • 32. Form objects (Inputs) class Input include Virtus.model include ActiveModel::Validations class ValidationError < StandardError; end def validate! raise ValidationError.new, errors unless valid? end end
  • 34. Incoming! Заказчику нужен небольшой сервис сбоку mkdir sinatra-app cd sinatra-app git init .
  • 35. class SenderApp < Sinatra::Base get '/sms/create/:message/:number' do input = MessageInput.new(params) sender = TwilioMessageSender.new('xxxxxx', 'xxxxxx', '+15005550006') use_case = SendSms.new(input, sender) begin use_case.run! rescue ValidationError => ex status 403 and return ex.message rescue SendingError => ex status 500 and return ex.message end status 201 end end
  • 36. Sinatra 4 встраивается в рельсовые роуты или запускается в связке с другими rack- приложениями 4 рельсовые роуты оказались ненужной абстракцией 4 отчего бы не применить такой подход для всего приложения?!
  • 38. А как же разделение бизнес-логики и хранения данных? 4 Очень хочется но непонятно как 4 Репозитории?
  • 40. Entities - зачем? statuses profiles -------- -------- id id name status_id profile.status.name
  • 41. Entities 4 Plain old ruby objects* class User < Entity attribute :id, Integer attribute :username, String attribute :max_profiles, Integer, default: 3 attribute :profiles, Array[Profile] attribute :roles, Array end
  • 42. Entities 4 Plain old ruby objects* class Entity include Virtus.model def new_record? !id end end
  • 44. Repositories - чем занимаются? 4 найди мне объект с таким-то id 4 дай мне все объекты по такому-то условию 4 сохрани вот эти данные в виде объекта
  • 45. AR-flavored repositories 4 под капотом все те же ActiveRecord модели 4 репозиторий отдает инстансы Entities
  • 47. Sequel FTW! sequel.jeremyevans.net 4 DSL для построения SQL-запросов 4 реализация паттерна ActiveRecord
  • 48. Repositories def find(id) dataset = table.select(:id, :username, :enabled, :date_created, :last_updated).where(id: id) user = User.new(dataset.first) user.roles = get_roles(id) user end
  • 49. Repositories def find(id) to_add = [:gender__name___gender, :profile_status__name___status, :picture_id___image_id] dataset = table.join(:profile_status, id: :profile_status_id) .join(:gender, id: :profile__gender_id) .select_all(:profile).select_append(*to_add) .where(profile__id: id) all = dataset.all.map do |record| Profile.new(record) end all.size > 1 ? all : all.first end
  • 50. Repositories def persist(profile) status_id = DB[:online_profile_status].select(:id).where(name: profile.status).get(:id) gender_id = DB[:gender].select(:id).where(name: profile.gender).get(:id) hash = { username: profile.username, profile_status_id: status_id, gender_id: gender_id, picture_id: profile.image_id } if profile.new_record? dates = { date_created: Time.now.utc, last_updated: Time.now.utc } profile.id = table.insert(hash.merge! dates) else table.where(id: profile.id).update(hash) end end
  • 51. Repositories class ImageRepo class ImageData < Sequel::Model set_dataset DB[:image].join(:asset, asset__id: :image__asset_id) many_to_many :images, join_table: :image_image, left_key: :image_id, right_key: :image_images_id, class: self end ...
  • 55. class ProfilePresenter def initialize(p, viewer) ... def wrap! hash = { id: p.id, deleted: p.deleted, username: p.username, is_online: p.is_online } hash[:user_id] = p.user_id if viewer.admin? hash[:image] = p.image.to_hash if add_image? hash end def self.wrap!(profiles, viewer) profiles.map do |profile| new(profile, viewer).wrap! end end ... end
  • 56. Presenters post '/profile' do begin use_case = CreateProfile.new(current_user) profile = use_case.run!(params) rescue Input::ValidationError => ex halt 403 end wrapped_profiles = ProfilePresenter.wrap!([profile], current_user) json(data: wrapped_profiles) end
  • 59.
  • 60. From rails-way to modular architecture Ivan Nemytchenko, independent consultant DevConf 2014 @inem, @inemation