Slide 1

Slide 1 text

@timriley @dry_rb @icelab @rom_rb

Slide 2

Slide 2 text

“Programmers at work maintaining a Ruby on Rails application” Eero Järnefelt, Oil on canvas, 1893 classicprogrammerpaintings.com

Slide 3

Slide 3 text

Next-generation web apps with dry-rb, rom-rb, and Roda

Slide 4

Slide 4 text

dry-rb rom-rb roda

Slide 5

Slide 5 text

Functional Ruby ➡

Slide 6

Slide 6 text

Functions as values Immutability Avoid side-effects

Slide 7

Slide 7 text

Functional objects

Slide 8

Slide 8 text

class CreateArticle def call(input) end end

Slide 9

Slide 9 text

class CreateArticle def call(input) output = some_action output end end

Slide 10

Slide 10 text

Separate data
 from behaviour

Slide 11

Slide 11 text

class CreateArticle attr_reader :repo def initialize(repo) @repo = repo end def call(input) repo.create(input) end end

Slide 12

Slide 12 text

# Initialize once create_article = CreateArticle.new(repo) # Reuse many times create_article.(title: "Hello World") create_article.(title: "Hello Singapore")

Slide 13

Slide 13 text

14 gems,
 20 minutes,
 1 app ⏱

Slide 14

Slide 14 text

GET /articles

Slide 15

Slide 15 text

Routing & HTTP Object dependency management Views Database queries Data modelling 1 2 3 4 5

Slide 16

Slide 16 text

Routing & HTTP dry-web & roda GET /articles 1

Slide 17

Slide 17 text

> gem install dry-web-roda > dry-web-roda new blog

Slide 18

Slide 18 text

class Blog < Dry::Web::Application route do |r| r.get "articles" do r.view "articles.index" end end end

Slide 19

Slide 19 text

Object dependency management dry-component &
 dry-container GET /articles 2

Slide 20

Slide 20 text

r.view "articles.index" Blog::Container["views.articles.index"] # # lib/views/articles/index.rb

Slide 21

Slide 21 text

require $LOAD_PATH

Slide 22

Slide 22 text

Views dry-view GET /articles 3

Slide 23

Slide 23 text

class Index < Blog::View configure do |config| config.template = "articles/index" end def locals(options = {}) {} end end

Slide 24

Slide 24 text

Object dependency management dry-auto_inject GET /articles 2

Slide 25

Slide 25 text

class Index < Blog::View configure do |config| config.template = "articles/index" end def locals(options = {}) {} end end

Slide 26

Slide 26 text

class Index < Blog::View configure do |config| config.template = "articles/index" end include Blog::Import["blog.repositories.articles"] def locals(options = {}) super.merge(articles: articles.listing) end end

Slide 27

Slide 27 text

Database queries rom &
 rom-repository GET /articles 4

Slide 28

Slide 28 text

Define a boundary

Slide 29

Slide 29 text

module Articles class Repository < ROM::Repository[:articles] def listing articles.published.to_a end end end

Slide 30

Slide 30 text

Database queries rom-sql GET /articles 4

Slide 31

Slide 31 text

module Relations class Articles < ROM::Relation[:sql] def published where(published: true) end end end

Slide 32

Slide 32 text

module Articles class Repository < ROM::Repository[:articles] def listing articles.published.to_a end end end

Slide 33

Slide 33 text

module Articles class Repository < ROM::Repository[:articles] def listing articles.published.as(Article).to_a end end end

Slide 34

Slide 34 text

Data modelling dry-types GET /articles 5

Slide 35

Slide 35 text

class Article < Dry::Types::Struct attribute :title, Types::Strict::String attribute :body, Types::Strict::String attribute :published_at, Types::Strict::Time end

Slide 36

Slide 36 text

class Index < Blog::View configure do |config| config.template = "articles/index" end include Blog::Import["blog.repositories.articles"] def locals(options = {}) super.merge(articles: articles.listing) end end

Slide 37

Slide 37 text

ol - articles.each do |article| li = article.title Blog::Container["views.articles.index"].()
  1. Hello Singapore
  2. Hello World

Slide 38

Slide 38 text

class Blog < Dry::Web::Application route do |r| r.get "articles" do r.view "articles.index" end end end

Slide 39

Slide 39 text

GET /articles

Slide 40

Slide 40 text

Routing & HTTP dry-web & roda Object dependency management dry-component, -container & -auto_inject Views dry-view Database queries rom, rom-sql & rom-repository Data modelling dry-types 1 2 3 4 5

Slide 41

Slide 41 text

POST /articles

Slide 42

Slide 42 text

Validation Database commands Success & error handling 1 2 3

Slide 43

Slide 43 text

class MyApp < Dry::Web::Application route do |r| r.post "articles" do r.resolve "operations.create_article" do |create| create.(r[:article]) end end end end

Slide 44

Slide 44 text

