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
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
580
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
Nav2、Nav3 ... はたまた自作?〜 作って理解する Nav3 の設計意図 〜 / Nav2, Nav3 ... or Build Your Own? — Understanding Nav3's design intent by building it from scratch
yanzm
0
260
AI時代におけるプロダクト横断勉強会の設計
zozotech
PRO
0
160
AI-DLCって実際どう? 〜聞きたいこと全部聞いてみる〜
news_it_enj
0
240
Bet AI Day 2026丨Agentは、「金融」という巨大産業の何を変えられるのか
layerx
PRO
0
1.1k
Tab5をRubyで動くパソコンにする
kishima
1
250
全員がプロダクトへ向き合う組織を持続成長させるために——組織づくりのフライホイールと4象限 / The Flywheel Model and Four Quadrants for Organizational Design
hiro_torii
3
890
PfEingのアプローチで働こう
rindrics
0
180
Microsoft 365 Copilot chat -tekoälypalvelun tietosuojaongelmat
hponka
0
520
こんなアーキテクチャ図は嫌だ BEYOND THE TIME: 半年後の自分へ贈る15のメッセージ / 15 of Anti-pattern in AWS Architecture Diagrams
naospon
3
260
ペアプロの価値はコードを書くことだけじゃない
codmoninc
PRO
0
130
スクラムで身についていた動き方を、XPで捉え直してみた
codmoninc
PRO
1
290
リージョンの壁を越える、 ちょっと変わったAWSサービスの話
falken
PRO
0
270
Featured
See All Featured
WENDY [Excerpt]
tessaabrams
12
39k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Thoughts on Productivity
jonyablonski
76
5.4k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
320
Six Lessons from altMBA
skipperchong
29
4.5k
Paper Plane (Part 1)
katiecoart
PRO
1
11k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
4 Signs Your Business is Dying
shpigford
187
23k
Dominate Local Search Results - an insider guide to GBP, reviews, and Local SEO
greggifford
PRO
0
320
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
510
Making the Leap to Tech Lead
cromwellryan
135
10k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
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