Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Say Goodbye to Procedural Programming - Nick Sutterer

143 views

Published on

Ruby Meditation 13 - 11.02.2017, BC Incom, Kyiv, 31-33 Smolenska str.

Published in: Technology
  • Be the first to comment

Say Goodbye to Procedural Programming - Nick Sutterer

  1. 1. SAY GOODBYE TO PROCEDURAL* PROGRAMMING ANOTHER USELESS PRESENTATION BROUGHT TO YOU BY @APOTONICK
  2. 2. SAY GOODBYE TO PROCEDURAL* PROGRAMMING * AS WE KNOW IT.
  3. 3. <wrong>
  4. 4. REVEAL.JS AND HOW TO MASTER IT, PART I OF VIII
  5. 5. [ ] DIAGRAMS [ ] 6 MEMES [ ] 2 BULLET POINT LISTS [ ] QUOTE FROM SOMEONE [ ] MORE DIAGRAMS [ ] TRUCKLOADS OF CODE (you wanted it)
  6. 6. [ ] DIAGRAMS [ ] 6 MEMES [x ] 2 BULLET POINT LISTS [ ] QUOTE FROM SOMEONE [ ] MORE DIAGRAMS [ ] TRUCKLOADS OF CODE (you wanted it)
  7. 7. class Post < ActiveRecord::Base validates :body, presence:true validates :author, presence:true after_save :notify_moderators!, if: :create? end
  8. 8.   class PostsController < ApplicationController def create return unless can?(current_user, Post, :new) post = Post.new(author: current_user) if post.update_attributes( params.require(:post).permit(:title) ) post.save notify_current_user! else render :new end end end
  9. 9. Let's not talk about persistence! Let's not talk about business logic! Let's not talk about views!
  10. 10. I SAID: RAILS VIEWS!
  11. 11. Notes: let's come back to the problems in our example it's hard to understand what we are trying to do and:
  12. 12. HOW DO I TEST THAT?
  13. 13. class Post < ActiveRecord::Base validates :body, presence:true validates :author, presence:true after_save :notify_moderators!, if: :create? end describe Post do it "validates and notifies moderators" do post = Post.create( valid_params ) expect(post).to be_persisted end end
  14. 14. class Post < ActiveRecord::Base validates :body, presence:true validates :author, presence:true after_save :notify_moderators!, if: :create? end describe Post do it "validates and notifies moderators" do post = Post.create( valid_params ) expect(post).to be_persisted end end
  15. 15. it do controller = Controller.new controller.create( valid_params ) expect(Post.last).to be_persisted end
  16. 16. describe BlogPostsController do it "creates BlogPost model" do post :create, blog_post: valid_params expect(response).to be_ok expect(BlogPost.last).to be_persisted end end
  17. 17. ...THINKING...
  18. 18. ...THINKING...
  19. 19. [...] It extends the basic MVC pattern with new abstractions.
  20. 20. NO!
  21. 21. class MyService def self.call(args) # do something here end end MyService.( valid_params )
  22. 22. Notes: we don't need any domain logic, that's very user specific and shouldn't be dictated by "my framework"
  23. 23. class MyService def call(params) return unless can?(current_user, Post, :new) post = Post.new(author: current_user) post.update_attributes( params.require(:post).permit(:title) ) if post.save notify_current_user! end end end
  24. 24. [ ] DIAGRAMS [x] 6 MEMES [x ] 2 BULLET POINT LISTS [ ] QUOTE FROM SOMEONE [ ] MORE DIAGRAMS [ ] TRUCKLOADS OF CODE (you wanted it)
  25. 25. class MyService def call(params) return unless can?(current_user, Post, :new) post = Post.new(author: current_user) post.update_attributes( params.require(:post).permit(:title) ) if post.save notify_current_user! end end end
  26. 26. TEST it do service = MyService.new service.call( valid_params ) expect(Post.last).to be_persisted end
  27. 27. HAPPY!
  28. 28. ...THINKING...
  29. 29. SERVICE OBJECTS, REVISITED [x] Encapsulation [x] Testing [ ] What to return? [ ] Validations extracted? [ ] Extendable class MyService def call(params) end end
  30. 30. Is the problem the procedural* code design?
  31. 31. AND THAT'S TRB. THANK YOU!
  32. 32. QUESTIONS?
  33. 33. OK, I GOT A QUESTION THEN:
  34. 34. DO YOU WANT SOME CODE?
  35. 35. DO YOU WANT SOME CODE?
  36. 36. NO?
  37. 37. class Create < Trailblazer::Operation # # # end
  38. 38. class BlogPost::Create < Trailblazer::Operation # # # end
  39. 39. class Create < Trailblazer::Operation # # # end
  40. 40. class Create < Trailblazer::Operation def process(params) # sam's code here end end
  41. 41. Notes: not really extendable
  42. 42. class Create < Trailblazer::Operation def process(params) return unless can?(current_user, Post, :new) post = Post.new(author: current_user) post.update_attributes( params.require(:post).permit(:title) ) if post.save notify_current_user! end end end
  43. 43. class Create < Trailblazer::Operation # # # end result = Create.() result.success? #=> true
  44. 44. Notes: Hooray, we have an API for service objects!
  45. 45. HOORAY, A SERVICE OBJECT API!
  46. 46. class Create < Trailblazer::Operation # # # # end
  47. 47. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify_current_user! end
  48. 48. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify_current_user! end
  49. 49. class Create < Trailblazer::Operation step :create_model! # # # # end
  50. 50. class Create < Trailblazer::Operation step :create_model! def create_model!(options, **) end end
  51. 51. CREATE MODEL class Create < Trailblazer::Operation step :create_model! def create_model!(options, **) options["model"] = BlogPost.new end end result = Create.() result.success? #=> true result["model"] #=> #<BlogPost id:nil, ..>
  52. 52. VALIDATE class Create < Trailblazer::Operation step :create_model! step :validate! def create_model!(options, **) # .. def validate!(options, params:, **) # validate params end end
  53. 53. valid_params = { body: "Blogging's fun. #not" } Create.( valid_params ) class Create < Trailblazer::Operation # .. def validate!(options, params:, **) params #=> { body: "Blogging's fun. #not" } end end
  54. 54. Notes: sending params into the op
  55. 55. class Create < Trailblazer::Operation # .. def validate!(options, params:, **) model = options["model"] # from the create_model! step... if model.update_attributes(params) true else false end end end
  56. 56. class Create < Trailblazer::Operation # .. def validate!(options, params:, model:, **) # # if model.update_attributes(params) true else false end end end
  57. 57. #class Create < Trailblazer::Operation # .. def validate!(options, params:, model:, **) if model.update_attributes(params) true else false end end #end
  58. 58. #class Create < Trailblazer::Operation # .. def validate!(options, params:, model:, **) # # # # model.update_attributes(params) end #end
  59. 59. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! # # # # # # end
  60. 60. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! #def create_model!(options, **) #def validate!(options, params:, **) def save!(options, params:, model:, **) true end end
  61. 61. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify! #def create_model!(options, **) #def validate!(options, params:, **) #def save!(options, params:, model:, **) def notify!(options, model:, **) MyMailer.call(model) end end
  62. 62. HAPPY TIMES!
  63. 63. ... AND WHEN THINGS GO WRONG?
  64. 64. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify! #def create_model!(options, **) #def validate!(options, params:, **) #def save!(options, params:, model:, **) #def notify!(options, model:, **) end
  65. 65. class Create < Trailblazer::Operation step :create_model! step :validate! [XXX] step :save! step :notify! #def create_model!(options, **) #def validate!(options, params:, **) #def save!(options, params:, model:, **) #def notify!(options, model:, **) end
  66. 66. #class Create < Trailblazer::Operation # .. def validate!(options, params:, model:, **) model.update_attributes(params) #=> false end #end
  67. 67. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify! failure :handle! #.. end
  68. 68. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify! failure :handle! #.. def handle!(options, **) options["error"] = "don't cry!" end end
  69. 69. result = Create.( { title: nil } ) result.success? #=> false result["error"] = "don't cry!"
  70. 70. RAILWAYS ROCK!
  71. 71. BUT ISN'T THAT SUPER COMPLEX?
  72. 72. DUDE.
  73. 73. Notes: do you find this more complex than this?
  74. 74. class MyService def call(params) return unless can?(current_user, Post, :new) post = Post.new(author: current_user) if post.update_attributes( params.require(:post).permit(:title) ) unless notify_current_user! if ... else end end end
  75. 75. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify! failure :handle! # .. end
  76. 76. def create_model!(options, **) def validate!( options, params:, **) def save!( options, params:, model:, **) def notify!( options, model:, **)
  77. 77. result = Create.( { title: nil } ) result.success? #=> false result["error"] #=> "don't cry!" result["model"] #=> #<BlogPost title: nil>
  78. 78. TEST it "fails with empty title" do result = Create.( { title: nil } ) expect(result).to be_success expect(result["error"]).to eq("don't cry!") expect(result["model"]).to be_persisted end
  79. 79. rspec-trailblazer minitest-trailblazer
  80. 80. Notes: validations still in model
  81. 81. class PostsController < ApplicationController def create return unless can?(current_user, Post, :new) post = Post.new(author: current_user) if post.update_attributes( params.require(:post).permit(:title)) notify_current_user! else render :new end end end
  82. 82. class PostsController < ApplicationController def create return unless can?(current_user, Post, :new) # # # result = BlogPost::Create.( params ) if result.failure? render :new end end end
  83. 83. AUTHORIZATION def create return unless can?(current_user, Post, :new) # ..
  84. 84. class Create < Trailblazer::Operation step :authorize! #step :create_model! #step :validate! #step :save! #step :notify! #failure :handle! # .. end
  85. 85. class Create < Trailblazer::Operation step :authorize! # .. def authorize!(options, current_user:, **) CouldCould.can?(current_user, Post, :new) end end
  86. 86. CURRENT WHAT?
  87. 87. def authorize!(options, current_user:, **) CouldCould.can?( current_user, # wtf? Post, :new ) end
  88. 88. class PostsController < ApplicationController def create return unless can?(current_user, Post, :new) result = BlogPost::Create.( params ) # # # if result.failure? render :new end end end
  89. 89. class PostsController < ApplicationController def create return unless can?(current_user, Post, :new) result = BlogPost::Create.( params, "current_user" => current_user ) if result.failure? render :new end end end
  90. 90. class PostsController < ApplicationController def create #return unless can?(current_user, Post, :new) # result = BlogPost::Create.( params, "current_user" => current_user ) if result.failure? render :new end end end
  91. 91. class PostsController < ApplicationController def create result = BlogPost::Create.( params, "current_user" => current_user ) if result.failure? render :new end end end
  92. 92. class PostsController < ApplicationController def create run BlogPost::Create, "current_user" => current_user do return end render :new end end
  93. 93. class PostsController < ApplicationController def create run BlogPost::Create do return end render :new end end
  94. 94. class PostsController < ApplicationController def create run BlogPost::Create do |result| return redirect_to blog_post_path(result["model"].id) end render :new end end
  95. 95. class PostsController < ApplicationController def create run BlogPost::Create do |result| return redirect_to blog_post_path(result["model" end render :new end end
  96. 96. DEPENDENCY INJECTION it "works with current_user" do result = Create.( valid_params, "current_user" => User.find(1) ) # .. end
  97. 97. class Create < Trailblazer::Operation step :authorize! # .. def authorize!(options, current_user:, **) CouldCould.can?(current_user, Post, :new) end end
  98. 98. class Create < Trailblazer::Operation step MyAuth # .. class MyAuth def self.call(options, current_user:, **) CouldCould.can?(current_user, Post, :new) end end end
  99. 99. class Create < Trailblazer::Operation step MyAuthCallableSittingSomewhere # .. #class MyAuth # def self.call(options, current_user:, **) # CouldCould.can?(current_user, Post, :new) # end #end end
  100. 100. DYI SUCKS
  101. 101. class Create < Trailblazer::Operation step Policy::CanCan( Post, :new ) # step :model! # .. end
  102. 102. VALIDATIONS: A STORY OF MANKIND
  103. 103. class Post < ActiveRecord::Base validates :title, presence:true validates :body, presence:true #after_save :notify_moderators!, if: :create? end
  104. 104. module BlogPost module Contract class Create::Create < Reform::Form property :title property :body validates :title, presence:true validates :body, presence:true end end end
  105. 105. class Create < Trailblazer::Operation step Policy::CanCan( Post, :new ) step :model! step :validate! step :notify! # .. def model!(options, **) def validate!(options, **) # .. end
  106. 106. class Create < Trailblazer::Operation step Policy::CanCan( Post, :new ) step :model! step Contract::Build( constant: Contract::Create ) step Contract::Validate() step Contract::Persist() step :notify! # .. def model!(options, **) # .. end
  107. 107. BPMN
  108. 108. class Create < Trailblazer::Operation step :create_model! step :validate! step :save! step :notify! failure :handle! # .. end
  109. 109. result = Create.( params, "current_user" => .. )
  110. 110. SAY GOODBYE TO PROCEDURAL* PROGRAMMING * AS WE KNOW IT.
  111. 111. Trailblazer is awesome!              -- Someone
  112. 112. [ ] DIAGRAMS [x] 6 MEMES [xX] 2 BULLET POINT LISTS [x] QUOTE FROM SOMEONE [x] MORE DIAGRAMS [ ] TRUCKLOADS OF CODE (you wanted it)
  113. 113. @APOTONICK ❤

×