r.resolve "operations.create_article" Blog::Container["operations.create_article"] # # lib/operations/create_article.rb

Slide 45

Slide 45 text

class CreateArticle def call(input) result = Validation::ArticleSchema.(input) end end

Slide 46

Slide 46 text

Validation dry-validation POST /articles 1

Slide 47

Slide 47 text

module Validation ArticleSchema = Dry::Validation.Form do required(:title).filled(min_size?: 3) required(:body).filled required(:published).filled(:bool?) required(:published_at).filled(:time?) end end

Slide 48

Slide 48 text

input = { "published" => "1", "published_at" => "2016-05-24 16:30" } ArticleSchema.(input).to_h { published: true, published_at: 2016-05-24 16:30:00 +1000 }

Slide 49

Slide 49 text

ArticleSchema.("published" => "1").messages { :title => [ "is missing", "size cannot be less than 5"], :body => ["is missing"], :published_at => ["is missing"] }

Slide 50

Slide 50 text

class CreateArticle include Blog::Import["repositories.articles"] def call(input) result = Validation::ArticleSchema.(input) if result.success? articles.create(result) end end end

Slide 51

Slide 51 text

class CreateArticle include Blog::Import["repositories.articles"] def call(input) result = Validation::ArticleSchema.(input) if result.success? articles.create(result) end end end

Slide 52

Slide 52 text

Database commands rom, rom-repository POST /articles 2

Slide 53

Slide 53 text

module Relations class Articles < ROM::Relation[:sql] schema(:articles) do attribute :id, Types::Serial attribute :title, Types::String attribute :body, Types::String attribute :published, Types::Bool attribute :published_at, Types::Time end end end

Slide 54

Slide 54 text

module Articles class Repository < ROM::Repository[:articles] commands :create end end

Slide 55

Slide 55 text

def call(input) result = Validation::ArticleSchema.(input) if result.success? attrs = articles.create(validation) Article.new(attrs) end end

Slide 56

Slide 56 text

Success & failure handling dry-monads &
 dry-result_matcher POST /articles 3

Slide 57

Slide 57 text

include Dry::Monads::Either::Mixin include Dry::ResultMatcher.for(:call) def call(input) validation = Validation::ArticleSchema.(input) if validation.success? result = articles.create(validation) Right(Article.new(result)) else Left(validation) end end

Slide 58

Slide 58 text

r.post do r.resolve "operations.create_article" do |create| create.(r[:article]) do |m| m.success do r.redirect "/articles" end m.failure do |validation| r.view "articles.new", validation: validation end end end end

Slide 59

Slide 59 text

r.post do r.resolve "operations.create_article" do |create| create.(r[:article]) do |m| m.success do r.redirect "/articles" end m.failure do |validation| r.view "articles.new", validation: validation end end end end

Slide 60

Slide 60 text

POST /articles

Slide 61

Slide 61 text

Validation dry-validation Database commands rom, rom-sql, rom-repository Success & error handling dry-monads & dry-result_matcher 1 2 3

Slide 62

Slide 62 text

Change

Slide 63

Slide 63 text

New article form
 for admins

Slide 64

Slide 64 text

Isolated testing

Slide 65

Slide 65 text

Fast testing!

Slide 66

Slide 66 text

Email articles to subscribers

Slide 67

Slide 67 text

c llbacks

Slide 68

Slide 68 text

dry-transaction

Slide 69

Slide 69 text

Blog::Transactions.define do |t| t.define "transactions.create_article" do step :create, with: "operations.create_article" step :notify, with: "operations.notify_subscribers" end end

Slide 70

Slide 70 text

Blog::Transactions.define do |t| t.define "transactions.create_article" do step :create, with: "operations.create_article" step :notify, with: "operations.notify_subscribers" end end

Slide 71

Slide 71 text

Blog::Transactions.define do |t| t.define "transactions.create_article" do step :create, with: "operations.create_article" step :notify, with: "operations.notify_subscribers" end end

Slide 72

Slide 72 text

create_article.(r[:article]) do |m| m.success do r.redirect "/articles" end m.failure :create do |errors| r.view "articles.new", validation: errors end m.failure :notify do |error| # ... end end

Slide 73

Slide 73 text

Founded on simplicity

Slide 74

Slide 74 text

Small components, single focus Easy to understand, reuse, test

Slide 75

Slide 75 text

Designed for
 the domain

Slide 76

Slide 76 text

A change-positive architecture

Slide 77

Slide 77 text

A change-positive app is maintainable

Slide 78

Slide 78 text

A change-positive app is sustainable

Slide 79

Slide 79 text

A change-positive app is joyful

Slide 80

Slide 80 text

An investment
 in Ruby

Slide 81

Slide 81 text

Jeremy Evans and many more! ❤ Andy Holland Piotr Solnica

Slide 82

Slide 82 text

hanami trailblazer

Slide 83

Slide 83 text

bit.ly / dry-rdrc