Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Real-world functional Ruby
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Tim Riley
October 06, 2017
Technology
1.2k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Real-world functional Ruby
Tim Riley
October 06, 2017
More Decks by Tim Riley
See All by Tim Riley
A tour of dry-schema and dry-validation 1.0
timriley
1
420
Views, from the top
timriley
0
1.9k
Real-world Functional Ruby
timriley
0
570
Reinvesting in Ruby
timriley
1
140
Next-generation Ruby web apps with dry-rb, rom-rb, and Roda
timriley
5
5.4k
Functional Ruby, Rodakase, and another world of Ruby web applications
timriley
7
1.2k
Introduction to RubyMotion
timriley
6
3.6k
Other Decks in Technology
See All in Technology
FPGAが実現する遠方宇宙の高空間分解能天体撮影 -大型地上望遠鏡の視力を補正する「補償光学」とは?-
komei_mt
0
270
AWS ネットワーク構築でハマった(ハマりかけた) 5選とそこから得た教訓
nagisa53
4
240
名刺メーカーDevグループ 紹介資料
sansan33
PRO
0
1.2k
20分でわかるセキュアAPI
nwiizo
2
290
QAエンジニア起点で進める、SmartHRにおける信頼性向上について
kaomi_wombat
1
140
オートロックマンションなのに、各部屋は施錠なし!? 攻撃者が組織内ネットワークで大暴れする理由 / The Front Door Is Locked, but the Rooms Are Wide Open: Why Attackers Move Freely Inside Enterprise Networks
nttcom
1
6k
なぜ Temporal の大小比較には compare しかないのか / Why Does Temporal Only Have compare() for Comparisons
kazukihayase
1
140
Master Dataグループ紹介資料
sansan33
PRO
1
4.8k
【CEDEC2026】コードレビュー支援ツール開発から学ぶ:LLMを用いた業務システムの実践的な運用設計と誤出力対策
cygames
PRO
0
820
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
250
Sets in Go
ramalho
1
860
トヨタ⽣産⽅式(TPS)⼊⾨
recruitengineers
PRO
3
910
Featured
See All Featured
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Git: the NoSQL Database
bkeepers
PRO
432
67k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2.4k
Into the Great Unknown - MozCon
thekraken
41
2.7k
Why Our Code Smells
bkeepers
PRO
340
58k
How to Talk to Developers About Accessibility
jct
2
520
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.2k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
280
Believing is Seeing
oripsolob
1
190
Transcript
Real-world Functional Ruby Southeast Ruby 2017
@timriley
None
@icelab
!
None
@dry_rb @rom_rb
None
None
PODCASTERS
“TBA”
None
None
“Programmers at work maintaining a Ruby on Rails application” Eero
Järnefelt Oil on canvas, 1893
None
None
user.rb after_save accepts_nested_attributes_for
⏱ DESIGN STAMINA HYPOTHESIS
⏱ DESIGN STAMINA HYPOTHESIS
⏱ DESIGN STAMINA HYPOTHESIS
⏱ DESIGN STAMINA HYPOTHESIS No design
⏱ DESIGN STAMINA HYPOTHESIS No design
⏱ DESIGN STAMINA HYPOTHESIS No design
⏱ DESIGN STAMINA HYPOTHESIS No design Good design
⏱ DESIGN STAMINA HYPOTHESIS No design Good design
⏱ DESIGN STAMINA HYPOTHESIS No design Good design Design payoff
line
Object-oriented design
The gap.
None
None
Haskell
Haskell
Haskell
Real-world Functional Ruby Southeast Ruby 2017 Tim Riley // @timriley
FP is hot AF
None
App as series of transformations
Request → [*] → [*] → Response
App as series of transformations
App as series of functions
Functional objects
class ImportProducts def call(feed) import_from(feed) end end
class ImportProducts def call(feed) import_from(feed) end end
class ImportProducts def call(feed) import_from(feed) end end
class ImportProducts def call(feed) import_from(feed) end end
Separate data from behaviour
Immutable
class ImportProducts def call(feed) end end records = download_feed.(feed) records.each
do |attrs| products_repo.create(attrs) end
class ImportProducts def call(feed) records = download_feed.(feed) records.each do |attrs|
products_repo.create(attrs) end end end
class ImportProducts def call(feed) records = download_feed.(feed) records.each do |attrs|
product_repo.create(attrs) end end end
download_feed: product_repo:
class ImportProducts attr_reader :download_feed attr_reader :products_repo def initialize(download_feed:, product_repo:) @download_feed
= download_feed @products_repo = products_repo end def call(feed) # ... end end
class ImportProducts attr_reader :download_feed attr_reader :product_repo def initialize(download_feed:, product_repo:) @download_feed
= download_feed @product_repo = product_repo end def call(feed) # ... end end
# Initialize once import = ImportProducts.new( download_feed: download, product_repo: repo,
) # Reuse many times import.(:books_feed) import.(:dvds_feed)
# Initialize once import = ImportProducts.new( download_feed: download, product_repo: repo,
) # Call many times import.(book_feed) import.(film_feed)
Feeds::Create Feeds::Update Feeds::Delete Products::Update
Behaviour
Data
Types
Values
class Product attr_reader :title, :isbn def initialize(**attrs) @title = attrs[:title]
@isbn = attrs[:isbn] end def slug "#{to_slug(title)}-#{isbn}" end end
class Product attr_reader :title, :isbn def initialize(**attrs) @title = attrs[:title]
@isbn = attrs[:isbn] end def slug "#{to_slug(title)}-#{isbn}" end end
Values objects are first-class
Values & Functions
Values Functions Hold data Operate on data
Values Functions Hold data Operate on data
Values Functions Hold data Operate on data Inert Active
Values Functions Hold data Operate on data Inert Active Content
Pipeline
Functional design
Functional, object-oriented design
Functional Ruby yields better object-oriented Ruby!
RSpec.describe ImportProducts
download_feed product_repo
let(:download) { double(:download) } let(:repo) { double(:repo) }
subject(:import) { ImportProducts.new( download_feed: download, product_repo: repo, ) }
let(:feed) { Feed.new(…) } before do allow(download_feed) .to receive(:call) .with(feed)
.and_return(books_data) end
let(:feed) { Feed.new(…) } before do allow(download) .to receive(:call) .with(feed)
.and_return(BOOKS_DATA) end
it "creates the products" do expect(repo) .to receive(:create).with(title: "…") import.(feed)
end
Import Products download_feed product_repo
Dependencies
App as graph
None
None
Good design makes change easier
Functional design makes change easier
Real developers hate him! He’s idealistic He’s making too many
.rb files He found a #method to build better apps. Learn the one WEIRD trick to his stunning results! GET FUNCTIONAL NOW
None
A real-world functional Ruby ecosystem
dry-rb
None
None
Dependency management
dry-system & dry-auto_inject
# lib/my_app/import_products.rb MyApp::Container[“import_products"] #<MyApp::ImportProducts>
# lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts>
class ImportProducts include MyApp::Import[ "products.download_feed", "product_repo", ] def call(feed) #
... end end
# lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts>
# lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts download_feed=#<DownloadFeed> product_repo=#<ProductRepo>>
ImportProducts.new( download_feed: double(:download) ) #<ImportProducts download_feed=#<Double :download> product_repo=#<ProductRepo>>
ImportProducts.new( download_feed: double(:download) ) #<ImportProducts download_feed=#<Double :download> product_repo=#<ProductRepo>>
Result handling
dry-monads & dry-matcher
# age, or nil Maybe(age) # successful import results Right(imported_products)
# age, or nil Maybe(age) Maybe(33) => Some(33) # successful
import results Right(imported_products)
# age, or nil Maybe(age) Maybe(33) => Some(33) Maybe(nil) =>
None
# Successful import result Right(imported_products)
import_products.() do |m| m.success do |imported_products| # … end m.failure
do |error| # … end end
Data & types
dry-validation
Dry::Validation.Schema do required(:name).filled required(:url).filled(format?: /…/) required(:active).filled(:bool?) end
dry-types & dry-struct
ISBN = Strict::String .constrained(format: /…/)
class Product < Dry::Struct attribute :title, Types::Strict::String attribute :isbn, Types::ISBN
end
View rendering
dry-view
expose :product do |slug:| product_repo.by_slug(slug) end
HTTP & routing
dry-web-roda
$ dry-web-roda new my_app
. ├── Gemfile ├── README.md ├── Rakefile ├── apps │
└── main │ ├── lib │ │ └── main │ │ └── views │ │ └── welcome.rb │ ├── system │ │ ├── boot │ │ │ └── view.rb │ │ ├── boot.rb │ │ └── main │ │ ├── application.rb │ │ ├── container.rb │ │ ├── import.rb │ │ ├── transactions.rb │ │ ├── view_context.rb │ │ └── view_controller.rb
│ │ └── relations │ └── types.rb ├── log ├──
spec │ ├── app_helper.rb │ ├── db_helper.rb │ ├── spec_helper.rb │ └── support │ ├── db │ │ └── test_factories.rb │ └── test_helpers.rb └── system ├── blog │ ├── application.rb │ ├── container.rb │ ├── import.rb │ └── settings.rb ├── boot │ └── rom.rb └── boot.rb 28 directories, 37 files
Persistence
rom-rb
Dependency management Result handling Data validation & types Views Routing
& HTTP Database persistence
None
A functional Ruby movement
None
None
hanami
RailsClub 2017 // @jodosha Functional Web with Hanami
Ruby is ready
There is nothing more powerful than an idea whose time
has come. — Victor Hugo
Bridging the gap for Ruby
Real-world Functional Ruby
FP + OO = Win!
FP + OO + Community = Win!
Functional Ruby is supported
Functional Ruby is practical
Functional Ruby is worth a try!
dry-rb/dry-web-blog
Thank you! @timriley dry-rb/dry-web-blog bit.ly/se-functional-ruby