Real-world functional Ruby

Ef3623d04d4727965ca295a6689b4d2f?s=47 Tim Riley
October 06, 2017

Real-world functional Ruby

Ef3623d04d4727965ca295a6689b4d2f?s=128

Tim Riley

October 06, 2017
Tweet

Transcript

  1. Real-world Functional Ruby Southeast Ruby 2017

  2. @timriley

  3. None
  4. @icelab

  5. !

  6. None
  7. @dry_rb @rom_rb

  8. None
  9. None
  10. PODCASTERS

  11. “TBA”

  12. None
  13. None
  14. “Programmers at work maintaining a Ruby on Rails application” Eero

    Järnefelt 
 Oil on canvas, 1893
  15. None
  16. None
  17. user.rb after_save accepts_nested_attributes_for

  18. ⏱ DESIGN STAMINA HYPOTHESIS

  19. ⏱ DESIGN STAMINA HYPOTHESIS

  20. ⏱ DESIGN STAMINA HYPOTHESIS

  21. ⏱ DESIGN STAMINA HYPOTHESIS No design

  22. ⏱ DESIGN STAMINA HYPOTHESIS No design

  23. ⏱ DESIGN STAMINA HYPOTHESIS No design

  24. ⏱ DESIGN STAMINA HYPOTHESIS No design Good design

  25. ⏱ DESIGN STAMINA HYPOTHESIS No design Good design

  26. ⏱ DESIGN STAMINA HYPOTHESIS No design Good design Design payoff

    line
  27. Object-oriented design

  28. The gap.

  29. None
  30. None
  31. Haskell

  32. Haskell

  33. Haskell

  34. Real-world Functional Ruby Southeast Ruby 2017 Tim Riley // @timriley

  35. FP is hot AF

  36. None
  37. App as series of transformations

  38. Request → [*] → [*] → Response

  39. App as series of transformations

  40. App as series of functions

  41. Functional objects

  42. class ImportProducts def call(feed) import_from(feed) end end

  43. class ImportProducts def call(feed) import_from(feed) end end

  44. class ImportProducts def call(feed) import_from(feed) end end

  45. class ImportProducts def call(feed) import_from(feed) end end

  46. Separate data from behaviour

  47. Immutable

  48. class ImportProducts def call(feed) end end records = download_feed.(feed) records.each

    do |attrs| products_repo.create(attrs) end
  49. class ImportProducts def call(feed) records = download_feed.(feed) records.each do |attrs|

    products_repo.create(attrs) end end end
  50. class ImportProducts def call(feed) records = download_feed.(feed) records.each do |attrs|

    product_repo.create(attrs) end end end
  51. download_feed: product_repo:

  52. 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
  53. 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
  54. # Initialize once import = ImportProducts.new( download_feed: download, product_repo: repo,

    ) # Reuse many times import.(:books_feed) import.(:dvds_feed)
  55. # Initialize once import = ImportProducts.new( download_feed: download, product_repo: repo,

    ) # Call many times import.(book_feed) import.(film_feed)
  56. Feeds::Create Feeds::Update Feeds::Delete Products::Update

  57. Behaviour

  58. Data

  59. Types

  60. Values

  61. class Product attr_reader :title, :isbn def initialize(**attrs) @title = attrs[:title]

    @isbn = attrs[:isbn] end def slug "#{to_slug(title)}-#{isbn}" end end
  62. class Product attr_reader :title, :isbn def initialize(**attrs) @title = attrs[:title]

    @isbn = attrs[:isbn] end def slug "#{to_slug(title)}-#{isbn}" end end
  63. Values objects are first-class

  64. Values & Functions

  65. Values Functions Hold data Operate on data

  66. Values Functions Hold data Operate on data

  67. Values Functions Hold data Operate on data Inert Active

  68. Values Functions Hold data Operate on data Inert Active Content

    Pipeline
  69. Functional design

  70. Functional, object-oriented design

  71. Functional Ruby yields better
 object-oriented Ruby!

  72. RSpec.describe ImportProducts

  73. download_feed product_repo

  74. let(:download) { double(:download) } let(:repo) { double(:repo) }

  75. subject(:import) { ImportProducts.new( download_feed: download, product_repo: repo, ) }

  76. let(:feed) { Feed.new(…) } before do allow(download_feed) .to receive(:call) .with(feed)

    .and_return(books_data) end
  77. let(:feed) { Feed.new(…) } before do allow(download) .to receive(:call) .with(feed)

    .and_return(BOOKS_DATA) end
  78. it "creates the products" do expect(repo) .to receive(:create).with(title: "…") import.(feed)

    end
  79. Import
 Products download_feed product_repo

  80. Dependencies

  81. App as graph

  82. None
  83. None
  84. Good design makes change easier

  85. Functional design makes change easier

  86. 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
  87. None
  88. A real-world functional Ruby ecosystem

  89. dry-rb

  90. None
  91. None
  92. Dependency management

  93. dry-system & dry-auto_inject

  94. # lib/my_app/import_products.rb MyApp::Container[“import_products"] #<MyApp::ImportProducts>

  95. # lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts>

  96. class ImportProducts include MyApp::Import[ "products.download_feed", "product_repo", ] def call(feed) #

    ... end end
  97. # lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts>

  98. # lib/my_app/import_products.rb MyApp::Container["import_products"] #<MyApp::ImportProducts download_feed=#<DownloadFeed> product_repo=#<ProductRepo>>

  99. ImportProducts.new( download_feed: double(:download) ) #<ImportProducts download_feed=#<Double :download> product_repo=#<ProductRepo>>

  100. ImportProducts.new( download_feed: double(:download) ) #<ImportProducts download_feed=#<Double :download> product_repo=#<ProductRepo>>

  101. Result handling

  102. dry-monads & dry-matcher

  103. # age, or nil Maybe(age) # successful import results Right(imported_products)

  104. # age, or nil Maybe(age) Maybe(33) => Some(33) # successful

    import results Right(imported_products)
  105. # age, or nil Maybe(age) Maybe(33) => Some(33) Maybe(nil) =>

    None
  106. # Successful import result Right(imported_products)

  107. import_products.() do |m| m.success do |imported_products| # … end m.failure

    do |error| # … end end
  108. Data & types

  109. dry-validation

  110. Dry::Validation.Schema do required(:name).filled required(:url).filled(format?: /…/) required(:active).filled(:bool?) end

  111. dry-types & dry-struct

  112. ISBN = Strict::String .constrained(format: /…/)

  113. class Product < Dry::Struct attribute :title, Types::Strict::String attribute :isbn, Types::ISBN

    end
  114. View rendering

  115. dry-view

  116. expose :product do |slug:| product_repo.by_slug(slug) end

  117. HTTP & routing

  118. dry-web-roda

  119. $ dry-web-roda new my_app

  120. . ├── 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
  121. │ │ └── 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
  122. Persistence

  123. rom-rb

  124. Dependency management Result handling Data validation & types Views Routing

    & HTTP Database persistence
  125. None
  126. A functional Ruby movement

  127. None
  128. None
  129. hanami

  130. RailsClub 2017 // @jodosha Functional Web with Hanami

  131. Ruby is ready

  132. There is nothing more powerful than an idea whose time

    has come. — Victor Hugo
  133. Bridging the gap for Ruby

  134. Real-world Functional Ruby

  135. FP + OO = Win!

  136. FP + OO + Community = Win!

  137. Functional Ruby is supported

  138. Functional Ruby is practical

  139. Functional Ruby is worth a try!

  140. dry-rb/dry-web-blog

  141. Thank you! @timriley dry-rb/dry-web-blog bit.ly/se-functional-ruby